Skip to content

🦌 Fix incomplete tool call recovery on token limit truncation#50

Merged
masudahiroto merged 13 commits intomainfrom
deer/fix-token-limit-truncation-recovery
Apr 7, 2026
Merged

🦌 Fix incomplete tool call recovery on token limit truncation#50
masudahiroto merged 13 commits intomainfrom
deer/fix-token-limit-truncation-recovery

Conversation

@masudahiroto
Copy link
Copy Markdown
Contributor

@masudahiroto masudahiroto commented Apr 1, 2026

Summary

Fixes a bug where streams truncated mid-tool-input due to token limits (finishReason: 'length') left the agent in a broken state. When a stream is cut off before a tool call completes, the agent now recovers by injecting synthetic tool result messages with truncated args context, allowing the model to retry with smaller inputs.

Also fixes the client-side executingTool state not being cleared when TOOL_CALL_END is never received (e.g., due to stream truncation), which would leave the UI stuck in a tool-executing state.

Changes

  • packages/server/src/agents/AISDKAgent.ts — Inline token limit recovery logic: detect finishReason: 'length' with incomplete tool calls, inject synthetic tool results with truncated args info, and preserve completed tool call results in the same step
  • packages/server/src/agents/AISDKAgent.test.ts — Add comprehensive tests for token limit recovery: truncated mid-tool-input, maxSteps enforcement, multi-tool truncation, non-length finish reason handling, and mixed complete/incomplete tool calls
  • packages/client/src/hooks/useServerEvents.ts — Clear executingTool state on RUN_FINISHED and RUN_ERROR events to handle cases where TOOL_CALL_END is never received
  • bun.lock — Bump package versions from 1.9.3 to 1.9.4

Created by deer — review carefully.

// When the stream is cut mid-tool-input, tool-input-start fires (adding to activeToolCalls)
// but tool-call never fires (not added to completedToolCalls).
// We inject error tool_results so the model can retry with shorter arguments.
const incompleteToolCallIds = [...activeToolCalls.keys()].filter(id => !completedToolCalls.has(id));
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

since detection is an edge case, I wonder can we move this code to helper function or separate 'middleware' type utility so we can avoid polluting main AISDKAgent code? This file always tends to do too much.

@masudahiroto masudahiroto marked this pull request as draft April 1, 2026 10:36
deer-agent and others added 4 commits April 1, 2026 19:57
When a step had both completed and incomplete tool calls (e.g.
maxOutputTokens allows 1 tool call to finish but truncates the 2nd),
the recovery path's `continue` skipped collecting response.messages,
causing successful tool results to be lost from conversation history.
The model then re-executed already-completed tool calls in a loop.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
output: {
type: 'text',
value: [
`Error: Your tool call for "${toolCall.name}" was truncated by the output token limit (maxOutputTokens: ${maxOutputTokens}).`,
Copy link
Copy Markdown
Contributor

@mm-zacharydavison mm-zacharydavison Apr 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't this too technical for end-user?
Also it should be in i18n strings I think.

Or maybe I misunderstood, and this text is for AI agent instead.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This message is not shown to end-user. It will be included to the context internally.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The message is only intended to teach the agent that they have to use smaller tool call args. Thus, it is written in details.

@masudahiroto masudahiroto marked this pull request as ready for review April 2, 2026 08:07
@masudahiroto
Copy link
Copy Markdown
Contributor Author

masudahiroto commented Apr 2, 2026

sorry but i will merge #53 first. please wait a minutes to fix merge conflicts

deer-agent added 2 commits April 2, 2026 20:08
@masudahiroto masudahiroto changed the title 🦌 Fix incomplete tool call recovery on token limit truncation 🦌 Interactive session Apr 2, 2026
@masudahiroto masudahiroto changed the title 🦌 Interactive session 🦌 Fix incomplete tool call recovery on token limit truncation Apr 2, 2026
@masudahiroto
Copy link
Copy Markdown
Contributor Author

I fixed the merge conflict caused by refactoring of AISDKAgent. Please re-review it. I apologize for requesting many reviews.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@masudahiroto masudahiroto merged commit 64053c3 into main Apr 7, 2026
1 check passed
masudahiroto pushed a commit that referenced this pull request Apr 7, 2026
Both branches added fields to StepContext and RUN_FINISHED handler.
Kept all additions from both sides:
- This branch: messageId, hasEmittedTextStart, streamingText/chatId cleanup
- PR #50: stepFinishReason, executingTool cleanup on truncation

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

2 participants