-
Notifications
You must be signed in to change notification settings - Fork 72
fix: synthesize function_call_arguments events for one-shot Codex tool calls#6
Open
srizzo wants to merge 1 commit into
Open
fix: synthesize function_call_arguments events for one-shot Codex tool calls #6srizzo wants to merge 1 commit into
srizzo wants to merge 1 commit into
Conversation
1 task
...l calls
Some Codex reasoning models (observed on gpt-5.3-codex-spark) emit tool-call
arguments in a single shot on response.output_item.done and skip the
response.function_call_arguments.delta / .done pair that the OpenAI
Responses API spec requires between output_item.added and output_item.done.
Downstream clients that assemble the final arguments string from the delta
events (LiteLLM's Anthropic-Messages adapter, Cursor, etc.) end up with
empty arguments — e.g. an Anthropic tool_use with input: {} — which breaks
any tool that requires parameters (Bash, file writes, ...).
openai-oauth is the layer that publishes OpenAI-public /v1/responses on
top of the Codex-internal backend, and already does request-side
normalization (stripping unsupported params, renaming tools). This commit
adds the symmetric response-side translation: a ReadableStream transformer
that tracks per-item_id whether any delta was seen, and when an
output_item.done arrives carrying a function_call with non-empty
arguments and no prior delta, synthesizes a spec-compliant
.delta + .done pair immediately before re-emitting the done event.
Models that already stream deltas (gpt-5.4 family) are unaffected — the
transformer's seenDeltas set ensures it only synthesizes when real deltas
were absent.
@srizzo
srizzo
force-pushed
the
fix/codex-responses-arguments-sse
branch
from
April 18, 2026 14:31
7ac0ade to
8909bb7
Compare
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
Some Codex reasoning models (observed on
gpt-5.3-codex-spark) emit tool-call arguments in a single shot onresponse.output_item.doneand skip theresponse.function_call_arguments.delta/.donepair that the OpenAI Responses API spec requires betweenoutput_item.addedandoutput_item.done.Downstream clients that assemble the final arguments string from the delta events (LiteLLM's Anthropic-Messages adapter, Cursor, etc.) end up with empty arguments — e.g. an Anthropic
tool_usewithinput: {}— which breaks any tool that needs parameters (Bash, file writes, ...).This is the symmetric counterpart to #3: that PR fixed the same class of issue on the Chat Completions path (via the Vercel AI SDK tool-call handler); this one fixes it on the Responses API path.
What changes
normalizeCodexResponsesSseStreamtransformer inopenai-oauth-core/src/sse.ts. Wraps the upstream Codex SSEReadableStreamand, when a function-calloutput_item.donearrives with non-emptyargumentsand no prior delta for the sameitem_id, injects a spec-compliant.delta+.donepair immediately before re-emitting the done event.handleResponsesRequestinpackages/openai-oauth/src/responses.tsnow pipes the upstream streaming body through that transformer instead of passing it through verbatim.openai-oauth-core's barrel.packages/openai-oauth-core/test/sse.test.tscovering:Why this is the right layer
openai-oauth publishes
/v1/responsesas an OpenAI-public endpoint on top of the Codex-internal backend. It already does request-side normalization (strippingtemperature/top_p/user, renamingweb_search_preview→web_search, ...). The response-side pass-through inhandleResponsesRequestassumed the upstream stream was already spec-conforming; for codex-spark that assumption is violated. Fixing it here means every downstream consumer of the proxy — not just LiteLLM — benefits without needing its own workaround.Test plan
bun run test— 64 tests pass (26 in openai-oauth-core, incl. 6 new)bun run buildand deployed to a local install; restartedopenai-oauth; confirmed Claude Code subagents routed through LiteLLM →codex-gpt-5.3-codex-sparknow receive populatedtool_use.inputfor Bash calls where previouslyinput: {}appeared and agents looped onInputValidationError: command is missing.bun run testANTHROPIC_DEFAULT_HAIKU_MODEL=codex-gpt-5.3-codex-spark claude-codex -p "list /tmp"and check the Bash tool call receives a realcommandargumentcodex-gpt-5.4to confirm delta-streaming models behave unchanged