diff --git a/packages/opencode/src/provider/transform.ts b/packages/opencode/src/provider/transform.ts index 7602d92af5..9ea2cbc556 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,19 @@ export namespace ProviderTransform { model.capabilities.reasoning ) { const id = model.api.id.toLowerCase() - if (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" } } } - 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..f5ac6420c2 100644 --- a/packages/opencode/test/provider/transform.test.ts +++ b/packages/opencode/test/provider/transform.test.ts @@ -307,6 +307,55 @@ 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: "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" } }, + { 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", {