diff --git a/.changeset/fix-infinite-tool-loop.md b/.changeset/fix-infinite-tool-loop.md new file mode 100644 index 000000000..9d3f4de1c --- /dev/null +++ b/.changeset/fix-infinite-tool-loop.md @@ -0,0 +1,7 @@ +--- +'@tanstack/ai-client': patch +--- + +fix: prevent infinite tool call loop when server tool finishes with stop + +When the server-side agent loop executes a tool and the model finishes with `finishReason: 'stop'`, the client no longer auto-sends another request. Previously this caused infinite loops with non-OpenAI providers that respond minimally after tool execution. diff --git a/packages/typescript/ai-client/src/chat-client.ts b/packages/typescript/ai-client/src/chat-client.ts index 4b0e40f70..7272394a3 100644 --- a/packages/typescript/ai-client/src/chat-client.ts +++ b/packages/typescript/ai-client/src/chat-client.ts @@ -657,11 +657,18 @@ export class ChatClient { await this.drainPostStreamActions() // Continue conversation if the stream ended with a tool result (server tool completed) + // but ONLY if the model indicated it wants to continue (finishReason !== 'stop'). + // When finishReason is 'stop', the model is done — don't re-send. if (streamCompletedSuccessfully) { const messages = this.processor.getMessages() const lastPart = messages.at(-1)?.parts.at(-1) + const { finishReason } = this.processor.getState() - if (lastPart?.type === 'tool-result' && this.shouldAutoSend()) { + if ( + lastPart?.type === 'tool-result' && + finishReason !== 'stop' && + this.shouldAutoSend() + ) { try { await this.checkForContinuation() } catch (error) {