TanStack AI version
0.2.0
Framework/Library version
react 18.2.0
Describe the bug and the steps to reproduce it
Summary: Server-side tool results don't update the tool-call part's output field, causing inconsistent UI state between server and client tool execution.
Expected behavior: Both server-executed tools and client-executed tools should have their results visible on the tool-call part's output field, and the state should transition to 'complete'.
Actual behavior:
- Client tools:
tool-call part shows state: 'complete' with output populated - Server tools:
tool-call part shows state: 'input-complete' with no output (result only exists in a separate tool-result part)
Root cause: In StreamProcessor, handleToolResultChunk() only creates a tool-result part via updateToolResultPart(), but doesn't update the corresponding tool-call part's output field or state. In contrast, addToolResult() (used for client tools) does both:
// addToolResult() does this (lines 283-300):letupdatedMessages=updateToolCallWithOutput(// Updates tool-call outputthis.messages,toolCallId,output,error ? 'input-complete' : undefined,error,)updatedMessages=updateToolResultPart(...)// Also creates tool-result part// handleToolResultChunk() only does this (lines 691-699):this.messages=updateToolResultPart(...)// Only creates tool-result part// Missing: updateToolCallWithOutput() call
Steps to reproduce:
- Define a tool with
toolDefinition() that has both inputSchema and outputSchema - Create a server-side implementation using
.server() with an execute function - Create a client-side implementation using
.client() with an execute function - Call both tools in a chat session
- Observe the
UIMessage.parts - client tool-call parts have output and state: 'complete', server tool-call parts only have state: 'input-complete' with no output
Example to reproduce
import{toolDefinition,chat}from'@tanstack/ai'import{z}from'zod'// Shared tool definitionconstmyToolDef=toolDefinition({name: 'myTool',description: 'A test tool',inputSchema: z.object({input: z.string()}),outputSchema: z.object({result: z.string()}),})// Server tool - has execute, runs on serverconstserverTool=myToolDef.server(async(args)=>{return{result: `Server processed: ${args.input}`}})// Client tool - has execute, runs on clientconstclientTool=myToolDef.client(async(args)=>{return{result: `Client processed: ${args.input}`}})// When server tool executes:// - StreamProcessor receives tool_result chunk// - handleToolResultChunk() creates tool-result part// - tool-call part remains at state: 'input-complete', output: undefined ❌// When client tool executes:// - StreamProcessor receives tool-input-available chunk// - Client executes tool and calls addToolResult()// - addToolResult() updates tool-call part with output AND creates tool-result part// - tool-call part shows state: 'complete', output: {...} ✅Screenshots or Videos (Optional)
In the screenshot:
getCurrentPipelineConfig (client tool): Shows "complete" status with RESULT section visiblegetPipelineSchema (server tool): Shows "input-complete" status with no RESULT sectionvalidatePipelineConfig (server tool): Shows "input-complete" status with no RESULT sectionapplyPipelineConfig (client tool): Shows "complete" status

Suggested Fix
In processor.ts, handleToolResultChunk() should also update the tool-call part similar to addToolResult():
privatehandleToolResultChunk(chunk: Extract<StreamChunk,{type: 'tool_result'}>,): void{conststate: ToolResultState='complete'// Emit legacy handlerthis.handlers.onToolResultStateChange?.(chunk.toolCallId,chunk.content,state,)// Update UIMessage if we have a current assistant messageif(this.currentAssistantMessageId){// NEW: Also update the tool-call part's output fieldconstoutput=this.parseToolResultContent(chunk.content)this.messages=updateToolCallWithOutput(this.messages,chunk.toolCallId,output,)// Existing: Create tool-result partthis.messages=updateToolResultPart(this.messages,this.currentAssistantMessageId,chunk.toolCallId,chunk.content,state,)this.emitMessagesChange()}}Do you intend to try to help solve this bug with your own PR?
None
Terms & Code of Conduct
TanStack AI version
0.2.0
Framework/Library version
react 18.2.0
Describe the bug and the steps to reproduce it
Summary: Server-side tool results don't update the
tool-callpart'soutputfield, causing inconsistent UI state between server and client tool execution.Expected behavior: Both server-executed tools and client-executed tools should have their results visible on the
tool-callpart'soutputfield, and the state should transition to'complete'.Actual behavior:
tool-callpart showsstate: 'complete'withoutputpopulatedtool-callpart showsstate: 'input-complete'with nooutput(result only exists in a separatetool-resultpart)Root cause: In
StreamProcessor,handleToolResultChunk()only creates atool-resultpart viaupdateToolResultPart(), but doesn't update the correspondingtool-callpart'soutputfield or state. In contrast,addToolResult()(used for client tools) does both:Steps to reproduce:
toolDefinition()that has bothinputSchemaandoutputSchema.server()with an execute function.client()with an execute functionUIMessage.parts- client tool-call parts haveoutputandstate: 'complete', server tool-call parts only havestate: 'input-complete'with nooutputExample to reproduce
Screenshots or Videos (Optional)
In the screenshot:
getCurrentPipelineConfig(client tool): Shows "complete" status with RESULT section visiblegetPipelineSchema(server tool): Shows "input-complete" status with no RESULT sectionvalidatePipelineConfig(server tool): Shows "input-complete" status with no RESULT sectionapplyPipelineConfig(client tool): Shows "complete" statusSuggested Fix
In
processor.ts,handleToolResultChunk()should also update the tool-call part similar toaddToolResult():Do you intend to try to help solve this bug with your own PR?
None
Terms & Code of Conduct