Skip to content

fix(ai): re-open force-completed tool calls when more args arrive - #1019

Closed
Mohith26 wants to merge 2 commits into
TanStack:mainfrom
Mohith26:fix/interleaved-text-tool-input-corruption
Closed

fix(ai): re-open force-completed tool calls when more args arrive#1019
Mohith26 wants to merge 2 commits into
TanStack:mainfrom
Mohith26:fix/interleaved-text-tool-input-corruption

Conversation

@Mohith26

@Mohith26 Mohith26 commented Jul 30, 2026

Copy link
Copy Markdown

Fixes #1017, reported by @andrewmcgivery, matching the diagnosis in the issue exactly.

handleTextMessageContentEvent calls completeAllToolCallsForMessage() on every text delta, which finalizes in-flight tool calls with a lenient partial-JSON parse into input. handleToolCallEndEvent then early-skips calls already in input-complete, so when a TEXT_MESSAGE_CONTENT delta interleaves between two TOOL_CALL_ARGS deltas, the truncated parse becomes permanent even though arguments holds the full correct JSON.

The fix is 8 lines in handleToolCallArgsEvent: a further args delta on an input-complete call re-opens it to input-streaming, letting the authoritative TOOL_CALL_END (or the RUN_FINISHED safety net) re-finalize input from the complete arguments string. Self-correcting across interleaving orders; the existing inferred-completion heuristic is preserved.

Patch changeset included. Five regression tests (END-finalized ordering, END-input-canonical, RUN_FINISHED safety net, multiple parallel tool calls, the internal re-open transition) were written fail-first and all fail without the fix, reproducing the issue's exact truncated output. Package suite: 1291 passed, zero new failures; oxlint and tsc clean.

Summary by CodeRabbit

  • Bug Fixes

    • Fixed tool-call inputs being truncated when text content appears between streamed argument updates.
    • Ensured completed tool calls use the full, authoritative arguments and input.
    • Improved recovery when tool-call completion events are missing.
    • Corrected handling for multiple tool calls processed in parallel.
  • Tests

    • Added regression coverage for interleaved text and tool-call streaming scenarios.

@coderabbitai

coderabbitai Bot commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

@tombeckenham, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 56 minutes

Limit details: You’ve used all 8 included reviews currently available.

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

How can I continue?

Wait for the limit to reset, then comment @coderabbitai review or push new commits to the PR.

An organization admin can change what happens after included review limits in Billing.

How do review limits work?

CodeRabbit enforces per-developer PR review limits within each organization.

For paid Pro and Pro+ reviews, CodeRabbit uses a developer's included PR review attempts over the past 7 days to set the current hourly allowance. At typical activity levels, the full plan allowance applies. Higher sustained activity can lower the allowance until earlier attempts leave the 7-day window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: cb1e9671-d416-466d-87b6-79b169ab0bc9

📥 Commits

Reviewing files that changed from the base of the PR and between f7c67a8 and afc804d.

📒 Files selected for processing (3)
  • .changeset/interleaved-text-tool-input.md
  • packages/ai/src/activities/chat/stream/processor.ts
  • packages/ai/tests/stream-processor.test.ts
📝 Walkthrough

Walkthrough

The stream processor reopens tool calls marked complete when later argument deltas arrive. Authoritative or safety-net completion can then rebuild input from the complete arguments. Regression tests cover interleaved text, end input, missing end events, and parallel calls.

Changes

Tool-call input repair

Layer / File(s) Summary
Reopen force-completed tool calls
packages/ai/src/activities/chat/stream/processor.ts, .changeset/interleaved-text-tool-input.md
Additional tool-call argument deltas transition input-complete calls back to input-streaming. The changeset documents the patch release and correction.
Validate interleaved argument finalization
packages/ai/tests/stream-processor.test.ts
Tests verify completion after interleaved text, canonical TOOL_CALL_END.input, RUN_FINISHED fallback completion, and independent repair of parallel tool calls.

Estimated code review effort: 2 (Simple) | ~15 minutes

Possibly related PRs

  • TanStack/ai#1020: Both PRs modify StreamProcessor and regression tests for interleaved text and tool-argument completion.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes the primary fix for reopening force-completed tool calls.
Description check ✅ Passed The description explains the bug, fix, tests, and release impact, although it does not reproduce the template headings or checklist format.
Linked Issues check ✅ Passed The implementation and regression tests address issue #1017 by restoring complete tool-call input after interleaved text deltas.
Out of Scope Changes check ✅ Passed The changeset, processor fix, and focused regression tests are directly related to issue #1017 and the stated pull request objectives.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

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.

Actionable comments posted: 1

🧹 Nitpick comments (1)
packages/ai/tests/stream-processor.test.ts (1)

829-855: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Make the canonical input intentionally differ from streamed JSON.

The supplied TOOL_CALL_END.input equals JSON.parse(FULL_ARGS), so this test cannot prove the canonical override path ran. Use a distinct canonical value and assert both part.input and parsedArguments use it while arguments remains FULL_ARGS.

Proposed regression strengthening
           ev.toolEnd('tc-1', 'offerTemplates', {
-            input: { templateIds: ['mock-gsk-efimosfermin'] },
+            input: { templateIds: ['canonical-template-id'] },
           }),
@@
+        expect(part?.arguments).toBe(FULL_ARGS)
-        expect(part?.input).toEqual({ templateIds: ['mock-gsk-efimosfermin'] })
+        expect(part?.input).toEqual({
+          templateIds: ['canonical-template-id'],
+        })
@@
-        ).toEqual({ templateIds: ['mock-gsk-efimosfermin'] })
+        ).toEqual({ templateIds: ['canonical-template-id'] })
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/ai/tests/stream-processor.test.ts` around lines 829 - 855,
Strengthen the test around StreamProcessor by making TOOL_CALL_END.input differ
from the streamed FULL_ARGS value. Update the canonical input fixture in the
toolEnd call, then assert part.input and the tc-1 parsedArguments both equal
that distinct canonical value while part.arguments remains FULL_ARGS.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/ai/tests/stream-processor.test.ts`:
- Around line 776-915: Move the regression test block “text delta interleaved
between TOOL_CALL_ARGS deltas (`#1017`)” from the package-level tests into a
*.test.ts file alongside the source containing StreamProcessor. Preserve all
five test cases and their existing assertions unchanged.

---

Nitpick comments:
In `@packages/ai/tests/stream-processor.test.ts`:
- Around line 829-855: Strengthen the test around StreamProcessor by making
TOOL_CALL_END.input differ from the streamed FULL_ARGS value. Update the
canonical input fixture in the toolEnd call, then assert part.input and the tc-1
parsedArguments both equal that distinct canonical value while part.arguments
remains FULL_ARGS.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 49951d96-af20-4710-9e97-9068fdfd5912

📥 Commits

Reviewing files that changed from the base of the PR and between 05280a5 and 6a4b458.

📒 Files selected for processing (3)
  • .changeset/interleaved-text-tool-input.md
  • packages/ai/src/activities/chat/stream/processor.ts
  • packages/ai/tests/stream-processor.test.ts

Comment thread packages/ai/tests/stream-processor.test.ts
@tombeckenham
tombeckenham force-pushed the fix/interleaved-text-tool-input-corruption branch from 6a4b458 to d4e99f9 Compare August 10, 2026 09:09
@coderabbitai

coderabbitai Bot commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

Note

GitHub couldn't provide a complete incremental comparison for this pull request, so CodeRabbit is performing a full review instead. This review may take a little longer.

@nx-cloud

nx-cloud Bot commented Aug 10, 2026

Copy link
Copy Markdown

View your CI Pipeline Execution ↗ for commit afc804d

Command Status Duration Result
nx run-many --targets=build --exclude=examples/... ✅ Succeeded 1m 50s View ↗

☁️ Nx Cloud last updated this comment at 2026-08-21 03:52:29 UTC

@github-actions

Copy link
Copy Markdown
Contributor

Thanks for the PR, @Mohith26! 🙌 @AlemTuzlak will take a look.

Automated pre-review checks

  • ⚠️ CI failing — worth a look before review
  • ✅ No merge conflicts
  • ✅ Changeset present
  • ⚠️ No E2E test changes detected — behavior changes need coverage under testing/e2e/ (see CONTRIBUTING)

Automated triage — a human review follows.

@github-actions github-actions Bot added the waiting-on: author Waiting for the author to respond or update label Aug 13, 2026
@tombeckenham
tombeckenham force-pushed the fix/interleaved-text-tool-input-corruption branch 2 times, most recently from 5440c52 to 2713e79 Compare August 20, 2026 10:41
A TEXT_MESSAGE_CONTENT event between two TOOL_CALL_ARGS events force-completes
the in-flight tool call, populating part.input from a lenient partial-JSON
parse of the arguments received so far. The subsequent authoritative
TOOL_CALL_END was then skipped because the call was already input-complete,
so the truncated input was never corrected even though the arguments string
held the complete JSON.

Re-open such an inferred-complete call back to input-streaming when a further
TOOL_CALL_ARGS delta arrives, so TOOL_CALL_END (or the RUN_FINISHED safety
net) re-finalizes input from the complete arguments string.

Fixes TanStack#1017
Regression tests for TanStack#1017: interleaving orders with TOOL_CALL_END,
TOOL_CALL_END.input as canonical, the RUN_FINISHED safety net with no
TOOL_CALL_END, multiple parallel tool calls, and the internal
input-complete -> input-streaming re-open transition.
@tombeckenham
tombeckenham force-pushed the fix/interleaved-text-tool-input-corruption branch from 2713e79 to afc804d Compare August 21, 2026 03:30
@pkg-pr-new

pkg-pr-new Bot commented Aug 21, 2026

Copy link
Copy Markdown

Open in StackBlitz

@tanstack/ai

npm i https://pkg.pr.new/@tanstack/ai@1019

@tanstack/ai-acp

npm i https://pkg.pr.new/@tanstack/ai-acp@1019

@tanstack/ai-angular

npm i https://pkg.pr.new/@tanstack/ai-angular@1019

@tanstack/ai-anthropic

npm i https://pkg.pr.new/@tanstack/ai-anthropic@1019

@tanstack/ai-bedrock

npm i https://pkg.pr.new/@tanstack/ai-bedrock@1019

@tanstack/ai-byteplus

npm i https://pkg.pr.new/@tanstack/ai-byteplus@1019

@tanstack/ai-claude-code

npm i https://pkg.pr.new/@tanstack/ai-claude-code@1019

@tanstack/ai-client

npm i https://pkg.pr.new/@tanstack/ai-client@1019

@tanstack/ai-code-mode

npm i https://pkg.pr.new/@tanstack/ai-code-mode@1019

@tanstack/ai-code-mode-snippets

npm i https://pkg.pr.new/@tanstack/ai-code-mode-snippets@1019

@tanstack/ai-codex

npm i https://pkg.pr.new/@tanstack/ai-codex@1019

@tanstack/ai-cohere

npm i https://pkg.pr.new/@tanstack/ai-cohere@1019

@tanstack/ai-devtools-core

npm i https://pkg.pr.new/@tanstack/ai-devtools-core@1019

@tanstack/ai-durable-stream

npm i https://pkg.pr.new/@tanstack/ai-durable-stream@1019

@tanstack/ai-elevenlabs

npm i https://pkg.pr.new/@tanstack/ai-elevenlabs@1019

@tanstack/ai-event-client

npm i https://pkg.pr.new/@tanstack/ai-event-client@1019

@tanstack/ai-fal

npm i https://pkg.pr.new/@tanstack/ai-fal@1019

@tanstack/ai-gemini

npm i https://pkg.pr.new/@tanstack/ai-gemini@1019

@tanstack/ai-grok

npm i https://pkg.pr.new/@tanstack/ai-grok@1019

@tanstack/ai-grok-build

npm i https://pkg.pr.new/@tanstack/ai-grok-build@1019

@tanstack/ai-groq

npm i https://pkg.pr.new/@tanstack/ai-groq@1019

@tanstack/ai-isolate-cloudflare

npm i https://pkg.pr.new/@tanstack/ai-isolate-cloudflare@1019

@tanstack/ai-isolate-daytona

npm i https://pkg.pr.new/@tanstack/ai-isolate-daytona@1019

@tanstack/ai-isolate-node

npm i https://pkg.pr.new/@tanstack/ai-isolate-node@1019

@tanstack/ai-isolate-quickjs

npm i https://pkg.pr.new/@tanstack/ai-isolate-quickjs@1019

@tanstack/ai-isolate-quickjs-bun

npm i https://pkg.pr.new/@tanstack/ai-isolate-quickjs-bun@1019

@tanstack/ai-mcp

npm i https://pkg.pr.new/@tanstack/ai-mcp@1019

@tanstack/ai-memory

npm i https://pkg.pr.new/@tanstack/ai-memory@1019

@tanstack/ai-mistral

npm i https://pkg.pr.new/@tanstack/ai-mistral@1019

@tanstack/ai-ollama

npm i https://pkg.pr.new/@tanstack/ai-ollama@1019

@tanstack/ai-openai

npm i https://pkg.pr.new/@tanstack/ai-openai@1019

@tanstack/ai-opencode

npm i https://pkg.pr.new/@tanstack/ai-opencode@1019

@tanstack/ai-openrouter

npm i https://pkg.pr.new/@tanstack/ai-openrouter@1019

@tanstack/ai-perplexity

npm i https://pkg.pr.new/@tanstack/ai-perplexity@1019

@tanstack/ai-persistence

npm i https://pkg.pr.new/@tanstack/ai-persistence@1019

@tanstack/ai-preact

npm i https://pkg.pr.new/@tanstack/ai-preact@1019

@tanstack/ai-react

npm i https://pkg.pr.new/@tanstack/ai-react@1019

@tanstack/ai-react-ui

npm i https://pkg.pr.new/@tanstack/ai-react-ui@1019

@tanstack/ai-sandbox

npm i https://pkg.pr.new/@tanstack/ai-sandbox@1019

@tanstack/ai-sandbox-cloudflare

npm i https://pkg.pr.new/@tanstack/ai-sandbox-cloudflare@1019

@tanstack/ai-sandbox-daytona

npm i https://pkg.pr.new/@tanstack/ai-sandbox-daytona@1019

@tanstack/ai-sandbox-docker

npm i https://pkg.pr.new/@tanstack/ai-sandbox-docker@1019

@tanstack/ai-sandbox-local-process

npm i https://pkg.pr.new/@tanstack/ai-sandbox-local-process@1019

@tanstack/ai-sandbox-sprites

npm i https://pkg.pr.new/@tanstack/ai-sandbox-sprites@1019

@tanstack/ai-sandbox-vercel

npm i https://pkg.pr.new/@tanstack/ai-sandbox-vercel@1019

@tanstack/ai-solid

npm i https://pkg.pr.new/@tanstack/ai-solid@1019

@tanstack/ai-solid-ui

npm i https://pkg.pr.new/@tanstack/ai-solid-ui@1019

@tanstack/ai-svelte

npm i https://pkg.pr.new/@tanstack/ai-svelte@1019

@tanstack/ai-utils

npm i https://pkg.pr.new/@tanstack/ai-utils@1019

@tanstack/ai-vercel-gateway

npm i https://pkg.pr.new/@tanstack/ai-vercel-gateway@1019

@tanstack/ai-vue

npm i https://pkg.pr.new/@tanstack/ai-vue@1019

@tanstack/ai-vue-ui

npm i https://pkg.pr.new/@tanstack/ai-vue-ui@1019

@tanstack/openai-base

npm i https://pkg.pr.new/@tanstack/openai-base@1019

@tanstack/preact-ai-devtools

npm i https://pkg.pr.new/@tanstack/preact-ai-devtools@1019

@tanstack/react-ai-devtools

npm i https://pkg.pr.new/@tanstack/react-ai-devtools@1019

@tanstack/solid-ai-devtools

npm i https://pkg.pr.new/@tanstack/solid-ai-devtools@1019

commit: afc804d

@tombeckenham tombeckenham added waiting-on: maintainer The ball is in the maintainers’ court and removed waiting-on: author Waiting for the author to respond or update labels Aug 21, 2026
AlemTuzlak added a commit that referenced this pull request Aug 21, 2026
Add coverage for END.input after interleaved text, two parallel
calls, and a full TEXT_START/CONTENT/END block between arg deltas.
Retarget toolCallToMessage when TEXT_MESSAGE_START remaps the
assistant message id, so the later ARGS deltas still accumulate.
@AlemTuzlak

AlemTuzlak commented Aug 21, 2026

Copy link
Copy Markdown
Contributor

This PR and #1183 target the same bug as #1020 (issue #1017). #1020 now has the smaller fix plus tests for this PR's cases (END.input, RUN_FINISHED safety net, parallel calls, streaming-through-text). See #1017 (comment) and #1020. This can close when #1020 merges.

@AlemTuzlak

Copy link
Copy Markdown
Contributor

Closing as a duplicate of #1020.

#1020 stops text events from completing in-flight tool calls, so the call stays input-streaming and TOOL_CALL_END still runs. The reopen-on-later-args path is not needed.

Issue: #1017

@AlemTuzlak AlemTuzlak closed this Aug 21, 2026
AlemTuzlak added a commit that referenced this pull request Aug 21, 2026
* fix(ai): stop text events from corrupting tool-call input

A TEXT_MESSAGE_CONTENT delta between TOOL_CALL_ARGS deltas
force-completed the call from a lenient partial-JSON parse.
TOOL_CALL_END then no-oped, so input stayed truncated while
arguments held the full JSON.

Text events no longer complete tool calls. completeToolCall
sets input only when JSON.parse of the arguments succeeds.

Fixes #1017

* test(ai): pin #1019 and #1183 interleaved-args cases

Add coverage for END.input after interleaved text, two parallel
calls, and a full TEXT_START/CONTENT/END block between arg deltas.
Retarget toolCallToMessage when TEXT_MESSAGE_START remaps the
assistant message id, so the later ARGS deltas still accumulate.

---------

Co-authored-by: Alem Tuzlak <t.zlak@hotmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

waiting-on: maintainer The ball is in the maintainers’ court

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Interleaved TEXT_MESSAGE_CONTENT permanently corrupts tool-call input with a partial-JSON parse

3 participants