Skip to content

fix: add missing index field to streaming tool_call chunks#49

Merged
CaddyGlow merged 2 commits intoCaddyGlow:mainfrom
chaizhenhua:fix/streaming-tool-call-index
Mar 22, 2026
Merged

fix: add missing index field to streaming tool_call chunks#49
CaddyGlow merged 2 commits intoCaddyGlow:mainfrom
chaizhenhua:fix/streaming-tool-call-index

Conversation

@chaizhenhua
Copy link
Copy Markdown
Contributor

Summary

The OpenAI Chat Completions streaming API spec requires each tool_calls delta chunk to include an index field (ref). This field identifies which tool call in the array the chunk belongs to, which is essential when the model invokes multiple tools in parallel.

Currently, ccproxy omits index from streaming tool_calls chunks. This causes clients that rely on index for chunk reassembly (e.g., the genai Rust crate) to silently discard all tool call data, resulting in agents that terminate immediately without executing any tools.

Root Cause

The ToolCall Pydantic model in ccproxy/llms/models/openai.py lacked an index field. Streaming formatters that construct ToolCall instances (anthropic_to_openai, openai_to_openai, claude_sdk) never set it, so serialized chunks were missing the field entirely.

Changes

  • ccproxy/llms/models/openai.py: Add index: int | None = None to ToolCall
  • ccproxy/llms/formatters/anthropic_to_openai/_helpers.py: Accept and pass through index in build_openai_tool_call
  • ccproxy/llms/formatters/anthropic_to_openai/streams.py: Pass accumulated index when building tool calls
  • ccproxy/llms/formatters/openai_to_openai/streams.py: Set index=state.index on all ToolCall instantiations
  • ccproxy/llms/formatters/openai_to_openai/responses.py: Set index based on position in tool_calls list
  • ccproxy/plugins/claude_sdk/parser.py: Pass positional index to format_openai_tool_call

Before (broken)

{"delta": {"tool_calls": [{"id": "fc_xxx", "type": "function", "function": {"name": "list_files", "arguments": ""}}]}}

After (fixed)

{"delta": {"tool_calls": [{"index": 0, "id": "fc_xxx", "type": "function", "function": {"name": "list_files", "arguments": ""}}]}}

Impact

This fix restores compatibility with any OpenAI-compatible client that parses streaming tool calls per the API spec. Without it, tool-calling agents built on frameworks like tirea + genai cannot function through ccproxy.

chaizhenhua and others added 2 commits March 21, 2026 18:47
The OpenAI Chat Completions streaming API requires each tool_call
delta to include an `index` field that identifies which tool call
in the array the chunk belongs to. Without it, clients like the
genai Rust crate silently discard tool_call chunks, causing agents
to terminate early without executing any tools.

Changes:
- Add optional `index` field to `ToolCall` model
- Pass `index` through `build_openai_tool_call` helper
- Set `index=state.index` on all `ToolCall` instantiations in
  openai_to_openai and anthropic_to_openai streaming formatters
- Set `index` in claude_sdk parser tool call conversion
The OpenAI spec defines two distinct types for tool calls:
- ChatCompletionMessageToolCall (non-streaming): id, type, function
- ChoiceDeltaToolCall (streaming): index (required), id, function, type

Split the single ToolCall model into ToolCall (non-streaming, no index)
and ToolCallChunk (streaming, required index). This keeps non-streaming
responses spec-compliant while enforcing index on streaming chunks.
@CaddyGlow CaddyGlow merged commit 982ff77 into CaddyGlow:main Mar 22, 2026
14 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.

2 participants