Skip to content

fix(ai): keep a successful tool-call part at input-complete - #1511

Open
jherr wants to merge 3 commits into
mainfrom
fix/ai-client-tool-state-tests
Open

jherr wants to merge 3 commits into
mainfrom
fix/ai-client-tool-state-tests

Conversation

@jherr

@jherr jherr commented Sep 26, 2026 •

Copy link
Copy Markdown
Contributor

A successful client tool left the assistant's tool-call part in state: 'complete' instead of 'input-complete', which broke three @tanstack/ai-client tests on main. This PR keeps the tool-call part at its terminal 'input-complete' state; the completed result stays on the separate tool-result part.

🎯 Changes

  • packages/ai/src/activities/chat/stream/processor.ts: addToolResult now keeps the tool-call part at 'input-complete' on success (only an execution error makes it 'error').
  • packages/ai/tests/stream-processor.test.ts: two unit tests that asserted the transient 'complete' value now assert 'input-complete'.
  • Changeset: patch for @tanstack/ai and @tanstack/ai-client.

✅ Checklist

  • I have followed the steps in the Contributing guide.
  • I have tested code changes locally with pnpm run test:pr, or these tests do not apply to this pull request.
  • I fully understand the code in this pull request, including any code generated with AI assistance.
  • Docs: I updated docs/ for this change, or this change is not user-facing.
  • Changeset: I added a changeset (pnpm changeset), or this PR does not change a published package.

🚀 Release Impact

  • This change affects published code, and I have generated a changeset.
  • This change is docs/CI/dev-only (no release).

Root cause

Issue. After a client tool runs successfully, the assistant's tool-call part ends in state: 'complete'. It must stay 'input-complete' (the model's request is complete; the result lives in the tool-result part). Three ai-client tests in chat-client-context.test.ts fail on main.

Cause. Two runtime paths write the tool-call part's UI state: processor.addToolResult writes 'complete', and the finalize-stream safety net completeToolCall writes 'input-complete'. #1233 (8e8ee2695) made client-tool output validation sync → async (await validateClientToolOutput), which added a microtask before addToolResult. That reordered the two writes, so 'complete' now wins the race. git bisect on the pre-existing snapshot test points to 8e8ee2695 as the first bad commit.

Fix. addToolResult now passes 'input-complete' (not the default 'complete') to updateToolCallWithOutput on success. The tool-call terminal state is deterministic regardless of async ordering.

Possible alternatives

  • Re-order the async writes in chat-client. Keep addToolResult at 'complete' and make the client re-assert 'input-complete' after continuation. Rejected: it re-creates the same order dependency and is fragile.
  • Change the shared default in updateToolCallWithOutput (line 238). Rejected: that default also serves the server streamed-result and snapshot-folding paths, which correctly end at 'complete'; changing it breaks those.
  • Update the three ai-client tests to expect 'complete'. Rejected: that ratifies an accidental breaking change to the tool-call state that useChat renderers observe, with no intent behind it. The 'input-complete' contract has held since the state was introduced (feat(ai): add typed runtime context #628).

Testing

Commands run.

  • pnpm test:pr — all targets pass (test:sherif, test:knip, test:docs, test:kiira, test:maintainer, test:ai-review, test:oxlint, test:lib, test:types, test:build, build, 98 projects).
  • Full framework suites: @tanstack/ai 1915, @tanstack/ai-client 849, @tanstack/ai-react 248, @tanstack/ai-vue 143, @tanstack/ai-solid 157, @tanstack/ai-svelte 97 — all green.

Gate 1 — repro on clean main (1e066cbd0) fails:

× uses the per-run client tool snapshot for automatic result validation
× awaits asynchronous outputSchema validation for executable client tools
× keeps non-JSON output of client tools that are not interrupts
 Tests  3 failed | 6 passed (9)

Gate 1 — same repro on this branch passes:

 Tests  9 passed (9)

Manual test.

  1. On clean main, run pnpm --filter @tanstack/ai-client exec vitest run tests/chat-client-context.test.ts — 3 tests fail (state is complete, expected input-complete).
  2. On this branch, run the same command — all 9 pass.

How this PR makes testing easy. The three failing tests in packages/ai-client/tests/chat-client-context.test.ts are the regression tests; they were red on main and are green here. Two stream-processor.test.ts unit tests pin the corrected processor contract.

No E2E test is added: this is a client-side content-part state detail that is fully covered by the ai-client integration tests and the processor unit tests above; the aimock E2E flows do not assert this internal state.

Risk / rollback

Low. The change narrows one terminal state for locally-applied tool results (client tools and addToolResult callers); server streamed results and snapshot folding are untouched and still end at 'complete'. To roll back, revert this PR.

Public API change

The state on a tool-call content part is observable on useChat messages. Behavior for a successful client tool:

Before

// message.parts → tool-call part after a successful client tool
{ type: 'tool-call', state: 'complete', output: { /* result */ } }

After

// message.parts → tool-call part after a successful client tool
{ type: 'tool-call', state: 'input-complete', output: { /* result */ } }
// the completed result also lives on the separate tool-result part (state: 'complete')

Summary by CodeRabbit

  • Bug Fixes
    • Successful tool calls now retain their input-complete status while the result is recorded separately, avoiding incorrect completion states during streaming.

A client tool result set the tool-call part's UI state to `complete` via
`processor.addToolResult`. #1233 made client-tool output validation async,
which reordered that write relative to the finalize-stream safety net
(`completeToolCall`, which writes `input-complete`). The visible state then
depended on async ordering, flipping the terminal tool-call state from
`input-complete` to `complete` and breaking three ai-client tests that had
passed since the state was introduced.

Make `addToolResult` keep the tool-call part at its terminal
`input-complete` state on success (only an execution error makes it
`error`). The completed result stays on the separate tool-result part.
This removes the race so the state is deterministic regardless of ordering.

Update two stream-processor unit tests that asserted the transient
`complete` value to expect `input-complete`.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Sep 26, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

Navigate logical layers of code changes, visualize relationships, and explore their blast radius.

Important

Review skipped

Review was skipped as selected files did not have any reviewable changes.

⚙️ Run configuration

Configuration used: Repository: TanStack/ai/.coderabbit.yaml

Review profile: CHILL

Plan: Advanced

Run ID: c8e1d4f7-9b6a-41b7-a67a-c6509805f461

📥 Commits

Reviewing files that changed from the base of the PR and between 551e300 and b13d781.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository: TanStack/ai/.coderabbit.yaml

Review profile: CHILL

Plan: Advanced

Run ID: 5e4f0abc-bdc1-4b82-ac56-37c8b2a04a98

📥 Commits

Reviewing files that changed from the base of the PR and between 1e066cb and 96faa9b.

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

Included review availability: This review used your included allowance. Your plan provides up to 8 included reviews per hour; 6 remain after this review.


📝 Walkthrough

Walkthrough

The stream processor now keeps a successful tool-call part in input-complete after applying its result. The tool-result part keeps its separate state. Tests cover direct result addition and snapshot reconciliation.

Changes

Tool-result state

Layer / File(s) Summary
Apply and verify tool-result state
packages/ai/src/activities/chat/stream/processor.ts, packages/ai/tests/stream-processor.test.ts, .changeset/fix-client-tool-call-input-complete.md
Successful results set the tool-call part to input-complete; errors still set it to error. Tests assert the tool-call state after result addition and snapshot reconciliation. The changeset records patch entries for @tanstack/ai and @tanstack/ai-client.

Priority: ➖ Normal

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

Change: Bug fix

Suggested reviewers: alemtuzlak

Merge Risk: ⚪ Minimal · up to 96faa

Successful tool results retain the documented call and result states, and the inspected consumers recognize that representation. No actionable merge risk remains.

Security Architecture Review

Security architecture risk: 🔵 Low · up to 96faa

A successful tool call will remain in the input-complete state while its separate result is complete. The reviewed paths show no change to tool execution or authorization. Compatibility with consumers outside the repository remains unverified.

Retained concerns
No architecture-level concerns identified.

Security review details

Security Blast Radius

  • inferred — The demonstrated effect is the state exposed for an existing tool-call message part; the changed path does not add a tool executor or a new result destination.

Trust Boundaries and Controls

  • observed — The inspected client validates output when a tool output schema is configured, then checks the continuation generation before applying the result to processor state.

Resilience and Maintainability Implications

  • observed — Finalization preserves rendered error and awaiting-user-action states rather than overwriting them with input-complete.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes the primary fix: keeping successful client tool-call parts at input-complete.
Description check ✅ Passed The description includes the required Changes, Checklist, and Release Impact sections. It also documents the root cause, fix, alternatives, testing, risk, rollback, and public API behavior. The report…
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check. Docstring coverage is scoped to functions touched by this diff. Analyzed 0 functions across 2…
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
📝 Generate docstrings
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR
🛠️ Fix failing CI checks 💡
  • Commit to this branch
  • Create a new PR

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.

@nx-cloud

nx-cloud Bot commented Sep 26, 2026 •

Copy link
Copy Markdown

View your CI Pipeline Execution ↗ for commit b13d781

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

☁️ Nx Cloud last updated this comment at 2026-09-27 07:28:52 UTC

@pkg-pr-new

pkg-pr-new Bot commented Sep 26, 2026 •

Copy link
Copy Markdown

Open in StackBlitz

@tanstack/ai

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

@tanstack/ai-acp

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

@tanstack/ai-angular

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

@tanstack/ai-anthropic

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

@tanstack/ai-bedrock

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

@tanstack/ai-byteplus

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

@tanstack/ai-claude-code

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

@tanstack/ai-client

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

@tanstack/ai-cloudflare

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

@tanstack/ai-code-mode

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

@tanstack/ai-code-mode-snippets

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

@tanstack/ai-codex

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

@tanstack/ai-cohere

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

@tanstack/ai-compaction

npm i https://pkg.pr.new/@tanstack/ai-compaction@1511

@tanstack/ai-devtools-core

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

@tanstack/ai-durable-stream

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

@tanstack/ai-elevenlabs

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

@tanstack/ai-event-client

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

@tanstack/ai-fal

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

@tanstack/ai-gemini

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

@tanstack/ai-grok

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

@tanstack/ai-grok-build

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

@tanstack/ai-groq

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

@tanstack/ai-isolate-cloudflare

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

@tanstack/ai-isolate-daytona

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

@tanstack/ai-isolate-node

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

@tanstack/ai-isolate-quickjs

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

@tanstack/ai-isolate-quickjs-bun

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

@tanstack/ai-llmgateway

npm i https://pkg.pr.new/@tanstack/ai-llmgateway@1511

@tanstack/ai-lovable

npm i https://pkg.pr.new/@tanstack/ai-lovable@1511

@tanstack/ai-mcp

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

@tanstack/ai-memory

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

@tanstack/ai-mistral

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

@tanstack/ai-octane

npm i https://pkg.pr.new/@tanstack/ai-octane@1511

@tanstack/ai-ollama

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

@tanstack/ai-openai

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

@tanstack/ai-opencode

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

@tanstack/ai-openrouter

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

@tanstack/ai-perplexity

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

@tanstack/ai-persistence

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

@tanstack/ai-preact

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

@tanstack/ai-react

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

@tanstack/ai-react-ui

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

@tanstack/ai-reactor

npm i https://pkg.pr.new/@tanstack/ai-reactor@1511

@tanstack/ai-remix

npm i https://pkg.pr.new/@tanstack/ai-remix@1511

@tanstack/ai-sandbox

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

@tanstack/ai-sandbox-blaxel

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

@tanstack/ai-sandbox-boxd

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

@tanstack/ai-sandbox-cloudflare

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

@tanstack/ai-sandbox-daytona

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

@tanstack/ai-sandbox-docker

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

@tanstack/ai-sandbox-e2b

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

@tanstack/ai-sandbox-local-process

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

@tanstack/ai-sandbox-sprites

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

@tanstack/ai-sandbox-upstash-box

npm i https://pkg.pr.new/@tanstack/ai-sandbox-upstash-box@1511

@tanstack/ai-sandbox-vercel

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

@tanstack/ai-skills

npm i https://pkg.pr.new/@tanstack/ai-skills@1511

@tanstack/ai-solid

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

@tanstack/ai-solid-ui

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

@tanstack/ai-svelte

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

@tanstack/ai-typesafe

npm i https://pkg.pr.new/@tanstack/ai-typesafe@1511

@tanstack/ai-utils

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

@tanstack/ai-vercel-gateway

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

@tanstack/ai-vertex

npm i https://pkg.pr.new/@tanstack/ai-vertex@1511

@tanstack/ai-vue

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

@tanstack/ai-vue-ui

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

@tanstack/ai-worldlabs

npm i https://pkg.pr.new/@tanstack/ai-worldlabs@1511

@tanstack/openai-base

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

@tanstack/preact-ai-devtools

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

@tanstack/react-ai-devtools

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

@tanstack/solid-ai-devtools

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

@tanstack/svelte-ai-devtools

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

commit: b13d781

@github-actions
github-actions Bot requested a review from AlemTuzlak September 26, 2026 05:01
@github-actions github-actions Bot added the waiting-on: author Waiting for the author to respond or update label Sep 26, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

waiting-on: author Waiting for the author to respond or update

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants