diff --git a/packages/web/server/lib/small-model/DOCUMENTATION.md b/packages/web/server/lib/small-model/DOCUMENTATION.md index 1776f1e1..23fdbfdd 100644 --- a/packages/web/server/lib/small-model/DOCUMENTATION.md +++ b/packages/web/server/lib/small-model/DOCUMENTATION.md @@ -96,7 +96,8 @@ other runtime API. `auth.openai.com` (single-flight) and written back to `auth.json`. - **Anthropic** (`type: api`): `/v1/messages` with `x-api-key`. - **Google** (`type: api`): `generateContent` with `x-goog-api-key`; Gemini 3 - uses `thinkingLevel` while older Flash models use `thinkingBudget: 0`. + uses `thinkingLevel`, Gemini 2.x uses `thinkingBudget: 0`, and all other + models omit `thinkingConfig` entirely. - Everything else: OpenAI-compatible `/chat/completions` against the provider's base URL, resolved from (1) `provider..options.baseURL` in the OpenCode config, (2) the hardcoded `https://api.openai.com/v1` diff --git a/packages/web/server/lib/small-model/call.js b/packages/web/server/lib/small-model/call.js index c3a090a1..75dc315f 100644 --- a/packages/web/server/lib/small-model/call.js +++ b/packages/web/server/lib/small-model/call.js @@ -402,9 +402,10 @@ const getCopilotEndpoint = async ({ baseURL, headers, modelID }) => { const callGoogle = async ({ apiKey, modelID, prompt, system, maxOutputTokens, responseSchema, timeoutMs, signal }) => { const url = `https://generativelanguage.googleapis.com/v1beta/models/${encodeURIComponent(modelID)}:generateContent`; - const thinkingConfig = modelID.toLowerCase().startsWith('gemini-3') - ? { thinkingLevel: modelID.toLowerCase().includes('flash') ? 'minimal' : 'low' } - : { thinkingBudget: 0 }; + const lowerModelID = modelID.toLowerCase(); + const thinkingConfig = lowerModelID.startsWith('gemini-3') + ? { thinkingLevel: lowerModelID.includes('flash') ? 'minimal' : 'low' } + : lowerModelID.startsWith('gemini-2') ? { thinkingBudget: 0 } : null; const response = await fetch(url, { method: 'POST', headers: { @@ -414,13 +415,11 @@ const callGoogle = async ({ apiKey, modelID, prompt, system, maxOutputTokens, re }, body: JSON.stringify({ contents: [{ role: 'user', parts: [{ text: prompt }] }], - ...(system ? { systemInstruction: { parts: [{ text: system }] } } : {}), + ...(system && { systemInstruction: { parts: [{ text: system }] } }), generationConfig: { maxOutputTokens, - thinkingConfig, - ...(responseSchema - ? { responseMimeType: 'application/json', responseSchema: toGoogleSchema(responseSchema) } - : {}), + ...(thinkingConfig && { thinkingConfig }), + ...(responseSchema && { responseMimeType: 'application/json', responseSchema: toGoogleSchema(responseSchema) }), }, }), signal: requestSignal(timeoutMs, signal), diff --git a/packages/web/server/lib/small-model/call.test.js b/packages/web/server/lib/small-model/call.test.js index 154fd986..bbb6b49a 100644 --- a/packages/web/server/lib/small-model/call.test.js +++ b/packages/web/server/lib/small-model/call.test.js @@ -457,6 +457,22 @@ describe('callSmallModel — Google thinking configuration', () => { const body = JSON.parse(lastCall(fetchMock).init.body); expect(body.generationConfig.thinkingConfig).toEqual({ thinkingBudget: 0 }); }); + + it('omits thinkingConfig for other Google/Gemini models', async () => { + fetchMock.mockResolvedValue(googleResponse('generated commit')); + + await callSmallModel({ + auth: { google: { type: 'api', key: 'google-key' } }, + catalog: {}, + workingDirectory: '/proj', + providerID: 'google', + modelID: 'gemini-1.5-flash', + prompt: 'generate', + }); + + const body = JSON.parse(lastCall(fetchMock).init.body); + expect(body.generationConfig.thinkingConfig).toBeUndefined(); + }); }); describe('callSmallModel — GitHub Copilot endpoint routing', () => {