Skip to content

fix(translator): strip client_metadata and other Responses-API-only fields before forwarding - #2318

Open
anki1kr wants to merge 5 commits into
decolua:masterfrom
anki1kr:fix/responses-strip-client-metadata-2311
Open

anki1kr wants to merge 5 commits into
decolua:masterfrom
anki1kr:fix/responses-strip-client-metadata-2311

Conversation

@anki1kr

@anki1kr anki1kr commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

Problem

When Codex sends a /v1/responses request containing client_metadata through a combo that routes to NVIDIA NIM (e.g. z-ai/glm-5.2), the upstream rejects with:

{
  "message": "Validation: Unsupported parameter(s): `client_metadata`",
  "type": "Bad Request",
  "code": 400
}

Same class of issue can affect background and truncation — both OpenAI Responses API-only fields that non-OpenAI upstreams reject.

Root Cause

openaiResponsesToOpenAIRequest (open-sse/translator/request/openai-responses.js) converts the Responses API format to Chat Completions by spreading body into result, then deleting known Responses-only fields. The existing cleanup deleted input, instructions, store, include, prompt_cache_key, and reasoning — but missed client_metadata, background, and truncation.

Because result = { ...body }, any field not explicitly deleted is forwarded to the upstream, including provider-specific rejections.

Fix

Three-line addition in the cleanup section:

// OpenAI-specific Responses API fields not supported by third-party providers (#2311)
delete result.client_metadata;
delete result.background;
delete result.truncation;

Test

tests/unit/openai-responses-strip-fields.test.js — 6 tests, all pass:

✓ strips client_metadata from the forwarded body
✓ strips background from the forwarded body  
✓ strips truncation from the forwarded body
✓ also strips the already-handled fields (input, store, include, prompt_cache_key)
✓ preserves the converted messages content
✓ does not strip unrelated fields like temperature or max_tokens

Closes #2311

anki1kr added 5 commits July 2, 2026 17:32
…olua#2224)

On a fresh install or after deleting %APPDATA%\9router, rootCA.key and
rootCA.crt are absent. server.js tried to readFileSync them and called
process.exit(1) on failure, making the MITM server permanently broken
without an obvious fix path for the user.

Root cause: generateRootCA() existed in cert/rootCA.js but was never
called at startup — only accessible via the UI 'Generate CA' button,
which itself requires the server to already be running.

Fix:
- cert/rootCA.js: adds ensureRootCASync() — same logic as generateRootCA()
  but truly synchronous (node-forge RSA is blocking), callable at module
  load time in CommonJS scripts without an async wrapper
- server.js: calls ensureRootCASync() before the readFileSync block;
  if generation fails the error is reported and the process exits cleanly;
  also regenerates automatically when an existing cert is expired/corrupt

5 unit tests cover: generate-when-absent, idempotent-when-valid,
regenerate-when-corrupt, generate-when-partial (key only), mkdir-when-dir-missing.
…patch

Kiro / CodeWhisperer rejects Claude model IDs that use dot notation for
the version number (e.g. claude-sonnet-4.5) and requires dash notation
(claude-sonnet-4-5). The upstream dispatch was forwarding the 9router
dot-format ID directly, producing INVALID_MODEL_ID (HTTP 400) for every
Claude model while non-Claude models (deepseek-3.2, glm-5) succeeded.

Add toKiroModelId() to kiroConstants.js that replaces dots with dashes
in Claude model IDs only, leaving all other model IDs unchanged. Apply
the conversion in both OpenAI→Kiro and Claude→Kiro translators right
after resolveKiroModel() strips the synthetic suffixes.

Fixes decolua#2308. Related: decolua#2257.
…alls

When the MITM dashboard button is clicked multiple times rapidly, or when
auto-restart races with a manual start, two concurrent startServer() calls
could both pass the serverProcess check (process not yet spawned) and race
to spawn two MITM servers. The second spawn fails silently and leaves the
state in an inconsistent position.

Add a module-level mitmStarting boolean that is set to true at the start
of the spawn path and cleared in a finally block on all exit paths. A
second caller that arrives while mitmStarting is true receives an explicit
"MITM server is already starting" error rather than a silent race.

The flag is reset via finally{} so crashes or thrown errors during startup
never leave it permanently set, avoiding the permanent-lock scenario
described in issue decolua#2310.

Fixes decolua#2310. Related: decolua#2286.
Gemini's function_declarations schema validator rejects any property that
contains multipleOf, returning:

  400 Invalid JSON payload received. Unknown name "multipleOf" at
  'request.tools[0].function_declarations[N].parameters.properties[K].value'

multipleOf is a valid JSON Schema keyword (used to constrain numeric
fields to multiples of a value) but is not part of Gemini's subset of
supported schema properties. Add it to UNSUPPORTED_SCHEMA_CONSTRAINTS
alongside minLength/maxLength/exclusiveMinimum/exclusiveMaximum so it
is removed recursively from all function declaration schemas before
the request reaches the Gemini backend.

Fixes decolua#2309.
…ields before forwarding

openaiResponsesToOpenAIRequest already deleted input, instructions, store,
include, prompt_cache_key and reasoning when converting the Responses API
format to Chat Completions. But it left client_metadata, background and
truncation in the outgoing body.

When Codex sends a request containing client_metadata to a non-OpenAI
upstream (e.g. NVIDIA NIM), the upstream rejects with:
  "Validation: Unsupported parameter(s): `client_metadata`" (400)

Fix: add the three missing deletes to the cleanup section.

Closes decolua#2311
Copilot AI review requested due to automatic review settings July 3, 2026 10:05

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

bloodf pushed a commit to bloodf/9router that referenced this pull request Jul 3, 2026
bloodf pushed a commit to bloodf/durindoor that referenced this pull request Jul 10, 2026
Translator regression asserting Responses->Chat translation drops
client_metadata/background/truncation (Responses-API-only fields rejected by
third-party chat providers with HTTP 400) while the plain OPENAI->OPENAI path
preserves them (negative control). Logic already present in dev; locks behavior.

Ported from decolua/9router#2318 @ edb20e143d
bloodf pushed a commit to bloodf/durindoor that referenced this pull request Jul 10, 2026
Translator regression asserting Responses->Chat translation drops
client_metadata/background/truncation (Responses-API-only fields rejected by
third-party chat providers with HTTP 400) while the plain OPENAI->OPENAI path
preserves them (negative control). Logic already present in dev; locks behavior.

Ported from decolua/9router#2318 @ edb20e143d
bloodf pushed a commit to bloodf/durindoor that referenced this pull request Jul 10, 2026
Translator regression asserting Responses->Chat translation drops
client_metadata/background/truncation (Responses-API-only fields rejected by
third-party chat providers with HTTP 400) while the plain OPENAI->OPENAI path
preserves them (negative control). Logic already present in dev; locks behavior.

Ported from decolua/9router#2318 @ edb20e143d
bloodf added a commit to bloodf/durindoor that referenced this pull request Jul 10, 2026
* port(upstream): #2237 - salvage orphaned tool results across formats

Non-lossy salvageOrphanedToolResults folds orphan tool output into user text
(`[Tool result: ...]`) instead of dropping it, across messages[] (OpenAI role:tool,
Claude tool_result blocks) and contents[] (Gemini/Antigravity functionResponse).
Runs unconditionally in the request pipeline and after each compression stage
(RTK/Headroom/PXPIPE) with fixMissingToolResponses to restore the tool-pairing
invariant. Responses API function_call_output stays structurally stripped in
openai-responses.js. Removes the obsolete shouldStripOrphanedToolResults gate;
Gemini-family standalone functionResponse (no functionCall) is preserved.

Ported from decolua/9router#2237 @ a32bda9e44

* port(upstream): #2279 - test doubled tool args collapse openai->claude

Translator regression asserting the OpenAI->Claude response translator
deduplicates doubled JSON tool arguments (same object emitted twice) into a
single parseable input_json_delta, and emits message_stop exactly once.

Logic (claudeFinishHandled finish guard + deduplicateDoubledJson) already
present in dev; this locks the behavior.

Ported from decolua/9router#2279 @ 1c9ad466ed

* port(upstream): #2190 - test thinking stays out of visible content

Translator regression asserting a Claude->OpenAI stream surfaces reasoning via
the reasoning channel and never emits literal <think>/</think> text in
delta.content. Logic (reasoningDelta channel, no think-tag content deltas)
already present in dev; this locks the behavior.

Ported from decolua/9router#2190 @ dc417f9b3f

* port(upstream): #2318 - test strip Responses-only fields before forward

Translator regression asserting Responses->Chat translation drops
client_metadata/background/truncation (Responses-API-only fields rejected by
third-party chat providers with HTTP 400) while the plain OPENAI->OPENAI path
preserves them (negative control). Logic already present in dev; locks behavior.

Ported from decolua/9router#2318 @ edb20e143d

* fix(translator): skip salvage on native Gemini contents[]

restore the Gemini-family (gemini/gemini-cli/antigravity/vertex) guard
around salvageOrphanedToolResults in the translateRequest pipeline. the
body still carries native contents[] at that point; salvage keys known
functionCalls globally and rewrites any functionResponse whose id is not
in that set into '[Tool result: ...]' text, dropping legitimate tool
results before gemini->openai conversion can read them.

fixes CI run 29122517702 (tests/unit/gemini-to-openai-function-response).

---------

Co-authored-by: CortexOS <cortexos@localhost>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

NVIDIA provider forwards unsupported client_metadata to upstream Responses API

2 participants