diff --git a/packages/opencode/src/provider/transform.ts b/packages/opencode/src/provider/transform.ts index 72ec881e7b..1021d83f02 100644 --- a/packages/opencode/src/provider/transform.ts +++ b/packages/opencode/src/provider/transform.ts @@ -617,14 +617,6 @@ function googleThinkingBudgetMax(apiId: string) { return 24_576 } -function googleSmallThinkingConfig(apiId: string) { - const levels = googleThinkingLevelEfforts(apiId) - if (apiId.toLowerCase().includes("gemini-3")) { - return { thinkingLevel: levels.includes("minimal") ? "minimal" : levels.includes("low") ? "low" : "high" } - } - return { thinkingBudget: googleThinkingBudgetMax(apiId) === 32_768 ? 128 : 0 } -} - export function variants(model: Provider.Model): Record> { if (!model.capabilities.reasoning) return {} @@ -1184,40 +1176,27 @@ export function options(input: { } export function smallOptions(model: Provider.Model) { + const small = Object.values(model.variants ?? {})[0] ?? {} if ( model.providerID === "openai" || model.api.npm === "@ai-sdk/openai" || model.api.npm === "@ai-sdk/github-copilot" ) { - if (model.api.id.includes("gpt-5")) { - if (model.api.id.includes("-chat")) { - if (gpt5Version(model.api.id) === undefined) return { store: false } - return { store: false, reasoningEffort: "medium" } - } - if (model.api.id.includes("search-api")) return { store: false } - if (model.api.id.includes("5.") || model.api.id.includes("5-mini")) { - return { store: false, reasoningEffort: "low" } - } - return { store: false, reasoningEffort: "minimal" } - } - return { store: false } - } - if (model.providerID === "google") { - // gemini-3 uses thinkingLevel, gemini-2.5 uses thinkingBudget - return { thinkingConfig: googleSmallThinkingConfig(model.api.id) } + const base = { store: false } + return mergeDeep(base, small) } if (model.providerID === "openrouter" || model.providerID === "llmgateway") { - if (model.api.id.includes("google")) { + if (Object.keys(small).length === 0 && model.api.id.includes("google")) { return { reasoning: { enabled: false } } } - return { reasoningEffort: "minimal" } } if (model.providerID === "venice") { + if (Object.keys(small).length > 0) return small return { veniceParameters: { disableThinking: true } } } - return {} + return small } // Maps model ID prefix to provider slug used in providerOptions. diff --git a/packages/opencode/test/provider/transform.test.ts b/packages/opencode/test/provider/transform.test.ts index df21922b09..9b6615ee8d 100644 --- a/packages/opencode/test/provider/transform.test.ts +++ b/packages/opencode/test/provider/transform.test.ts @@ -3602,8 +3602,8 @@ describe("ProviderTransform.variants", () => { }) describe("ProviderTransform.smallOptions - gpt-5 chat/search", () => { - const createModel = (apiId: string) => - ({ + const createModel = (apiId: string) => { + const model = { id: `openai/${apiId}`, providerID: "openai", api: { @@ -3611,13 +3611,43 @@ describe("ProviderTransform.smallOptions - gpt-5 chat/search", () => { url: "https://api.openai.com", npm: "@ai-sdk/openai", }, - }) as any + capabilities: { reasoning: true }, + limit: { output: 64_000 }, + release_date: "2026-01-01", + } as any + model.variants = ProviderTransform.variants(model) + return model + } for (const testCase of [ { id: "gpt-5-chat-latest", options: { store: false } }, - { id: "gpt-5.1-chat-latest", options: { store: false, reasoningEffort: "medium" } }, - { id: "gpt-5.2-chat-latest", options: { store: false, reasoningEffort: "medium" } }, - { id: "gpt-5-search-api", options: { store: false } }, + { + id: "gpt-5.1-chat-latest", + options: { + store: false, + reasoningEffort: "medium", + reasoningSummary: "auto", + include: ["reasoning.encrypted_content"], + }, + }, + { + id: "gpt-5.2-chat-latest", + options: { + store: false, + reasoningEffort: "medium", + reasoningSummary: "auto", + include: ["reasoning.encrypted_content"], + }, + }, + { + id: "gpt-5-search-api", + options: { + store: false, + reasoningEffort: "none", + reasoningSummary: "auto", + include: ["reasoning.encrypted_content"], + }, + }, ]) { test(`${testCase.id} returns only supported small options`, () => { expect(ProviderTransform.smallOptions(createModel(testCase.id))).toEqual(testCase.options) @@ -3626,8 +3656,8 @@ describe("ProviderTransform.smallOptions - gpt-5 chat/search", () => { }) describe("ProviderTransform.smallOptions - google thinking controls", () => { - const createGoogleModel = (apiId: string) => - ({ + const createGoogleModel = (apiId: string) => { + const model = { id: `google/${apiId}`, providerID: "google", api: { @@ -3635,20 +3665,44 @@ describe("ProviderTransform.smallOptions - google thinking controls", () => { url: "https://generativelanguage.googleapis.com", npm: "@ai-sdk/google", }, - }) as any + capabilities: { reasoning: true }, + limit: { output: 64_000 }, + } as any + model.variants = ProviderTransform.variants(model) + return model + } for (const testCase of [ - { id: "gemini-3-pro-preview", options: { thinkingConfig: { thinkingLevel: "low" } } }, - { id: "gemini-3-flash-preview", options: { thinkingConfig: { thinkingLevel: "minimal" } } }, - { id: "gemini-3.1-flash-image-preview", options: { thinkingConfig: { thinkingLevel: "minimal" } } }, - { id: "gemini-3-pro-image-preview", options: { thinkingConfig: { thinkingLevel: "high" } } }, - { id: "gemini-2.5-pro", options: { thinkingConfig: { thinkingBudget: 128 } } }, - { id: "gemini-2.5-flash", options: { thinkingConfig: { thinkingBudget: 0 } } }, + { id: "gemini-3-pro-preview", options: { thinkingConfig: { includeThoughts: true, thinkingLevel: "low" } } }, + { id: "gemini-3-flash-preview", options: { thinkingConfig: { includeThoughts: true, thinkingLevel: "minimal" } } }, + { + id: "gemini-3.1-flash-image-preview", + options: { thinkingConfig: { includeThoughts: true, thinkingLevel: "minimal" } }, + }, + { id: "gemini-3-pro-image-preview", options: { thinkingConfig: { includeThoughts: true, thinkingLevel: "high" } } }, + { id: "gemini-2.5-pro", options: { thinkingConfig: { includeThoughts: true, thinkingBudget: 16000 } } }, + { id: "gemini-2.5-flash", options: { thinkingConfig: { includeThoughts: true, thinkingBudget: 16000 } } }, ]) { test(`${testCase.id} returns supported small thinking options`, () => { expect(ProviderTransform.smallOptions(createGoogleModel(testCase.id))).toEqual(testCase.options) }) } + + test("uses the first configured variant when available", () => { + expect( + ProviderTransform.smallOptions({ + ...createGoogleModel("gemini-2.5-pro"), + variants: { + high: { thinkingConfig: { includeThoughts: true, thinkingBudget: 16000 } }, + max: { thinkingConfig: { includeThoughts: true, thinkingBudget: 32768 } }, + }, + }), + ).toEqual({ thinkingConfig: { includeThoughts: true, thinkingBudget: 16000 } }) + }) + + test("does not synthesize thinking options when variants are empty", () => { + expect(ProviderTransform.smallOptions({ ...createGoogleModel("gemini-2.5-pro"), variants: {} })).toEqual({}) + }) }) describe("ProviderTransform.providerOptions - ai-gateway-provider", () => {