@tanstack/ai 0.0.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.
Files changed (83) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +131 -0
  3. package/dist/esm/base-adapter.d.ts +35 -0
  4. package/dist/esm/base-adapter.js +12 -0
  5. package/dist/esm/base-adapter.js.map +1 -0
  6. package/dist/esm/core/chat-common-options.d.ts +52 -0
  7. package/dist/esm/core/chat.d.ts +30 -0
  8. package/dist/esm/core/chat.js +533 -0
  9. package/dist/esm/core/chat.js.map +1 -0
  10. package/dist/esm/core/embedding.d.ts +8 -0
  11. package/dist/esm/core/embedding.js +33 -0
  12. package/dist/esm/core/embedding.js.map +1 -0
  13. package/dist/esm/core/summarize.d.ts +9 -0
  14. package/dist/esm/core/summarize.js +36 -0
  15. package/dist/esm/core/summarize.js.map +1 -0
  16. package/dist/esm/event-client.d.ts +311 -0
  17. package/dist/esm/event-client.js +62 -0
  18. package/dist/esm/event-client.js.map +1 -0
  19. package/dist/esm/index.d.ts +16 -0
  20. package/dist/esm/index.js +50 -0
  21. package/dist/esm/index.js.map +1 -0
  22. package/dist/esm/message-converters.d.ts +52 -0
  23. package/dist/esm/message-converters.js +162 -0
  24. package/dist/esm/message-converters.js.map +1 -0
  25. package/dist/esm/stream/index.d.ts +11 -0
  26. package/dist/esm/stream/json-parser.d.ts +38 -0
  27. package/dist/esm/stream/json-parser.js +28 -0
  28. package/dist/esm/stream/json-parser.js.map +1 -0
  29. package/dist/esm/stream/message-updaters.d.ts +44 -0
  30. package/dist/esm/stream/message-updaters.js +141 -0
  31. package/dist/esm/stream/message-updaters.js.map +1 -0
  32. package/dist/esm/stream/processor.d.ts +242 -0
  33. package/dist/esm/stream/processor.js +693 -0
  34. package/dist/esm/stream/processor.js.map +1 -0
  35. package/dist/esm/stream/strategies.d.ts +43 -0
  36. package/dist/esm/stream/strategies.js +54 -0
  37. package/dist/esm/stream/strategies.js.map +1 -0
  38. package/dist/esm/stream/types.d.ts +71 -0
  39. package/dist/esm/tools/tool-calls.d.ts +112 -0
  40. package/dist/esm/tools/tool-calls.js +302 -0
  41. package/dist/esm/tools/tool-calls.js.map +1 -0
  42. package/dist/esm/tools/tool-definition.d.ts +125 -0
  43. package/dist/esm/tools/tool-definition.js +25 -0
  44. package/dist/esm/tools/tool-definition.js.map +1 -0
  45. package/dist/esm/tools/zod-converter.d.ts +30 -0
  46. package/dist/esm/tools/zod-converter.js +36 -0
  47. package/dist/esm/tools/zod-converter.js.map +1 -0
  48. package/dist/esm/types.d.ts +619 -0
  49. package/dist/esm/utilities/agent-loop-strategies.d.ts +59 -0
  50. package/dist/esm/utilities/agent-loop-strategies.js +23 -0
  51. package/dist/esm/utilities/agent-loop-strategies.js.map +1 -0
  52. package/dist/esm/utilities/chat-options.d.ts +6 -0
  53. package/dist/esm/utilities/chat-options.js +7 -0
  54. package/dist/esm/utilities/chat-options.js.map +1 -0
  55. package/dist/esm/utilities/messages.d.ts +30 -0
  56. package/dist/esm/utilities/messages.js +7 -0
  57. package/dist/esm/utilities/messages.js.map +1 -0
  58. package/dist/esm/utilities/stream-to-response.d.ts +48 -0
  59. package/dist/esm/utilities/stream-to-response.js +62 -0
  60. package/dist/esm/utilities/stream-to-response.js.map +1 -0
  61. package/package.json +65 -0
  62. package/src/base-adapter.ts +85 -0
  63. package/src/core/chat-common-options.ts +55 -0
  64. package/src/core/chat.ts +771 -0
  65. package/src/core/embedding.ts +54 -0
  66. package/src/core/summarize.ts +56 -0
  67. package/src/event-client.ts +389 -0
  68. package/src/index.ts +68 -0
  69. package/src/message-converters.ts +285 -0
  70. package/src/stream/index.ts +41 -0
  71. package/src/stream/json-parser.ts +58 -0
  72. package/src/stream/message-updaters.ts +275 -0
  73. package/src/stream/processor.ts +1092 -0
  74. package/src/stream/strategies.ts +78 -0
  75. package/src/stream/types.ts +94 -0
  76. package/src/tools/tool-calls.ts +471 -0
  77. package/src/tools/tool-definition.ts +206 -0
  78. package/src/tools/zod-converter.ts +85 -0
  79. package/src/types.ts +872 -0
  80. package/src/utilities/agent-loop-strategies.ts +85 -0
  81. package/src/utilities/chat-options.ts +35 -0
  82. package/src/utilities/messages.ts +63 -0
  83. package/src/utilities/stream-to-response.ts +116 -0
@@ -0,0 +1,693 @@
1
+ import { generateMessageId, uiMessageToModelMessages } from "../message-converters.js";
2
+ import { defaultJSONParser } from "./json-parser.js";
3
+ import { updateToolCallWithOutput, updateToolResultPart, updateToolCallApprovalResponse, updateToolCallPart, updateThinkingPart, updateToolCallApproval, updateTextPart } from "./message-updaters.js";
4
+ import { ImmediateStrategy } from "./strategies.js";
5
+ class StreamProcessor {
6
+ constructor(options = {}) {
7
+ this.messages = [];
8
+ this.currentAssistantMessageId = null;
9
+ this.totalTextContent = "";
10
+ this.currentSegmentText = "";
11
+ this.lastEmittedText = "";
12
+ this.thinkingContent = "";
13
+ this.toolCalls = /* @__PURE__ */ new Map();
14
+ this.toolCallOrder = [];
15
+ this.finishReason = null;
16
+ this.isDone = false;
17
+ this.hasToolCallsSinceTextStart = false;
18
+ this.recording = null;
19
+ this.recordingStartTime = 0;
20
+ this.chunkStrategy = options.chunkStrategy || new ImmediateStrategy();
21
+ this.events = options.events || {};
22
+ this.handlers = options.handlers || {};
23
+ this.jsonParser = options.jsonParser || defaultJSONParser;
24
+ this.recordingEnabled = options.recording ?? false;
25
+ if (options.initialMessages) {
26
+ this.messages = [...options.initialMessages];
27
+ }
28
+ }
29
+ // ============================================
30
+ // Message Management Methods
31
+ // ============================================
32
+ /**
33
+ * Set the messages array (e.g., from persisted state)
34
+ */
35
+ setMessages(messages) {
36
+ this.messages = [...messages];
37
+ this.emitMessagesChange();
38
+ }
39
+ /**
40
+ * Add a user message to the conversation
41
+ */
42
+ addUserMessage(content) {
43
+ const userMessage = {
44
+ id: generateMessageId(),
45
+ role: "user",
46
+ parts: [{ type: "text", content }],
47
+ createdAt: /* @__PURE__ */ new Date()
48
+ };
49
+ this.messages = [...this.messages, userMessage];
50
+ this.emitMessagesChange();
51
+ return userMessage;
52
+ }
53
+ /**
54
+ * Start streaming a new assistant message
55
+ * Returns the message ID
56
+ */
57
+ startAssistantMessage() {
58
+ this.resetStreamState();
59
+ const assistantMessage = {
60
+ id: generateMessageId(),
61
+ role: "assistant",
62
+ parts: [],
63
+ createdAt: /* @__PURE__ */ new Date()
64
+ };
65
+ this.currentAssistantMessageId = assistantMessage.id;
66
+ this.messages = [...this.messages, assistantMessage];
67
+ this.events.onStreamStart?.();
68
+ this.emitMessagesChange();
69
+ return assistantMessage.id;
70
+ }
71
+ /**
72
+ * Add a tool result (called by client after handling onToolCall)
73
+ */
74
+ addToolResult(toolCallId, output, error) {
75
+ const messageWithToolCall = this.messages.find(
76
+ (msg) => msg.parts.some(
77
+ (p) => p.type === "tool-call" && p.id === toolCallId
78
+ )
79
+ );
80
+ if (!messageWithToolCall) {
81
+ console.warn(
82
+ `[StreamProcessor] Could not find message with tool call ${toolCallId}`
83
+ );
84
+ return;
85
+ }
86
+ let updatedMessages = updateToolCallWithOutput(
87
+ this.messages,
88
+ toolCallId,
89
+ output,
90
+ error ? "input-complete" : void 0,
91
+ error
92
+ );
93
+ const content = typeof output === "string" ? output : JSON.stringify(output);
94
+ const toolResultState = error ? "error" : "complete";
95
+ updatedMessages = updateToolResultPart(
96
+ updatedMessages,
97
+ messageWithToolCall.id,
98
+ toolCallId,
99
+ content,
100
+ toolResultState,
101
+ error
102
+ );
103
+ this.messages = updatedMessages;
104
+ this.emitMessagesChange();
105
+ }
106
+ /**
107
+ * Add an approval response (called by client after handling onApprovalRequest)
108
+ */
109
+ addToolApprovalResponse(approvalId, approved) {
110
+ this.messages = updateToolCallApprovalResponse(
111
+ this.messages,
112
+ approvalId,
113
+ approved
114
+ );
115
+ this.emitMessagesChange();
116
+ }
117
+ /**
118
+ * Get the conversation as ModelMessages (for sending to LLM)
119
+ */
120
+ toModelMessages() {
121
+ const modelMessages = [];
122
+ for (const msg of this.messages) {
123
+ modelMessages.push(...uiMessageToModelMessages(msg));
124
+ }
125
+ return modelMessages;
126
+ }
127
+ /**
128
+ * Get current messages
129
+ */
130
+ getMessages() {
131
+ return this.messages;
132
+ }
133
+ /**
134
+ * Check if all tool calls in the last assistant message are complete
135
+ * Useful for auto-continue logic
136
+ */
137
+ areAllToolsComplete() {
138
+ const lastAssistant = this.messages.findLast(
139
+ (m) => m.role === "assistant"
140
+ );
141
+ if (!lastAssistant) return true;
142
+ const toolParts = lastAssistant.parts.filter(
143
+ (p) => p.type === "tool-call"
144
+ );
145
+ if (toolParts.length === 0) return true;
146
+ return toolParts.every(
147
+ (part) => part.state === "approval-responded" || part.output !== void 0 && !part.approval
148
+ );
149
+ }
150
+ /**
151
+ * Remove messages after a certain index (for reload/retry)
152
+ */
153
+ removeMessagesAfter(index) {
154
+ this.messages = this.messages.slice(0, index + 1);
155
+ this.emitMessagesChange();
156
+ }
157
+ /**
158
+ * Clear all messages
159
+ */
160
+ clearMessages() {
161
+ this.messages = [];
162
+ this.currentAssistantMessageId = null;
163
+ this.emitMessagesChange();
164
+ }
165
+ // ============================================
166
+ // Stream Processing Methods
167
+ // ============================================
168
+ /**
169
+ * Process a stream and emit events through handlers
170
+ */
171
+ async process(stream) {
172
+ this.resetStreamState();
173
+ if (this.recordingEnabled) {
174
+ this.startRecording();
175
+ }
176
+ for await (const chunk of stream) {
177
+ this.processChunk(chunk);
178
+ }
179
+ this.finalizeStream();
180
+ if (this.recording) {
181
+ this.recording.result = this.getResult();
182
+ }
183
+ return this.getResult();
184
+ }
185
+ /**
186
+ * Process a single chunk from the stream
187
+ */
188
+ processChunk(chunk) {
189
+ if (this.recording) {
190
+ this.recording.chunks.push({
191
+ chunk,
192
+ timestamp: Date.now(),
193
+ index: this.recording.chunks.length
194
+ });
195
+ }
196
+ switch (chunk.type) {
197
+ case "content":
198
+ this.handleContentChunk(chunk);
199
+ break;
200
+ case "tool_call":
201
+ this.handleToolCallChunk(chunk);
202
+ break;
203
+ case "tool_result":
204
+ this.handleToolResultChunk(chunk);
205
+ break;
206
+ case "done":
207
+ this.handleDoneChunk(chunk);
208
+ break;
209
+ case "error":
210
+ this.handleErrorChunk(chunk);
211
+ break;
212
+ case "thinking":
213
+ this.handleThinkingChunk(chunk);
214
+ break;
215
+ case "approval-requested":
216
+ this.handleApprovalRequestedChunk(chunk);
217
+ break;
218
+ case "tool-input-available":
219
+ this.handleToolInputAvailableChunk(chunk);
220
+ break;
221
+ }
222
+ }
223
+ /**
224
+ * Handle a content chunk
225
+ */
226
+ handleContentChunk(chunk) {
227
+ this.completeAllToolCalls();
228
+ const previousSegment = this.currentSegmentText;
229
+ const isNewSegment = this.hasToolCallsSinceTextStart && previousSegment.length > 0 && this.isNewTextSegment(chunk, previousSegment);
230
+ if (isNewSegment) {
231
+ if (previousSegment !== this.lastEmittedText) {
232
+ this.emitTextUpdate();
233
+ }
234
+ this.currentSegmentText = "";
235
+ this.lastEmittedText = "";
236
+ this.hasToolCallsSinceTextStart = false;
237
+ }
238
+ const currentText = this.currentSegmentText;
239
+ let nextText = currentText;
240
+ if (chunk.delta !== "") {
241
+ nextText = currentText + chunk.delta;
242
+ } else if (chunk.content !== "") {
243
+ if (chunk.content.startsWith(currentText)) {
244
+ nextText = chunk.content;
245
+ } else if (currentText.startsWith(chunk.content)) {
246
+ nextText = currentText;
247
+ } else {
248
+ nextText = currentText + chunk.content;
249
+ }
250
+ }
251
+ const textDelta = nextText.slice(currentText.length);
252
+ this.currentSegmentText = nextText;
253
+ this.totalTextContent += textDelta;
254
+ const chunkPortion = chunk.delta ?? chunk.content ?? "";
255
+ const shouldEmit = this.chunkStrategy.shouldEmit(
256
+ chunkPortion,
257
+ this.currentSegmentText
258
+ );
259
+ if (shouldEmit && this.currentSegmentText !== this.lastEmittedText) {
260
+ this.emitTextUpdate();
261
+ }
262
+ }
263
+ /**
264
+ * Handle a tool call chunk
265
+ */
266
+ handleToolCallChunk(chunk) {
267
+ this.hasToolCallsSinceTextStart = true;
268
+ const toolCallId = chunk.toolCall.id;
269
+ const existingToolCall = this.toolCalls.get(toolCallId);
270
+ if (!existingToolCall) {
271
+ const initialState = chunk.toolCall.function.arguments ? "input-streaming" : "awaiting-input";
272
+ const newToolCall = {
273
+ id: chunk.toolCall.id,
274
+ name: chunk.toolCall.function.name,
275
+ arguments: chunk.toolCall.function.arguments || "",
276
+ state: initialState,
277
+ parsedArguments: void 0,
278
+ index: chunk.index
279
+ };
280
+ if (chunk.toolCall.function.arguments) {
281
+ newToolCall.parsedArguments = this.jsonParser.parse(
282
+ chunk.toolCall.function.arguments
283
+ );
284
+ }
285
+ this.toolCalls.set(toolCallId, newToolCall);
286
+ this.toolCallOrder.push(toolCallId);
287
+ const actualIndex = this.toolCallOrder.indexOf(toolCallId);
288
+ this.handlers.onToolCallStart?.(
289
+ actualIndex,
290
+ chunk.toolCall.id,
291
+ chunk.toolCall.function.name
292
+ );
293
+ this.handlers.onToolCallStateChange?.(
294
+ actualIndex,
295
+ chunk.toolCall.id,
296
+ chunk.toolCall.function.name,
297
+ initialState,
298
+ chunk.toolCall.function.arguments || "",
299
+ newToolCall.parsedArguments
300
+ );
301
+ if (chunk.toolCall.function.arguments) {
302
+ this.handlers.onToolCallDelta?.(
303
+ actualIndex,
304
+ chunk.toolCall.function.arguments
305
+ );
306
+ }
307
+ if (this.currentAssistantMessageId) {
308
+ this.messages = updateToolCallPart(
309
+ this.messages,
310
+ this.currentAssistantMessageId,
311
+ {
312
+ id: chunk.toolCall.id,
313
+ name: chunk.toolCall.function.name,
314
+ arguments: chunk.toolCall.function.arguments || "",
315
+ state: initialState
316
+ }
317
+ );
318
+ this.emitMessagesChange();
319
+ this.events.onToolCallStateChange?.(
320
+ this.currentAssistantMessageId,
321
+ chunk.toolCall.id,
322
+ initialState,
323
+ chunk.toolCall.function.arguments || ""
324
+ );
325
+ }
326
+ } else {
327
+ const wasAwaitingInput = existingToolCall.state === "awaiting-input";
328
+ existingToolCall.arguments += chunk.toolCall.function.arguments || "";
329
+ if (wasAwaitingInput && chunk.toolCall.function.arguments) {
330
+ existingToolCall.state = "input-streaming";
331
+ }
332
+ existingToolCall.parsedArguments = this.jsonParser.parse(
333
+ existingToolCall.arguments
334
+ );
335
+ const actualIndex = this.toolCallOrder.indexOf(toolCallId);
336
+ this.handlers.onToolCallStateChange?.(
337
+ actualIndex,
338
+ existingToolCall.id,
339
+ existingToolCall.name,
340
+ existingToolCall.state,
341
+ existingToolCall.arguments,
342
+ existingToolCall.parsedArguments
343
+ );
344
+ if (chunk.toolCall.function.arguments) {
345
+ this.handlers.onToolCallDelta?.(
346
+ actualIndex,
347
+ chunk.toolCall.function.arguments
348
+ );
349
+ }
350
+ if (this.currentAssistantMessageId) {
351
+ this.messages = updateToolCallPart(
352
+ this.messages,
353
+ this.currentAssistantMessageId,
354
+ {
355
+ id: existingToolCall.id,
356
+ name: existingToolCall.name,
357
+ arguments: existingToolCall.arguments,
358
+ state: existingToolCall.state
359
+ }
360
+ );
361
+ this.emitMessagesChange();
362
+ this.events.onToolCallStateChange?.(
363
+ this.currentAssistantMessageId,
364
+ existingToolCall.id,
365
+ existingToolCall.state,
366
+ existingToolCall.arguments
367
+ );
368
+ }
369
+ }
370
+ }
371
+ /**
372
+ * Handle a tool result chunk
373
+ */
374
+ handleToolResultChunk(chunk) {
375
+ const state = "complete";
376
+ this.handlers.onToolResultStateChange?.(
377
+ chunk.toolCallId,
378
+ chunk.content,
379
+ state
380
+ );
381
+ if (this.currentAssistantMessageId) {
382
+ this.messages = updateToolResultPart(
383
+ this.messages,
384
+ this.currentAssistantMessageId,
385
+ chunk.toolCallId,
386
+ chunk.content,
387
+ state
388
+ );
389
+ this.emitMessagesChange();
390
+ }
391
+ }
392
+ /**
393
+ * Handle a done chunk
394
+ */
395
+ handleDoneChunk(chunk) {
396
+ this.finishReason = chunk.finishReason;
397
+ this.isDone = true;
398
+ this.completeAllToolCalls();
399
+ }
400
+ /**
401
+ * Handle an error chunk
402
+ */
403
+ handleErrorChunk(chunk) {
404
+ this.handlers.onError?.(chunk.error);
405
+ this.events.onError?.(new Error(chunk.error.message));
406
+ }
407
+ /**
408
+ * Handle a thinking chunk
409
+ */
410
+ handleThinkingChunk(chunk) {
411
+ const previous = this.thinkingContent;
412
+ let nextThinking = previous;
413
+ if (chunk.delta !== "") {
414
+ nextThinking = previous + chunk.delta;
415
+ } else if (chunk.content !== "") {
416
+ if (chunk.content.startsWith(previous)) {
417
+ nextThinking = chunk.content;
418
+ } else if (previous.startsWith(chunk.content)) {
419
+ nextThinking = previous;
420
+ } else {
421
+ nextThinking = previous + chunk.content;
422
+ }
423
+ }
424
+ this.thinkingContent = nextThinking;
425
+ this.handlers.onThinkingUpdate?.(this.thinkingContent);
426
+ if (this.currentAssistantMessageId) {
427
+ this.messages = updateThinkingPart(
428
+ this.messages,
429
+ this.currentAssistantMessageId,
430
+ this.thinkingContent
431
+ );
432
+ this.emitMessagesChange();
433
+ this.events.onThinkingUpdate?.(
434
+ this.currentAssistantMessageId,
435
+ this.thinkingContent
436
+ );
437
+ }
438
+ }
439
+ /**
440
+ * Handle an approval-requested chunk
441
+ */
442
+ handleApprovalRequestedChunk(chunk) {
443
+ this.handlers.onApprovalRequested?.(
444
+ chunk.toolCallId,
445
+ chunk.toolName,
446
+ chunk.input,
447
+ chunk.approval.id
448
+ );
449
+ if (this.currentAssistantMessageId) {
450
+ this.messages = updateToolCallApproval(
451
+ this.messages,
452
+ this.currentAssistantMessageId,
453
+ chunk.toolCallId,
454
+ chunk.approval.id
455
+ );
456
+ this.emitMessagesChange();
457
+ }
458
+ this.events.onApprovalRequest?.({
459
+ toolCallId: chunk.toolCallId,
460
+ toolName: chunk.toolName,
461
+ input: chunk.input,
462
+ approvalId: chunk.approval.id
463
+ });
464
+ }
465
+ /**
466
+ * Handle a tool-input-available chunk
467
+ */
468
+ handleToolInputAvailableChunk(chunk) {
469
+ this.handlers.onToolInputAvailable?.(
470
+ chunk.toolCallId,
471
+ chunk.toolName,
472
+ chunk.input
473
+ );
474
+ this.events.onToolCall?.({
475
+ toolCallId: chunk.toolCallId,
476
+ toolName: chunk.toolName,
477
+ input: chunk.input
478
+ });
479
+ }
480
+ /**
481
+ * Detect if an incoming content chunk represents a NEW text segment
482
+ */
483
+ isNewTextSegment(chunk, previous) {
484
+ if (chunk.delta !== void 0 && chunk.content !== void 0) {
485
+ if (chunk.content.length < previous.length) {
486
+ return true;
487
+ }
488
+ if (!chunk.content.startsWith(previous) && !previous.startsWith(chunk.content)) {
489
+ return true;
490
+ }
491
+ }
492
+ return false;
493
+ }
494
+ /**
495
+ * Complete all tool calls
496
+ */
497
+ completeAllToolCalls() {
498
+ this.toolCalls.forEach((toolCall, id) => {
499
+ if (toolCall.state !== "input-complete") {
500
+ const index = this.toolCallOrder.indexOf(id);
501
+ this.completeToolCall(index, toolCall);
502
+ }
503
+ });
504
+ }
505
+ /**
506
+ * Mark a tool call as complete and emit event
507
+ */
508
+ completeToolCall(index, toolCall) {
509
+ toolCall.state = "input-complete";
510
+ toolCall.parsedArguments = this.jsonParser.parse(toolCall.arguments);
511
+ this.handlers.onToolCallStateChange?.(
512
+ index,
513
+ toolCall.id,
514
+ toolCall.name,
515
+ "input-complete",
516
+ toolCall.arguments,
517
+ toolCall.parsedArguments
518
+ );
519
+ this.handlers.onToolCallComplete?.(
520
+ index,
521
+ toolCall.id,
522
+ toolCall.name,
523
+ toolCall.arguments
524
+ );
525
+ if (this.currentAssistantMessageId) {
526
+ this.messages = updateToolCallPart(
527
+ this.messages,
528
+ this.currentAssistantMessageId,
529
+ {
530
+ id: toolCall.id,
531
+ name: toolCall.name,
532
+ arguments: toolCall.arguments,
533
+ state: "input-complete"
534
+ }
535
+ );
536
+ this.emitMessagesChange();
537
+ this.events.onToolCallStateChange?.(
538
+ this.currentAssistantMessageId,
539
+ toolCall.id,
540
+ "input-complete",
541
+ toolCall.arguments
542
+ );
543
+ }
544
+ }
545
+ /**
546
+ * Emit pending text update
547
+ */
548
+ emitTextUpdate() {
549
+ this.lastEmittedText = this.currentSegmentText;
550
+ this.handlers.onTextUpdate?.(this.currentSegmentText);
551
+ if (this.currentAssistantMessageId) {
552
+ this.messages = updateTextPart(
553
+ this.messages,
554
+ this.currentAssistantMessageId,
555
+ this.currentSegmentText
556
+ );
557
+ this.emitMessagesChange();
558
+ this.events.onTextUpdate?.(
559
+ this.currentAssistantMessageId,
560
+ this.currentSegmentText
561
+ );
562
+ }
563
+ }
564
+ /**
565
+ * Emit messages change event
566
+ */
567
+ emitMessagesChange() {
568
+ this.events.onMessagesChange?.([...this.messages]);
569
+ }
570
+ /**
571
+ * Finalize the stream - complete all pending operations
572
+ */
573
+ finalizeStream() {
574
+ this.completeAllToolCalls();
575
+ if (this.currentSegmentText !== this.lastEmittedText) {
576
+ this.emitTextUpdate();
577
+ }
578
+ const toolCalls = this.getCompletedToolCalls();
579
+ this.handlers.onStreamEnd?.(
580
+ this.totalTextContent,
581
+ toolCalls.length > 0 ? toolCalls : void 0
582
+ );
583
+ if (this.currentAssistantMessageId) {
584
+ const assistantMessage = this.messages.find(
585
+ (m) => m.id === this.currentAssistantMessageId
586
+ );
587
+ if (assistantMessage) {
588
+ this.events.onStreamEnd?.(assistantMessage);
589
+ }
590
+ }
591
+ }
592
+ /**
593
+ * Get completed tool calls in API format
594
+ */
595
+ getCompletedToolCalls() {
596
+ return Array.from(this.toolCalls.values()).filter((tc) => tc.state === "input-complete").map((tc) => ({
597
+ id: tc.id,
598
+ type: "function",
599
+ function: {
600
+ name: tc.name,
601
+ arguments: tc.arguments
602
+ }
603
+ }));
604
+ }
605
+ /**
606
+ * Get current result
607
+ */
608
+ getResult() {
609
+ const toolCalls = this.getCompletedToolCalls();
610
+ return {
611
+ content: this.totalTextContent,
612
+ thinking: this.thinkingContent || void 0,
613
+ toolCalls: toolCalls.length > 0 ? toolCalls : void 0,
614
+ finishReason: this.finishReason
615
+ };
616
+ }
617
+ /**
618
+ * Get current processor state (legacy)
619
+ */
620
+ getState() {
621
+ return {
622
+ content: this.totalTextContent,
623
+ thinking: this.thinkingContent,
624
+ toolCalls: new Map(this.toolCalls),
625
+ toolCallOrder: [...this.toolCallOrder],
626
+ finishReason: this.finishReason,
627
+ done: this.isDone
628
+ };
629
+ }
630
+ /**
631
+ * Start recording chunks
632
+ */
633
+ startRecording() {
634
+ this.recordingEnabled = true;
635
+ this.recordingStartTime = Date.now();
636
+ this.recording = {
637
+ version: "1.0",
638
+ timestamp: this.recordingStartTime,
639
+ chunks: []
640
+ };
641
+ }
642
+ /**
643
+ * Get the current recording
644
+ */
645
+ getRecording() {
646
+ return this.recording;
647
+ }
648
+ /**
649
+ * Reset stream state (but keep messages)
650
+ */
651
+ resetStreamState() {
652
+ this.totalTextContent = "";
653
+ this.currentSegmentText = "";
654
+ this.lastEmittedText = "";
655
+ this.thinkingContent = "";
656
+ this.toolCalls.clear();
657
+ this.toolCallOrder = [];
658
+ this.finishReason = null;
659
+ this.isDone = false;
660
+ this.hasToolCallsSinceTextStart = false;
661
+ this.chunkStrategy.reset?.();
662
+ }
663
+ /**
664
+ * Full reset (including messages)
665
+ */
666
+ reset() {
667
+ this.resetStreamState();
668
+ this.messages = [];
669
+ this.currentAssistantMessageId = null;
670
+ }
671
+ /**
672
+ * Replay a recording through the processor
673
+ */
674
+ static async replay(recording, options) {
675
+ const processor = new StreamProcessor(options);
676
+ return processor.process(createReplayStream(recording));
677
+ }
678
+ }
679
+ function createReplayStream(recording) {
680
+ return {
681
+ // eslint-disable-next-line @typescript-eslint/require-await
682
+ async *[Symbol.asyncIterator]() {
683
+ for (const { chunk } of recording.chunks) {
684
+ yield chunk;
685
+ }
686
+ }
687
+ };
688
+ }
689
+ export {
690
+ StreamProcessor,
691
+ createReplayStream
692
+ };
693
+ //# sourceMappingURL=processor.js.map