Skip to content

Commit c36ab3f

Browse files
authored
fix(provider): align Gemini thinking controls (#26279)
1 parent e3c983c commit c36ab3f

2 files changed

Lines changed: 119 additions & 92 deletions

File tree

packages/opencode/src/provider/transform.ts

Lines changed: 26 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -603,6 +603,29 @@ function anthropicAdaptiveEfforts(apiId: string): string[] | null {
603603
return null
604604
}
605605

606+
function googleThinkingLevelEfforts(apiId: string) {
607+
const id = apiId.toLowerCase()
608+
if (!id.includes("gemini-3")) return ["low", "high"]
609+
if (id.includes("flash-image")) return ["minimal", "high"]
610+
if (id.includes("pro-image")) return ["high"]
611+
if (id.includes("flash")) return ["minimal", "low", "medium", "high"]
612+
return ["low", "medium", "high"]
613+
}
614+
615+
function googleThinkingBudgetMax(apiId: string) {
616+
const id = apiId.toLowerCase()
617+
if (id.includes("2.5") && id.includes("pro") && !id.includes("flash")) return 32_768
618+
return 24_576
619+
}
620+
621+
function googleSmallThinkingConfig(apiId: string) {
622+
const levels = googleThinkingLevelEfforts(apiId)
623+
if (apiId.toLowerCase().includes("gemini-3")) {
624+
return { thinkingLevel: levels.includes("minimal") ? "minimal" : levels.includes("low") ? "low" : "high" }
625+
}
626+
return { thinkingBudget: googleThinkingBudgetMax(apiId) === 32_768 ? 128 : 0 }
627+
}
628+
606629
export function variants(model: Provider.Model): Record<string, Record<string, any>> {
607630
if (!model.capabilities.reasoning) return {}
608631

@@ -908,18 +931,14 @@ export function variants(model: Provider.Model): Record<string, Record<string, a
908931
max: {
909932
thinkingConfig: {
910933
includeThoughts: true,
911-
thinkingBudget: 24576,
934+
thinkingBudget: googleThinkingBudgetMax(id),
912935
},
913936
},
914937
}
915938
}
916-
let levels = ["low", "high"]
917-
if (id.includes("3.1")) {
918-
levels = ["low", "medium", "high"]
919-
}
920939

921940
return Object.fromEntries(
922-
levels.map((effort) => [
941+
googleThinkingLevelEfforts(id).map((effort) => [
923942
effort,
924943
{
925944
thinkingConfig: {
@@ -1186,10 +1205,7 @@ export function smallOptions(model: Provider.Model) {
11861205
}
11871206
if (model.providerID === "google") {
11881207
// gemini-3 uses thinkingLevel, gemini-2.5 uses thinkingBudget
1189-
if (model.api.id.includes("gemini-3")) {
1190-
return { thinkingConfig: { thinkingLevel: "minimal" } }
1191-
}
1192-
return { thinkingConfig: { thinkingBudget: 0 } }
1208+
return { thinkingConfig: googleSmallThinkingConfig(model.api.id) }
11931209
}
11941210
if (model.providerID === "openrouter" || model.providerID === "llmgateway") {
11951211
if (model.api.id.includes("google")) {

packages/opencode/test/provider/transform.test.ts

Lines changed: 93 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -3292,89 +3292,74 @@ describe("ProviderTransform.variants", () => {
32923292
})
32933293
})
32943294

3295-
describe("@ai-sdk/google", () => {
3296-
test("gemini-2.5 returns high and max with thinkingConfig and thinkingBudget", () => {
3297-
const model = createMockModel({
3298-
id: "google/gemini-2.5-pro",
3299-
providerID: "google",
3300-
api: {
3301-
id: "gemini-2.5-pro",
3302-
url: "https://generativelanguage.googleapis.com",
3303-
npm: "@ai-sdk/google",
3304-
},
3305-
})
3306-
const result = ProviderTransform.variants(model)
3307-
expect(Object.keys(result)).toEqual(["high", "max"])
3308-
expect(result.high).toEqual({
3309-
thinkingConfig: {
3310-
includeThoughts: true,
3311-
thinkingBudget: 16000,
3312-
},
3313-
})
3314-
expect(result.max).toEqual({
3315-
thinkingConfig: {
3316-
includeThoughts: true,
3317-
thinkingBudget: 24576,
3318-
},
3319-
})
3320-
})
3321-
3322-
test("other gemini models return low and high with thinkingLevel", () => {
3323-
const model = createMockModel({
3324-
id: "google/gemini-2.0-pro",
3325-
providerID: "google",
3326-
api: {
3327-
id: "gemini-2.0-pro",
3328-
url: "https://generativelanguage.googleapis.com",
3329-
npm: "@ai-sdk/google",
3330-
},
3331-
})
3332-
const result = ProviderTransform.variants(model)
3333-
expect(Object.keys(result)).toEqual(["low", "high"])
3334-
expect(result.low).toEqual({
3335-
thinkingConfig: {
3336-
includeThoughts: true,
3337-
thinkingLevel: "low",
3338-
},
3339-
})
3340-
expect(result.high).toEqual({
3341-
thinkingConfig: {
3342-
includeThoughts: true,
3343-
thinkingLevel: "high",
3344-
},
3345-
})
3346-
})
3347-
})
3348-
3349-
describe("@ai-sdk/google-vertex", () => {
3350-
test("gemini-2.5 returns high and max with thinkingConfig and thinkingBudget", () => {
3351-
const model = createMockModel({
3352-
id: "google-vertex/gemini-2.5-pro",
3353-
providerID: "google-vertex",
3354-
api: {
3355-
id: "gemini-2.5-pro",
3356-
url: "https://vertexai.googleapis.com",
3357-
npm: "@ai-sdk/google-vertex",
3358-
},
3359-
})
3360-
const result = ProviderTransform.variants(model)
3361-
expect(Object.keys(result)).toEqual(["high", "max"])
3362-
})
3363-
3364-
test("other vertex models return low and high with thinkingLevel", () => {
3365-
const model = createMockModel({
3366-
id: "google-vertex/gemini-2.0-pro",
3367-
providerID: "google-vertex",
3368-
api: {
3369-
id: "gemini-2.0-pro",
3370-
url: "https://vertexai.googleapis.com",
3371-
npm: "@ai-sdk/google-vertex",
3372-
},
3373-
})
3374-
const result = ProviderTransform.variants(model)
3375-
expect(Object.keys(result)).toEqual(["low", "high"])
3295+
for (const provider of [
3296+
{ name: "@ai-sdk/google", providerID: "google", url: "https://generativelanguage.googleapis.com" },
3297+
{ name: "@ai-sdk/google-vertex", providerID: "google-vertex", url: "https://vertexai.googleapis.com" },
3298+
]) {
3299+
describe(provider.name, () => {
3300+
for (const testCase of [
3301+
{
3302+
apiId: "gemini-2.5-pro",
3303+
efforts: ["high", "max"],
3304+
expectedHigh: { thinkingConfig: { includeThoughts: true, thinkingBudget: 16_000 } },
3305+
expectedMax: { thinkingConfig: { includeThoughts: true, thinkingBudget: 32_768 } },
3306+
},
3307+
{
3308+
apiId: "gemini-2.5-flash",
3309+
efforts: ["high", "max"],
3310+
expectedHigh: { thinkingConfig: { includeThoughts: true, thinkingBudget: 16_000 } },
3311+
expectedMax: { thinkingConfig: { includeThoughts: true, thinkingBudget: 24_576 } },
3312+
},
3313+
{
3314+
apiId: "gemini-3-pro-preview",
3315+
efforts: ["low", "medium", "high"],
3316+
expectedHigh: { thinkingConfig: { includeThoughts: true, thinkingLevel: "high" } },
3317+
},
3318+
{
3319+
apiId: "gemini-3.1-pro-preview",
3320+
efforts: ["low", "medium", "high"],
3321+
expectedHigh: { thinkingConfig: { includeThoughts: true, thinkingLevel: "high" } },
3322+
},
3323+
{
3324+
apiId: "gemini-3-flash-preview",
3325+
efforts: ["minimal", "low", "medium", "high"],
3326+
expectedHigh: { thinkingConfig: { includeThoughts: true, thinkingLevel: "high" } },
3327+
},
3328+
{
3329+
apiId: "gemini-3.1-flash-lite",
3330+
efforts: ["minimal", "low", "medium", "high"],
3331+
expectedHigh: { thinkingConfig: { includeThoughts: true, thinkingLevel: "high" } },
3332+
},
3333+
{
3334+
apiId: "gemini-3.1-flash-image-preview",
3335+
efforts: ["minimal", "high"],
3336+
expectedHigh: { thinkingConfig: { includeThoughts: true, thinkingLevel: "high" } },
3337+
},
3338+
{
3339+
apiId: "gemini-3-pro-image-preview",
3340+
efforts: ["high"],
3341+
expectedHigh: { thinkingConfig: { includeThoughts: true, thinkingLevel: "high" } },
3342+
},
3343+
]) {
3344+
test(`${testCase.apiId} returns supported thinking controls`, () => {
3345+
const result = ProviderTransform.variants(
3346+
createMockModel({
3347+
id: `${provider.providerID}/${testCase.apiId}`,
3348+
providerID: provider.providerID,
3349+
api: {
3350+
id: testCase.apiId,
3351+
url: provider.url,
3352+
npm: provider.name,
3353+
},
3354+
}),
3355+
)
3356+
expect(Object.keys(result)).toEqual(testCase.efforts)
3357+
expect(result.high).toEqual(testCase.expectedHigh)
3358+
if (testCase.expectedMax) expect(result.max).toEqual(testCase.expectedMax)
3359+
})
3360+
}
33763361
})
3377-
})
3362+
}
33783363

33793364
describe("@ai-sdk/cohere", () => {
33803365
test("returns empty object", () => {
@@ -3640,6 +3625,32 @@ describe("ProviderTransform.smallOptions - gpt-5 chat/search", () => {
36403625
}
36413626
})
36423627

3628+
describe("ProviderTransform.smallOptions - google thinking controls", () => {
3629+
const createGoogleModel = (apiId: string) =>
3630+
({
3631+
id: `google/${apiId}`,
3632+
providerID: "google",
3633+
api: {
3634+
id: apiId,
3635+
url: "https://generativelanguage.googleapis.com",
3636+
npm: "@ai-sdk/google",
3637+
},
3638+
}) as any
3639+
3640+
for (const testCase of [
3641+
{ id: "gemini-3-pro-preview", options: { thinkingConfig: { thinkingLevel: "low" } } },
3642+
{ id: "gemini-3-flash-preview", options: { thinkingConfig: { thinkingLevel: "minimal" } } },
3643+
{ id: "gemini-3.1-flash-image-preview", options: { thinkingConfig: { thinkingLevel: "minimal" } } },
3644+
{ id: "gemini-3-pro-image-preview", options: { thinkingConfig: { thinkingLevel: "high" } } },
3645+
{ id: "gemini-2.5-pro", options: { thinkingConfig: { thinkingBudget: 128 } } },
3646+
{ id: "gemini-2.5-flash", options: { thinkingConfig: { thinkingBudget: 0 } } },
3647+
]) {
3648+
test(`${testCase.id} returns supported small thinking options`, () => {
3649+
expect(ProviderTransform.smallOptions(createGoogleModel(testCase.id))).toEqual(testCase.options)
3650+
})
3651+
}
3652+
})
3653+
36433654
describe("ProviderTransform.providerOptions - ai-gateway-provider", () => {
36443655
const createModel = (overrides: Partial<any> = {}) =>
36453656
({

0 commit comments

Comments
 (0)