Problem
When a streaming tool call is interrupted by token exhaustion, tool calls end up with empty nativeArgs and the user sees:
Invalid tool call for 'write_to_file': missing nativeArgs. This usually means the model streamed invalid or incomplete arguments and the call could not be finalized.
This affects any OpenAI-compatible provider when the output token limit is hit mid-tool-call — it is not provider-specific.
Root Cause
1. processToolCalls() only handles finish_reason: "tool_calls"
File: src/api/providers/openai.ts:497
if (finishReason === "tool_calls" && activeToolCallIds.size > 0) {
for (const id of activeToolCallIds) {
yield { type: "tool_call_end", id }
}
activeToolCallIds.clear()
}
This does NOT handle:
finish_reason: "length" — standard OpenAI behavior when max_tokens is reached mid-tool-call
- Stream termination without any
finish_reason — behavior of some OpenAI-compatible proxies (e.g., MiMo)
2. No post-loop cleanup in stream handlers
Both handleStreamResponse() (openai.ts:437-463) and MimoHandler.createMessage() (mimo.ts:110-142) end their for await loops without any cleanup of activeToolCallIds. If processToolCalls() never emitted tool_call_end, the tool calls stay as partials forever.
3. Error message lacks actionable guidance
presentAssistantMessage.ts:422-424 reports the error but gives the user no hint about what to do.
Reproduction
Direct API reproduction (curl)
With max_tokens:100 (forces premature termination):
curl -s -X POST https://token-plan-sgp.xiaomimimo.com/v1/chat/completions -H "Content-Type: application/json" -H "Authorization: Bearer <KEY>" -d '{
"model":"mimo-v2.5-pro",
"messages":[{"role":"user","content":"Use write_to_file to create file test.txt with 2000 chars of lorem ipsum"}],
"tools":[{"type":"function","function":{"name":"write_to_file","parameters":{"type":"object","properties":{"path":{"type":"string"},"content":{"type":"string"}},"required":["path","content"]}}}],
"max_tokens":100,
"stream":true,
"stream_options":{"include_usage":true}
}'
Result: Stream ends with {"choices":[]} — no finish_reason, empty choices array. Only 55 tokens available for tool call after reasoning.
Standard OpenAI behavior
When OpenAI hits max_tokens mid-tool-call, it emits finish_reason: "length". Zoo Code's processToolCalls() ignores this entirely, leaving tool calls unfinalized.
| Scenario |
OpenAI Standard |
MiMo Proxy |
| Token limit hit mid-tool-call |
finish_reason: "length" ❌ ignored |
choices: [] (no finish_reason) ❌ ignored |
| Tool call completed |
finish_reason: "tool_calls" ✅ |
finish_reason: "tool_calls" ✅ |
| Normal stop |
finish_reason: "stop" ✅ |
finish_reason: "stop" ✅ |
Proposed Fix
Fix 1 (P0): Handle finish_reason: "length" in processToolCalls()
File: src/api/providers/openai.ts:497
// Before:
if (finishReason === "tool_calls" && activeToolCallIds.size > 0) {
// After:
if ((finishReason === "tool_calls" || finishReason === "length") && activeToolCallIds.size > 0) {
Fix 2 (P0): Add post-loop cleanup in stream handlers
File: src/api/providers/openai.ts — handleStreamResponse() after the for await loop (line 462):
// Finalize any tool calls that weren't explicitly ended
if (activeToolCallIds.size > 0) {
for (const id of activeToolCallIds) {
yield { type: "tool_call_end", id }
}
activeToolCallIds.clear()
}
File: src/api/providers/mimo.ts — createMessage() after the for await loop (line 142):
// Finalize any tool calls that weren't explicitly ended by finish_reason
for (const id of activeToolCallIds) {
yield { type: "tool_call_end", id }
}
activeToolCallIds.clear()
Fix 3 (P1): Improve error message
File: src/core/assistant-message/presentAssistantMessage.ts:422-424
const errorMessage =
`Invalid tool call for '${block.name}': missing nativeArgs. ` +
`This usually means the model ran out of output tokens before completing the tool arguments. ` +
`Try: (1) simplifying the request, (2) breaking it into smaller steps, ` +
`or (3) increasing the model's max output tokens in settings.`
Files to Modify
| File |
Change |
Priority |
src/api/providers/openai.ts |
Handle finish_reason: "length" + post-loop cleanup |
P0 |
src/api/providers/mimo.ts |
Post-loop cleanup after stream |
P0 |
src/core/assistant-message/presentAssistantMessage.ts |
Better error message |
P1 |
src/api/providers/__tests__/openai.spec.ts |
Tests for new behavior |
P1 |
src/api/providers/__tests__/mimo.spec.ts |
Integration test for premature stream termination |
P1 |
Related Issues
Alignment with CONTRIBUTING.md
Reliability First: Ensure diff editing and command execution are consistently reliable. Expand robust support for a wide variety of AI providers and models.
This fix directly improves reliability for all OpenAI-compatible providers when output tokens are exhausted mid-tool-call.
Problem
When a streaming tool call is interrupted by token exhaustion, tool calls end up with empty
nativeArgsand the user sees:This affects any OpenAI-compatible provider when the output token limit is hit mid-tool-call — it is not provider-specific.
Root Cause
1.
processToolCalls()only handlesfinish_reason: "tool_calls"File:
src/api/providers/openai.ts:497This does NOT handle:
finish_reason: "length"— standard OpenAI behavior whenmax_tokensis reached mid-tool-callfinish_reason— behavior of some OpenAI-compatible proxies (e.g., MiMo)2. No post-loop cleanup in stream handlers
Both
handleStreamResponse()(openai.ts:437-463) andMimoHandler.createMessage()(mimo.ts:110-142) end theirfor awaitloops without any cleanup ofactiveToolCallIds. IfprocessToolCalls()never emittedtool_call_end, the tool calls stay as partials forever.3. Error message lacks actionable guidance
presentAssistantMessage.ts:422-424reports the error but gives the user no hint about what to do.Reproduction
Direct API reproduction (curl)
With
max_tokens:100(forces premature termination):Result: Stream ends with
{"choices":[]}— nofinish_reason, emptychoicesarray. Only 55 tokens available for tool call after reasoning.Standard OpenAI behavior
When OpenAI hits
max_tokensmid-tool-call, it emitsfinish_reason: "length". Zoo Code'sprocessToolCalls()ignores this entirely, leaving tool calls unfinalized.finish_reason: "length"❌ ignoredchoices: [](no finish_reason) ❌ ignoredfinish_reason: "tool_calls"✅finish_reason: "tool_calls"✅finish_reason: "stop"✅finish_reason: "stop"✅Proposed Fix
Fix 1 (P0): Handle
finish_reason: "length"inprocessToolCalls()File:
src/api/providers/openai.ts:497Fix 2 (P0): Add post-loop cleanup in stream handlers
File:
src/api/providers/openai.ts—handleStreamResponse()after thefor awaitloop (line 462):File:
src/api/providers/mimo.ts—createMessage()after thefor awaitloop (line 142):Fix 3 (P1): Improve error message
File:
src/core/assistant-message/presentAssistantMessage.ts:422-424Files to Modify
src/api/providers/openai.tsfinish_reason: "length"+ post-loop cleanupsrc/api/providers/mimo.tssrc/core/assistant-message/presentAssistantMessage.tssrc/api/providers/__tests__/openai.spec.tssrc/api/providers/__tests__/mimo.spec.tsRelated Issues
missing nativeArgs) but different root cause (strict schema + truthy check in parser). Our fix addresses a separate, complementary gap in the core streaming handler.Alignment with CONTRIBUTING.md
This fix directly improves reliability for all OpenAI-compatible providers when output tokens are exhausted mid-tool-call.