fix(translator): keep Responses namespace identity across the hub-and-spoke pivot (#9783)

Step 1 of the pivot (openai-responses -> openai) flattens namespace sub-tools
to a qualified wire name (#8295) and records the `{namespace, name}` pair on a
non-enumerable `_toolNameMap`. Step 2 (openai -> target) returns a brand-new
object, so the property was dropped for every non-OpenAI target. chatCore then
handed `null` to the #7936 response seam and namespace sub-tool calls reached
the client under their flattened name, which Codex rejects with
`unsupported call: <name>` — the symptom #7936 was opened to fix.

Copying `_toolNameMap` through is not viable: openai-to-claude and
openai-to-gemini publish their own `Map<string, string>` alias map on that same
property during step 2, so it carries two incompatible types. This adds a
dedicated `_namespaceToolIdentityMap`, propagated by translateRequest across
the pivot; chatCore prefers it and falls back to `_toolNameMap` for the
non-pivot producers. Both keys are stripped from the cliproxyapi wire body.

Fixes #9780
This commit is contained in:
VXNCXNX
2026-08-11 12:58:03 +02:00
committed by GitHub
parent 4a4cc8736f
commit bc92c063ed
3 changed files with 29 additions and 14 deletions

View File

@@ -0,0 +1 @@
- **Translator**: keep the Responses namespace identity map across the hub-and-spoke pivot — namespace sub-tool calls routed to non-OpenAI targets (Kiro, Cursor) no longer come back flattened (`unsupported call: functions__exec` in Codex CLI) (#9783 — thanks @VXNCXNX)

View File

@@ -1,3 +1,4 @@
import { extractRequestToolIdentityMap } from "./chatCore/requestToolIdentity.ts";
import { injectMemoryAndSkills } from "./chatCore/memorySkillsInjection.ts";
import { resolveChatCoreRequestSetup } from "./chatCore/requestSetup.ts";
import { buildFailureUsageRecord } from "./chatCore/failureUsage.ts";
@@ -2264,20 +2265,7 @@ export async function handleChatCore({
// the latter is a Kiro/Claude passthrough alias channel with string values,
// while namespace identities carry `{namespace, name}` for the #7936 response
// seam. Extract first because Kiro merge may reuse `_toolNameMap` below.
//
// #9780 — prefer the dedicated channel: on a pivot the openai->claude/gemini
// step publishes its own alias map on `_toolNameMap`, so that property alone
// yields aliases here. The `_toolNameMap` read stays as the fallback for the
// non-pivot producers (executors/base.ts, cliproxyapi.ts, antigravity).
const namespaceIdentityMap = translatedBody._namespaceToolIdentityMap;
const requestToolIdentityMap =
namespaceIdentityMap instanceof Map
? namespaceIdentityMap
: translatedBody._toolNameMap instanceof Map
? translatedBody._toolNameMap
: null;
delete translatedBody._namespaceToolIdentityMap;
delete translatedBody._toolNameMap;
const requestToolIdentityMap = extractRequestToolIdentityMap(translatedBody);
// Kiro: sanitize tool schemas before dispatch. Kiro returns 400 "Improperly
// formed request" for unsupported JSON-Schema keywords (anyOf/$ref/if-then,

View File

@@ -0,0 +1,26 @@
type NamespaceIdentity = { namespace: string; name: string };
/**
* Extract the #7936 request-tool identity map from the translated body and
* strip both side channels before dispatch.
*
* #9780 — prefer the dedicated `_namespaceToolIdentityMap`: on a pivot the
* openai->claude/gemini step publishes its own alias `Map<string, string>` on
* `_toolNameMap`, so that property alone can yield aliases instead of
* identities. The `_toolNameMap` read stays as the fallback for the non-pivot
* producers (executors/base.ts, cliproxyapi.ts, antigravity).
*/
export function extractRequestToolIdentityMap(
translatedBody: Record<string, unknown>
): Map<string, NamespaceIdentity> | null {
const namespaceIdentityMap = translatedBody._namespaceToolIdentityMap;
const requestToolIdentityMap =
namespaceIdentityMap instanceof Map
? namespaceIdentityMap
: translatedBody._toolNameMap instanceof Map
? translatedBody._toolNameMap
: null;
delete translatedBody._namespaceToolIdentityMap;
delete translatedBody._toolNameMap;
return requestToolIdentityMap as Map<string, NamespaceIdentity> | null;
}