From 89643f913b0a867074093a61932697c804000c83 Mon Sep 17 00:00:00 2001 From: Harlley Oliveira Date: Sat, 13 Dec 2025 20:23:09 +0000 Subject: [PATCH 1/7] fix: Continue conversation after client tool execution --- packages/typescript/ai-client/src/chat-client.ts | 10 ++++++++++ packages/typescript/ai/src/stream/processor.ts | 11 ++++++++++- 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/packages/typescript/ai-client/src/chat-client.ts b/packages/typescript/ai-client/src/chat-client.ts index 3b9e1787..f23d2af4 100644 --- a/packages/typescript/ai-client/src/chat-client.ts +++ b/packages/typescript/ai-client/src/chat-client.ts @@ -27,6 +27,7 @@ export class ChatClient { private currentStreamId: string | null = null private currentMessageId: string | null = null + private callbacksRef: { current: { onResponse: (response?: Response) => void | Promise @@ -323,6 +324,15 @@ export class ChatClient { } finally { this.abortController = null this.setIsLoading(false) + + // Continue conversation if the stream ended with a tool result + const messages = this.processor.getMessages() + const lastMessage = messages[messages.length - 1] + const lastPart = lastMessage?.parts[lastMessage.parts.length - 1] + + if (lastPart?.type === 'tool-result' && this.shouldAutoSend()) { + await this.continueFlow() + } } } diff --git a/packages/typescript/ai/src/stream/processor.ts b/packages/typescript/ai/src/stream/processor.ts index d8441a33..adef614c 100644 --- a/packages/typescript/ai/src/stream/processor.ts +++ b/packages/typescript/ai/src/stream/processor.ts @@ -355,11 +355,20 @@ export class StreamProcessor { if (toolParts.length === 0) return true + // Check for server tool completions via tool-result parts + const toolResultParts = lastAssistant.parts.filter( + (p) => p.type === 'tool-result', + ) + const completedToolCallIds = new Set( + toolResultParts.map((p) => (p as { toolCallId: string }).toolCallId), + ) + // All tool calls must be in a terminal state return toolParts.every( (part) => part.state === 'approval-responded' || - (part.output !== undefined && !part.approval), + (part.output !== undefined && !part.approval) || + completedToolCallIds.has(part.id), ) } From 081b263af6356e7ba144eb27b83d6ec575de512b Mon Sep 17 00:00:00 2001 From: Harlley Oliveira Date: Sat, 13 Dec 2025 20:48:36 +0000 Subject: [PATCH 2/7] add changeset --- .changeset/fix-silent-chat-continuation.md | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .changeset/fix-silent-chat-continuation.md diff --git a/.changeset/fix-silent-chat-continuation.md b/.changeset/fix-silent-chat-continuation.md new file mode 100644 index 00000000..a36ce6b7 --- /dev/null +++ b/.changeset/fix-silent-chat-continuation.md @@ -0,0 +1,6 @@ +--- +"@tanstack/ai": patch +"@tanstack/ai-client": patch +--- + +fix: Continue conversation after client tool execution From 863776da0a7258cfc303160af94300e1523cb890 Mon Sep 17 00:00:00 2001 From: Harlley Oliveira Date: Sat, 13 Dec 2025 21:34:37 +0000 Subject: [PATCH 3/7] improve type --- packages/typescript/ai/src/stream/processor.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/typescript/ai/src/stream/processor.ts b/packages/typescript/ai/src/stream/processor.ts index adef614c..dd3429f6 100644 --- a/packages/typescript/ai/src/stream/processor.ts +++ b/packages/typescript/ai/src/stream/processor.ts @@ -357,10 +357,11 @@ export class StreamProcessor { // Check for server tool completions via tool-result parts const toolResultParts = lastAssistant.parts.filter( - (p) => p.type === 'tool-result', + (p): p is Extract => + p.type === 'tool-result', ) const completedToolCallIds = new Set( - toolResultParts.map((p) => (p as { toolCallId: string }).toolCallId), + toolResultParts.map((p) => p.toolCallId), ) // All tool calls must be in a terminal state From 8e8283ea907c98388ba6ace71967ae3e19afe5f2 Mon Sep 17 00:00:00 2001 From: Harlley Oliveira Date: Sat, 13 Dec 2025 21:35:19 +0000 Subject: [PATCH 4/7] add error handling --- packages/typescript/ai-client/src/chat-client.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/typescript/ai-client/src/chat-client.ts b/packages/typescript/ai-client/src/chat-client.ts index f23d2af4..ede9acd9 100644 --- a/packages/typescript/ai-client/src/chat-client.ts +++ b/packages/typescript/ai-client/src/chat-client.ts @@ -331,7 +331,11 @@ export class ChatClient { const lastPart = lastMessage?.parts[lastMessage.parts.length - 1] if (lastPart?.type === 'tool-result' && this.shouldAutoSend()) { - await this.continueFlow() + try { + await this.continueFlow() + } catch (error) { + console.error('Failed to continue flow after tool result:', error) + } } } } From e0142c24fc9dbd332373b48eb56f45dc0d9ec1db Mon Sep 17 00:00:00 2001 From: Harlley Oliveira Date: Sat, 13 Dec 2025 22:06:41 +0000 Subject: [PATCH 5/7] fix: Auto-continue logic runs after abort or error --- .../typescript/ai-client/src/chat-client.ts | 23 +++++++++++-------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/packages/typescript/ai-client/src/chat-client.ts b/packages/typescript/ai-client/src/chat-client.ts index ede9acd9..81bf2414 100644 --- a/packages/typescript/ai-client/src/chat-client.ts +++ b/packages/typescript/ai-client/src/chat-client.ts @@ -27,7 +27,6 @@ export class ChatClient { private currentStreamId: string | null = null private currentMessageId: string | null = null - private callbacksRef: { current: { onResponse: (response?: Response) => void | Promise @@ -291,6 +290,7 @@ export class ChatClient { this.setIsLoading(true) this.setError(undefined) this.abortController = new AbortController() + let streamCompletedSuccessfully = false try { // Get model messages for the LLM @@ -313,6 +313,7 @@ export class ChatClient { ) await this.processStream(stream) + streamCompletedSuccessfully = true } catch (err) { if (err instanceof Error) { if (err.name === 'AbortError') { @@ -326,15 +327,17 @@ export class ChatClient { this.setIsLoading(false) // Continue conversation if the stream ended with a tool result - const messages = this.processor.getMessages() - const lastMessage = messages[messages.length - 1] - const lastPart = lastMessage?.parts[lastMessage.parts.length - 1] - - if (lastPart?.type === 'tool-result' && this.shouldAutoSend()) { - try { - await this.continueFlow() - } catch (error) { - console.error('Failed to continue flow after tool result:', error) + if (streamCompletedSuccessfully) { + const messages = this.processor.getMessages() + const lastMessage = messages[messages.length - 1] + const lastPart = lastMessage?.parts[lastMessage.parts.length - 1] + + if (lastPart?.type === 'tool-result' && this.shouldAutoSend()) { + try { + await this.continueFlow() + } catch (error) { + console.error('Failed to continue flow after tool result:', error) + } } } } From 1392d00c6473ad3131e23aa3168efa23669a1204 Mon Sep 17 00:00:00 2001 From: Harlley Oliveira Date: Sat, 13 Dec 2025 22:20:32 +0000 Subject: [PATCH 6/7] Fix optional chaining syntax to prevent potential runtime error. --- .../typescript/ai-client/src/chat-client.ts | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/packages/typescript/ai-client/src/chat-client.ts b/packages/typescript/ai-client/src/chat-client.ts index 81bf2414..885d671b 100644 --- a/packages/typescript/ai-client/src/chat-client.ts +++ b/packages/typescript/ai-client/src/chat-client.ts @@ -55,13 +55,13 @@ export class ChatClient { this.callbacksRef = { current: { - onResponse: options.onResponse || (() => {}), - onChunk: options.onChunk || (() => {}), - onFinish: options.onFinish || (() => {}), - onError: options.onError || (() => {}), - onMessagesChange: options.onMessagesChange || (() => {}), - onLoadingChange: options.onLoadingChange || (() => {}), - onErrorChange: options.onErrorChange || (() => {}), + onResponse: options.onResponse || (() => { }), + onChunk: options.onChunk || (() => { }), + onFinish: options.onFinish || (() => { }), + onError: options.onError || (() => { }), + onMessagesChange: options.onMessagesChange || (() => { }), + onLoadingChange: options.onLoadingChange || (() => { }), + onErrorChange: options.onErrorChange || (() => { }), }, } @@ -329,8 +329,7 @@ export class ChatClient { // Continue conversation if the stream ended with a tool result if (streamCompletedSuccessfully) { const messages = this.processor.getMessages() - const lastMessage = messages[messages.length - 1] - const lastPart = lastMessage?.parts[lastMessage.parts.length - 1] + const lastPart = messages.at(-1)?.parts?.at(-1) if (lastPart?.type === 'tool-result' && this.shouldAutoSend()) { try { From 891fe8a7eac6cccaa3a68455c08a3c27add3039b Mon Sep 17 00:00:00 2001 From: Harlley Oliveira Date: Sat, 13 Dec 2025 22:26:20 +0000 Subject: [PATCH 7/7] style: remove whitespace from empty arrow function bodies in callback initializations --- packages/typescript/ai-client/src/chat-client.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/typescript/ai-client/src/chat-client.ts b/packages/typescript/ai-client/src/chat-client.ts index 885d671b..5276b009 100644 --- a/packages/typescript/ai-client/src/chat-client.ts +++ b/packages/typescript/ai-client/src/chat-client.ts @@ -55,13 +55,13 @@ export class ChatClient { this.callbacksRef = { current: { - onResponse: options.onResponse || (() => { }), - onChunk: options.onChunk || (() => { }), - onFinish: options.onFinish || (() => { }), - onError: options.onError || (() => { }), - onMessagesChange: options.onMessagesChange || (() => { }), - onLoadingChange: options.onLoadingChange || (() => { }), - onErrorChange: options.onErrorChange || (() => { }), + onResponse: options.onResponse || (() => {}), + onChunk: options.onChunk || (() => {}), + onFinish: options.onFinish || (() => {}), + onError: options.onError || (() => {}), + onMessagesChange: options.onMessagesChange || (() => {}), + onLoadingChange: options.onLoadingChange || (() => {}), + onErrorChange: options.onErrorChange || (() => {}), }, }