Skip to content

fix(chat-completions): merge a streamed turn's message into its pending tool-call message - #4728

Merged
seratch merged 1 commit into
openai:mainfrom
mmjerge:fix-chatcmpl-streamed-toolcall-order
Aug 28, 2026
Merged

seratch merged 1 commit into
openai:mainfrom
mmjerge:fix-chatcmpl-streamed-toolcall-order

Conversation

@mmjerge

@mmjerge mmjerge commented Aug 28, 2026

Copy link
Copy Markdown
Contributor

Summary

A provider that streams tool-call deltas before the same turn's text yields response outputs ordered [function_call, message]. Replaying that history through Converter.items_to_messages flushed the pending tool_calls message when the message item arrived and opened a second assistant message for the text. Chat Completions rejects an assistant message with tool_calls that is not directly followed by its tool messages, so the next turn failed with 400 and the session was unrecoverable. The identical turn converted correctly when not streamed.

This PR merges the message into the pending assistant message instead. The merge only triggers on the same-turn streamed ordering: a function_call_output always flushes, so tool calls from a previous turn cannot still be pending when a message item arrives. Streamed and non-streamed histories now convert identically, and histories already persisted in sessions are healed on replay. When legacy thinking blocks were already reconstructed into content parts, the text is appended as a content part rather than discarded.

Fixes #4727.

Test plan

  • Four regression tests in tests/models/test_openai_chatcompletions_converter.py: the merged turn, streamed/non-streamed convergence, no merging across turn boundaries, and refusal merging. All four fail on main.
  • Full verification stack (.agents/skills/code-change-verification/scripts/run.sh): format, lint, typecheck pass; tests 9217 passed, 28 skipped, with one failure — tests/sandbox/test_run_cwd.py::test_python_skill_uses_absolute_root_from_nested_workdir, which fails identically on a clean main checkout in this environment (local sandbox setup, unrelated to this change).

Issue number

Closes #4727

Checks

  • I've added new tests, if relevant
  • I've run .agents/skills/code-change-verification/scripts/run.sh
  • I've confirmed all verification steps pass
  • If using Codex, I've run /review before submitting this PR

…ll message

A provider that streams tool-call deltas before the same turn's text
yields response outputs ordered [function_call, message]. Replaying that
history through Converter.items_to_messages flushed the tool_calls
message and opened a second assistant message for the text. Chat
Completions rejects an assistant message with tool_calls that is not
directly followed by its tool messages, so the next turn failed with 400
and the session was unrecoverable. The same turn converted correctly
when not streamed.

Merge the message into the pending assistant message instead. A
function_call_output always flushes, so tool calls from a previous turn
cannot still be pending when a message item arrives: the merge only
triggers on the same-turn streamed ordering. Streamed and non-streamed
histories now convert identically, and histories already persisted in
sessions are healed on replay.

Four regression tests; all fail on main.
@seratch seratch changed the title fix(models): merge a streamed turn's message into its pending tool-call message fix(chat-completions): merge a streamed turn's message into its pending tool-call message Aug 28, 2026
@seratch seratch added this to the 0.22.x milestone Aug 28, 2026
@seratch
seratch merged commit 38636a5 into openai:main Aug 28, 2026
18 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Streamed tool-call turns from Chat Completions providers poison the session: replay produces an invalid message sequence and every later turn 400s

2 participants