From 610df0b56674fa0ebcae89093ada08b98731bea6 Mon Sep 17 00:00:00 2001 From: Mark McDonald Date: Thu, 24 Sep 2026 00:41:37 +0800 Subject: [PATCH] fix(gemini): switch thinking default logic (#50841) Co-authored-by: Aiden Cline --- packages/opencode/src/provider/transform.ts | 29 +++-- .../opencode/test/provider/transform.test.ts | 114 +++++++++++++++++- 2 files changed, 131 insertions(+), 12 deletions(-) diff --git a/packages/opencode/src/provider/transform.ts b/packages/opencode/src/provider/transform.ts index f0be2d1537..8ae8b4d9f1 100644 --- a/packages/opencode/src/provider/transform.ts +++ b/packages/opencode/src/provider/transform.ts @@ -517,8 +517,11 @@ export function message(msgs: ModelMessage[], model: Provider.Model, options: Re return msgs } +const GEMINI_2_5_RE = /gemini-2[.-]5(?:[.-]|$)/i +const GEMINI_LEGACY_RE = /gemini-(?:(?:flash|pro)-)?[12](?:[.-]|$)/i + const GEMINI_MODELS_WITH_SAMPLING_DEFAULTS = [ - /gemini-2[.-]5(?:[.-]|$)/, + GEMINI_2_5_RE, /gemini-3-(?:flash|pro)(?:[.-]|$)/, /gemini-3[.-]1(?:[.-]|$)/, /gemini-3[.-]5-flash(?!-lite)(?:[.-]|$)/, @@ -735,9 +738,19 @@ function anthropicBlockBinding(model: Provider.Model, options: { [x: string]: an return options } +function isLegacyGemini(apiId: string) { + return GEMINI_LEGACY_RE.test(apiId) +} + +function isGemini25(apiId: string) { + return GEMINI_2_5_RE.test(apiId) +} + function googleThinkingLevelEfforts(apiId: string) { const id = apiId.toLowerCase() - if (!id.includes("gemini-3")) return ["low", "high"] + // Gemma 4 only toggles thinking: "minimal" disables it and "high" enables it. + if (id.includes("gemma")) return ["minimal", "high"] + if (isLegacyGemini(id)) return ["low", "high"] if (id.includes("flash-image")) return ["minimal", "high"] if (id.includes("pro-image")) return ["high"] if (id.includes("flash")) return ["minimal", "low", "medium", "high"] @@ -746,7 +759,7 @@ function googleThinkingLevelEfforts(apiId: string) { function googleThinkingBudgetMax(apiId: string) { const id = apiId.toLowerCase() - if (id.includes("2.5") && id.includes("pro") && !id.includes("flash")) return 32_768 + if (isGemini25(id) && id.includes("pro") && !id.includes("flash")) return 32_768 return 24_576 } @@ -758,7 +771,7 @@ function wrapInSapModelParams(variants: Record>): Re function googleThinkingVariants(model: Provider.Model): Record> { const id = model.api.id.toLowerCase() - if (id.includes("2.5")) { + if (isGemini25(id)) { return { high: { thinkingConfig: { includeThoughts: true, thinkingBudget: 16000 } }, max: { @@ -912,7 +925,7 @@ export function variants(model: Provider.Model): Record { describe("ProviderTransform.options - google thinkingConfig gating", () => { const sessionID = "test-session-123" - const createGoogleModel = (reasoning: boolean, npm: "@ai-sdk/google" | "@ai-sdk/google-vertex") => + const createGoogleModel = ( + reasoning: boolean, + npm: "@ai-sdk/google" | "@ai-sdk/google-vertex", + apiId = "gemini-2.0-flash", + ) => ({ - id: `${npm === "@ai-sdk/google" ? "google" : "google-vertex"}/gemini-2.0-flash`, + id: `${npm === "@ai-sdk/google" ? "google" : "google-vertex"}/${apiId}`, providerID: npm === "@ai-sdk/google" ? "google" : "google-vertex", api: { - id: "gemini-2.0-flash", + id: apiId, url: npm === "@ai-sdk/google" ? "https://generativelanguage.googleapis.com" : "https://vertexai.googleapis.com", npm, }, - name: "Gemini 2.0 Flash", + name: apiId, capabilities: { temperature: true, reasoning, @@ -454,6 +458,73 @@ describe("ProviderTransform.options - google thinkingConfig gating", () => { }) expect(result.thinkingConfig).toBeUndefined() }) + + test.each(["gemini-1.5-pro", "gemini-2.0-flash", "gemini-2.5-pro", "gemini-2.5-flash"])( + "omits default thinkingLevel for legacy model %s", + (apiId) => { + for (const npm of ["@ai-sdk/google", "@ai-sdk/google-vertex"] as const) { + const result = ProviderTransform.options({ + model: createGoogleModel(true, npm, apiId), + sessionID, + providerOptions: {}, + }) + expect(result.thinkingConfig).toEqual({ + includeThoughts: true, + }) + } + + const openrouterResult = ProviderTransform.options({ + model: { + ...createGoogleModel(true, "@ai-sdk/google", `google/${apiId}`), + providerID: "openrouter", + api: { + id: `google/${apiId}`, + url: "https://openrouter.ai/api/v1", + npm: "@openrouter/ai-sdk-provider", + }, + }, + sessionID, + providerOptions: {}, + }) + expect(openrouterResult.reasoning).toBeUndefined() + }, + ) + + test.each([ + "gemini-3-pro-preview", + "gemini-3-flash-preview", + "gemini-9-pro", + "gemini-9-flash", + "gemini-pro-latest", + "gemini-flash-latest", + ])("sets default thinkingLevel=high and OpenRouter reasoning effort=high for %s", (apiId) => { + for (const npm of ["@ai-sdk/google", "@ai-sdk/google-vertex"] as const) { + const result = ProviderTransform.options({ + model: createGoogleModel(true, npm, apiId), + sessionID, + providerOptions: {}, + }) + expect(result.thinkingConfig).toEqual({ + includeThoughts: true, + thinkingLevel: "high", + }) + } + + const openrouterResult = ProviderTransform.options({ + model: { + ...createGoogleModel(true, "@ai-sdk/google", `google/${apiId}`), + providerID: "openrouter", + api: { + id: `google/${apiId}`, + url: "https://openrouter.ai/api/v1", + npm: "@openrouter/ai-sdk-provider", + }, + }, + sessionID, + providerOptions: {}, + }) + expect(openrouterResult.reasoning).toEqual({ effort: "high" }) + }) }) describe("ProviderTransform.options - gpt-5 textVerbosity", () => { @@ -5651,6 +5722,16 @@ describe("ProviderTransform.variants", () => { ]) { describe(provider.name, () => { for (const testCase of [ + { + apiId: "gemini-1.5-pro", + efforts: ["low", "high"], + expectedHigh: { thinkingConfig: { includeThoughts: true, thinkingLevel: "high" } }, + }, + { + apiId: "gemini-2.0-flash", + efforts: ["low", "high"], + expectedHigh: { thinkingConfig: { includeThoughts: true, thinkingLevel: "high" } }, + }, { apiId: "gemini-2.5-pro", efforts: ["high", "max"], @@ -5693,6 +5774,31 @@ describe("ProviderTransform.variants", () => { efforts: ["high"], expectedHigh: { thinkingConfig: { includeThoughts: true, thinkingLevel: "high" } }, }, + { + apiId: "gemini-9-pro", + efforts: ["low", "medium", "high"], + expectedHigh: { thinkingConfig: { includeThoughts: true, thinkingLevel: "high" } }, + }, + { + apiId: "gemini-9-flash", + efforts: ["minimal", "low", "medium", "high"], + expectedHigh: { thinkingConfig: { includeThoughts: true, thinkingLevel: "high" } }, + }, + { + apiId: "gemini-pro-latest", + efforts: ["low", "medium", "high"], + expectedHigh: { thinkingConfig: { includeThoughts: true, thinkingLevel: "high" } }, + }, + { + apiId: "gemini-flash-latest", + efforts: ["minimal", "low", "medium", "high"], + expectedHigh: { thinkingConfig: { includeThoughts: true, thinkingLevel: "high" } }, + }, + { + apiId: "gemma-4-31b-it", + efforts: ["minimal", "high"], + expectedHigh: { thinkingConfig: { includeThoughts: true, thinkingLevel: "high" } }, + }, ]) { test(`${testCase.apiId} returns supported thinking controls`, () => { const result = ProviderTransform.variants(