fix(api): restore function_call name on non-streaming /v1/responses - #13824
Merged
Merged
Conversation
…12370) A plain (non-namespace) tool call's #7936 identity restore in nonStreamingClientTranslate.ts unconditionally applied whatever requestToolIdentityMap.get(item.name) returned as a {namespace, name} object. That map falls back to the _toolNameMap alias table (a plain Map<string, string>, e.g. "shell" -> "shell") published by the openai->gemini/claude pivot whenever no namespace tools are present. Applying that string as an identity object read undefined for both .namespace and .name, blanking item.name to undefined -- a key JSON.stringify then drops entirely, leaving Codex with a function_call it cannot dispatch and causing it to re-narrate its tool intent in a loop instead of executing it. Guard the restore so it only fires when the map entry actually has the {namespace, name} shape. Live VPS check (192.168.0.15, real gemini API key, Codex-shaped payload: tool_choice "auto", one shell function tool, gemini/gemini-3-flash-preview) reproduced the bug 3/3 on stream:false /v1/responses; stream:false /v1/chat/completions and stream:true /v1/responses were both already correct.
muhamadgalihsaputra
pushed a commit
to niyatna/NiyatnaRoute
that referenced
this pull request
Sep 27, 2026
…iegosouzapw#12370) (diegosouzapw#13824) Merged in the 2026-09-16 sweep of the maintainer's own open PRs, at the owner's explicit instruction. No push was made to the PR branch: the merge took the head as the owning session left it (verified OPEN, non-draft and MERGEABLE against the release tip immediately before merging).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #12370
Root cause
open-sse/handlers/chatCore/nonStreamingClientTranslate.ts:132-138— the#7936identity-restore loop for non-streaming/v1/responsesfunction_calloutput items:requestToolIdentityMapis typedMap<string, {namespace, name}>, butextractRequestToolIdentityMap()(open-sse/handlers/chatCore/requestToolIdentity.ts) falls back to the_toolNameMapside channel whenever no namespace tools were declared on the request. For a Gemini/Claude pivot that side channel is a plainMap<string, string>alias table ("shell" -> "shell"), not a{namespace, name}identity — a fact the code comment there already documents (#9780), but the consumer innonStreamingClientTranslate.tsnever checked the shape.For Codex's plain (non-namespace)
shellfunction tool,identitycomes back as the string"shell".("shell").namespaceand("shell").nameare bothundefined, so the loop overwrote a perfectly validitem.name("shell") withundefined— a keyJSON.stringifydrops entirely. Codex then receives afunction_callwith noname, can't dispatch it, and re-narrates its intent instead — the reported loop.Fix: only apply the restore when
identityactually has the{namespace, name}shape (typeof identity === "object" && typeof identity.name === "string").Live VPS evidence (192.168.0.15, real
geminiAPI key, Codex-shaped payload:tool_choice: "auto", oneshellfunction tool,gemini/gemini-3-flash-preview)TDD — RED → GREEN
New test
tests/unit/non-streaming-client-translate.test.ts→"#12370: alias-shaped requestToolIdentityMap must not blank out function_call name", feedingtranslateNonStreamingClientResponse()a GeminifunctionCallresponse plus an alias-shapedrequestToolIdentityMap(Map(["shell","shell"])), matching the live-VPS shape.RED (guard removed):
GREEN (fix applied) — full file, 11/11 pass:
Also confirmed the pre-existing
#7936regression test (genuine{namespace, name}restore, e.g.mcp__server__tool) is unaffected — still green, unmodified.Gates run
node scripts/check/check-file-size.mjs— no new violation on touched filesnode scripts/check/check-complexity.mjs— OK (2826 violations vs. baseline 3218)node scripts/check/check-cognitive-complexity.mjs— OK (1276 violations vs. baseline 1437)npm run typecheck:core— exit 0npm run check:open-sse-typecheck— OK, 0 pre-existing errorsnpx eslint --suppressions-location config/quality/eslint-suppressions.json <changed files>— exit 0node --import tsx/esm --test tests/unit/non-streaming-client-translate.test.ts— 11/11 passtests/unit/chatcore-response-toolname-map-recovery.test.ts,tests/unit/9780-namespace-identity-pivot.test.ts,tests/unit/translator-resp-openai-responses-namespace-identity.test.tsnode scripts/check/check-changelog-integrity.mjs— OKbase-redissue forrelease/v3.8.51; branch fast-forwarded onto the current tip (cde49c9) before opening this PRNote for a maintainer (not fixed here, out of scope for this failing test)
open-sse/utils/stream.ts::restoreResponsesPassthroughFunctionCallIdentity(the native-Codex-passthrough streaming counterpart) has the identical unguardedidentity.namespace/identity.nameshape assumption. It isn't currently reachable with an alias-shaped map (the alias_toolNameMapis only published by the non-passthrough Gemini/Claude pivot, which doesn't feed the codex-native passthrough branch), so no failing test proves it broken today — flagging as a latent variant worth a follow-up look rather than fixing speculatively.