@tanstack/ai 0.8.0 → 0.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -87,18 +87,18 @@ Available adapters: `openaiText`, `openaiEmbed`, `openaiSummarize`, `anthropicTe
87
87
  <td>
88
88
  <a href="https://www.coderabbit.ai/?via=tanstack&dub_id=aCcEEdAOqqutX6OS" >
89
89
  <picture>
90
- <source media="(prefers-color-scheme: dark)" srcset="https://tanstack.com/assets/coderabbit-dark-CMcuvjEy.svg" height="40" />
91
- <source media="(prefers-color-scheme: light)" srcset="https://tanstack.com/assets/coderabbit-light-DVMJ2jHi.svg" height="40" />
92
- <img src="https://tanstack.com/assets/coderabbit-light-DVMJ2jHi.svg" height="40" alt="CodeRabbit" />
90
+ <source media="(prefers-color-scheme: dark)" srcset="https://tanstack.com/assets/coderabbit-dark-D643Zkrv.svg" height="40" />
91
+ <source media="(prefers-color-scheme: light)" srcset="https://tanstack.com/assets/coderabbit-light-CIzGLYU_.svg" height="40" />
92
+ <img src="https://tanstack.com/assets/coderabbit-light-CIzGLYU_.svg" height="40" alt="CodeRabbit" />
93
93
  </picture>
94
94
  </a>
95
95
  </td>
96
96
  <td>
97
97
  <a href="https://www.cloudflare.com?utm_source=tanstack">
98
98
  <picture>
99
- <source media="(prefers-color-scheme: dark)" srcset="https://tanstack.com/assets/cloudflare-white-DQDB7UaL.svg" height="60" />
100
- <source media="(prefers-color-scheme: light)" srcset="https://tanstack.com/assets/cloudflare-black-CPufaW0B.svg" height="60" />
101
- <img src="https://tanstack.com/assets/cloudflare-black-CPufaW0B.svg" height="60" alt="Cloudflare" />
99
+ <source media="(prefers-color-scheme: dark)" srcset="https://tanstack.com/assets/cloudflare-white-Co-Tyjbl.svg" height="60" />
100
+ <source media="(prefers-color-scheme: light)" srcset="https://tanstack.com/assets/cloudflare-black-6Ojsn8yh.svg" height="60" />
101
+ <img src="https://tanstack.com/assets/cloudflare-black-6Ojsn8yh.svg" height="60" alt="Cloudflare" />
102
102
  </picture>
103
103
  </a>
104
104
  </td>
@@ -50,9 +50,8 @@ export interface StreamProcessorOptions {
50
50
  *
51
51
  * State tracking:
52
52
  * - Full message array
53
- * - Current assistant message being streamed
54
- * - Text content accumulation (reset on TEXT_MESSAGE_START)
55
- * - Multiple parallel tool calls
53
+ * - Per-message stream state (text, tool calls, thinking)
54
+ * - Multiple concurrent message streams
56
55
  * - Tool call completion via TOOL_CALL_END events
57
56
  *
58
57
  * @see docs/chat-architecture.md#streamprocessor-internal-state — State field reference
@@ -64,13 +63,11 @@ export declare class StreamProcessor {
64
63
  private jsonParser;
65
64
  private recordingEnabled;
66
65
  private messages;
67
- private currentAssistantMessageId;
68
- private totalTextContent;
69
- private currentSegmentText;
70
- private lastEmittedText;
71
- private thinkingContent;
72
- private toolCalls;
73
- private toolCallOrder;
66
+ private messageStates;
67
+ private activeMessageIds;
68
+ private toolCallToMessage;
69
+ private pendingManualMessageId;
70
+ private activeRuns;
74
71
  private finishReason;
75
72
  private hasError;
76
73
  private isDone;
@@ -117,24 +114,13 @@ export declare class StreamProcessor {
117
114
  * @deprecated Use prepareAssistantMessage() instead. This eagerly creates
118
115
  * an assistant message which can cause empty message flicker.
119
116
  */
120
- startAssistantMessage(): string;
117
+ startAssistantMessage(messageId?: string): string;
121
118
  /**
122
119
  * Get the current assistant message ID (if one has been created).
123
120
  * Returns null if prepareAssistantMessage() was called but no content
124
121
  * has arrived yet.
125
122
  */
126
123
  getCurrentAssistantMessageId(): string | null;
127
- /**
128
- * Lazily create the assistant message if it hasn't been created yet.
129
- * Called by content handlers on the first content-bearing chunk.
130
- * Returns the message ID.
131
- *
132
- * Content-bearing chunks that trigger this:
133
- * TEXT_MESSAGE_CONTENT, TOOL_CALL_START, STEP_FINISHED, RUN_ERROR.
134
- *
135
- * @see docs/chat-architecture.md#streamprocessor-internal-state — Lazy creation pattern
136
- */
137
- private ensureAssistantMessage;
138
124
  /**
139
125
  * Add a tool result (called by client after handling onToolCall)
140
126
  */
@@ -173,23 +159,46 @@ export declare class StreamProcessor {
173
159
  *
174
160
  * Central dispatch for all AG-UI events. Each event type maps to a specific
175
161
  * handler. Events not listed in the switch are intentionally ignored
176
- * (RUN_STARTED, TEXT_MESSAGE_END, STEP_STARTED, STATE_SNAPSHOT, STATE_DELTA).
162
+ * (RUN_STARTED, STEP_STARTED, STATE_DELTA).
177
163
  *
178
164
  * @see docs/chat-architecture.md#adapter-contract — Expected event types and ordering
179
165
  */
180
166
  processChunk(chunk: StreamChunk): void;
181
167
  /**
182
- * Handle TEXT_MESSAGE_START event — marks the beginning of a new text segment.
183
- * Resets segment accumulation so text after tool calls starts fresh.
184
- *
185
- * This is the key mechanism for multi-segment text (text before and after tool
186
- * calls becoming separate TextParts). Without this reset, all text would merge
187
- * into a single TextPart and tool-call interleaving would be lost.
168
+ * Create a new MessageStreamState for a message
169
+ */
170
+ private createMessageState;
171
+ /**
172
+ * Get the MessageStreamState for a message
173
+ */
174
+ private getMessageState;
175
+ /**
176
+ * Get the most recent active assistant message ID.
177
+ * Used as fallback for events that don't include a messageId.
178
+ */
179
+ private getActiveAssistantMessageId;
180
+ /**
181
+ * Ensure an active assistant message exists, creating one if needed.
182
+ * Used for backward compat when events arrive without prior TEXT_MESSAGE_START.
188
183
  *
189
- * @see docs/chat-architecture.md#single-shot-text-response — Step-by-step text processing
190
- * @see docs/chat-architecture.md#text-then-tool-interleaving-single-shot — Multi-segment text
184
+ * On reconnect/resume, a TEXT_MESSAGE_CONTENT may arrive for a message that
185
+ * already exists in this.messages (e.g. from initialMessages or a prior
186
+ * MESSAGES_SNAPSHOT) but whose transient state was cleared. In that case we
187
+ * hydrate state from the existing message rather than creating a duplicate.
188
+ */
189
+ private ensureAssistantMessage;
190
+ /**
191
+ * Handle TEXT_MESSAGE_START event
191
192
  */
192
193
  private handleTextMessageStartEvent;
194
+ /**
195
+ * Handle TEXT_MESSAGE_END event
196
+ */
197
+ private handleTextMessageEndEvent;
198
+ /**
199
+ * Handle MESSAGES_SNAPSHOT event
200
+ */
201
+ private handleMessagesSnapshotEvent;
193
202
  /**
194
203
  * Handle TEXT_MESSAGE_CONTENT event.
195
204
  *
@@ -244,12 +253,19 @@ export declare class StreamProcessor {
244
253
  * @see docs/chat-architecture.md#single-shot-tool-call-response — End-to-end flow
245
254
  */
246
255
  private handleToolCallEndEvent;
256
+ /**
257
+ * Handle RUN_STARTED event.
258
+ *
259
+ * Registers the run so that RUN_FINISHED can determine whether other
260
+ * runs are still active before finalizing.
261
+ */
262
+ private handleRunStartedEvent;
247
263
  /**
248
264
  * Handle RUN_FINISHED event.
249
265
  *
250
- * Records the finishReason and calls completeAllToolCalls() as a safety net
251
- * to force-complete any tool calls that didn't receive an explicit TOOL_CALL_END.
252
- * This handles cases like aborted streams or adapter bugs.
266
+ * Records the finishReason and removes the run from activeRuns.
267
+ * Only finalizes when no more runs are active, so that concurrent
268
+ * runs don't interfere with each other.
253
269
  *
254
270
  * @see docs/chat-architecture.md#single-shot-tool-call-response — finishReason semantics
255
271
  * @see docs/chat-architecture.md#adapter-contract — Why RUN_FINISHED is mandatory
@@ -280,7 +296,11 @@ export declare class StreamProcessor {
280
296
  */
281
297
  private handleCustomEvent;
282
298
  /**
283
- * Complete all tool calls — safety net for stream termination.
299
+ * Detect if an incoming content chunk represents a NEW text segment
300
+ */
301
+ private isNewTextSegment;
302
+ /**
303
+ * Complete all tool calls across all active messages — safety net for stream termination.
284
304
  *
285
305
  * Called by RUN_FINISHED and finalizeStream(). Force-transitions any tool call
286
306
  * not yet in input-complete state. Handles cases where TOOL_CALL_END was
@@ -289,12 +309,16 @@ export declare class StreamProcessor {
289
309
  * @see docs/chat-architecture.md#single-shot-tool-call-response — Safety net behavior
290
310
  */
291
311
  private completeAllToolCalls;
312
+ /**
313
+ * Complete all tool calls for a specific message
314
+ */
315
+ private completeAllToolCallsForMessage;
292
316
  /**
293
317
  * Mark a tool call as complete and emit event
294
318
  */
295
319
  private completeToolCall;
296
320
  /**
297
- * Emit pending text update.
321
+ * Emit pending text update for a specific message.
298
322
  *
299
323
  * Calls updateTextPart() which has critical append-vs-replace logic:
300
324
  * - If last UIMessage part is TextPart → replaces its content (same segment).
@@ -302,7 +326,7 @@ export declare class StreamProcessor {
302
326
  *
303
327
  * @see docs/chat-architecture.md#uimessage-part-ordering-invariants — Replace vs. push logic
304
328
  */
305
- private emitTextUpdate;
329
+ private emitTextUpdateForMessage;
306
330
  /**
307
331
  * Emit messages change event
308
332
  */
@@ -318,15 +342,15 @@ export declare class StreamProcessor {
318
342
  */
319
343
  finalizeStream(): void;
320
344
  /**
321
- * Get completed tool calls in API format
345
+ * Get completed tool calls in API format (aggregated across all messages)
322
346
  */
323
347
  private getCompletedToolCalls;
324
348
  /**
325
- * Get current result
349
+ * Get current result (aggregated across all messages)
326
350
  */
327
351
  private getResult;
328
352
  /**
329
- * Get current processor state
353
+ * Get current processor state (aggregated across all messages)
330
354
  */
331
355
  getState(): ProcessorState;
332
356
  /**