Skip to content

fix(responses): resolve bidirectional tool namespace identity and custom tool payload for Codex - #12928

Closed
multi-forge wants to merge 4 commits into
cheaper-inference:release/v3.8.51from
multi-forge:fix/responses-codex-tool-namespace-resolution
Closed

multi-forge wants to merge 4 commits into
cheaper-inference:release/v3.8.51from
multi-forge:fix/responses-codex-tool-namespace-resolution

Conversation

@multi-forge

Copy link
Copy Markdown

Summary

When downstream clients using the OpenAI Responses wire protocol (such as OpenAI Codex CLI) submit namespaced tools (e.g. namespace: "functions", name: "exec"), OmniRoute flattens the wire descriptor to "functions__exec". Providers like Gemini, Antigravity, and Vertex AI normalize consecutive underscores to single underscores ("functions_exec").

This change fixes three interrelated issues that previously prevented Codex CLI from executing tools through Gemini/Antigravity:

  1. Enhances resolveRequestToolIdentity to resolve tool names against registered namespace candidates across delimiter variations (_, __, ., -) and bare leaf names, restoring the {namespace, name} tuple on response items.
  2. Prevents false-positive tool collisions in geminiToolsSanitizer.ts when both flattened and normalized forms appear in context history.
  3. Resolves the tool identity before checking customToolNames, enabling namespaced custom tools (like Codex's JavaScript isolate exec) to emit custom_tool_call with raw input rather than function_call with JSON arguments.

Related Issues

Validation

  • Change type: routing / provider
  • Focused tests and category gates from the golden path
  • Reconciled with the current active release base (release/v3.8.51); focused checks rerun afterward
  • Production-code changes include a new or updated automated test in this PR

Tests Added Or Updated

  • tests/unit/translator-resp-openai-responses-namespace-identity.test.ts
    • Added test for namespace restoration when delimiter is collapsed to single underscore (functions_exec).
    • Added test for namespace restoration when provider emits dot notation (functions.exec).

Coverage Notes

  • Covered by unit tests in tests/unit/translator-resp-openai-responses-namespace-identity.test.ts.
  • Manually validated end-to-end with live Codex CLI v0.153.4 running multiple turns of shell command execution and file operations through OmniRoute backed by antigravity/gemini-3.8-flash.

Reviewer Notes

  • No breaking changes or schema migrations.
  • Added changelog fragment under changelog.d/fixes/responses-codex-tool-namespace-resolution.md.

@diegosouzapw

Copy link
Copy Markdown
Collaborator

Thanks for tackling the Codex/Gemini namespace-delimiter mismatch — the core problem is
real and the identity-resolution approach is reasonable. However, comparing your diff
against the current tip turned up two regressions unrelated to this PR's stated scope:

  1. sendCompleted() in openai-responses.ts now emits the raw upstream error message
    (upstreamErr.message) instead of routing through projectCompletedStreamError() /
    buildErrorBody() — this drops our error-sanitization guarantee.
  2. The #12141 fix (stripping a neutral tool_choice when tools is empty, to avoid a
    vLLM 400) is fully removed from openai-responses.ts in your diff — it looks like an
    accidental loss during a rebase/reconciliation rather than an intended change.
    Could you restore both, add a test for the namespace-leaf-name collision case (two
    different namespaces sharing a tool named the same, e.g. exec), and rebase onto the
    current release/v3.8.51 tip (currently shows as conflicting)? Happy to re-review once
    those are in.

…tom tool payload for Codex

When downstream clients using the OpenAI Responses wire protocol (such as OpenAI
Codex CLI) submit namespaced tools (e.g. namespace: 'functions', name: 'exec'),
OmniRoute flattens the wire descriptor to 'functions__exec'. Providers like
Gemini, Antigravity, and Vertex AI normalize consecutive underscores to single
underscores ('functions_exec').

This commit addresses three interconnected issues in the translation pipeline:

1. Bidirectional Request Tool Identity Resolution:
   - Enhance resolveRequestToolIdentity to resolve tool names against candidates
     using delimiter variations (single underscore, double underscore, dot, hyphen)
     and leaf names, ensuring the original {namespace, name} tuple is restored
     even when the upstream provider alters the separator.

2. Gemini Tool Sanitizer De-duplication:
   - Update isSanitizedNameTaken to compare normalized forms rather than raw
     strings, preventing false collision detection when both 'functions_exec'
     (from conversation history) and 'functions__exec' (from declaration) exist.
   - Replace non-deterministic Date.now() collision suffixing with deterministic
     hash digests.

3. Custom Tool Payload Classification:
   - Ensure isCustomTool resolves the underlying tool identity before checking
     state.customToolNames, allowing namespaced custom tools (such as Codex's
     functions.exec) to correctly emit custom_tool_call events with raw input
     rather than regular function_call arguments.
   - Populate compound namespace variations in collectResponsesCustomToolNames.
@multi-forge
multi-forge force-pushed the fix/responses-codex-tool-namespace-resolution branch from edd9fb4 to a96510d Compare September 15, 2026 14:10
@multi-forge

Copy link
Copy Markdown
Author

Thanks for the review, @diegosouzapw!

All items addressed:

  1. Restored error sanitization in \sendCompleted()\ via \projectCompletedStreamError(upstreamErr).
  2. Restored the [BUG] Responses-to-Chat forwards tool_choice="auto" without tools, causing upstream 400 #12141 fix stripping neutral \ ool_choice\ when \ ools\ is empty/absent.
  3. Added unit tests covering the cross-namespace leaf-name collision case (e.g. \ unctions\ and \sandbox\ both providing \exec) across delimiter variations (_, ., __), plus safe ambiguity handling when a bare leaf is echoed.
  4. Cleanly rebased onto current
    elease/v3.8.51\ tip (PR is now \MERGEABLE\ with no conflicts, and \ ypecheck:core\ + 78/78 related unit tests pass).

Ready for re-review!

multi-forge and others added 3 commits September 16, 2026 01:13
…sification

resolveRequestToolIdentity()'s suffix-match step matched a dotted tool name
against any candidate's leaf regardless of namespace (e.g. "other.exec_command"
resolved to the "exec" namespace's identity), breaking the pre-existing
dotted-alias contract. Dot notation is now handled exclusively by the exact
namespace.name check; the suffix step stays underscore-only for hash-prefixed
wire names.

The Codex-specific "exec" custom-tool carve-out in emitToolCall/closeToolCall
matched ANY tool literally named "exec", including plain function tools with
no namespace (e.g. Gemma), misclassifying them as custom_tool_call. Scope the
carve-out to only fire when the name resolved through a namespace identity.

Also extract the duplicated isCustomTool computation from emitToolCall and
closeToolCall into a shared resolveToolCustomStatus() helper so the two call
sites cannot drift apart, which brings openai-responses.ts back under its
frozen file-size budget.

Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
restoreResponsesPassthroughFunctionCallIdentity() was switched from a raw
Map.get() to resolveRequestToolIdentity() to pick up the same delimiter-variant
resolution as the openai-responses translator, but the import was missing —
a live TS2552 (undeclared reference) that would throw a ReferenceError on the
first Responses passthrough function_call restore.

Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
Resolves the conflict in nonStreamingClientTranslate.ts — both sides call resolveRequestToolIdentity on the same line; kept the base's explanatory comment (cheaper-inference#12370). Overlaps cheaper-inference#13128 in this area — whichever lands second needs a re-sync.

Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
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.

2 participants