diff --git a/changelog.d/fixes/10286-gemini-3-5-flash-thinking.md b/changelog.d/fixes/10286-gemini-3-5-flash-thinking.md new file mode 100644 index 0000000000..30a3c44bcb --- /dev/null +++ b/changelog.d/fixes/10286-gemini-3-5-flash-thinking.md @@ -0,0 +1 @@ +- 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/src/shared/constants/modelSpecs.ts b/src/shared/constants/modelSpecs.ts index d15fe50425..7d38a776f5 100644 --- a/src/shared/constants/modelSpecs.ts +++ b/src/shared/constants/modelSpecs.ts @@ -226,8 +226,16 @@ export const MODEL_SPECS: Record = { }, // ── 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"], }, diff --git a/tests/unit/gemini-3-5-flash-thinking.test.ts b/tests/unit/gemini-3-5-flash-thinking.test.ts new file mode 100644 index 0000000000..80636a0b03 --- /dev/null +++ b/tests/unit/gemini-3-5-flash-thinking.test.ts @@ -0,0 +1,76 @@ +// 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"); +});