Skip to content

Conversation

@shakibdshy
Copy link

@shakibdshy shakibdshy commented Jan 15, 2026

  • refactor(stream): improve tool call part updates and message handling
  • test(chat): add tests for approval flow and full message sending

Resolves #225 by ensuring client-side tools execute immediately after approval and fixing a state overwrite bug in the stream processor.

🎯 Changes

✅ Checklist

  • I have followed the steps in the Contributing guide.
  • I have tested this code locally with pnpm run test:pr.

🚀 Release Impact

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

Summary by CodeRabbit

Release Notes

  • New Features

    • Client-side tools now execute automatically upon approval, enabling seamless tool interactions without server round-trips.
  • Bug Fixes

    • Improved tool approval workflow to capture and validate tool inputs correctly.
  • Tests

    • Added comprehensive test coverage for tool approval workflows and message transmission scenarios.

✏️ Tip: You can customize this high-level summary in your review settings.

- refactor(stream): improve tool call part updates and message handling
- test(chat): add tests for approval flow and full message sending

Resolves TanStack#225 by ensuring client-side tools execute immediately after approval and fixing a state overwrite bug in the stream processor.
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 15, 2026

📝 Walkthrough

Walkthrough

This PR implements client-side tool execution upon approval. The chat client now parses tool call arguments when approval is confirmed, executes matching client-side tools with the parsed input, and records execution results (success or error states). A new sendFullMessages flag controls whether full UIMessage objects or converted ModelMessages are sent to servers.

Changes

Cohort / File(s) Summary
Chat Client Tool Execution
packages/typescript/ai-client/src/chat-client.ts
Added tool approval resolution logic that parses toolCallPart.arguments via JSON.parse() with fallback, attempts client-side tool execution with parsed input, and records output results with 'output-available' or 'output-error' states before returning early to skip continuation.
Connection Message Serialization
packages/typescript/ai-client/src/connection-adapters.ts
Introduced sendFullMessages?: boolean flag to FetchConnectionOptions interface; routes fetchServerSentEvents and fetchHttpStream to send either full UIMessage objects or converted ModelMessages based on flag state, replacing hardcoded ModelMessage conversion.
Chat Activity Message Handling
packages/typescript/ai/src/activities/chat/index.ts
Expanded internal message union to include UIMessage; added convertMessagesToModelMessages() conversions at adapter interfaces and final message retrieval; refined tool-call approval condition from part.approval truthiness to explicit part.approval.approved presence; parameterized function generics with TAdapter/TSchema replacing AnyTextAdapter/SchemaInput.
Tool Call Part Updates
packages/typescript/ai/src/activities/chat/stream/message-updaters.ts
Refactored updateToolCallPart to merge new fields into existing part via spread operator rather than constructing fresh object; structural change without behavioral impact.
Approval Flow Tests
packages/typescript/ai-client/tests/chat-client-approval.test.ts
Added new test suite validating tool execution upon approval: mock tool setup, approval trigger via addToolApprovalResponse, and assertion that tool executes with expected input; uses stream chunk processing and async synchronization.
Connection Adapter Tests
packages/typescript/ai-client/tests/connection-adapters.test.ts
Added two tests for fetchServerSentEvents: validation of default model message conversion (excluding parts array) and validation of full UIMessage serialization when sendFullMessages: true (including parts with approval data).
AI Text Activity Tests
packages/typescript/ai/tests/ai-text.test.ts
Added test scenario for pending tool calls represented in UI message parts; defines PendingToolAdapter and verifies tool execution with correct arguments and tool_result chunk emission.
Demo Application Updates
testing/panel/src/routes/api.chat.ts, testing/panel/src/routes/index.tsx
Enhanced addToCartToolServer with console logging side-effect; removed addToCartToolDef client tool from tools list; enabled sendFullMessages: true in fetchServerSentEvents call for chat initialization.

Sequence Diagram

sequenceDiagram
    participant Client as Chat Client
    participant Stream as Message Stream
    participant Processor as Message Processor
    participant Tool as Client Tool
    participant Result as Output Recorder

    Client->>Stream: Receive tool_call chunk (approval-requested)
    Stream->>Client: Update message with approval request
    Client->>Client: User triggers addToolApprovalResponse()
    Client->>Processor: Parse toolCallPart.arguments
    Processor-->>Client: foundToolInput (JSON parsed)
    Client->>Tool: Execute client tool with foundToolInput
    alt Tool succeeds
        Tool-->>Client: Return tool result
        Client->>Result: Record output with state: 'output-available'
    else Tool fails
        Tool-->>Client: Throw error
        Client->>Result: Record output with state: 'output-error' + errorText
    end
    Result-->>Client: Return early, skip continuation
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

Suggested reviewers

  • AlemTuzlak
  • harry-whorlow

Poem

🐰 A hop of approval, a parse, and a call,
The client-side tool answers the approval's call,
With input in hand and result set to true,
The workflow completes—no more hung in the blue!

🚥 Pre-merge checks | ✅ 2 | ❌ 3
❌ Failed checks (2 warnings, 1 inconclusive)
Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning The PR includes one out-of-scope change: removal of addToCartToolDef from the testing panel's tools list, which is unrelated to the approval execution bug fix. Remove the unrelated changes to testing/panel/src/routes/index.tsx that eliminate the add-to-cart tool, as this is not part of issue #225's objectives.
Docstring Coverage ⚠️ Warning Docstring coverage is 66.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Description check ❓ Inconclusive The PR description follows the template structure with completed checklist items and release impact declaration. However, the 'Changes' section is empty with only a placeholder comment. Fill in the empty 'Changes' section with a detailed explanation of what was changed and the motivation behind it, rather than leaving it as a placeholder comment.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The PR title clearly and specifically describes the main feature: adding support for client-side tool execution after approval, which is the central objective of this changeset.
Linked Issues check ✅ Passed The PR comprehensively addresses all coding objectives from issue #225: executes client tools upon approval, sends results back to the model, and prevents UI from hanging after approval.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

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 and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/typescript/ai-client/src/chat-client.ts (1)

419-434: Handle JSON parse failure more explicitly.

When JSON.parse(toolCallPart.arguments) fails, foundToolInput remains undefined, but the tool still executes on line 455 with potentially undefined input. This may cause unexpected behavior in client tools that expect specific input shapes.

Consider setting a fallback or logging a warning:

♻️ Suggested improvement
         foundToolName = toolCallPart.name
         try {
           foundToolInput = JSON.parse(toolCallPart.arguments)
         } catch {
-          // Ignore parse errors
+          // Log warning but continue - tool will receive undefined input
+          console.warn(`Failed to parse arguments for tool ${toolCallPart.name}`)
         }

Alternatively, skip tool execution if input parsing fails, as the tool likely cannot function correctly without its expected input.

🧹 Nitpick comments (5)
packages/typescript/ai-client/src/connection-adapters.ts (1)

312-324: Consider adding sendFullMessages support to stream() and rpcStream() adapters for consistency.

The stream() and rpcStream() adapters always convert to ModelMessages, while fetchServerSentEvents and fetchHttpStream now support the sendFullMessages option. If UIMessage metadata (like tool approval state) needs to flow through these adapters in the future, they would need similar updates.

This may be intentional if these adapters are only used in contexts where ModelMessages suffice, but worth noting for future extensibility.

Also applies to: 342-356

packages/typescript/ai-client/tests/chat-client-approval.test.ts (3)

67-67: Type cast suggests incomplete type definitions.

The as any cast indicates that the ApprovalRequestedStreamChunk type may not fully align with what's being constructed here. Consider updating the type definitions in @tanstack/ai to avoid the cast, or add a comment explaining why it's necessary.


82-132: Consider adding test cases for rejection and error scenarios.

The current test validates the happy path (approved = true). For comprehensive coverage of the linked issue #225, consider adding tests for:

  1. Rejection flow: approved: false should not execute the tool
  2. Tool execution error: Mock execute to throw and verify output-error state is recorded
  3. Missing tool: Approve a tool that isn't registered in tools

These scenarios ensure the fix is robust against edge cases.

Would you like me to generate additional test cases for these scenarios?


116-116: Use vi.waitFor() to wait for assertions instead of arbitrary timeouts.

Hardcoded 100ms delays are unreliable across environments and can cause test flakiness. Vitest's vi.waitFor() polls the callback until the condition is met (or timeout at 1000ms), making tests more robust:

Example refactoring
// Line 128: Instead of:
await new Promise((resolve) => setTimeout(resolve, 100))
expect(execute).toHaveBeenCalledWith(input)

// Use:
await vi.waitFor(() => {
  expect(execute).toHaveBeenCalledWith(input)
})

This approach also applies to line 116 where a condition-based wait would be more reliable than a fixed delay.

packages/typescript/ai-client/src/chat-client.ts (1)

463-471: Type error more precisely than any.

The error: any catch clause loses type information. Consider using unknown with type narrowing:

♻️ Suggested improvement
-        } catch (error: any) {
+        } catch (error: unknown) {
           await this.addToolResult({
             toolCallId: foundToolCallId,
             tool: foundToolName,
             output: null,
             state: 'output-error',
-            errorText: error.message,
+            errorText: error instanceof Error ? error.message : String(error),
           })
           return
         }

This provides safer error handling and aligns with TypeScript best practices.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8e93ce2 and eacc6a7.

📒 Files selected for processing (9)
  • packages/typescript/ai-client/src/chat-client.ts
  • packages/typescript/ai-client/src/connection-adapters.ts
  • packages/typescript/ai-client/tests/chat-client-approval.test.ts
  • packages/typescript/ai-client/tests/connection-adapters.test.ts
  • packages/typescript/ai/src/activities/chat/index.ts
  • packages/typescript/ai/src/activities/chat/stream/message-updaters.ts
  • packages/typescript/ai/tests/ai-text.test.ts
  • testing/panel/src/routes/api.chat.ts
  • testing/panel/src/routes/index.tsx
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx}: Use tree-shakeable adapter architecture for provider implementations - export specialized adapters (text, embedding, summarize, image) as separate imports from /adapters subpath rather than monolithic adapters
Use Zod for runtime schema validation and type inference, particularly for tool input/output definitions with toolDefinition() and Zod schema inference
Implement isomorphic tool system using toolDefinition() with .server() and .client() implementations for dual-environment execution
Use type-safe per-model configuration with provider options typed based on selected model to ensure compile-time safety
Implement stream processing with StreamProcessor for handling chunked responses and support partial JSON parsing for streaming AI responses

Files:

  • testing/panel/src/routes/api.chat.ts
  • packages/typescript/ai/tests/ai-text.test.ts
  • packages/typescript/ai-client/src/connection-adapters.ts
  • packages/typescript/ai-client/tests/chat-client-approval.test.ts
  • packages/typescript/ai-client/tests/connection-adapters.test.ts
  • testing/panel/src/routes/index.tsx
  • packages/typescript/ai-client/src/chat-client.ts
  • packages/typescript/ai/src/activities/chat/stream/message-updaters.ts
  • packages/typescript/ai/src/activities/chat/index.ts
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Use camelCase for function and variable names throughout the codebase

Files:

  • testing/panel/src/routes/api.chat.ts
  • packages/typescript/ai/tests/ai-text.test.ts
  • packages/typescript/ai-client/src/connection-adapters.ts
  • packages/typescript/ai-client/tests/chat-client-approval.test.ts
  • packages/typescript/ai-client/tests/connection-adapters.test.ts
  • testing/panel/src/routes/index.tsx
  • packages/typescript/ai-client/src/chat-client.ts
  • packages/typescript/ai/src/activities/chat/stream/message-updaters.ts
  • packages/typescript/ai/src/activities/chat/index.ts
**/*.test.ts

📄 CodeRabbit inference engine (CLAUDE.md)

Write unit tests using Vitest alongside source files with .test.ts naming convention

Files:

  • packages/typescript/ai/tests/ai-text.test.ts
  • packages/typescript/ai-client/tests/chat-client-approval.test.ts
  • packages/typescript/ai-client/tests/connection-adapters.test.ts
🧠 Learnings (4)
📚 Learning: 2025-12-13T17:09:09.794Z
Learnt from: CR
Repo: TanStack/ai PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-13T17:09:09.794Z
Learning: Applies to packages/typescript/*/src/adapters/*.ts : Create individual adapter implementations for each provider capability (text, embed, summarize, image) with separate exports to enable tree-shaking

Applied to files:

  • packages/typescript/ai-client/src/connection-adapters.ts
  • packages/typescript/ai-client/tests/connection-adapters.test.ts
  • packages/typescript/ai/src/activities/chat/index.ts
📚 Learning: 2025-12-13T17:09:09.794Z
Learnt from: CR
Repo: TanStack/ai PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-13T17:09:09.794Z
Learning: Applies to **/*.{ts,tsx} : Implement isomorphic tool system using `toolDefinition()` with `.server()` and `.client()` implementations for dual-environment execution

Applied to files:

  • testing/panel/src/routes/index.tsx
📚 Learning: 2025-12-13T17:09:09.794Z
Learnt from: CR
Repo: TanStack/ai PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-13T17:09:09.794Z
Learning: Applies to **/*.{ts,tsx} : Implement stream processing with StreamProcessor for handling chunked responses and support partial JSON parsing for streaming AI responses

Applied to files:

  • packages/typescript/ai-client/src/chat-client.ts
  • packages/typescript/ai/src/activities/chat/index.ts
📚 Learning: 2025-12-13T17:09:09.794Z
Learnt from: CR
Repo: TanStack/ai PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-13T17:09:09.794Z
Learning: Applies to packages/typescript/*/src/index.ts : Export tree-shakeable adapters with clear subpath exports in package.json (e.g., `tanstack/ai/adapters`, `tanstack/ai-openai/adapters`) to minimize bundle size

Applied to files:

  • packages/typescript/ai/src/activities/chat/index.ts
🧬 Code graph analysis (7)
testing/panel/src/routes/api.chat.ts (3)
examples/ts-solid-chat/src/lib/guitar-tools.ts (1)
  • addToCartToolServer (109-117)
examples/ts-react-chat/src/lib/guitar-tools.ts (1)
  • addToCartToolDef (69-84)
testing/panel/src/lib/guitar-tools.ts (1)
  • addToCartToolDef (69-84)
packages/typescript/ai/tests/ai-text.test.ts (3)
packages/typescript/ai/src/types.ts (2)
  • TextOptions (565-650)
  • StreamChunk (740-748)
packages/typescript/ai-client/src/connection-adapters.ts (1)
  • stream (312-324)
packages/typescript/ai/src/activities/chat/index.ts (1)
  • chat (975-998)
packages/typescript/ai-client/tests/chat-client-approval.test.ts (3)
packages/typescript/ai/src/types.ts (1)
  • StreamChunk (740-748)
packages/typescript/ai-client/src/connection-adapters.ts (1)
  • stream (312-324)
packages/typescript/ai-client/src/chat-client.ts (1)
  • ChatClient (17-583)
packages/typescript/ai-client/tests/connection-adapters.test.ts (2)
packages/typescript/ai-client/src/connection-adapters.ts (1)
  • fetchServerSentEvents (134-200)
packages/typescript/ai-client/src/index.ts (1)
  • fetchServerSentEvents (23-23)
testing/panel/src/routes/index.tsx (1)
packages/typescript/ai-client/src/connection-adapters.ts (1)
  • fetchServerSentEvents (134-200)
packages/typescript/ai-client/src/chat-client.ts (1)
packages/typescript/ai-svelte/src/create-chat.svelte.ts (1)
  • messages (121-123)
packages/typescript/ai/src/activities/chat/index.ts (3)
packages/typescript/ai/src/types.ts (4)
  • ModelMessage (232-243)
  • UIMessage (293-298)
  • ToolCall (87-94)
  • StreamChunk (740-748)
packages/typescript/ai/src/index.ts (1)
  • convertMessagesToModelMessages (81-81)
packages/typescript/ai-client/src/connection-adapters.ts (1)
  • stream (312-324)
🔇 Additional comments (17)
testing/panel/src/routes/api.chat.ts (1)

47-56: LGTM! Consider removing debug logging before production.

The refactor from arrow function to block-bodied function is clean and preserves the same return structure. The console.log is useful for debugging the tool approval flow during development.

Consider removing or conditionalizing the console.log statement before production deployment to avoid noisy server logs.

testing/panel/src/routes/index.tsx (1)

448-450: LGTM! Enables full UIMessage transmission for tool approval workflow.

The sendFullMessages: true option is essential for the tool approval flow. This ensures that when messages contain tool-call parts with approval state (e.g., approval-responded), the full UIMessage objects—including the parts array—are sent to the server, allowing it to process pending tool executions correctly.

packages/typescript/ai/src/activities/chat/stream/message-updaters.ts (2)

73-80: Critical fix: Preserves existing tool call fields during updates.

The spread operator ...existing ensures that fields like approval and output are preserved when updating tool call parts. This is essential for the tool approval workflow—without this fix, updating the state would have inadvertently wiped out the approval metadata, causing the approval flow to break.


83-90: LGTM!

Clean refactor to create the toolCallPart object explicitly before pushing. The explicit type annotation improves code clarity.

packages/typescript/ai-client/tests/connection-adapters.test.ts (2)

321-364: LGTM! Good test coverage for default message conversion behavior.

This test correctly validates that without sendFullMessages, the connection adapter converts UIMessage objects to ModelMessage format, stripping the parts array. The assertion at line 360-361 properly verifies the conversion.


366-420: LGTM! Validates full UIMessage transmission.

This test correctly validates that with sendFullMessages: true, the full UIMessage objects are sent including the parts array with tool-call approval metadata. This is essential for the tool approval workflow.

packages/typescript/ai/tests/ai-text.test.ts (1)

1476-1554: LGTM! Good test coverage for UIMessage-based tool execution.

This test validates the new flow where approved tool calls are extracted from UIMessage.parts rather than the legacy toolCalls array. This is essential for the tool approval workflow where the client sends back UIMessages with approval-responded state.

The test correctly verifies:

  1. Tool execution with parsed arguments from the parts array
  2. Tool result message is passed to the adapter
  3. Only one adapter call (tool executed before streaming)
packages/typescript/ai-client/src/connection-adapters.ts (2)

92-98: LGTM! Well-documented option for advanced message handling.

The sendFullMessages option is clearly documented and provides the flexibility needed for tool approval flows that require UIMessage metadata.


147-149: LGTM! Clean conditional message transformation.

The ternary logic correctly chooses between passing messages as-is or converting them based on the sendFullMessages flag.

packages/typescript/ai-client/tests/chat-client-approval.test.ts (1)

6-12: LGTM! Clean mock adapter factory.

The helper correctly wraps the stream() adapter to yield predetermined chunks for testing.

packages/typescript/ai-client/src/chat-client.ts (2)

296-312: LGTM! Correctly passes messages to the connection adapter.

The change to use this.processor.getMessages() and pass messages directly aligns with the new sendFullMessages capability in connection adapters, allowing UIMessage metadata to flow through when needed.


450-474: LGTM! Core fix for issue #225 - client tool execution on approval.

This block correctly:

  1. Checks if the response is approved and tool details are available
  2. Looks up the client tool by name
  3. Executes the tool and records the result via addToolResult
  4. Uses early returns to avoid duplicate continuation checks

The structure mirrors the onToolCall handler (lines 130-155), maintaining consistency.

packages/typescript/ai/src/activities/chat/index.ts (5)

211-211: LGTM! Type expansion enables UIMessage support in the engine.

Expanding the internal messages type to accept both ModelMessage and UIMessage allows the engine to work with UI-originated messages while maintaining proper conversion at API boundaries.


248-250: LGTM! Consistent conversion at public API boundary.

The getMessages() method correctly converts to ModelMessage[] to maintain a stable public API while the internal state can hold either message type.


723-731: Good defensive checks for approval and output properties.

The added null checks (part.approval && and part.output !== undefined) prevent potential runtime errors when processing messages that may not have these optional properties populated.


847-868: LGTM! Consistent message conversion for tool call detection.

Converting messages at the start of getPendingToolCallsFromMessages() ensures the filtering and iteration logic operates on a uniform ModelMessage[] structure.


1003-1017: LGTM! Helper functions updated for flexible message handling.

The function signatures and internal logic properly handle the generic types while providing sensible defaults (messages ?? []) when messages are not provided.

Also applies to: 1023-1030, 1038-1055

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

addToolApprovalResponse hangs execution

1 participant