From 380beb30bed71ed839626d460d1faa07b71a1241 Mon Sep 17 00:00:00 2001 From: anandgupta42 Date: Fri, 16 Oct 2026 19:38:20 -0700 Subject: [PATCH 1/3] fix: use thinking levels for Gemini 4 small calls `smallOptions` gave every Google reasoning model except Gemini 3 `thinkingBudget: 0`. Gemini 4 rejects budgets with a 400 ("Thinking budget is not supported for this model. Use thinking level instead."), so every small call (session title, prompt enhancement, project copy) failed on it. - Gemini 2.x and unversioned aliases keep `thinkingBudget: 0`; Gemini 3 keeps `thinkingLevel: "minimal"`; Gemini 4+ uses the lowest configured supported level, falling back to `low`. - Tests cover Google and Vertex for 2.0/2.5/3/3.1/4/5/30 and an unversioned alias, with and without generated variants. Closes #1533 Co-Authored-By: Claude Opus 5.5 --- packages/opencode/src/provider/transform.ts | 14 ++++-- .../opencode/test/provider/transform.test.ts | 44 +++++++++++++++++++ 2 files changed, 55 insertions(+), 3 deletions(-) diff --git a/packages/opencode/src/provider/transform.ts b/packages/opencode/src/provider/transform.ts index 7602d92af5..f1c91bf43a 100644 --- a/packages/opencode/src/provider/transform.ts +++ b/packages/opencode/src/provider/transform.ts @@ -1347,7 +1347,7 @@ export namespace ProviderTransform { ) { return mergeDeep({ store: false }, small) } - // altimate_change start — upstream_fix: restore Google minimal thinking for small calls only on reasoning models + // altimate_change start — upstream_fix: use generation-supported Google thinking controls for small reasoning calls if ( (model.providerID === "google" || model.api.npm === "@ai-sdk/google" || @@ -1355,10 +1355,18 @@ export namespace ProviderTransform { model.capabilities.reasoning ) { const id = model.api.id.toLowerCase() - if (id.includes("gemini-3")) { + const generation = Number(/(?:^|\/)gemini-(\d+)(?:[.-]|$)/.exec(id)?.[1]) + if (generation === 3) { return { thinkingConfig: { thinkingLevel: "minimal" } } } - return { thinkingConfig: { thinkingBudget: 0 } } + // Gemini 2.x and unversioned aliases (e.g. gemini-flash-latest) keep the budget control. + if (!(generation >= 4)) { + return { thinkingConfig: { thinkingBudget: 0 } } + } + // Gemini 4+ rejects budgets and "minimal"; prefer the lowest configured supported level. + const levels = Object.values(model.variants ?? {}).map((variant) => variant.thinkingConfig?.thinkingLevel) + const thinkingLevel = WIDELY_SUPPORTED_EFFORTS.find((level) => levels.includes(level)) ?? "low" + return { thinkingConfig: { thinkingLevel } } } // altimate_change end if (model.providerID === "openrouter" || model.providerID === "llmgateway") { diff --git a/packages/opencode/test/provider/transform.test.ts b/packages/opencode/test/provider/transform.test.ts index fb9ed195c9..c2a8b82737 100644 --- a/packages/opencode/test/provider/transform.test.ts +++ b/packages/opencode/test/provider/transform.test.ts @@ -307,6 +307,50 @@ describe("ProviderTransform.smallOptions - google thinking", () => { variants: overrides.variants ?? {}, }) as any + // altimate_change start — small calls must use thinking controls supported by each Gemini generation + for (const provider of [ + { providerID: "google", npm: "@ai-sdk/google" }, + { providerID: "google-vertex", npm: "@ai-sdk/google-vertex" }, + ] as const) { + for (const testCase of [ + { id: "gemini-2.0-flash", thinkingConfig: { thinkingBudget: 0 } }, + { id: "gemini-2.5-flash", thinkingConfig: { thinkingBudget: 0 } }, + { id: "gemini-3-pro", thinkingConfig: { thinkingLevel: "minimal" } }, + { id: "gemini-3.1-pro", thinkingConfig: { thinkingLevel: "minimal" } }, + { id: "gemini-4-argon", thinkingConfig: { thinkingLevel: "low" } }, + { id: "gemini-5-pro", thinkingConfig: { thinkingLevel: "low" } }, + { id: "gemini-30-pro", thinkingConfig: { thinkingLevel: "low" } }, + { id: "gemini-flash-latest", thinkingConfig: { thinkingBudget: 0 } }, + ]) { + for (const configured of [false, true]) { + test(`${provider.providerID} ${testCase.id} uses supported small thinking controls with ${configured ? "generated" : "empty"} variants`, () => { + const model = createGoogleModel(testCase.id, provider) + if (configured) model.variants = ProviderTransform.variants(model) + + const result = ProviderTransform.smallOptions(model) + + expect(result).toEqual({ thinkingConfig: testCase.thinkingConfig }) + if (testCase.thinkingConfig.thinkingLevel) { + expect(result.thinkingConfig).not.toHaveProperty("thinkingBudget") + } + }) + } + } + + test(`${provider.providerID} uses the lowest configured Gemini 4 thinking level regardless of variant names or order`, () => { + const model = createGoogleModel("gemini-4-argon", { + ...provider, + variants: { + detailed: { thinkingConfig: { includeThoughts: true, thinkingLevel: "high" } }, + balanced: { thinkingConfig: { includeThoughts: true, thinkingLevel: "medium" } }, + }, + }) + + expect(ProviderTransform.smallOptions(model)).toEqual({ thinkingConfig: { thinkingLevel: "medium" } }) + }) + } + // altimate_change end + test("disables Gemini 2.5 thinking budget for small calls even when high is the first variant", () => { const result = ProviderTransform.smallOptions( createGoogleModel("gemini-2.5-pro", { From 82775cc01dd5b39980f8526492651f0e8f4a55d5 Mon Sep 17 00:00:00 2001 From: anandgupta42 Date: Fri, 16 Oct 2026 19:44:32 -0700 Subject: [PATCH 2/3] fix: keep the substring rule for Gemini 3 ids without a parseable generation Ids such as `vertex_gemini-3pro` matched the earlier `includes("gemini-3")` check but not the generation regex, so they fell back to `thinkingBudget: 0`. Adds prefixed, dated-preview and unparseable id cases. Co-Authored-By: Claude Opus 5.5 --- packages/opencode/src/provider/transform.ts | 3 ++- packages/opencode/test/provider/transform.test.ts | 3 +++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/opencode/src/provider/transform.ts b/packages/opencode/src/provider/transform.ts index f1c91bf43a..c2f491149b 100644 --- a/packages/opencode/src/provider/transform.ts +++ b/packages/opencode/src/provider/transform.ts @@ -1356,7 +1356,8 @@ export namespace ProviderTransform { ) { const id = model.api.id.toLowerCase() const generation = Number(/(?:^|\/)gemini-(\d+)(?:[.-]|$)/.exec(id)?.[1]) - if (generation === 3) { + // Fall back to the earlier substring rule when the id has no parseable generation. + if (generation === 3 || (Number.isNaN(generation) && id.includes("gemini-3"))) { return { thinkingConfig: { thinkingLevel: "minimal" } } } // Gemini 2.x and unversioned aliases (e.g. gemini-flash-latest) keep the budget control. diff --git a/packages/opencode/test/provider/transform.test.ts b/packages/opencode/test/provider/transform.test.ts index c2a8b82737..c909fc7f4d 100644 --- a/packages/opencode/test/provider/transform.test.ts +++ b/packages/opencode/test/provider/transform.test.ts @@ -317,6 +317,9 @@ describe("ProviderTransform.smallOptions - google thinking", () => { { id: "gemini-2.5-flash", thinkingConfig: { thinkingBudget: 0 } }, { id: "gemini-3-pro", thinkingConfig: { thinkingLevel: "minimal" } }, { id: "gemini-3.1-pro", thinkingConfig: { thinkingLevel: "minimal" } }, + { id: "models/gemini-3-pro-preview", thinkingConfig: { thinkingLevel: "minimal" } }, + { id: "vertex_gemini-3pro", thinkingConfig: { thinkingLevel: "minimal" } }, + { id: "gemini-2.5-flash-lite-preview-06-17", thinkingConfig: { thinkingBudget: 0 } }, { id: "gemini-4-argon", thinkingConfig: { thinkingLevel: "low" } }, { id: "gemini-5-pro", thinkingConfig: { thinkingLevel: "low" } }, { id: "gemini-30-pro", thinkingConfig: { thinkingLevel: "low" } }, From 3d7f63e549a9d311e7817fe74aee84b767fc5729 Mon Sep 17 00:00:00 2001 From: anandgupta42 Date: Fri, 16 Oct 2026 19:50:07 -0700 Subject: [PATCH 3/3] fix: parse the Gemini generation from any `gemini-` id form Prefixed or concatenated ids such as `vertex_gemini-4argon` did not match the anchored pattern, so they fell back to `thinkingBudget: 0` and the 400. One unanchored `gemini-(\d+)` parse now covers Gemini 3 and Gemini 4+ alike, which also replaces the substring fallback. Co-Authored-By: Claude Opus 5.5 --- packages/opencode/src/provider/transform.ts | 6 +++--- packages/opencode/test/provider/transform.test.ts | 2 ++ 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/packages/opencode/src/provider/transform.ts b/packages/opencode/src/provider/transform.ts index c2f491149b..9ea2cbc556 100644 --- a/packages/opencode/src/provider/transform.ts +++ b/packages/opencode/src/provider/transform.ts @@ -1355,9 +1355,9 @@ export namespace ProviderTransform { model.capabilities.reasoning ) { const id = model.api.id.toLowerCase() - const generation = Number(/(?:^|\/)gemini-(\d+)(?:[.-]|$)/.exec(id)?.[1]) - // Fall back to the earlier substring rule when the id has no parseable generation. - if (generation === 3 || (Number.isNaN(generation) && id.includes("gemini-3"))) { + // Leading digits after "gemini-", wherever it appears (prefixed, concatenated or dated ids). + const generation = Number(/gemini-(\d+)/.exec(id)?.[1]) + if (generation === 3) { return { thinkingConfig: { thinkingLevel: "minimal" } } } // Gemini 2.x and unversioned aliases (e.g. gemini-flash-latest) keep the budget control. diff --git a/packages/opencode/test/provider/transform.test.ts b/packages/opencode/test/provider/transform.test.ts index c909fc7f4d..f5ac6420c2 100644 --- a/packages/opencode/test/provider/transform.test.ts +++ b/packages/opencode/test/provider/transform.test.ts @@ -319,6 +319,8 @@ describe("ProviderTransform.smallOptions - google thinking", () => { { id: "gemini-3.1-pro", thinkingConfig: { thinkingLevel: "minimal" } }, { id: "models/gemini-3-pro-preview", thinkingConfig: { thinkingLevel: "minimal" } }, { id: "vertex_gemini-3pro", thinkingConfig: { thinkingLevel: "minimal" } }, + { id: "vertex_gemini-4argon", thinkingConfig: { thinkingLevel: "low" } }, + { id: "publishers/google/models/gemini-4-argon", thinkingConfig: { thinkingLevel: "low" } }, { id: "gemini-2.5-flash-lite-preview-06-17", thinkingConfig: { thinkingBudget: 0 } }, { id: "gemini-4-argon", thinkingConfig: { thinkingLevel: "low" } }, { id: "gemini-5-pro", thinkingConfig: { thinkingLevel: "low" } },