[codex] fix claude codex composition semantics#178
Merged
Conversation
Preserve explicit parallel_tool_calls values and strip output_parts from function_call_output before forwarding Responses payloads to Codex. This keeps the Claude /v1/messages -> Responses -> Codex composition aligned with the working individual legs and locks the regression with focused tests. Co-authored-by: Codex <noreply@openai.com>
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.
Summary
This PR fixes a composition bug in the Claude-to-Codex request path.
The individual legs already worked:
POST /v1/messages -> openai-responsesPOST /v1/responses -> codexBut the composed path
POST /v1/messages -> codexcould still misbehave for agentic traffic because theResponses -> Codexadapter was not preserving request semantics.User impact
For Claude Code style traffic routed through
Anthropic -> Responses -> Codex, the proxy could change the meaning of the request before it reached Codex. In practice this meant the composed path was not reliably equivalent to the two working legs above, which made Claude/Codex interoperability fragile in tool-calling flows.Root cause
The issue was in
crates/token_proxy_core/src/proxy/codex_compat/request.rs:parallel_tool_callswas always overwritten totrue, even when the upstream conversion had explicitly set it tofalsefunction_call_output.output_partswas forwarded to Codex unchanged, even though Codex only needs the flattenedoutputstring for this request shapeThis made the adapter non-compositional: the intermediate
Anthropic -> Responsesconversion produced one semantic payload, and the subsequentResponses -> Codexconversion changed that payload again.Fix
This PR makes the
Responses -> Codexadapter preserve the upstream semantics instead of overriding them:parallel_tool_callstotruewhen the field is absentoutput_partsfromfunction_call_outputitems before forwarding to CodexThe change is intentionally narrow and only affects the
Responses -> Codexrequest path.Tests
Added focused regression tests for the exact broken behavior:
responses_request_to_codex_preserves_parallel_tool_calls_falseresponses_request_to_codex_strips_output_parts_from_function_call_outputValidated with:
cargo test -p token_proxy_core responses_request_to_codex_preserves_parallel_tool_calls_false -- --nocapturecargo test -p token_proxy_core responses_request_to_codex_strips_output_parts_from_function_call_output -- --nocapturecargo test -p token_proxy_core anthropic_messages_request_routes_to_codex -- --nocapturecargo test -p token_proxy_core anthropic_messages_request_falls_back_from_responses_to_codex -- --nocapturecargo fmt --allcargo test -p token_proxy_core