Skip to content

fix(translate): resolve redeclared Responses tools to their latest declaration - #714

Merged
Menci merged 103 commits into
mainfrom
fix/responses-tool-redeclaration
Nov 13, 2026
Merged

Menci merged 103 commits into
mainfrom
fix/responses-tool-redeclaration

Conversation

@Menci

@Menci Menci commented Nov 12, 2026 •

Copy link
Copy Markdown
Owner

Problem

Codex declares the same namespaces in top-level tools, again in an additional_tools input item, and once more after each compaction. The Responses source flattened every inventory into IR tools verbatim, so a captured Codex request routed to Anthropic Messages carried 89 tools with only 70 unique names, and the upstream refused it with 400 invalid_body (duplicate tool names).

Change

  • The Responses source collapses repeated callable identities (type, namespace, name) to the latest declaration, placed in the slot of the first occurrence. The latest declaration is the caller's current statement of the tool; keeping the first slot keeps the declared prefix stable for prompt caching.
  • Group (namespace) selectors in allowed_tools expand over the resolved declarations, so they no longer produce duplicate selectors.
  • The IR request header documents that each callable identity appears once, and allocateToolNames refuses a request that repeats one instead of emitting duplicate names a target wire rejects.

Replaying the captured request through the fix yields 70 tools, 70 unique.

Test Plan

  • New tests in allowed-tools_test.ts (Chat Completions and Messages targets) and composition/request/tool-names_test.ts fail without the change and pass with it.
  • Related translate suites pass locally: allowed-tools, composition/request/tool-names, as-source/input-to, as-source/programmatic-tooling, openai-responses/round-trip-request, pairs/openai-responses-to-anthropic-messages-request, pairs/input-controls-request, call-association.
  • tsc --noEmit for translate and ESLint on the changed files are clean.
  • CI on the final head.

Menci added 30 commits November 12, 2026 04:38
Remove the request-count ceiling from output IR construction and Chat event routing while requiring sequential valid indexes. Verify that every upstream choice is retained and skipped indexes still fail.
Replace fluent protocol subpaths with named root translators returning plain trips or input failures. Keep request builders and event adapters intact while containing expected input exceptions at public operations.

Consume translation failures at gateway attempt and payload boundaries, and encode rewritten JSON errors at the transport boundary. Preserve the current model context and remote-image loader requirements for the pairwise request backend.

Validate named translation and gateway suites, owning TypeScript projects, and changed lint targets.
Keep refusals on assistant history and use role-specific content contracts in translation and provider consumers. Preserve retained refusal text when compaction restates history as input content.
Split native replay coordination into reader, writer, and coordinator owners. Distinguish native input turns, read and emitted reference states, text fragments, content-reference tags, and the transport codec without changing replay wire behavior.

Preserve the pairwise request replay signature and reasoning warnings. Keep native output-to-input normalization and Gemini target slots, and move converter assertions to their protocol owners.

Validated 38 named test files (307 tests), translate typecheck, and touched-file ESLint. Production HMAC/CBOR sidecar bytes, digests, selected references, restored turns, and native converter outputs match the main baseline for literal, astral, and unpaired-surrogate fixtures.
Restate recognized upstream errors through source-owned converters before and during streams. Preserve independent error classifications, nullable code and param values, envelope metadata, and one merged provider extension container.

Use the same recognized context-overflow decision for Gemini HTTP and body codes. Keep unknown response bytes untouched and propagate unexpected parser failures with their original error and cause.

Validate 322 tests in 19 named files, changed-file ESLint, and the translate and gateway TypeScript projects.
Separate output construction, records, accumulation, lifecycle and path
operations. Name output model types explicitly and share JSON and text-range
shapes through their owners.

Move protocol fixtures and assertions out of output IR tests. Let unit
queues own their links and remove dead builder and path-query APIs.

Preserve configured choice limits and native SSE, error and sidecar behavior.
Validate with 612 focused tests, 67 baseline differential probes, 39 logical
correspondences, scoped lint and the owning TypeScript project.
Normalize HTTP and streaming error envelopes through the same metadata merge. Preserve outer request identifiers and trace fields in the existing extension container while retaining error-level collision priority.

Distinguish event transport fields from native error payloads so canonical payloads retain their original reference and classification when no envelope data remains.

Validate 324 tests in 19 named files, ESLint on all four changed files, and the translate and gateway TypeScript projects.
Use directional events modules and read/write entrypoints for the seven
native output adapters, and mirror those owners in their tests. Move the
unchanged source writer options shape to its shared owner.

Finish schema phantom, absent-string and type-equality names so the input
work has no remaining output-only naming migration.

Preserve native behavior with 622 named tests, 67 controlled main/final
wire and replay probes, 66 runtime/declaration/test-body correspondences,
98 scoped lint targets and the translate TypeScript project.
Unify recursive and assistant-turn restoration through RestoreResult.value while retaining the pairwise request boundary's native turn wrapper.

Centralize the four protocols' existing digest material projection and version dispatch in shared digest factories. Preserve synchronous projector failures and skip projection for unsupported versions.

Validated 38 named test files (309 tests), translate typecheck, and 14 touched-file lint targets. Production HMAC/CBOR bytes and restored native values match main; all four protocol digest projections and version behavior also match main.
Retain the lossless error fields in existing output assertions after their ownership migration. Order imports at the combined output and replay owners without changing runtime behavior.
Apply the accepted reader/writer, digest material ownership, and text placement comments at their replay owners.

Verify unchanged compiler-token and directive streams for all four files, scoped lint, translate typecheck, named replay tests, and production byte and source digest invariance.
Read source requests into immutable input concepts and compose target writers with lazy authenticated native replay. Preserve complete tool declarations, logical call identity, request controls and media references across all public routes.\n\nRemove pairwise request conversion and carry the resolved Gemini model in the formal source request. Keep prerequisite output lifecycle, error projection and signed replay behavior intact.
Preserve sparse namespace provenance coverage with logical IR call kinds. Align restored native call names and Responses error assertions with the final request allocation and prerequisite error contracts.
Keep output validation diagnostics, type refinements and reviewed comments
with their output owner. Preserve configured choice limits and gap filling
while narrowing relative paths to strings and explicit path arrays.

Give completion-aware JSON parsing its own module and mirrored tests.
Reuse the existing structural record guard from the JSON owner in native
writers. Retain the reviewed sentinel and write-validation rationale.

Validate 625 tests in 43 named files, 68 controlled main/final differential
probes, 89 runtime/declaration/test correspondences, 101 scoped lint targets
and the translate TypeScript project. Track diagnostic terminology changes
explicitly while comparing complete wire, digest and native replay values.
Move each source protocol's error converters into as-source/error.ts so HTTP error restatement and SSE output share the focused error owner. Preserve the extracted implementation byte for byte.

Align six existing error expectations in three test files with retained upstream details and independent classifications.

Validate 376 tests in 22 named files, all 17 changed TypeScript lint targets, and the translate and gateway TypeScript projects.
Preserve the param and code supplied by raw translation input failures in Messages and GenerateContent error envelopes. Omit absent details and keep the rejecting candidate performance context intact.

Keep protocol error results precise and validate both detail envelopes, the named translation boundary suites, and gateway TypeScript checks.
Port the reviewed capture, error telemetry, and Responses attempt explanations to their public-boundary owners. Remove redundant dispatcher and assertion narration and update the dashboard capture reference.
Apply reviewed input-reader, target-writer and request regression comments from the complete feature snapshot. Preserve finalized prerequisite error, replay and output ownership.
Menci added 26 commits November 12, 2026 19:44
…claration

Codex declares a namespace in top-level `tools`, again in an
`additional_tools` input item, and once more after each compaction. The
Responses source flattened every inventory into IR tools verbatim, so a
captured Codex request translated to Anthropic Messages carried 89 tools
with only 70 unique names and the upstream refused it with 400.

The Responses source now collapses repeated callable identities
(type, namespace, name) to the latest declaration, which is the caller's
current statement of the tool, placed in the slot of the first so the
declared prefix stays stable for prompt caching. Group selectors expand
over the resolved declarations, so they no longer yield duplicates.

IR tools now carry each callable identity once, and name allocation
refuses a request that repeats one rather than emitting duplicate names
a target wire rejects.
Integrate the landed translation prerequisites and later main updates without replaying their squashed history. Preserve the input IR leaf delta and place target-request preparation under composition, where its restored-turn state belongs.
Emit max_tokens beside max_completion_tokens for every translated Chat request so OpenAI's replacement spelling and compatible upstreams that still read the legacy spelling receive the same ceiling. Preserve null and zero explicitly and cover all Chat-target source routes.
Keep URLs and file_data strings exact in the input IR, including data URL parameters and non-base64 forms. Parse data URLs only at a target that needs inline bytes, preserve stated media types, and invent a generic MIME type only where the target requires one.
Represent top-level source instructions separately from inline system and developer messages so target hoisting cannot merge their roles or positions. Preserve explicitly empty Chat assistant arrays and Gemini thought turns instead of deleting their conversation boundaries.
Represent user, system and developer history as messages with explicit roles while keeping top-level instructions distinct. Carry nullable OpenAI controls through the IR, normalize ProtoJSON nulls as unset, preserve separate refusal values, and avoid empty namespace-description prefixes.
Reject deferred and programmatic tools from every Responses inventory, refuse namespace selector extensions, and preserve each source protocol's established orphan-choice behavior. Avoid synthesizing a Messages parallel-tool choice when no declarations exist.
Map Messages document results into formal IR files, retain unmodeled structured blocks as opaque JSON, preserve JSON null function arguments, and carry server-tool errors through the shared incomplete/is_error result state.
Remove source-side call identities from restored turns and reserve their native wire names without rewriting them. Allocate call/result IDs before wire names, and pair legacy Chat calls only with the unique id-less result stating the same function name before modern matching.
Stop translating includeThoughts into an upstream reasoning-summary request. Record complete target reasoning for sidecar replay while emitting plaintext thought text only when the Gemini source requested it, using a separate visible digest so authenticated replay retains the original reasoning.
Pin the restored no-tool choice behavior and distinguish malformed argument fallback from valid non-object JSON arguments after the input IR fixes.
Make reasoning-output fixtures request Gemini thought plaintext explicitly, assert restored turns remain target-native, retain ProtoJSON null arguments, and update gateway instruction expectations after the finalized IR behavior.
Keep historical roles and empty turns intact, preserve nullable schema fields and required tool choices at the correct target boundaries, and associate legacy Chat results before modern calls.\n\nRepresent Messages documents formally in tool results, move data URL decoding to its target owner, and remove restored-turn name reservations and duplicate Gemini visibility state.
Treat ProtoJSON null message fields as unset, preserve text-only content documents as formal files, and let distinct legacy function calls match their named results.\n\nSimplify the Responses instruction projection and remove the remaining stale prompt and strict-state wording.
Preserve text-block boundaries without changing transport-fragment concatenation, retain nullable Responses controls and immutable request snapshots, and constrain generated Responses call IDs at the target.\n\nKeep leading Messages system content hoisted, order tool results legally, drop foreign encrypted reasoning, and carry Gemini function-response media through formal IR parts.
Use a truly unrepresentable content document in input-error coverage, assert the single Gemini accumulator, and encode the final decisions for foreign encrypted reasoning and inline historical role messages.
Remove the unsupported input-IR block distinction and restore direct text-part concatenation. The previous change inferred desired behavior from main without a user decision.
Keep top-level instructions distinct from historical role messages, preserve readable reasoning beside opaque encrypted data, and remove speculative request snapshots.\n\nDrop the implementation-coupled Gemini accumulator test.
Represent Messages documents separately from files, retain content-backed text and image sequences, and project unsupported document semantics through provenance tags.\n\nRestore native Messages documents, emit tagged Responses content, and lift Chat tool-result files and documents alongside images.
Remove comments that restate field names and types, retaining only cross-protocol invariants and non-obvious representation decisions.
Annotate every input IR type and field with the source protocols that can populate it, matching the compact coverage style used by output IR.
Remove redundant validation and indirection while preserving request projections. Correct protocol coverage annotations and current translation ownership notes.
Carry document metadata and nested attributes through input IR, projecting citation and cache controls where supported. Preserve unrecognized custom tool formats until target writing and return an input error when they cannot be translated.
Clear stale namespaces when a Chat tool call changes its name, and apply the updated identity before a delayed call ID opens the Responses item.
Cover function and custom calls when names or namespaces change across added, done, and terminal snapshots.
@Menci
Menci changed the base branch from feat/fit-ir-input to main November 13, 2026 08:28
@Menci
Menci marked this pull request as ready for review November 13, 2026 08:28
…declaration

# Conflicts:
#	packages/translate/__tests__/composition/request/tool-names_test.ts
#	packages/translate/__tests__/protocols/openai-responses/as-source/allowed-tools_test.ts
#	packages/translate/src/composition/request/tool-names.ts
#	packages/translate/src/ir/input/types.ts
#	packages/translate/src/protocols/openai-responses/as-source/input-to.ts
@Menci
Menci merged commit 2e5a4be into main Nov 13, 2026
21 checks passed
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.

1 participant