diff --git a/src/services/api/openaiShim.test.ts b/src/services/api/openaiShim.test.ts index 4ba62de62d..a15cd008f1 100644 --- a/src/services/api/openaiShim.test.ts +++ b/src/services/api/openaiShim.test.ts @@ -345,6 +345,46 @@ test('uses OpenAI-compatible responses endpoint when OPENAI_API_FORMAT=responses ]) }) +test('nests reasoning under a reasoning object for the responses API (#1638)', async () => { + process.env.OPENAI_API_FORMAT = 'responses' + let capturedBody: Record | undefined + + globalThis.fetch = (async (_input, init) => { + capturedBody = JSON.parse(String(init?.body)) as Record + + return new Response( + JSON.stringify({ + id: 'resp-1', + model: 'gpt-5.5', + output: [ + { + type: 'message', + role: 'assistant', + content: [{ type: 'output_text', text: 'ok' }], + }, + ], + usage: { input_tokens: 8, output_tokens: 3, total_tokens: 11 }, + }), + { headers: { 'Content-Type': 'application/json' } }, + ) + }) as unknown as FetchType + + const client = createOpenAIShimClient({ + reasoningEffort: 'high', + }) as OpenAIShimClient + + await client.beta.messages.create({ + model: 'gpt-5.5', + messages: [{ role: 'user', content: 'hello' }], + max_tokens: 64, + stream: false, + }) + + // The Responses API rejects the flat `reasoning_effort`; it must be nested. + expect(capturedBody?.reasoning).toEqual({ effort: 'high', summary: 'auto' }) + expect('reasoning_effort' in (capturedBody ?? {})).toBe(false) +}) + test('uses OpenAI-compatible responses endpoint with text chunk types when OPENAI_API_FORMAT=responses_compat', async () => { process.env.OPENAI_API_FORMAT = 'responses_compat' let capturedUrl = '' diff --git a/src/services/api/openaiShim.ts b/src/services/api/openaiShim.ts index efb29c23c2..863c66df19 100644 --- a/src/services/api/openaiShim.ts +++ b/src/services/api/openaiShim.ts @@ -2572,8 +2572,14 @@ class OpenAIShimMessages { if (params.temperature !== undefined) responsesBody.temperature = params.temperature if (params.top_p !== undefined) responsesBody.top_p = params.top_p if (request.reasoning?.effort) { - responsesBody.reasoning_effort = request.reasoning.effort - responsesBody.reasoning_summary = 'auto' + // The Responses API nests reasoning controls under a `reasoning` object + // (`reasoning.effort` / `reasoning.summary`); the flat `reasoning_effort` + // form is Chat Completions only and is rejected here with + // "Unsupported parameter: 'reasoning_effort'" (#1638). + responsesBody.reasoning = { + effort: request.reasoning.effort, + summary: 'auto', + } responsesBody.include = ['reasoning.encrypted_content'] }