From 0be660a2e3b64cb2ed03b67119cc7ed72568301e Mon Sep 17 00:00:00 2001 From: iF2007 Date: Mon, 17 Aug 2026 13:54:24 +0800 Subject: [PATCH 1/3] fix(antigravity): match live agy model discovery --- src/adapters/google.ts | 7 +- src/codex/catalog/provider-fetch.ts | 3 +- src/providers/antigravity-models.ts | 101 ++++++++++++++++++++---- tests/gemini-37-flash-migration.test.ts | 14 ++-- tests/google-antigravity-wire.test.ts | 64 ++++++++++++++- tests/google-models-listing.test.ts | 2 - 6 files changed, 162 insertions(+), 29 deletions(-) diff --git a/src/adapters/google.ts b/src/adapters/google.ts index ab27089e7d..ffa7f03a55 100644 --- a/src/adapters/google.ts +++ b/src/adapters/google.ts @@ -395,6 +395,7 @@ export function createGoogleAdapter(provider: OcxProviderConfig): ProviderAdapte ? resolveAntigravityEffortWireModel( parsed.modelId, mapReasoningEffort(provider, parsed.modelId, parsed.options.reasoning), + provider.baseUrl, ).wireModelId : resolveDirectGeminiWireModelId(parsed.modelId); const { systemInstruction, contents } = messagesToGeminiFormat(parsed, routedModelId); @@ -450,7 +451,11 @@ export function createGoogleAdapter(provider: OcxProviderConfig): ProviderAdapte if (!project) throw new Error("Antigravity requires a discovered Cloud Code Assist project id (re-run `ocx login google-antigravity`)."); const sessionId = antigravitySessionId(parsed); const mappedEffort = mapReasoningEffort(provider, parsed.modelId, parsed.options.reasoning); - const { wireModelId, thinkingLevel } = resolveAntigravityEffortWireModel(parsed.modelId, mappedEffort); + const { wireModelId, thinkingLevel } = resolveAntigravityEffortWireModel( + parsed.modelId, + mappedEffort, + provider.baseUrl, + ); antigravityModel = wireModelId; antigravitySession = sessionId; // Effort → thinkingConfig for CCA (CLIProxyAPI proven: request.generationConfig.thinkingConfig). diff --git a/src/codex/catalog/provider-fetch.ts b/src/codex/catalog/provider-fetch.ts index c858524eff..2462aad9fe 100644 --- a/src/codex/catalog/provider-fetch.ts +++ b/src/codex/catalog/provider-fetch.ts @@ -38,7 +38,7 @@ import { type CapturedServiceTierAdapterAuthority, } from "../../providers/service-tier"; import { effectiveGoogleMode, getProviderRegistryEntry, providerMatchesRegistryTransport } from "../../providers/registry"; -import { parseAntigravityAvailableModels } from "../../providers/antigravity-models"; +import { parseAntigravityAvailableModels, registerAntigravityDiscoveredWireModels } from "../../providers/antigravity-models"; import { applyProviderContextCap, providerContextCap } from "../../providers/context-cap"; import { routedSlug, slugEquals, slugsEquivalent } from "../../providers/slug-codec"; import { CODEX_GPT5_IDENTITY_LINE } from "../../adapters/identity"; @@ -1361,6 +1361,7 @@ async function fetchProviderModelsWithAuth( return observed(models, "degraded"); } if (antigravity) { + registerAntigravityDiscoveredWireModels(prov.baseUrl, antigravity); const live = antigravity.map(model => applyProviderConfigHints(name, prov, { id: model.id, provider: name, diff --git a/src/providers/antigravity-models.ts b/src/providers/antigravity-models.ts index 15adece9a1..8abd7f751c 100644 --- a/src/providers/antigravity-models.ts +++ b/src/providers/antigravity-models.ts @@ -74,8 +74,12 @@ const ANTIGRAVITY_DISCOVERY_EFFORTS = ["low", "medium", "high"] as const; function pickerModelIdForDiscoveredWireId( wireId: string, + info: Record, available: ReadonlyMap>, ): string { + const displayModelId = antigravityDisplayModelId(info.displayName, wireId); + if (displayModelId) return displayModelId; + const explicitPickerId = Object.hasOwn(ANTIGRAVITY_PICKER_MODEL_BY_WIRE_ID, wireId) ? ANTIGRAVITY_PICKER_MODEL_BY_WIRE_ID[wireId] : undefined; @@ -240,6 +244,8 @@ export const ANTIGRAVITY_MODEL_INPUT_MODALITIES: Record = { export interface AntigravityAvailableModel { id: string; + /** CCA model id used by the agent envelope when `id` comes from display metadata. */ + wireModelId: string; contextWindow?: number; inputModalities?: string[]; } @@ -254,6 +260,69 @@ function antigravityPositiveInteger(value: unknown): number | undefined { return typeof value === "number" && Number.isSafeInteger(value) && value > 0 ? value : undefined; } +const discoveredWireModelsByBaseUrl = new Map>(); + +function antigravityBaseUrlKey(baseUrl: string | undefined): string | undefined { + if (typeof baseUrl !== "string" || !baseUrl.trim()) return undefined; + const trimmed = baseUrl.trim().replace(/\/+$/, ""); + try { + const url = new URL(trimmed); + url.hash = ""; + url.search = ""; + return url.toString().replace(/\/+$/, "").toLowerCase(); + } catch { + return trimmed.toLowerCase(); + } +} + +/** Remember the wire ids returned by one live CCA discovery for request routing. */ +export function registerAntigravityDiscoveredWireModels( + baseUrl: string | undefined, + models: readonly AntigravityAvailableModel[], +): void { + const key = antigravityBaseUrlKey(baseUrl); + if (!key) return; + const wireModels = new Map(); + for (const model of models) wireModels.set(model.id, model.wireModelId); + discoveredWireModelsByBaseUrl.set(key, wireModels); +} + +function discoveredAntigravityWireModelId( + modelId: string, + baseUrl: string | undefined, +): string | undefined { + const key = antigravityBaseUrlKey(baseUrl); + return key ? discoveredWireModelsByBaseUrl.get(key)?.get(modelId) : undefined; +} + +/** + * Convert the CCA display label used by `agy` into its public model selector. + * + * The wire id is authoritative for requests, while the label is authoritative for the + * user-facing selector when Google has renamed or re-tiered a model. Keep both instead + * of maintaining a provider-specific list of known model names. + */ +function antigravityDisplayModelId(displayName: unknown, wireId: string): string | undefined { + if (typeof displayName !== "string") return undefined; + const label = displayName.trim(); + if (!label || label.length > 512) return undefined; + const slug = (replaceDots: boolean): string => label + .normalize("NFKC") + .toLowerCase() + .replace(replaceDots ? /\./g : /\s+/g, replaceDots ? "-" : " ") + .replace(/[^a-z0-9.-]+/g, "-") + .replace(/-+/g, "-") + .replace(/^-|-$/g, ""); + const preserved = slug(false); + const compact = slug(true); + if (!isValidModelDiscoveryModelId(preserved) && !isValidModelDiscoveryModelId(compact)) return undefined; + if (preserved === wireId || compact === wireId + || preserved === `${wireId}-thinking` || compact === `${wireId}-thinking`) { + return wireId; + } + return isValidModelDiscoveryModelId(preserved) ? preserved : compact; +} + /** * Extract the CCA models that are valid for agent requests. The endpoint also returns tab, * command, commit-message, transcription, and standalone image-generation models; those are not @@ -288,13 +357,6 @@ export function parseAntigravityAvailableModels( } } } - // This model is exposed by Antigravity's agent chat surface even though it is grouped under - // image generation in the discovery response. - if (Array.isArray(body.imageGenerationModelIds) - && body.imageGenerationModelIds.includes("gemini-3.1-flash-image")) { - if (ids.length >= limit) return null; - ids.push("gemini-3.1-flash-image"); - } // Newer CCA responses identify tiered Flash models through this index instead of // adding their synthetic wire ids to agentModelSorts. const tieredModelIds = antigravityRecord(body.tieredModelIds); @@ -305,6 +367,8 @@ export function parseAntigravityAvailableModels( || !Object.hasOwn(models, id) || !antigravityRecord(models[id]) || ids.length >= limit) return null; + const baseId = id.endsWith("-tiered") ? id.slice(0, -"-tiered".length) : id; + if (ids.some(agentId => agentId === baseId || agentId.startsWith(`${baseId}-`))) continue; ids.push(id); } } @@ -313,23 +377,18 @@ export function parseAntigravityAvailableModels( for (const wireId of ids) { const info = antigravityRecord(models[wireId]); if (!info || available.has(wireId)) continue; - // Legacy compatibility aliases are deliberately routed to newer wire ids for saved - // selections. They are not safe as independently discovered picker rows. - const alias = Object.hasOwn(ANTIGRAVITY_MODEL_ALIASES, wireId) - ? ANTIGRAVITY_MODEL_ALIASES[wireId] - : undefined; - if (alias && alias !== wireId) continue; available.set(wireId, info); } const out: AntigravityAvailableModel[] = []; const seen = new Set(); for (const [wireId, info] of available) { - const id = pickerModelIdForDiscoveredWireId(wireId, available); + const id = pickerModelIdForDiscoveredWireId(wireId, info, available); if (seen.has(id)) continue; seen.add(id); out.push({ id, + wireModelId: wireId, ...(antigravityPositiveInteger(info.maxTokens) ? { contextWindow: antigravityPositiveInteger(info.maxTokens) } : {}), // Tri-state, deliberately not a ternary: `true` asserts image support, // `false` asserts against it, and ABSENT is unknown. Collapsing absent into @@ -347,7 +406,9 @@ export function parseAntigravityAvailableModels( return out; } -export function resolveAntigravityWireModelId(modelId: string): string { +export function resolveAntigravityWireModelId(modelId: string, baseUrl?: string): string { + const discovered = discoveredAntigravityWireModelId(modelId, baseUrl); + if (discovered) return discovered; return Object.hasOwn(ANTIGRAVITY_MODEL_ALIASES, modelId) ? ANTIGRAVITY_MODEL_ALIASES[modelId] : modelId; @@ -380,7 +441,13 @@ export function retiredAntigravityFlashTier(modelId: string): string | undefined export function resolveAntigravityEffortWireModel( modelId: string, effort?: string, + baseUrl?: string, ): { wireModelId: string; thinkingLevel?: string } { + const discoveredWireModelId = discoveredAntigravityWireModelId(modelId, baseUrl); + if (discoveredWireModelId && (discoveredWireModelId !== modelId || isAntigravitySuffixModelId(modelId))) { + return { wireModelId: discoveredWireModelId }; + } + // Rule 0: retired Flash id — Google has taken the wire id offline, so route to the // current generation and carry the tier the retired id encoded. This runs BEFORE the // suffix check because those ids are aliases, and rule 1 would drop the tier. @@ -394,7 +461,7 @@ export function resolveAntigravityEffortWireModel( // Rule 1: suffix/compat alias — suffix IS the effort. if (isAntigravitySuffixModelId(modelId)) { - return { wireModelId: resolveAntigravityWireModelId(modelId) }; + return { wireModelId: resolveAntigravityWireModelId(modelId, baseUrl) }; } // Rule 1b: single-wire-id Gemini model whose tiers ride on thinkingLevel. Without @@ -424,7 +491,7 @@ export function resolveAntigravityEffortWireModel( } // Rule 5: everything else. - return { wireModelId: resolveAntigravityWireModelId(modelId) }; + return { wireModelId: resolveAntigravityWireModelId(modelId, baseUrl) }; } diff --git a/tests/gemini-37-flash-migration.test.ts b/tests/gemini-37-flash-migration.test.ts index c577c76060..1fed29366d 100644 --- a/tests/gemini-37-flash-migration.test.ts +++ b/tests/gemini-37-flash-migration.test.ts @@ -110,8 +110,8 @@ describe("3.7 reasoning control", () => { }); }); -describe("stale discovery cannot republish a retired model", () => { - test("a CCA payload still listing 3.6 tiers yields no retired picker row", () => { +describe("live discovery follows the CCA agent catalog", () => { + test("a CCA payload still listing 3.6 tiers preserves those live rows", () => { const payload = { models: Object.fromEntries( ["gemini-3.6-flash-low", "gemini-3.6-flash-medium", "gemini-3.6-flash-high", "gemini-3.7-flash"] @@ -124,10 +124,12 @@ describe("stale discovery cannot republish a retired model", () => { }], }; const ids = parseAntigravityAvailableModels(payload)?.map(model => model.id) ?? []; - expect(ids).toContain("gemini-3.7-flash"); - for (const retired of Object.keys(RETIRED_TIERS)) { - expect(ids).not.toContain(retired); - } + expect(ids).toEqual([ + "gemini-3.6-flash-low", + "gemini-3.6-flash-medium", + "gemini-3.6-flash-high", + "gemini-3.7-flash", + ]); }); }); diff --git a/tests/google-antigravity-wire.test.ts b/tests/google-antigravity-wire.test.ts index 5ea15eff2a..9b4e32de32 100644 --- a/tests/google-antigravity-wire.test.ts +++ b/tests/google-antigravity-wire.test.ts @@ -1,7 +1,7 @@ import { describe, expect, test } from "bun:test"; import { createGoogleAdapter as createGoogleAdapterProduction } from "../src/adapters/google"; import { antigravitySessionId, isLikelyRealThoughtSignature } from "../src/adapters/google-antigravity-wire"; -import { ANTIGRAVITY_MODELS, ANTIGRAVITY_MODEL_EFFORTS, canonicalAntigravityUsageModel, parseAntigravityAvailableModels, resolveAntigravityEffortWireModel, resolveAntigravityWireModelId } from "../src/providers/antigravity-models"; +import { ANTIGRAVITY_MODELS, ANTIGRAVITY_MODEL_EFFORTS, canonicalAntigravityUsageModel, parseAntigravityAvailableModels, registerAntigravityDiscoveredWireModels, resolveAntigravityEffortWireModel, resolveAntigravityWireModelId } from "../src/providers/antigravity-models"; import { MODEL_DISCOVERY_MAX_MODEL_ID_LENGTH, MODEL_DISCOVERY_MAX_MODELS } from "../src/providers/model-discovery"; import type { AdapterEvent, OcxParsedRequest, OcxProviderConfig } from "../src/types"; import { withTestTranslatorBudget } from "./helpers/translator-budget"; @@ -188,6 +188,66 @@ describe("antigravity CCA envelope", () => { ]); }); + test("uses live CCA display names while retaining their wire ids", async () => { + const payload = { + models: { + "gemini-3.7-flash-high": { displayName: "Gemini 3.7 Flash (High)", maxTokens: 1_048_576 }, + "gemini-3.6-flash-high": { displayName: "Gemini 3.6 Flash (High)", maxTokens: 1_048_576 }, + "gemini-3-flash-agent": { displayName: "Gemini 3.5 Flash (High)", maxTokens: 1_048_576 }, + "gemini-3.5-flash-low": { displayName: "Gemini 3.5 Flash (Medium)", maxTokens: 1_048_576 }, + "gemini-3.5-flash-extra-low": { displayName: "Gemini 3.5 Flash (Low)", maxTokens: 1_048_576 }, + "gemini-pro-agent": { displayName: "Gemini 3.1 Pro (High)", maxTokens: 1_048_576 }, + "gemini-3.1-pro-low": { displayName: "Gemini 3.1 Pro (Low)", maxTokens: 1_048_576 }, + "claude-sonnet-4-6": { displayName: "Claude Sonnet 4.6 (Thinking)", maxTokens: 250_000 }, + }, + agentModelSorts: [{ groups: [{ modelIds: [ + "gemini-3.7-flash-high", + "gemini-3.6-flash-high", + "gemini-3-flash-agent", + "gemini-3.5-flash-low", + "gemini-3.5-flash-extra-low", + "gemini-pro-agent", + "gemini-3.1-pro-low", + "claude-sonnet-4-6", + ] }] }], + }; + const rows = parseAntigravityAvailableModels(payload)!; + expect(rows.map(model => model.id)).toEqual([ + "gemini-3.7-flash-high", + "gemini-3.6-flash-high", + "gemini-3.5-flash-high", + "gemini-3.5-flash-medium", + "gemini-3.5-flash-low", + "gemini-3.1-pro-high", + "gemini-3.1-pro-low", + "claude-sonnet-4-6", + ]); + expect(rows.map(model => [model.id, model.wireModelId])).toEqual([ + ["gemini-3.7-flash-high", "gemini-3.7-flash-high"], + ["gemini-3.6-flash-high", "gemini-3.6-flash-high"], + ["gemini-3.5-flash-high", "gemini-3-flash-agent"], + ["gemini-3.5-flash-medium", "gemini-3.5-flash-low"], + ["gemini-3.5-flash-low", "gemini-3.5-flash-extra-low"], + ["gemini-3.1-pro-high", "gemini-pro-agent"], + ["gemini-3.1-pro-low", "gemini-3.1-pro-low"], + ["claude-sonnet-4-6", "claude-sonnet-4-6"], + ]); + + const baseUrl = "https://cca.example"; + registerAntigravityDiscoveredWireModels(baseUrl, rows); + expect(resolveAntigravityEffortWireModel("gemini-3.5-flash-high", undefined, baseUrl)) + .toEqual({ wireModelId: "gemini-3-flash-agent" }); + expect(resolveAntigravityEffortWireModel("gemini-3.6-flash-high", undefined, baseUrl)) + .toEqual({ wireModelId: "gemini-3.6-flash-high" }); + expect(resolveAntigravityEffortWireModel("claude-sonnet-4-6", "high", baseUrl)) + .toEqual({ wireModelId: "claude-sonnet-4-6", thinkingLevel: "high" }); + + const req = await createGoogleAdapter({ ...effortProvider, baseUrl }).buildRequest( + parsedWithEffort("gemini-3.5-flash-high"), + ); + expect(JSON.parse(req.body).model).toBe("gemini-3-flash-agent"); + }); + test("keeps unknown discovered tier IDs directly routable", async () => { for (const modelId of ["future-flash-tiered", "future-flash-low"]) { const req = await createGoogleAdapter(effortProvider).buildRequest(parsedWithEffort(modelId, "high")); @@ -267,7 +327,7 @@ describe("antigravity CCA envelope", () => { models: { "agent-model": { maxTokens: 1_048_576 } }, agentModelSorts: [{ groups: [{ modelIds: ["agent-model"] }] }], imageGenerationModelIds: ["gemini-3.1-flash-image"], - }, 1)).toBeNull(); + }, 1)?.map(model => model.id)).toEqual(["agent-model"]); }); test("throws when no project id is available", async () => { diff --git a/tests/google-models-listing.test.ts b/tests/google-models-listing.test.ts index 023c3ec07e..198ac5108e 100644 --- a/tests/google-models-listing.test.ts +++ b/tests/google-models-listing.test.ts @@ -129,8 +129,6 @@ describe("Antigravity live model discovery", () => { "future-flash-high", "future-flash-low", "future-flash-medium", - "future-flash-tiered", - "gemini-3.1-flash-image", "gemini-3.1-pro-low", "gemini-3.7-flash", ]); From 77a6e048def8e8ddc14eb08f79d425ffae97ee13 Mon Sep 17 00:00:00 2001 From: iF2007 Date: Mon, 17 Aug 2026 14:32:27 +0800 Subject: [PATCH 2/3] fix(antigravity): harden discovered model mappings --- src/codex/catalog/provider-fetch.ts | 2 +- src/providers/antigravity-models.ts | 14 ++++++- tests/google-antigravity-wire.test.ts | 39 ++++++++++++++++++++ tests/google-models-listing.test.ts | 53 +++++++++++++++++++++++++++ 4 files changed, 105 insertions(+), 3 deletions(-) diff --git a/src/codex/catalog/provider-fetch.ts b/src/codex/catalog/provider-fetch.ts index 2462aad9fe..cd34adc88c 100644 --- a/src/codex/catalog/provider-fetch.ts +++ b/src/codex/catalog/provider-fetch.ts @@ -1361,7 +1361,6 @@ async function fetchProviderModelsWithAuth( return observed(models, "degraded"); } if (antigravity) { - registerAntigravityDiscoveredWireModels(prov.baseUrl, antigravity); const live = antigravity.map(model => applyProviderConfigHints(name, prov, { id: model.id, provider: name, @@ -1375,6 +1374,7 @@ async function fetchProviderModelsWithAuth( if (!setCached(name, forCache, Date.now(), cacheGeneration)) { return observed(withConfiguredRetention(configured), "degraded"); } + registerAntigravityDiscoveredWireModels(prov.baseUrl, antigravity); markProviderDiscoveryOk(name, live.length); return observed(withConfiguredRetention(forCache, { warnDrops: true }), "authoritative"); } diff --git a/src/providers/antigravity-models.ts b/src/providers/antigravity-models.ts index 8abd7f751c..31542e455a 100644 --- a/src/providers/antigravity-models.ts +++ b/src/providers/antigravity-models.ts @@ -368,7 +368,11 @@ export function parseAntigravityAvailableModels( || !antigravityRecord(models[id]) || ids.length >= limit) return null; const baseId = id.endsWith("-tiered") ? id.slice(0, -"-tiered".length) : id; - if (ids.some(agentId => agentId === baseId || agentId.startsWith(`${baseId}-`))) continue; + if (ids.some(agentId => + agentId === id + || agentId === baseId + || ANTIGRAVITY_DISCOVERY_EFFORTS.some(effort => agentId === `${baseId}-${effort}`) + )) continue; ids.push(id); } } @@ -445,7 +449,13 @@ export function resolveAntigravityEffortWireModel( ): { wireModelId: string; thinkingLevel?: string } { const discoveredWireModelId = discoveredAntigravityWireModelId(modelId, baseUrl); if (discoveredWireModelId && (discoveredWireModelId !== modelId || isAntigravitySuffixModelId(modelId))) { - return { wireModelId: discoveredWireModelId }; + const defaultLevel = ANTIGRAVITY_THINKING_LEVEL_MODELS[modelId]; + return { + wireModelId: discoveredWireModelId, + ...(defaultLevel + ? { thinkingLevel: effort ? resolveAntigravityThinkingLevel(effort) ?? defaultLevel : defaultLevel } + : {}), + }; } // Rule 0: retired Flash id — Google has taken the wire id offline, so route to the diff --git a/tests/google-antigravity-wire.test.ts b/tests/google-antigravity-wire.test.ts index 9b4e32de32..cc3b0d458f 100644 --- a/tests/google-antigravity-wire.test.ts +++ b/tests/google-antigravity-wire.test.ts @@ -168,6 +168,17 @@ describe("antigravity CCA envelope", () => { agentModelSorts: [{ groups: [{ modelIds: [] }] }], tieredModelIds: { flash: ["gemini-3.7-flash-tiered"] }, })?.map(model => model.id)).toEqual(["gemini-3.7-flash"]); + expect(parseAntigravityAvailableModels({ + models: { + "gemini-3.7-flash-image": { maxTokens: 1_048_576 }, + "gemini-3.7-flash-tiered": { maxTokens: 1_048_576 }, + }, + agentModelSorts: [{ groups: [{ modelIds: ["gemini-3.7-flash-image"] }] }], + tieredModelIds: { flash: ["gemini-3.7-flash-tiered"] }, + })?.map(model => model.id)).toEqual([ + "gemini-3.7-flash-image", + "gemini-3.7-flash", + ]); expect(parseAntigravityAvailableModels({ models: { "-tiered": { maxTokens: 1_048_576 } }, agentModelSorts: [{ groups: [{ modelIds: ["-tiered"] }] }], @@ -248,6 +259,34 @@ describe("antigravity CCA envelope", () => { expect(JSON.parse(req.body).model).toBe("gemini-3-flash-agent"); }); + test("preserves thinkingLevel for a display-derived tiered Flash model", async () => { + const payload = { + models: { + "gemini-3.7-flash-tiered": { displayName: "Gemini 3.7 Flash", maxTokens: 1_048_576 }, + }, + agentModelSorts: [{ groups: [{ modelIds: [] }] }], + tieredModelIds: { flash: ["gemini-3.7-flash-tiered"] }, + }; + const rows = parseAntigravityAvailableModels(payload)!; + expect(rows).toEqual([{ + id: "gemini-3.7-flash", + wireModelId: "gemini-3.7-flash-tiered", + contextWindow: 1_048_576, + }]); + + const baseUrl = "https://cca-tiered-discovery.example"; + registerAntigravityDiscoveredWireModels(baseUrl, rows); + expect(resolveAntigravityEffortWireModel("gemini-3.7-flash", "high", baseUrl)) + .toEqual({ wireModelId: "gemini-3.7-flash-tiered", thinkingLevel: "high" }); + + const req = await createGoogleAdapter({ ...effortProvider, baseUrl }).buildRequest( + parsedWithEffort("gemini-3.7-flash", "high"), + ); + const envelope = JSON.parse(req.body); + expect(envelope.model).toBe("gemini-3.7-flash-tiered"); + expect(envelope.request.generationConfig.thinkingConfig).toEqual({ thinkingLevel: "high" }); + }); + test("keeps unknown discovered tier IDs directly routable", async () => { for (const modelId of ["future-flash-tiered", "future-flash-low"]) { const req = await createGoogleAdapter(effortProvider).buildRequest(parsedWithEffort(modelId, "high")); diff --git a/tests/google-models-listing.test.ts b/tests/google-models-listing.test.ts index 198ac5108e..d6f69bb534 100644 --- a/tests/google-models-listing.test.ts +++ b/tests/google-models-listing.test.ts @@ -5,6 +5,7 @@ import { join } from "node:path"; import { buildCatalogEntries, gatherRoutedModels as gatherRoutedModelsDirect } from "../src/codex/catalog"; import { buildModelsRequest } from "../src/oauth"; import { clearModelCache, getStaleCached } from "../src/codex/model-cache"; +import { resolveAntigravityWireModelId } from "../src/providers/antigravity-models"; import type { OcxConfig, OcxProviderConfig } from "../src/types"; import { withStubbedProviderFetch } from "./helpers/catalog-provider-fetch"; @@ -213,6 +214,58 @@ describe("Antigravity live model discovery", () => { } }); + test("does not register wire mappings from a stale CCA discovery", async () => { + const home = mkdtempSync(join(tmpdir(), "ocx-antigravity-stale-discovery-")); + process.env.OPENCODEX_HOME = home; + writeFileSync(join(home, "auth.json"), JSON.stringify({ + "google-antigravity": { + activeAccountId: "active", + accounts: [{ + id: "active", + credential: { + access: "access-token", + refresh: "refresh-token", + expires: Date.now() + 3_600_000, + projectId: "project-id", + }, + }], + }, + })); + let releaseResponse!: () => void; + let markFetchStarted!: () => void; + const responseGate = new Promise(resolve => { releaseResponse = resolve; }); + const fetchStarted = new Promise(resolve => { markFetchStarted = resolve; }); + const baseUrl = "https://cca-stale-discovery.example"; + globalThis.fetch = (async () => { + markFetchStarted(); + await responseGate; + return Response.json({ + models: { "stale-wire-model": { displayName: "Stale Model" } }, + agentModelSorts: [{ groups: [{ modelIds: ["stale-wire-model"] }] }], + }); + }) as typeof fetch; + + try { + const pending = gatherRoutedModels(configWith("google-antigravity", { + adapter: "google", + authMode: "oauth", + baseUrl, + project: "configured-project", + liveModels: true, + models: ["configured-only"], + })); + await fetchStarted; + clearModelCache("google-antigravity"); + releaseResponse(); + + expect((await pending).filter(model => model.provider === "google-antigravity").map(model => model.id)) + .toEqual(["configured-only"]); + expect(resolveAntigravityWireModelId("stale-model", baseUrl)).toBe("stale-model"); + } finally { + rmSync(home, { recursive: true, force: true }); + } + }); + test("uses the configured key for a custom CCA provider", async () => { const seen: { headers: Record }[] = []; globalThis.fetch = (async (_input: RequestInfo | URL, init?: RequestInit) => { From 38c25aed825695ca5dd50723cac07f59003211c1 Mon Sep 17 00:00:00 2001 From: iF2007 Date: Mon, 17 Aug 2026 14:44:08 +0800 Subject: [PATCH 3/3] fix(antigravity): invalidate discovered mappings with cache --- src/codex/catalog/provider-fetch.ts | 5 ++++- src/providers/antigravity-models.ts | 24 +++++++++++++++++++++--- tests/google-models-listing.test.ts | 10 ++++++++-- 3 files changed, 33 insertions(+), 6 deletions(-) diff --git a/src/codex/catalog/provider-fetch.ts b/src/codex/catalog/provider-fetch.ts index cd34adc88c..2293a45143 100644 --- a/src/codex/catalog/provider-fetch.ts +++ b/src/codex/catalog/provider-fetch.ts @@ -1374,7 +1374,10 @@ async function fetchProviderModelsWithAuth( if (!setCached(name, forCache, Date.now(), cacheGeneration)) { return observed(withConfiguredRetention(configured), "degraded"); } - registerAntigravityDiscoveredWireModels(prov.baseUrl, antigravity); + registerAntigravityDiscoveredWireModels(prov.baseUrl, antigravity, { + provider: name, + cacheGeneration, + }); markProviderDiscoveryOk(name, live.length); return observed(withConfiguredRetention(forCache, { warnDrops: true }), "authoritative"); } diff --git a/src/providers/antigravity-models.ts b/src/providers/antigravity-models.ts index 31542e455a..53c64f37a6 100644 --- a/src/providers/antigravity-models.ts +++ b/src/providers/antigravity-models.ts @@ -1,4 +1,5 @@ import { isValidModelDiscoveryModelId, MODEL_DISCOVERY_MAX_MODELS } from "./model-discovery-limits"; +import { isModelCacheGenerationCurrent } from "../codex/model-cache"; // Google Antigravity (Cloud Code Assist) bundled model list. // @@ -260,7 +261,12 @@ function antigravityPositiveInteger(value: unknown): number | undefined { return typeof value === "number" && Number.isSafeInteger(value) && value > 0 ? value : undefined; } -const discoveredWireModelsByBaseUrl = new Map>(); +interface DiscoveredWireModelMapping { + readonly models: ReadonlyMap; + readonly generation?: { provider: string; cacheGeneration: string }; +} + +const discoveredWireModelsByBaseUrl = new Map(); function antigravityBaseUrlKey(baseUrl: string | undefined): string | undefined { if (typeof baseUrl !== "string" || !baseUrl.trim()) return undefined; @@ -279,12 +285,16 @@ function antigravityBaseUrlKey(baseUrl: string | undefined): string | undefined export function registerAntigravityDiscoveredWireModels( baseUrl: string | undefined, models: readonly AntigravityAvailableModel[], + generation?: { provider: string; cacheGeneration: string }, ): void { const key = antigravityBaseUrlKey(baseUrl); if (!key) return; const wireModels = new Map(); for (const model of models) wireModels.set(model.id, model.wireModelId); - discoveredWireModelsByBaseUrl.set(key, wireModels); + discoveredWireModelsByBaseUrl.set(key, { + models: wireModels, + ...(generation ? { generation } : {}), + }); } function discoveredAntigravityWireModelId( @@ -292,7 +302,15 @@ function discoveredAntigravityWireModelId( baseUrl: string | undefined, ): string | undefined { const key = antigravityBaseUrlKey(baseUrl); - return key ? discoveredWireModelsByBaseUrl.get(key)?.get(modelId) : undefined; + if (!key) return undefined; + const mapping = discoveredWireModelsByBaseUrl.get(key); + if (!mapping) return undefined; + if (mapping.generation + && !isModelCacheGenerationCurrent(mapping.generation.provider, mapping.generation.cacheGeneration)) { + discoveredWireModelsByBaseUrl.delete(key); + return undefined; + } + return mapping.models.get(modelId); } /** diff --git a/tests/google-models-listing.test.ts b/tests/google-models-listing.test.ts index d6f69bb534..01a40230a6 100644 --- a/tests/google-models-listing.test.ts +++ b/tests/google-models-listing.test.ts @@ -4,8 +4,8 @@ import { tmpdir } from "node:os"; import { join } from "node:path"; import { buildCatalogEntries, gatherRoutedModels as gatherRoutedModelsDirect } from "../src/codex/catalog"; import { buildModelsRequest } from "../src/oauth"; -import { clearModelCache, getStaleCached } from "../src/codex/model-cache"; -import { resolveAntigravityWireModelId } from "../src/providers/antigravity-models"; +import { captureModelCacheGeneration, clearModelCache, getStaleCached } from "../src/codex/model-cache"; +import { registerAntigravityDiscoveredWireModels, resolveAntigravityWireModelId } from "../src/providers/antigravity-models"; import type { OcxConfig, OcxProviderConfig } from "../src/types"; import { withStubbedProviderFetch } from "./helpers/catalog-provider-fetch"; @@ -236,6 +236,12 @@ describe("Antigravity live model discovery", () => { const responseGate = new Promise(resolve => { releaseResponse = resolve; }); const fetchStarted = new Promise(resolve => { markFetchStarted = resolve; }); const baseUrl = "https://cca-stale-discovery.example"; + const priorGeneration = captureModelCacheGeneration("google-antigravity"); + registerAntigravityDiscoveredWireModels(baseUrl, [{ id: "stale-model", wireModelId: "old-wire-model" }], { + provider: "google-antigravity", + cacheGeneration: priorGeneration, + }); + expect(resolveAntigravityWireModelId("stale-model", baseUrl)).toBe("old-wire-model"); globalThis.fetch = (async () => { markFetchStarted(); await responseGate;