diff --git a/open-sse/translator/helpers/openaiHelper.ts b/open-sse/translator/helpers/openaiHelper.ts index b53bbb64e7..502d309fe3 100644 --- a/open-sse/translator/helpers/openaiHelper.ts +++ b/open-sse/translator/helpers/openaiHelper.ts @@ -148,6 +148,9 @@ export function filterToOpenAIFormat(body) { // Strip Claude-specific fields that OpenAI-compatible providers reject delete body.metadata; delete body.anthropic_version; + // Codex clients send a top-level `client_metadata` object; OpenAI rejects it + // with 400 "Unknown parameter: 'client_metadata'" (9router#1157). + delete body.client_metadata; // Map max_output_tokens (from Vercel AI SDK) to max_tokens logic if (body.max_output_tokens !== undefined) { diff --git a/tests/unit/translator-strip-client-metadata-1157.test.ts b/tests/unit/translator-strip-client-metadata-1157.test.ts new file mode 100644 index 0000000000..243b1407a3 --- /dev/null +++ b/tests/unit/translator-strip-client-metadata-1157.test.ts @@ -0,0 +1,26 @@ +import test from "node:test"; +import assert from "node:assert/strict"; + +const openaiHelper = await import("../../open-sse/translator/helpers/openaiHelper.ts"); + +// Regression for upstream issue 9router#1157: Codex-origin requests carry a +// top-level `client_metadata` object. On the OpenAI->OpenAI chat-completions +// passthrough, `filterToOpenAIFormat` is the only sanitizer, and it forwarded +// `client_metadata` to api.openai.com, which rejects it with +// 400 "Unknown parameter: 'client_metadata'". It must be stripped alongside +// the other Claude/Codex-specific fields. +test("filterToOpenAIFormat strips top-level client_metadata (9router#1157)", () => { + const body = { + messages: [{ role: "user", content: "hi" }], + client_metadata: { user_id: "abc", session_id: "xyz" }, + metadata: { remove: true }, + anthropic_version: "2023-06-01", + }; + + const result = openaiHelper.filterToOpenAIFormat(body); + + assert.equal("client_metadata" in result, false); + assert.equal("metadata" in result, false); + assert.equal("anthropic_version" in result, false); + assert.equal(result.messages.length, 1); +});