diff --git a/open-sse/handlers/chatCore.ts b/open-sse/handlers/chatCore.ts index 24532fda14..cd2f999e49 100644 --- a/open-sse/handlers/chatCore.ts +++ b/open-sse/handlers/chatCore.ts @@ -910,8 +910,12 @@ export async function handleChatCore({ } // ── Common input sanitization (runs for ALL paths including passthrough) ── - // #994: Normalize max_output_tokens to max_tokens for universal compatibility - if (body.max_output_tokens !== undefined) { + // #994: Normalize max_output_tokens to max_tokens for universal compatibility. + // Skip for Responses API targets where max_output_tokens is the canonical field; + // normalizing it to max_tokens there causes "Unsupported parameter: max_tokens" + // errors because Responses→Responses passthrough skips the translator that would + // convert it back (see openaiToOpenAIResponsesRequest). + if (body.max_output_tokens !== undefined && targetFormat !== FORMATS.OPENAI_RESPONSES) { if (body.max_tokens === undefined) { body.max_tokens = body.max_output_tokens; } diff --git a/tests/unit/chatcore-sanitization.test.ts b/tests/unit/chatcore-sanitization.test.ts index 3f56eadda6..be55dc3dc7 100644 --- a/tests/unit/chatcore-sanitization.test.ts +++ b/tests/unit/chatcore-sanitization.test.ts @@ -179,6 +179,38 @@ test("chatCore sanitization normalizes max_output_tokens into max_tokens", async assert.equal("max_tokens" in untouched.call.body, false); }); +test("chatCore sanitization preserves max_output_tokens for openai-responses targets", async () => { + // When the target provider uses openai-responses format (e.g. Codex), + // max_output_tokens is the canonical field and must NOT be normalized to + // max_tokens. Normalizing it breaks Responses→Responses passthrough because + // the translator (which converts max_tokens back) is skipped for same-format. + const { call } = await invokeChatCore({ + endpoint: "/v1/responses", + body: { + model: "gpt-5.4", + max_output_tokens: 4096, + input: [{ role: "user", content: "hello" }], + }, + responseFactory: () => + new Response( + JSON.stringify({ + id: "resp_test", + object: "response", + status: "completed", + model: "gpt-5.4", + output: [ + { type: "message", role: "assistant", content: [{ type: "output_text", text: "ok" }] }, + ], + usage: { input_tokens: 1, output_tokens: 1, total_tokens: 2 }, + }), + { status: 200, headers: { "Content-Type": "application/json" } } + ), + }); + + // max_output_tokens should survive sanitization for Responses targets + assert.equal("max_tokens" in call.body, false, "max_tokens must not be injected for Responses targets"); +}); + test("chatCore sanitization strips empty message names and filters empty tool names", async () => { // Note: `input` field is tested separately because its presence triggers // Responses format detection (PR #1002), which changes message handling.