Repository navigation
Description
Description
OpenAI-compatible streaming tool calls can fail when a provider/model sends the first tool_calls delta with an id and function.arguments, but without function.name, and then sends function.name in a later delta.
OpenCode currently treats the first partial tool-call delta as invalid and throws:
AI_InvalidResponseDataError: Expected 'function.name' to be a string.This appears to be a streaming parser robustness issue. OpenAI-compatible streaming responses may split tool-call fields across deltas, so a partial chunk should be accumulated until enough data exists to emit the tool call.
This is similar in spirit to vercel/ai#6687, where provider-specific OpenAI-compatible tool-call chunks were valid after accumulation but failed because the parser expected a more complete shape too early.
Expected Behavior
The following streamed chunks should be accumulated into a single valid tool call:
{
"id": "call_123",
"type": "function",
"function": {
"name": "bash",
"arguments": "{\"command\": \"ls -la\"}"
}
}OpenCode should execute the bash tool after the function.name and complete JSON arguments have been received.
Actual Behavior
OpenCode throws before the later chunk containing function.name is processed:
AI_InvalidResponseDataError: Expected 'function.name' to be a string.Minimal Reproduction
This mock OpenAI-compatible SSE stream reproduces the chunk ordering. The key part is the second chunk: it contains id, type, and function.arguments, but no function.name. The third chunk then provides function.name.
const chunks = [
`data: {"choices":[{"finish_reason":null,"index":0,"delta":{"role":"assistant","content":null}}]}`,
`data: {"choices":[{"finish_reason":null,"index":0,"delta":{"tool_calls":[{"index":0,"id":"call_123","type":"function","function":{"arguments":""}}]}}]}`,
`data: {"choices":[{"finish_reason":null,"index":0,"delta":{"tool_calls":[{"index":0,"id":"call_123","type":"function","function":{"name":"bash","arguments":"{"}}]}}]}`,
`data: {"choices":[{"finish_reason":null,"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"\\u0022command\\u0022"}}]}}]}`,
`data: {"choices":[{"finish_reason":null,"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":": \\u0022ls -la\\u0022}"}}]}}]}`,
`data: {"choices":[{"finish_reason":"tool_calls","index":0,"delta":{}}]}`,
`data: [DONE]`,
]
const stream = new ReadableStream({
start(controller) {
for (const chunk of chunks) {
controller.enqueue(new TextEncoder().encode(`${chunk}\n\n`))
}
controller.close()
},
})
const response = new Response(stream, {
status: 200,
headers: { "Content-Type": "text/event-stream" },
})A parser that consumes this response should delay validation/emission of the tool call until function.name has arrived and the accumulated arguments are complete.
Raw SSE Stream
data: {"choices":[{"finish_reason":null,"index":0,"delta":{"role":"assistant","content":null}}]}
data: {"choices":[{"finish_reason":null,"index":0,"delta":{"tool_calls":[{"index":0,"id":"call_123","type":"function","function":{"arguments":""}}]}}]}
data: {"choices":[{"finish_reason":null,"index":0,"delta":{"tool_calls":[{"index":0,"id":"call_123","type":"function","function":{"name":"bash","arguments":"{"}}]}}]}
data: {"choices":[{"finish_reason":null,"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"\\u0022command\\u0022"}}]}}]}
data: {"choices":[{"finish_reason":null,"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":": \\u0022ls -la\\u0022}"}}]}}]}
data: {"choices":[{"finish_reason":"tool_calls","index":0,"delta":{}}]}
data: [DONE]Suggested Fix Direction
Accumulate partial tool-call deltas by index/id and only emit/validate the tool call once function.name is available. If the stream finishes and a pending tool call still has no function.name, then it should remain an invalid response.
Related
- Similar parser robustness issue in Vercel AI SDK: Tool Calls Fail with Empty Arguments in OpenAI-Compatible Provider vercel/ai#6687
- Existing OpenCode report with captured local-model stream: Weird error with toolcalling on local model #6649
Activity
github-actions commentedon Apr 24, 2026
This issue might be a duplicate of existing issues. Please check:
AI_InvalidResponseDataError: Expected 'function.name' to be a string.) with the same chunk ordering pattern —function.namearriving in a later delta thanfunction.arguments. You referenced this issue yourself; it may be worth consolidating there.wattmto commentedon Apr 24, 2026
Closing because this minimum reproduction targets the upstream OpenAI-compatible streaming parser behavior and should be filed against vercel/ai instead.
darkhipo commentedon Jun 17, 2026
Can confirm: bug appears fixed in v1.17.8
We reproduced this exact error (
AI_InvalidResponseDataError: Expected 'function.name' to be a string.) on opencode v1.17.5 withdashscope/qwen3.7-max-2026-06-08— an OpenAI-compatible streaming provider.After upgrading to v1.17.8 (installed via
curl -fsSL https://opencode.ai/install | bash), the bug no longer reproduces. Ran three consecutive reproduction tests with tool-calling prompts — all tool calls (ls,grep, file reads) executed cleanly with zero instances of the error, even under--log-level DEBUG.Environment:
~/.opencode/bin/opencode)dashscope/qwen3.7-max-2026-06-08(OpenAI-compatible streaming)Note: brew formula (anomalyco/tap) is not yet updated past v1.17.5; the install script gets v1.17.8 directly.
Thanks for the fix — this was causing significant tool-call reliability issues on our setup.
fix(chat): buffer streamed tool call until name arrives (#577)
nazerim commentedon Oct 1, 2026
Still reproducible on 1.18.34. Root cause confirmed by grepping published tarballs: the throw string
Expected 'function.name'ships in@ai-sdk/openai-compatibleup to 2.0.7 and is gone from 3.0.0 onward (vercel/ai#18440 / PR #18445 fixed it upstream) — but opencode's compiled binary still bundles a pre-3.0.0 copy, so every closed tracker reads "fixed/not-ours" while users keep hitting it.Filed with evidence + a deterministic synthetic-SSE reproduction (no real API key needed): #52462
Interim fix (MIT, plugin-only, no core changes): https://github.com/nazerim/toolcall-guard — repairs/buffers malformed
tool_callsdeltas at the fetch layer, scrubs replayed ChatML tokens from outbound history (they re-arm the provider's parser every turn), and auto-continues turns the provider silently truncates at a cleanfinish_reason:"stop".