diff --git a/CHANGELOG.md b/CHANGELOG.md index 8e3a61628e..305cd7d24d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,8 @@ ### 🔧 Bug Fixes +- **translator (Responses → Chat Completions):** strip the Responses-API-only `truncation` field before forwarding a `/v1/responses` request to a non-OpenAI Chat Completions upstream. Strict upstreams (e.g. NVIDIA NIM) rejected it with HTTP 400 `Unsupported parameter(s): truncation`, breaking Codex-style clients routed to those providers. `client_metadata`, `background`, and `safety_identifier` were already stripped — `truncation` was the remaining gap. Regression guard: `tests/unit/responses-strip-truncation-2311.test.ts`. (thanks @TuanNguyen0708) + - **combo (prefer known context capacity over unknown):** when a combo filters out at least one target for exceeding a *known* context limit, the router now prefers the remaining known-compatible targets over targets whose context metadata is simply unknown, instead of letting unknown-metadata targets be the only survivors. If no known-compatible context target remains, context-only candidates fall back to the normal strategy order. Regression guard: `tests/unit/combo-context-window-filter.test.ts`. ([#6088](https://github.com/diegosouzapw/OmniRoute/pull/6088) — thanks @Thinkscape) - **models (GLM-5.2 context normalization):** stop treating every hosted GLM-5.2 provider alias as the native 1M-context model. Native/bare GLM-5.2 and verified OpenCode / ZenMux routes keep their 1,000,000-token context, while hosted-provider aliases now respect the caps declared in their provider metadata instead of inheriting the native max. Regression guards: `tests/unit/model-capabilities-registry.test.ts`, `tests/unit/models-catalog-route.test.ts`. ([#6091](https://github.com/diegosouzapw/OmniRoute/pull/6091) — thanks @Thinkscape) diff --git a/open-sse/translator/request/openai-responses.ts b/open-sse/translator/request/openai-responses.ts index 23f32f53d4..83b1aedb20 100644 --- a/open-sse/translator/request/openai-responses.ts +++ b/open-sse/translator/request/openai-responses.ts @@ -520,6 +520,10 @@ export function openaiResponsesToOpenAIRequest( // client_metadata is sent by Codex CLI and has no Chat Completions equivalent. // Strict upstreams (e.g. Mistral) reject it with HTTP 422 extra_forbidden. delete result.client_metadata; + // truncation ("auto"/"disabled") is a Responses-API-only field with no Chat + // Completions equivalent. Strict non-OpenAI upstreams (e.g. NVIDIA NIM) reject + // it with HTTP 400 "Unsupported parameter(s): truncation" (#2311). + delete result.truncation; return result; } diff --git a/tests/unit/responses-strip-truncation-2311.test.ts b/tests/unit/responses-strip-truncation-2311.test.ts new file mode 100644 index 0000000000..a8bedb873d --- /dev/null +++ b/tests/unit/responses-strip-truncation-2311.test.ts @@ -0,0 +1,44 @@ +import test from "node:test"; +import assert from "node:assert/strict"; + +// Regression for port of decolua/9router#2311 (PR #2318): OpenAI Responses-API-only +// fields must be stripped before forwarding to a Chat Completions upstream, otherwise +// strict non-OpenAI upstreams (e.g. NVIDIA NIM) reject with HTTP 400 +// "Unsupported parameter(s): ...". OmniRoute already strips `client_metadata`, +// `background`, and `safety_identifier`; `truncation` was the remaining gap. +const { openaiResponsesToOpenAIRequest } = await import( + "../../open-sse/translator/request/openai-responses.ts" +); + +test("Responses -> OpenAI: truncation is stripped (never forwarded to Chat Completions)", () => { + const result = openaiResponsesToOpenAIRequest( + "z-ai/glm-5.2", + { + input: [{ type: "message", role: "user", content: [{ type: "input_text", text: "hi" }] }], + truncation: "auto", + }, + false, + {} + ) as Record; + + assert.equal("truncation" in result, false); +}); + +test("Responses -> OpenAI: full Responses-only field set is stripped together", () => { + const result = openaiResponsesToOpenAIRequest( + "z-ai/glm-5.2", + { + input: [{ type: "message", role: "user", content: [{ type: "input_text", text: "hi" }] }], + truncation: "disabled", + client_metadata: { user_id: "abc" }, + background: true, + safety_identifier: "lobehub-user", + }, + false, + {} + ) as Record; + + for (const field of ["truncation", "client_metadata", "background", "safety_identifier"]) { + assert.equal(field in result, false, `${field} should be stripped`); + } +});