diff --git a/changelog.d/fixes/10286-gemini-3-5-flash-thinking.md b/changelog.d/fixes/10286-gemini-3-5-flash-thinking.md deleted file mode 100644 index 30a3c44bcb..0000000000 --- a/changelog.d/fixes/10286-gemini-3-5-flash-thinking.md +++ /dev/null @@ -1 +0,0 @@ -- fix(sse): mark gemini-3.5-flash as thinking-capable so reasoning_effort is no longer rejected with a spurious 400 (#10286) diff --git a/docs/guides/ANTIGRAVITY-ONBOARDING.md b/docs/guides/ANTIGRAVITY-ONBOARDING.md index 6b16feaeda..1d694a70b9 100644 --- a/docs/guides/ANTIGRAVITY-ONBOARDING.md +++ b/docs/guides/ANTIGRAVITY-ONBOARDING.md @@ -6,7 +6,7 @@ lastUpdated: 2026-07-31 # OmniRoute Antigravity (Google One AI) Onboarding Guide -> **What you get**: Access to Gemini 3.1 Pro, Gemini 3.5 Flash, Claude Sonnet 4.6, and other models through your Google One AI Pro subscription — routed through OmniRoute as a unified gateway. +> **What you get**: Access to Gemini 3.1 Pro, Gemini 3.7 Flash, Claude Sonnet 4.6, and other models through your Google One AI Pro subscription — routed through OmniRoute as a unified gateway. **Official references**: @@ -45,7 +45,7 @@ Both providers share the **same Google backend** — identical OAuth client, tok **Why the model catalog differs**: Google's CLI is "optimized for speed and low overhead" and "co-optimized with Gemini models" (per Google's official blog). The Web/IDE product is "optimized for comprehensiveness." The CLI uses `:fetchAvailableModels` to dynamically discover models, while the IDE uses a static curated list. -**In practice**: Use `agy/` prefix for Gemini models (e.g. `agy/gemini-3.5-flash-high`). Use `antigravity/` for the static curated list. Both hit the same Google backend, but expose different model naming. The quota is shared — using either provider counts against the same Google account's limits. +**In practice**: Use `agy/` prefix for Gemini models (e.g. `agy/gemini-3.7-flash-high`). Use `antigravity/` for the static curated list. Both hit the same Google backend, but expose different model naming. The quota is shared — using either provider counts against the same Google account's limits. --- diff --git a/open-sse/config/agyModels.ts b/open-sse/config/agyModels.ts index 5e9f37b84e..d43fcc2531 100644 --- a/open-sse/config/agyModels.ts +++ b/open-sse/config/agyModels.ts @@ -113,6 +113,7 @@ const AGY_RETIRED_MODEL_IDS = new Set([ "gemini-3.6-flash-medium", "gemini-3.6-flash-low", "gemini-3-flash-agent", + "gemini-3.5-flash", "gemini-3.5-flash-extra-low", "gemini-3.5-flash-low", "gemini-3.5-flash-high", diff --git a/open-sse/config/antigravityModelAliases.ts b/open-sse/config/antigravityModelAliases.ts index 3946b776d0..3cf9094cbb 100644 --- a/open-sse/config/antigravityModelAliases.ts +++ b/open-sse/config/antigravityModelAliases.ts @@ -179,6 +179,7 @@ const ANTIGRAVITY_RETIRED_MODEL_IDS = new Set([ "gemini-3.6-flash-medium", "gemini-3.6-flash-low", "gemini-3-flash-agent", + "gemini-3.5-flash", "gemini-3.5-flash-extra-low", "gemini-3.5-flash-low", "gemini-3.5-flash-high", diff --git a/open-sse/config/geminiRateLimits.json b/open-sse/config/geminiRateLimits.json index 9a159073a4..e30bc27bb7 100644 --- a/open-sse/config/geminiRateLimits.json +++ b/open-sse/config/geminiRateLimits.json @@ -8,7 +8,6 @@ "gemma-4-26b-it": { "rpm": 16000, "rpd": 14400, "tpm": 16000 }, "gemma-4-31b-it": { "rpm": 16000, "rpd": 14400, "tpm": 16000 }, "gemini-embedding-exp-03-07": { "rpm": 100, "rpd": 1000, "tpm": 30000 }, - "gemini-3.5-flash": { "rpm": 5, "rpd": 20, "tpm": 250000 }, "gemini-3.1-flash-lite": { "rpm": 15, "rpd": 500, "tpm": 250000 }, "gemini-3.1-pro": { "rpm": 0, "rpd": 0, "tpm": 0 }, "gemini-2.5-flash-lite": { "rpm": 10, "rpd": 20, "tpm": 250000 }, diff --git a/open-sse/config/providers/registry/cursor/index.ts b/open-sse/config/providers/registry/cursor/index.ts index 67dfb74467..7d54a2252f 100644 --- a/open-sse/config/providers/registry/cursor/index.ts +++ b/open-sse/config/providers/registry/cursor/index.ts @@ -228,14 +228,14 @@ export const cursorProvider: RegistryEntry = { { id: "gpt-5.1-low", name: "GPT-5.1 Low" }, { id: "gpt-5.1", name: "GPT-5.1" }, { id: "gpt-5.1-high", name: "GPT-5.1 High" }, - { id: "gemini-3.5-flash", name: "Gemini 3.5 Flash" }, { id: "claude-4-sonnet", name: "Sonnet 4" }, { id: "claude-4-sonnet-thinking", name: "Sonnet 4 Thinking" }, { id: "gpt-5-mini", name: "GPT-5 Mini" }, { id: "kimi-k3-low", name: "Kimi K3 Low" }, { id: "kimi-k3-max", name: "Kimi K3" }, { id: "glm-5.2-high", name: "GLM 5.2" }, - { id: "glm-5.2-max", name: "GLM 5.2 Max" }, ], + { id: "glm-5.2-max", name: "GLM 5.2 Max" }, + ], }; /** diff --git a/open-sse/services/conolModels.ts b/open-sse/services/conolModels.ts index ed96fe66f9..9979e7a784 100644 --- a/open-sse/services/conolModels.ts +++ b/open-sse/services/conolModels.ts @@ -78,7 +78,7 @@ const FALLBACK_MODEL_SEEDS: FallbackModelSeed[] = [ /** Presets exposed by the web client's model picker (id → text/multimodal model). */ export const CONOL_FALLBACK_MODEL_PRESETS: ConolModelPreset[] = [ - { id: "flash", text: "deepseek/deepseek-v4-flash", multimodal: "google/gemini-3.5-flash" }, + { id: "flash", text: "deepseek/deepseek-v4-flash", multimodal: "google/gemini-3.7-flash" }, { id: "moderate", text: "deepseek/deepseek-v4-pro", multimodal: "claude-sonnet-5" }, { id: "pro", text: "z-ai/glm-5.2", multimodal: "moonshotai/kimi-k3" }, { id: "ultra", text: "claude-fable-5", multimodal: "claude-fable-5" }, diff --git a/open-sse/services/promptqlModels.ts b/open-sse/services/promptqlModels.ts index 55935c7719..5604f85c80 100644 --- a/open-sse/services/promptqlModels.ts +++ b/open-sse/services/promptqlModels.ts @@ -6,7 +6,7 @@ */ export interface PromptQlModel { - /** Client-facing id (model_reference slug, e.g. gemini-3.5-flash). */ + /** Client-facing id (model_reference slug, e.g. gemini-3.7-flash). */ id: string; /** Friendly picker label. */ name: string; diff --git a/src/lib/providerModels/geminiModelsParser.ts b/src/lib/providerModels/geminiModelsParser.ts index 9fef2b3e52..26c0d8f4cf 100644 --- a/src/lib/providerModels/geminiModelsParser.ts +++ b/src/lib/providerModels/geminiModelsParser.ts @@ -34,6 +34,8 @@ const IGNORED_METHODS = new Set([ "asyncBatchEmbedContent", ]); +const RETIRED_GEMINI_MODEL_IDS = new Set(["gemini-3.5-flash"]); + export interface GeminiDiscoveryModel { id: string; name: string; @@ -46,36 +48,38 @@ export interface GeminiDiscoveryModel { } export function parseGeminiModelsList(data: any): GeminiDiscoveryModel[] { - return (data?.models || []).map((m: Record) => { - const methods: string[] = Array.isArray(m.supportedGenerationMethods) - ? (m.supportedGenerationMethods as string[]) - : []; + return (data?.models || []) + .map((m: Record) => { + const methods: string[] = Array.isArray(m.supportedGenerationMethods) + ? (m.supportedGenerationMethods as string[]) + : []; - const endpoints = new Set( - methods - .filter((method) => !IGNORED_METHODS.has(method)) - .map((method) => METHOD_TO_ENDPOINT[method] || "chat") - ); + const endpoints = new Set( + methods + .filter((method) => !IGNORED_METHODS.has(method)) + .map((method) => METHOD_TO_ENDPOINT[method] || "chat") + ); - const id = ((m.name as string) || (m.id as string) || "").replace(/^models\//, ""); - const lowerId = id.toLowerCase(); + const id = ((m.name as string) || (m.id as string) || "").replace(/^models\//, ""); + const lowerId = id.toLowerCase(); - // Keep Veo models in the video bucket even when the method list is incomplete. - if (lowerId.includes("veo")) { - endpoints.add("video"); - } + // Keep Veo models in the video bucket even when the method list is incomplete. + if (lowerId.includes("veo")) { + endpoints.add("video"); + } - if (endpoints.size === 0) endpoints.add("chat"); + if (endpoints.size === 0) endpoints.add("chat"); - return { - ...m, - id, - name: (m.displayName as string) || id, - supportedEndpoints: [...endpoints], - ...(typeof m.inputTokenLimit === "number" ? { inputTokenLimit: m.inputTokenLimit } : {}), - ...(typeof m.outputTokenLimit === "number" ? { outputTokenLimit: m.outputTokenLimit } : {}), - ...(typeof m.description === "string" ? { description: m.description } : {}), - ...(m.thinking === true ? { supportsThinking: true } : {}), - } as GeminiDiscoveryModel; - }); + return { + ...m, + id, + name: (m.displayName as string) || id, + supportedEndpoints: [...endpoints], + ...(typeof m.inputTokenLimit === "number" ? { inputTokenLimit: m.inputTokenLimit } : {}), + ...(typeof m.outputTokenLimit === "number" ? { outputTokenLimit: m.outputTokenLimit } : {}), + ...(typeof m.description === "string" ? { description: m.description } : {}), + ...(m.thinking === true ? { supportsThinking: true } : {}), + } as GeminiDiscoveryModel; + }) + .filter((model: GeminiDiscoveryModel) => !RETIRED_GEMINI_MODEL_IDS.has(model.id)); } diff --git a/src/lib/providerModels/managedModelImport.ts b/src/lib/providerModels/managedModelImport.ts index 60d6aad6bd..787edd10e1 100644 --- a/src/lib/providerModels/managedModelImport.ts +++ b/src/lib/providerModels/managedModelImport.ts @@ -20,7 +20,9 @@ import { normalizeDiscoveredModels } from "@/lib/providerModels/modelDiscovery"; import { ANTIGRAVITY_MODEL_ALIASES, ANTIGRAVITY_REVERSE_MODEL_ALIASES, + isDiscoverableAntigravityModelId, } from "@omniroute/open-sse/config/antigravityModelAliases.ts"; +import { isDiscoverableAgyModelId } from "@omniroute/open-sse/config/agyModels.ts"; import { filterChatSelectableModels } from "@omniroute/open-sse/services/modelEndpointPolicy.ts"; import { filterSelectableModels } from "@omniroute/open-sse/services/modelLifecycle.ts"; import { isSelfHostedChatProvider } from "@/shared/constants/providers"; @@ -254,14 +256,22 @@ export async function importManagedModels({ const previousSyncedAvailableModels = previousSyncedAvailableModelsInput ?? (await getSyncedAvailableModelsForConnection(providerId, connectionId)); + const normalizedDiscoveredModels = normalizeDiscoveredModels(fetchedModels, providerId); + // Gemini 3.5 Flash elimination (ddf1bb760, carried from #11259): antigravity/ + // agy discovery is restricted to each family's discoverable ids BEFORE any + // chat-selection filtering. + const providerFilteredModels = + providerId === "antigravity" + ? normalizedDiscoveredModels.filter((model) => isDiscoverableAntigravityModelId(model.id)) + : providerId === "agy" + ? normalizedDiscoveredModels.filter((model) => isDiscoverableAgyModelId(model.id)) + : normalizedDiscoveredModels; // #11088 (option 1): self-hosted providers keep their non-chat models — chat // filtering happens at read time (resolveLocalSyncedEndpointRoute). Every other // provider keeps the import-time chat filter: the read-time path is gated on // isSelfHostedChatProvider, so dropping it globally leaked image/video models // into OpenAI chat selections (#11271). - const selectableModels = filterSelectableModels( - providerId, - normalizeDiscoveredModels(fetchedModels, providerId) + const selectableModels = filterSelectableModels(providerId, providerFilteredModels); ); const discoveredModels = isSelfHostedChatProvider(providerId) ? selectableModels diff --git a/src/shared/constants/modelSpecs.ts b/src/shared/constants/modelSpecs.ts index f26b3f653f..8428284627 100644 --- a/src/shared/constants/modelSpecs.ts +++ b/src/shared/constants/modelSpecs.ts @@ -99,7 +99,7 @@ const GPT_5_6_MODEL_SPEC = { supportsVision: true, } satisfies ModelSpec; -const GEMINI_35_FLASH_MODEL_SPEC = { +const GEMINI_36_FLASH_MODEL_SPEC = { maxOutputTokens: 65536, contextWindow: 1048576, supportsThinking: false, @@ -160,7 +160,7 @@ export const MODEL_SPECS: Record = { aliases: ["openai/gpt-4o"], }, - // ── Gemini 2.5 and provider-neutral 3.5 Flash series ───────────── + // ── Gemini 2.5 Flash ───────────────────────────────────────────── "gemini-2.5-flash": { maxOutputTokens: 65536, contextWindow: 1048576, @@ -171,16 +171,6 @@ export const MODEL_SPECS: Record = { supportsTools: true, supportsVision: true, }, - "gemini-3.5-flash-extra-low": { - ...GEMINI_35_FLASH_MODEL_SPEC, - thinkingBudgetCap: 0, - }, - "gemini-3.5-flash-low": { ...GEMINI_35_FLASH_MODEL_SPEC }, - "gemini-3-flash-agent": { - ...GEMINI_35_FLASH_MODEL_SPEC, - thinkingBudgetCap: 0, - }, - // ── Gemini 3.7 Flash (current Antigravity/AGY live tiers) ───────── // The tier suffix configures the thinking budget passed to the upstream // gemini-3.7-flash-tiered backend (high: 24.5k, medium: 8k, low: 1k). @@ -234,9 +224,9 @@ export const MODEL_SPECS: Record = { // Provider-neutral compatibility for providers that still serve Gemini 3.6. // Antigravity/AGY availability is governed by their own provider catalogs and // retirement filters; these shared specs must not be treated as an allowlist. - "gemini-3.6-flash-high": { ...GEMINI_35_FLASH_MODEL_SPEC }, - "gemini-3.6-flash-medium": { ...GEMINI_35_FLASH_MODEL_SPEC }, - "gemini-3.6-flash-low": { ...GEMINI_35_FLASH_MODEL_SPEC }, + "gemini-3.6-flash-high": { ...GEMINI_36_FLASH_MODEL_SPEC }, + "gemini-3.6-flash-medium": { ...GEMINI_36_FLASH_MODEL_SPEC }, + "gemini-3.6-flash-low": { ...GEMINI_36_FLASH_MODEL_SPEC }, // ── Gemini 3 Flash series ─────────────────────────────────────── "gemini-3-flash": { @@ -282,20 +272,6 @@ export const MODEL_SPECS: Record = { aliases: ["gemini-3-pro-low"], }, - // ── Gemini 3.5 Flash ───────────────────────────────────────────── - // #10286: the base Google AI Studio model DOES support reasoning (it has - // an effort-tier alias gemini-3.5-flash-high) — override the shared spec's - // supportsThinking:false here only. Do NOT flip GEMINI_35_FLASH_MODEL_SPEC - // itself: it is also spread into the Antigravity flash-tier aliases - // (gemini-3.5-flash-low/-extra-low, gemini-3-flash-agent, gemini-3.6-flash-*) - // which reject client-supplied thinking params because the model id itself - // selects the reasoning tier upstream. - "gemini-3.5-flash": { - ...GEMINI_35_FLASH_MODEL_SPEC, - supportsThinking: true, - aliases: ["gemini-3.5-flash-high"], - }, - // ── Claude Opus 4.5 ───────────────────────────────────────────── "claude-opus-4-5": { maxOutputTokens: 32768, diff --git a/tests/unit/agy-provider.test.ts b/tests/unit/agy-provider.test.ts index dc91937044..4c7c7152fb 100644 --- a/tests/unit/agy-provider.test.ts +++ b/tests/unit/agy-provider.test.ts @@ -57,6 +57,7 @@ test("agy ships its own live callable model catalog", () => { assert.ok(!ids.includes("gemini-3.6-flash-low")); assert.ok(!ids.includes("gemini-3.6-flash-medium")); assert.ok(!ids.includes("gemini-3.6-flash-high")); + assert.ok(!ids.includes("gemini-3.5-flash")); assert.ok(!ids.includes("gemini-3.5-flash-extra-low")); assert.ok(!ids.includes("gemini-3.5-flash-low")); assert.ok(!ids.includes("gemini-3-flash-agent")); @@ -87,6 +88,7 @@ test("agy model helpers resolve catalog ids and display names", () => { assert.equal(isUserCallableAgyModelId("gemini-3.6-flash-low"), false); assert.equal(isUserCallableAgyModelId("gemini-3.6-flash-medium"), false); assert.equal(isUserCallableAgyModelId("gemini-3.6-flash-high"), false); + assert.equal(isUserCallableAgyModelId("gemini-3.5-flash"), false); assert.equal(isUserCallableAgyModelId("gemini-3.5-flash-extra-low"), false); assert.equal(isUserCallableAgyModelId("gemini-3.5-flash-low"), false); assert.equal(isUserCallableAgyModelId("gemini-3-flash-agent"), false); diff --git a/tests/unit/antigravity-429-quota-tdd.test.ts b/tests/unit/antigravity-429-quota-tdd.test.ts index c085adf2c3..da4fafdd61 100644 --- a/tests/unit/antigravity-429-quota-tdd.test.ts +++ b/tests/unit/antigravity-429-quota-tdd.test.ts @@ -74,7 +74,7 @@ test("TDD S3: checkFallbackError extracts retry hint for oauth providers even if 429, errorText, 0, - "gemini-3.5-flash", + "gemini-3.7-flash", "antigravity", // which uses oauth provider profile (useUpstreamRetryHints: false) null ); diff --git a/tests/unit/antigravity-model-aliases.test.ts b/tests/unit/antigravity-model-aliases.test.ts index 82ad399215..5f0ab5c0b4 100644 --- a/tests/unit/antigravity-model-aliases.test.ts +++ b/tests/unit/antigravity-model-aliases.test.ts @@ -31,6 +31,7 @@ const RETIRED_FLASH_IDS = [ "gemini-3.6-flash-low", "gemini-3.6-flash-medium", "gemini-3.6-flash-high", + "gemini-3.5-flash", "gemini-3.5-flash-extra-low", "gemini-3.5-flash-low", "gemini-3-flash-agent", diff --git a/tests/unit/antigravity-retired-public-models.test.ts b/tests/unit/antigravity-retired-public-models.test.ts index 890509bb73..55885e3f2e 100644 --- a/tests/unit/antigravity-retired-public-models.test.ts +++ b/tests/unit/antigravity-retired-public-models.test.ts @@ -21,6 +21,7 @@ const RETIRED_PUBLIC_MODELS = [ "gemini-3.6-flash-medium", "gemini-3.6-flash-low", "gemini-3-flash-agent", + "gemini-3.5-flash", "gemini-3.5-flash-low", "gemini-3.5-flash-extra-low", "gemini-2.5-pro", diff --git a/tests/unit/conol-web.test.ts b/tests/unit/conol-web.test.ts index 8ed8f2b9cb..1280cc2b0d 100644 --- a/tests/unit/conol-web.test.ts +++ b/tests/unit/conol-web.test.ts @@ -14,6 +14,7 @@ import { } from "../../open-sse/executors/conol-web.ts"; import { CONOL_FALLBACK_MODELS, + CONOL_FALLBACK_MODEL_PRESETS, clampConolEffort, parseConolAgentServers, resolveConolModelSelection, @@ -27,6 +28,11 @@ import { getResolvedModelCapabilities } from "../../src/lib/modelCapabilities.ts const SESSION_COOKIE_NAME = "__Secure-better-auth.session_token"; describe("Conol web provider", () => { + it("routes the Flash preset multimodal path to Gemini 3.7", () => { + const flashPreset = CONOL_FALLBACK_MODEL_PRESETS.find((preset) => preset.id === "flash"); + assert.equal(flashPreset?.multimodal, "google/gemini-3.7-flash"); + }); + it("normalizes raw, full-header, JSON, and provider-data credentials", () => { assert.equal(normalizeConolCookie("token-value"), `${SESSION_COOKIE_NAME}=token-value`); assert.equal( diff --git a/tests/unit/copilot-gemini-claude-route-no-responses.test.ts b/tests/unit/copilot-gemini-claude-route-no-responses.test.ts index b0fd5116dd..a03a21f7bd 100644 --- a/tests/unit/copilot-gemini-claude-route-no-responses.test.ts +++ b/tests/unit/copilot-gemini-claude-route-no-responses.test.ts @@ -55,7 +55,7 @@ describe("GithubExecutor — Gemini/Claude must never hit /responses (port 9rout it("routes registered Gemini Copilot models to chat/completions", () => { const exec = new GithubExecutor(); - for (const id of ["gemini-3.1-pro-preview", "gemini-3.5-flash"]) { + for (const id of ["gemini-3.1-pro-preview", "gemini-3.7-flash"]) { assert.equal(exec.buildUrl(id, false), CHAT_URL, `${id} must route to chat/completions`); } }); diff --git a/tests/unit/cursor-registry-claude-families.test.ts b/tests/unit/cursor-registry-claude-families.test.ts index 58df088b38..46a41970a0 100644 --- a/tests/unit/cursor-registry-claude-families.test.ts +++ b/tests/unit/cursor-registry-claude-families.test.ts @@ -8,6 +8,10 @@ function modelIds(): Set { return new Set(cursorProvider.models.map((m) => m.id)); } +test("cursor registry excludes retired Gemini 3.5 Flash", () => { + assert.equal(modelIds().has("gemini-3.5-flash"), false); +}); + test("cursor registry includes Claude Opus 4.8 effort + thinking + fast variants", () => { const ids = modelIds(); for (const effort of EFFORTS) { diff --git a/tests/unit/executor-promptql.test.ts b/tests/unit/executor-promptql.test.ts index 07ebfb94c6..97f52dcdc8 100644 --- a/tests/unit/executor-promptql.test.ts +++ b/tests/unit/executor-promptql.test.ts @@ -56,7 +56,7 @@ describe("PromptQl — registry consistency", () => { it("registers a model catalog via getModelsByProviderId", () => { const catalog = getModelsByProviderId("promptql"); assert.ok(catalog.length >= 5); - assert.ok(catalog.some((m) => m.id === "gemini-3.5-flash" || m.id.includes("gemini"))); + assert.ok(catalog.some((m) => m.id === "gemini-3.7-flash" || m.id.includes("gemini"))); assert.ok(catalog.some((m) => m.id.includes("gpt-5.6") || m.id.includes("fable"))); }); }); @@ -209,7 +209,10 @@ describe("PromptQl — helpers", () => { }); it("resolves model slugs and prefixes", () => { - assert.equal(models.clientFacingPromptQlModelId("promptql/gemini-3.5-flash"), "gemini-3.5-flash"); + assert.equal( + models.clientFacingPromptQlModelId("promptql/gemini-3.7-flash"), + "gemini-3.7-flash" + ); assert.equal(models.clientFacingPromptQlModelId("pql/gpt-5.6-sol"), "gpt-5.6-sol"); const r = models.resolvePromptQlModel("Claude Fable 5"); assert.ok(r); @@ -265,7 +268,7 @@ describe("PromptQlExecutor — auth / validation", () => { it("returns 401 when no token is supplied", async () => { const executor = new mod.PromptQlExecutor(); const result = await executor.execute({ - model: "gemini-3.5-flash", + model: "gemini-3.7-flash", body: { messages: [{ role: "user", content: "hi" }] }, stream: false, credentials: {}, @@ -279,7 +282,7 @@ describe("PromptQlExecutor — auth / validation", () => { it("returns 400 when no user message is present", async () => { const executor = new mod.PromptQlExecutor(); const result = await executor.execute({ - model: "gemini-3.5-flash", + model: "gemini-3.7-flash", body: { messages: [{ role: "assistant", content: "hi" }] }, stream: false, credentials: { apiKey: sampleJwt }, @@ -614,7 +617,7 @@ describe("PromptQlExecutor — mocked GraphQL turn", () => { try { const executor = new mod.PromptQlExecutor(); const result = await executor.execute({ - model: "gemini-3.5-flash", + model: "gemini-3.7-flash", body: { messages: [{ role: "user", content: "ping" }] }, stream: false, credentials: { apiKey: sampleJwt }, @@ -628,7 +631,7 @@ describe("PromptQlExecutor — mocked GraphQL turn", () => { }; assert.equal(json.choices[0]!.message.content, "HELLO-PQL"); assert.equal(json.promptql_thread_id, "thread-1"); - assert.equal(json.model, "gemini-3.5-flash"); + assert.equal(json.model, "gemini-3.7-flash"); assert.ok(call >= 2); assert.equal(result.response.headers.get("X-PromptQL-Thread-Id"), "thread-1"); } finally { diff --git a/tests/unit/gemini-3-5-flash-thinking.test.ts b/tests/unit/gemini-3-5-flash-thinking.test.ts deleted file mode 100644 index 80636a0b03..0000000000 --- a/tests/unit/gemini-3-5-flash-thinking.test.ts +++ /dev/null @@ -1,76 +0,0 @@ -// Regression test for #10286: gemini-3.5-flash was incorrectly marked -// supportsThinking:false, causing a spurious pre-provider HTTP 400 for any -// request with reasoning_effort set, even though the base Google AI Studio -// model supports reasoning (it has an effort-tier alias gemini-3.5-flash-high). -import test from "node:test"; -import assert from "node:assert/strict"; -import fs from "node:fs"; -import os from "node:os"; -import path from "node:path"; - -const TEST_DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-repro-10286-")); -process.env.DATA_DIR = TEST_DATA_DIR; -process.env.API_KEY_SECRET = process.env.API_KEY_SECRET || "test-repro-10286-secret"; - -const caps = await import("../../src/lib/modelCapabilities.ts"); -const core = await import("../../src/lib/db/core.ts"); -const rulesDb = await import("../../src/lib/db/reasoningRoutingRules.ts"); -const policy = await import("../../src/lib/reasoningRouting/policy.ts"); - -async function resetStorage() { - core.resetDbInstance(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); - fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); - rulesDb.invalidateReasoningRoutingRuleCache(); -} - -function ruleInput(patch: Record = {}) { - return { - name: "Enable thinking on gemini-3.5-flash", - description: "", - scope: "global", - apiKeyId: null, - comboId: null, - connectionId: null, - modelPattern: "gemini-3.5-flash", - sourceEffort: "any", - requestTags: [], - tagMatchMode: "any", - effortMode: "inherit", - targetEffort: null, - targetKind: "keep", - targetModel: null, - targetComboId: null, - budgetAction: "preserve", - budgetTokens: null, - priority: 0, - enabled: true, - ...patch, - }; -} - -test.beforeEach(resetStorage); -test.after(async () => { - await resetStorage(); - fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); -}); - -test("gemini-3.5-flash (AI Studio provider) resolves as thinking-capable", () => { - const resolved = caps.getResolvedModelCapabilities({ - provider: "gemini", - model: "gemini-3.5-flash", - }); - assert.equal(resolved.supportsThinking, true); -}); - -test("reasoning_effort 'high' on gemini-3.5-flash is NOT rejected by routing policy", async () => { - await rulesDb.createReasoningRoutingRule(ruleInput()); - const decision = await policy.resolveReasoningRoutingRule({ - sourceModel: "gemini/gemini-3.5-flash", - sourceModelAliases: ["gemini-3.5-flash"], - sourceEffort: "high", - hasReasoningSignal: true, - }); - assert.ok(decision, "a matching rule must produce a decision"); - assert.equal(decision.capability, "supported"); -}); diff --git a/tests/unit/gemini-codex-encrypted-tool-schema.test.ts b/tests/unit/gemini-codex-encrypted-tool-schema.test.ts index 6d76a0ac84..58498ced06 100644 --- a/tests/unit/gemini-codex-encrypted-tool-schema.test.ts +++ b/tests/unit/gemini-codex-encrypted-tool-schema.test.ts @@ -71,7 +71,7 @@ test("OpenAI -> Gemini request strips encrypted from Codex collaboration tool pa ], }; - const result = openaiToGeminiRequest("gemini-3.5-flash-low", body, false) as { + const result = openaiToGeminiRequest("gemini-3.7-flash-low", body, false) as { tools?: Array<{ functionDeclarations?: Array<{ parameters: unknown }> }>; }; diff --git a/tests/unit/gemini-models-parser.test.ts b/tests/unit/gemini-models-parser.test.ts index 6d3dfbd752..0e07a6f7a1 100644 --- a/tests/unit/gemini-models-parser.test.ts +++ b/tests/unit/gemini-models-parser.test.ts @@ -14,6 +14,16 @@ const SAMPLE = { supportedGenerationMethods: ["generateContent", "countTokens", "batchGenerateContent"], thinking: true, }, + { + name: "models/gemini-3.5-flash", + displayName: "Gemini 3.5 Flash", + supportedGenerationMethods: ["generateContent"], + }, + { + name: "models/gemini-3.5-flash-lite", + displayName: "Gemini 3.5 Flash Lite", + supportedGenerationMethods: ["generateContent"], + }, { name: "models/gemini-3-pro-image-preview", displayName: "Gemini 3 Pro Image Preview", @@ -48,6 +58,12 @@ test("parseGeminiModelsList strips the models/ prefix and maps display name", () assert.deepEqual(flash!.supportedEndpoints, ["chat"]); }); +test("parseGeminiModelsList excludes retired Gemini 3.5 Flash but keeps Flash Lite", () => { + const ids = parseGeminiModelsList(SAMPLE).map((model) => model.id); + assert.equal(ids.includes("gemini-3.5-flash"), false); + assert.equal(ids.includes("gemini-3.5-flash-lite"), true); +}); + test("parseGeminiModelsList maps generateContent image models to the chat endpoint", () => { const models = parseGeminiModelsList(SAMPLE); const proImage = models.find((m) => m.id === "gemini-3-pro-image-preview"); diff --git a/tests/unit/gemini-strict-tool-schema.test.ts b/tests/unit/gemini-strict-tool-schema.test.ts index 9745353527..0aaac46bf0 100644 --- a/tests/unit/gemini-strict-tool-schema.test.ts +++ b/tests/unit/gemini-strict-tool-schema.test.ts @@ -56,7 +56,7 @@ test("OpenAI -> Gemini request strips strict from OpenAI-style function tool par ], }; - const result = openaiToGeminiRequest("gemini-3.5-flash-low", body, false) as { + const result = openaiToGeminiRequest("gemini-3.7-flash-low", body, false) as { tools?: Array<{ functionDeclarations?: Array<{ parameters: unknown }> }>; }; diff --git a/tests/unit/ghe-copilot.test.ts b/tests/unit/ghe-copilot.test.ts index 333c5b7f50..d71a1fd444 100644 --- a/tests/unit/ghe-copilot.test.ts +++ b/tests/unit/ghe-copilot.test.ts @@ -99,7 +99,7 @@ test("buildUrl uses chat/completions endpoint for gemini models", () => { }; // Gemini has no native shim on Copilot — it stays on /chat/completions. assert.strictEqual( - executor.buildUrl("gemini-3.5-flash", true, 0, credentials), + executor.buildUrl("gemini-3.7-flash", true, 0, credentials), "https://ghe.company.com/chat/completions" ); }); diff --git a/tests/unit/managed-model-import.test.ts b/tests/unit/managed-model-import.test.ts index 24f5a6554c..e6e3bc04b7 100644 --- a/tests/unit/managed-model-import.test.ts +++ b/tests/unit/managed-model-import.test.ts @@ -279,6 +279,7 @@ test("antigravity sync dynamically builds and saves mitmAlias mappings", async ( mode: "sync", fetchedModels: [ { id: "gemini-3.5-flash", name: "Gemini 3.5 Flash" }, + { id: "gemini-3.7-flash-high", name: "Gemini 3.7 Flash High" }, { id: "custom-antigravity-model", name: "Custom Antigravity Model" }, ], }); @@ -289,8 +290,13 @@ test("antigravity sync dynamically builds and saves mitmAlias mappings", async ( const mitmMappings = await modelsDb.getMitmAlias("antigravity"); console.log("MITM MAPPINGS IN TEST:", mitmMappings); - // Should contain standard mapping - assert.equal(mitmMappings["gemini-3.5-flash"], "antigravity/gemini-3.5-flash"); + // Retired models reported by upstream must not be imported or mapped. + assert.equal(mitmMappings["gemini-3.5-flash"], undefined); + assert.equal( + models.some((model) => model.id === "gemini-3.5-flash"), + false + ); + assert.equal(mitmMappings["gemini-3.7-flash-high"], "antigravity/gemini-3.7-flash-high"); assert.equal(mitmMappings["custom-antigravity-model"], "antigravity/custom-antigravity-model"); // Removed Antigravity 2.0 preview/agent aliases must not be reintroduced. diff --git a/tests/unit/model-capabilities-registry.test.ts b/tests/unit/model-capabilities-registry.test.ts index 80d4efd7aa..ce75e9342a 100644 --- a/tests/unit/model-capabilities-registry.test.ts +++ b/tests/unit/model-capabilities-registry.test.ts @@ -154,21 +154,14 @@ test("unknown models keep maxOutputTokens null instead of using a generic defaul ); }); -test("provider-neutral Gemini 3.5 tier IDs retain their non-thinking capabilities", () => { +test("retired Gemini 3.5 Flash IDs have no provider-neutral model specs", () => { for (const modelId of [ + "gemini-3.5-flash", "gemini-3.5-flash-extra-low", "gemini-3.5-flash-low", "gemini-3-flash-agent", ]) { - const spec = MODEL_SPECS[modelId]; - assert.ok(spec, `missing exact MODEL_SPECS entry for ${modelId}`); - const capabilities = modelCapabilities.getResolvedModelCapabilities(modelId); - assert.equal(capabilities.contextWindow, 1048576, modelId); - assert.equal(capabilities.maxOutputTokens, 65536, modelId); - // These ids encode the upstream reasoning tier and do not accept a client-supplied effort. - assert.equal(capabilities.supportsThinking, false, modelId); - assert.equal(capabilities.supportsTools, true, modelId); - assert.equal(capabilities.supportsVision, true, modelId); + assert.equal(MODEL_SPECS[modelId], undefined, modelId); } }); diff --git a/tests/unit/models-catalog-route.test.ts b/tests/unit/models-catalog-route.test.ts index 7c1887f8a6..7c2ed395d9 100644 --- a/tests/unit/models-catalog-route.test.ts +++ b/tests/unit/models-catalog-route.test.ts @@ -702,6 +702,7 @@ test("v1 models catalog exposes current Antigravity aliases without retired mode assert.equal(ids.has("antigravity/gemini-3.6-flash-high"), false); assert.equal(ids.has("antigravity/gemini-3.6-flash-medium"), false); assert.equal(ids.has("antigravity/gemini-3.6-flash-low"), false); + assert.equal(ids.has("antigravity/gemini-3.5-flash"), false); assert.equal(ids.has("antigravity/gemini-3.5-flash-extra-low"), false); assert.equal(ids.has("antigravity/gemini-3.5-flash-low"), false); assert.equal(ids.has("antigravity/gemini-3-flash-agent"), false); diff --git a/tests/unit/omniroute-response-meta.test.ts b/tests/unit/omniroute-response-meta.test.ts index 0b7bbd8e2b..50b13278b2 100644 --- a/tests/unit/omniroute-response-meta.test.ts +++ b/tests/unit/omniroute-response-meta.test.ts @@ -60,7 +60,7 @@ test("buildOmniRouteResponseMetaHeaders keeps ASCII model header values unchange }); test("buildOmniRouteResponseMetaHeaders percent-encodes non-ASCII model header values", () => { - const model = "free-mix/[假流式]gemini-3.5-flash"; + const model = "free-mix/[假流式]gemini-3.7-flash"; const headers = buildOmniRouteResponseMetaHeaders({ provider: "openai", model, diff --git a/tests/unit/provider-limits-ui.test.ts b/tests/unit/provider-limits-ui.test.ts index 5074bcd04e..3298a057b3 100644 --- a/tests/unit/provider-limits-ui.test.ts +++ b/tests/unit/provider-limits-ui.test.ts @@ -288,7 +288,7 @@ test("hidden provider models are filtered from per-model quota rows", () => { }); const hidden = providerLimitUtils.collectHiddenQuotaModelIds("antigravity", { models: [{ id: "antigravity/gpt-oss-120b-medium", isHidden: true }], - modelCompatOverrides: [{ id: "gemini-3.5-flash", isHidden: true }], + modelCompatOverrides: [{ id: "gemini-3.7-flash", isHidden: true }], }); const visible = providerLimitUtils.filterHiddenModelQuotas("antigravity", quotas, hidden); diff --git a/tests/unit/sse-parser.test.ts b/tests/unit/sse-parser.test.ts index 36fcd7deda..5c8bfe627a 100644 --- a/tests/unit/sse-parser.test.ts +++ b/tests/unit/sse-parser.test.ts @@ -394,7 +394,7 @@ test("parseSSEToGeminiResponse extracts tool calls from textual format", () => { })}`, ].join("\n"); - const parsed = parseSSEToGeminiResponse(rawSSE, "gemini-3.5-flash-low"); + const parsed = parseSSEToGeminiResponse(rawSSE, "gemini-3.7-flash-low"); assert.ok(parsed); assert.equal(parsed.choices[0].finish_reason, "tool_calls"); diff --git a/tests/unit/stream-utils.test.ts b/tests/unit/stream-utils.test.ts index 046e793b61..7ef51687d1 100644 --- a/tests/unit/stream-utils.test.ts +++ b/tests/unit/stream-utils.test.ts @@ -232,14 +232,14 @@ test("createSSEStream passthrough converts textual tool-call content into struct id: "chatcmpl_textual_tool", object: "chat.completion.chunk", created: 1, - model: "antigravity/gemini-3.5-flash-low", + model: "antigravity/gemini-3.7-flash-low", choices: [{ index: 0, delta: { role: "assistant", content: toolText } }], })}\n\n`, `data: ${JSON.stringify({ id: "chatcmpl_textual_tool", object: "chat.completion.chunk", created: 1, - model: "antigravity/gemini-3.5-flash-low", + model: "antigravity/gemini-3.7-flash-low", choices: [{ index: 0, delta: {}, finish_reason: "stop" }], })}\n\n`, ], @@ -247,7 +247,7 @@ test("createSSEStream passthrough converts textual tool-call content into struct mode: "passthrough", sourceFormat: FORMATS.OPENAI, provider: "antigravity", - model: "antigravity/gemini-3.5-flash-low", + model: "antigravity/gemini-3.7-flash-low", body: { messages: [{ role: "user", content: "inspect db" }], }, @@ -284,21 +284,21 @@ test("createSSEStream passthrough converts split textual tool-call content at co id: "chatcmpl_split_textual_tool", object: "chat.completion.chunk", created: 1, - model: "antigravity/gemini-3.5-flash-low", + model: "antigravity/gemini-3.7-flash-low", choices: [{ index: 0, delta: { role: "assistant", content: chunks[0] } }], })}\n\n`, `data: ${JSON.stringify({ id: "chatcmpl_split_textual_tool", object: "chat.completion.chunk", created: 1, - model: "antigravity/gemini-3.5-flash-low", + model: "antigravity/gemini-3.7-flash-low", choices: [{ index: 0, delta: { content: chunks[1] } }], })}\n\n`, `data: ${JSON.stringify({ id: "chatcmpl_split_textual_tool", object: "chat.completion.chunk", created: 1, - model: "antigravity/gemini-3.5-flash-low", + model: "antigravity/gemini-3.7-flash-low", choices: [{ index: 0, delta: {}, finish_reason: "stop" }], })}\n\n`, ], @@ -306,7 +306,7 @@ test("createSSEStream passthrough converts split textual tool-call content at co mode: "passthrough", sourceFormat: FORMATS.OPENAI, provider: "antigravity", - model: "antigravity/gemini-3.5-flash-low", + model: "antigravity/gemini-3.7-flash-low", body: { messages: [{ role: "user", content: "inspect db" }] }, onComplete(payload) { onCompletePayload = payload; @@ -340,28 +340,28 @@ test("createSSEStream passthrough handles textual tool-call content split inside id: "chatcmpl_split_prefix_textual_tool", object: "chat.completion.chunk", created: 1, - model: "antigravity/gemini-3.5-flash-low", + model: "antigravity/gemini-3.7-flash-low", choices: [{ index: 0, delta: { role: "assistant", content: chunks[0] } }], })}\n\n`, `data: ${JSON.stringify({ id: "chatcmpl_split_prefix_textual_tool", object: "chat.completion.chunk", created: 1, - model: "antigravity/gemini-3.5-flash-low", + model: "antigravity/gemini-3.7-flash-low", choices: [{ index: 0, delta: { content: chunks[1] } }], })}\n\n`, `data: ${JSON.stringify({ id: "chatcmpl_split_prefix_textual_tool", object: "chat.completion.chunk", created: 1, - model: "antigravity/gemini-3.5-flash-low", + model: "antigravity/gemini-3.7-flash-low", choices: [{ index: 0, delta: { content: chunks[2] } }], })}\n\n`, `data: ${JSON.stringify({ id: "chatcmpl_split_prefix_textual_tool", object: "chat.completion.chunk", created: 1, - model: "antigravity/gemini-3.5-flash-low", + model: "antigravity/gemini-3.7-flash-low", choices: [{ index: 0, delta: {}, finish_reason: "stop" }], })}\n\n`, ], @@ -369,7 +369,7 @@ test("createSSEStream passthrough handles textual tool-call content split inside mode: "passthrough", sourceFormat: FORMATS.OPENAI, provider: "antigravity", - model: "antigravity/gemini-3.5-flash-low", + model: "antigravity/gemini-3.7-flash-low", body: { messages: [{ role: "user", content: "inspect db" }] }, onComplete(payload) { onCompletePayload = payload; @@ -515,14 +515,14 @@ Arguments: {"path":"/opt/OmniRoute/src","target":"files"}`; id: "chatcmpl_unknown_textual_tool", object: "chat.completion.chunk", created: 1, - model: "antigravity/gemini-3.5-flash-low", + model: "antigravity/gemini-3.7-flash-low", choices: [{ index: 0, delta: { role: "assistant", content: toolText } }], })}\n\n`, `data: ${JSON.stringify({ id: "chatcmpl_unknown_textual_tool", object: "chat.completion.chunk", created: 1, - model: "antigravity/gemini-3.5-flash-low", + model: "antigravity/gemini-3.7-flash-low", choices: [{ index: 0, delta: {}, finish_reason: "stop" }], })}\n\n`, ], @@ -530,7 +530,7 @@ Arguments: {"path":"/opt/OmniRoute/src","target":"files"}`; mode: "passthrough", sourceFormat: FORMATS.OPENAI, provider: "antigravity", - model: "antigravity/gemini-3.5-flash-low", + model: "antigravity/gemini-3.7-flash-low", body: { messages: [{ role: "user", content: "inspect files" }], tools: [ @@ -561,14 +561,14 @@ test("createSSEStream passthrough suppresses malformed textual tool-call content id: "chatcmpl_malformed_textual_tool", object: "chat.completion.chunk", created: 1, - model: "antigravity/gemini-3.5-flash-low", + model: "antigravity/gemini-3.7-flash-low", choices: [{ index: 0, delta: { role: "assistant", content: malformedToolText } }], })}\n\n`, `data: ${JSON.stringify({ id: "chatcmpl_malformed_textual_tool", object: "chat.completion.chunk", created: 1, - model: "antigravity/gemini-3.5-flash-low", + model: "antigravity/gemini-3.7-flash-low", choices: [{ index: 0, delta: {}, finish_reason: "stop" }], })}\n\n`, ], @@ -576,7 +576,7 @@ test("createSSEStream passthrough suppresses malformed textual tool-call content mode: "passthrough", sourceFormat: FORMATS.OPENAI, provider: "antigravity", - model: "antigravity/gemini-3.5-flash-low", + model: "antigravity/gemini-3.7-flash-low", body: { messages: [{ role: "user", content: "inspect db" }] }, onComplete(payload) { onCompletePayload = payload; @@ -617,7 +617,7 @@ test("createSSEStream suppresses malformed compact textual tool-call content", a targetFormat: FORMATS.ANTIGRAVITY, sourceFormat: FORMATS.OPENAI, provider: "antigravity", - model: "antigravity/gemini-3.5-flash-low", + model: "antigravity/gemini-3.7-flash-low", body: { messages: [{ role: "user", content: "inspect files" }] }, onComplete(payload) { onCompletePayload = payload; @@ -1024,7 +1024,7 @@ Arguments: {"command":"systemctl status omniroute"}`; response: { id: "resp_textual_tool", object: "response", - model: "antigravity/gemini-3.5-flash-low", + model: "antigravity/gemini-3.7-flash-low", status: "completed", output: [], usage: { input_tokens: 10, output_tokens: 4, total_tokens: 14 }, @@ -1038,7 +1038,7 @@ Arguments: {"command":"systemctl status omniroute"}`; sourceFormat: FORMATS.OPENAI_RESPONSES, clientResponseFormat: FORMATS.OPENAI_RESPONSES, provider: "antigravity", - model: "antigravity/gemini-3.5-flash-low", + model: "antigravity/gemini-3.7-flash-low", body: { input: "check service", tools: [{ type: "function", name: "terminal", parameters: { type: "object" } }], diff --git a/tests/unit/t28-model-catalog-updates.test.ts b/tests/unit/t28-model-catalog-updates.test.ts index 6c44ebcf45..600cd44229 100644 --- a/tests/unit/t28-model-catalog-updates.test.ts +++ b/tests/unit/t28-model-catalog-updates.test.ts @@ -30,6 +30,7 @@ test("T28: antigravity static catalog exposes only callable Gemini tier IDs", () assert.ok(!staticIds.includes("gemini-3.6-flash-high")); assert.ok(!staticIds.includes("gemini-3.6-flash-medium")); assert.ok(!staticIds.includes("gemini-3.6-flash-low")); + assert.ok(!staticIds.includes("gemini-3.5-flash")); assert.ok(!staticIds.includes("gemini-3.5-flash-extra-low")); assert.ok(!staticIds.includes("gemini-3.5-flash-low")); assert.ok(!staticIds.includes("gemini-3-flash-agent")); diff --git a/tests/unit/translator-openai-to-gemini.test.ts b/tests/unit/translator-openai-to-gemini.test.ts index cf81c24de1..2111f23f64 100644 --- a/tests/unit/translator-openai-to-gemini.test.ts +++ b/tests/unit/translator-openai-to-gemini.test.ts @@ -607,7 +607,7 @@ test("OpenAI -> Antigravity wraps Gemini requests in a Cloud Code envelope", () test("OpenAI -> Antigravity Gemini omits signature-less historical tool calls and keeps response context", () => { const result = openaiToAntigravityRequest( - "gemini-3.5-flash-low", + "gemini-3.7-flash-low", { messages: [ { role: "user", content: "Update todo" }, @@ -686,7 +686,7 @@ test("OpenAI -> Antigravity Gemini omits signature-less historical tool calls an test("OpenAI -> Antigravity preserves multiple signature-less historical tool responses as context", () => { const result = openaiToAntigravityRequest( - "gemini-3.5-flash-low", + "gemini-3.7-flash-low", { messages: [ { role: "user", content: "Inspect OmniRoute config" }, @@ -747,7 +747,7 @@ test("OpenAI -> Antigravity preserves signed Gemini tool calls in native form", storeGeminiThoughtSignature(buildGeminiThoughtSignatureKey(ns, toolId), "SIG_AG_SIGNED_XYZ"); const result = openaiToAntigravityRequest( - "gemini-3.5-flash-low", + "gemini-3.7-flash-low", { messages: [ { role: "user", content: "Read status" }, @@ -787,7 +787,7 @@ test("OpenAI -> Antigravity preserves signed Gemini tool calls in native form", test("OpenAI -> Antigravity escapes signature-less tool response context content", () => { const result = openaiToAntigravityRequest( - "gemini-3.5-flash-low", + "gemini-3.7-flash-low", { messages: [ { role: "user", content: "Inspect previous output" }, diff --git a/tests/unit/translator-resp-gemini-to-openai.test.ts b/tests/unit/translator-resp-gemini-to-openai.test.ts index 108eb8a62b..0b5425562c 100644 --- a/tests/unit/translator-resp-gemini-to-openai.test.ts +++ b/tests/unit/translator-resp-gemini-to-openai.test.ts @@ -346,7 +346,7 @@ test("Gemini stream: converts textual Tool call block to structured tool_calls", const result = geminiToOpenAIResponse( { responseId: "resp-textual-tool", - modelVersion: "gemini-3.5-flash-low", + modelVersion: "gemini-3.7-flash-low", candidates: [ { content: { @@ -386,7 +386,7 @@ test("Gemini stream: routes textual reasoning tags to reasoning_content before t const result = geminiToOpenAIResponse( { responseId: "resp-textual-thought-tool", - modelVersion: "gemini-3.5-flash-high", + modelVersion: "gemini-3.7-flash-high", candidates: [ { content: { @@ -431,7 +431,7 @@ test("Gemini stream: keeps textual reasoning hidden across split chunks", () => const first = geminiToOpenAIResponse( { responseId: "resp-split-thought", - modelVersion: "gemini-3.5-flash-high", + modelVersion: "gemini-3.7-flash-high", candidates: [{ content: { parts: [{ text: "§54§ const fourth = geminiToOpenAIResponse( { responseId: "resp-split-thought", - modelVersion: "gemini-3.5-flash-high", + modelVersion: "gemini-3.7-flash-high", candidates: [{ content: { parts: [{ text: "ught>Visible answer" }] } }], }, state @@ -498,7 +498,7 @@ test("Gemini stream: converts prefixed textual Tool call block with zero-width c const result = geminiToOpenAIResponse( { responseId: "resp-textual-tool-prefixed", - modelVersion: "gemini-3.5-flash-low", + modelVersion: "gemini-3.7-flash-low", candidates: [ { content: { @@ -612,7 +612,7 @@ test("Gemini stream: unwraps native functionCall args when emitted as JSON strin const result = geminiToOpenAIResponse( { responseId: "resp-native-tool-json-string", - modelVersion: "gemini-3.5-flash-low", + modelVersion: "gemini-3.7-flash-low", candidates: [ { content: { @@ -648,7 +648,7 @@ test("Gemini stream: converts JSON-string encoded textual Tool call arguments", const result = geminiToOpenAIResponse( { responseId: "resp-textual-tool-json-string", - modelVersion: "gemini-3.5-flash-low", + modelVersion: "gemini-3.7-flash-low", candidates: [ { content: { @@ -685,7 +685,7 @@ test("Gemini stream: suppresses malformed textual Tool call marker", () => { const result = geminiToOpenAIResponse( { responseId: "resp-textual-tool-malformed", - modelVersion: "gemini-3.5-flash-low", + modelVersion: "gemini-3.7-flash-low", candidates: [ { content: { @@ -717,7 +717,7 @@ test("Gemini stream: handles textual Tool call block split across chunks", () => const state = createStreamingState(); const chunk1 = { responseId: "resp-split", - modelVersion: "gemini-3.5-flash-low", + modelVersion: "gemini-3.7-flash-low", candidates: [ { content: { @@ -732,7 +732,7 @@ test("Gemini stream: handles textual Tool call block split across chunks", () => }; const chunk2 = { responseId: "resp-split", - modelVersion: "gemini-3.5-flash-low", + modelVersion: "gemini-3.7-flash-low", candidates: [ { content: { @@ -768,7 +768,7 @@ test("Gemini stream: does not swallow false positive textual tool call in backti const state = createStreamingState(); const chunk1 = { responseId: "resp-false-positive", - modelVersion: "gemini-3.5-flash-low", + modelVersion: "gemini-3.7-flash-low", candidates: [ { content: { @@ -783,7 +783,7 @@ test("Gemini stream: does not swallow false positive textual tool call in backti }; const chunk2 = { responseId: "resp-false-positive", - modelVersion: "gemini-3.5-flash-low", + modelVersion: "gemini-3.7-flash-low", candidates: [ { content: { @@ -817,7 +817,7 @@ test("Gemini stream: does not swallow terminated trailing false positive textual const state = createStreamingState(); const chunk1 = { responseId: "resp-false-positive-terminated", - modelVersion: "gemini-3.5-flash-low", + modelVersion: "gemini-3.7-flash-low", candidates: [ { content: { @@ -842,7 +842,7 @@ test("Gemini stream: flushes left part before textual tool call candidate and fl const state = createStreamingState() as any; const chunk1 = { responseId: "resp-test-flush-left", - modelVersion: "gemini-3.5-flash-low", + modelVersion: "gemini-3.7-flash-low", candidates: [ { content: { @@ -884,7 +884,7 @@ test("Gemini stream: splits mid-stream partial candidate but preserves tool call const state = createStreamingState() as any; const chunk1 = { responseId: "resp-test-split-candidate", - modelVersion: "gemini-3.5-flash-low", + modelVersion: "gemini-3.7-flash-low", candidates: [ { content: { @@ -932,7 +932,7 @@ test("Gemini stream: index mismatch regression test with zero-width characters i const result = geminiToOpenAIResponse( { responseId: "resp-textual-tool-index-mismatch", - modelVersion: "gemini-3.5-flash-low", + modelVersion: "gemini-3.7-flash-low", candidates: [ { content: { @@ -964,7 +964,7 @@ test("Gemini stream: partial tool call with (empty) prefix check at chunk end do const state = createStreamingState(); const chunk1 = { responseId: "resp-empty-leak", - modelVersion: "gemini-3.5-flash-low", + modelVersion: "gemini-3.7-flash-low", candidates: [ { content: { @@ -1011,7 +1011,7 @@ test("Gemini stream: parses textual tool call that starts in a subsequent chunk const state = createStreamingState() as any; const chunk1 = { responseId: "resp-test-after-prose", - modelVersion: "gemini-3.5-flash-low", + modelVersion: "gemini-3.7-flash-low", candidates: [ { content: { @@ -1060,7 +1060,7 @@ test("Gemini stream: checks lastParen before lastBracket when identifying partia // Имитируем чанк, который кончается на частичный "(empty)[Tool call:" маркер, например "(em" const chunk1 = { responseId: "resp-test-empty-partial", - modelVersion: "gemini-3.5-flash-low", + modelVersion: "gemini-3.7-flash-low", candidates: [ { content: { @@ -1187,7 +1187,7 @@ test("Gemini stream: partial textual tool call survives a reasoning-only chunk", geminiToOpenAIResponse( { responseId: "resp-interleave", - modelVersion: "gemini-3.5-flash-low", + modelVersion: "gemini-3.7-flash-low", candidates: [ { content: { parts: [{ text: '[Tool call: terminal]\nArguments: {"command":"ls' }] } }, ], @@ -1200,7 +1200,7 @@ test("Gemini stream: partial textual tool call survives a reasoning-only chunk", geminiToOpenAIResponse( { responseId: "resp-interleave", - modelVersion: "gemini-3.5-flash-low", + modelVersion: "gemini-3.7-flash-low", candidates: [{ content: { parts: [{ text: "pondering" }] } }], }, state @@ -1221,7 +1221,7 @@ test("Gemini stream: partial textual tool call survives a reasoning-only chunk", geminiToOpenAIResponse( { responseId: "resp-interleave", - modelVersion: "gemini-3.5-flash-low", + modelVersion: "gemini-3.7-flash-low", candidates: [{ content: { parts: [{ text: '"}' }] }, finishReason: "STOP" }], }, state