Skip to content

Emit early progress frames for streaming tool calls#113

Open
adambalogh wants to merge 1 commit into
mainfrom
claude/agent-file-write-perf-x8efl9
Open

Emit early progress frames for streaming tool calls#113
adambalogh wants to merge 1 commit into
mainfrom
claude/agent-file-write-perf-x8efl9

Conversation

@adambalogh

Copy link
Copy Markdown
Contributor

Summary

Add support for emitting lightweight progress frames during streaming tool calls on buffered providers (OpenAI/Anthropic). This allows clients to display tool activity immediately (e.g., "writing file…") while the complete, argument-bearing tool call is still accumulated and flushed at the end of the stream for signing.

Problem

On buffered providers like OpenAI and Anthropic, tool calls are held until the stream ends so the signature can cover the complete arguments. This means clients receive no "tool starting" signal until generation finishes — a large write_file call can appear frozen for minutes while the file is being generated. Additionally, the lack of streaming bytes can cause read timeouts.

Solution

  • Add _MockStreamChunk test helper to simulate LangChain AIMessageChunk objects yielded by model.stream(), with support for tool_call_chunks and usage_metadata
  • Emit progress frames in chat_controller.py when tool call chunks arrive with a name but incomplete arguments:
    • Progress frame carries only the tool index, id, and name — never the accumulating arguments
    • Keeps bytes flowing to the client and prevents timeout
    • The authoritative, complete tool call (with full arguments) is still flushed once after the stream loop, ahead of the signed final frame
  • Add comprehensive test (test_streaming_tool_call_emits_early_progress_frame) that:
    • Simulates realistic streaming chunks (name in first fragment, arguments trickling in)
    • Verifies an early progress frame announces the tool name without arguments
    • Confirms the complete arguments arrive in a later frame
    • Ensures progress frames arrive before the signed final frame

Implementation Details

The progress frame logic is gated by checking whether the tool call chunk has a name field but the accumulated tool call is incomplete. This ensures:

  • Early frames are emitted only when the tool name becomes known
  • Arguments are never leaked in progress frames (only in the final signed frame)
  • The mechanism is transparent to non-buffered providers (e.g., Gemini) that already stream complete tool calls

https://claude.ai/code/session_01DKT5v2Ep7RU5Law5xh2L4i

OpenAI/Anthropic tool calls are buffered server-side so the response
signature can cover the complete arguments, and the full tool call was
only flushed after the entire stream finished. For a large write_file
call this meant the client received zero bytes for the whole generation
(minutes): the UI showed no "writing file" activity and the connection
sat idle long enough to trip read timeouts.

Emit a lightweight progress frame as fragments arrive — carrying just
the tool name/index, never the accumulating arguments — so the client's
existing "preparing tool" indicator fires immediately and bytes keep
flowing. The authoritative, argument-bearing tool call is still flushed
once at the end of the stream ahead of the signed final frame, so the
signed output and verification are unchanged.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DKT5v2Ep7RU5Law5xh2L4i
@adambalogh adambalogh marked this pull request as ready for review June 26, 2026 15:53
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