@tanstack/ai 0.2.2 → 0.3.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 (44) hide show
  1. package/README.md +1 -0
  2. package/dist/esm/activities/chat/index.js +264 -186
  3. package/dist/esm/activities/chat/index.js.map +1 -1
  4. package/dist/esm/activities/chat/messages.js +22 -0
  5. package/dist/esm/activities/chat/messages.js.map +1 -1
  6. package/dist/esm/activities/chat/stream/index.d.ts +1 -1
  7. package/dist/esm/activities/chat/stream/processor.d.ts +22 -43
  8. package/dist/esm/activities/chat/stream/processor.js +93 -157
  9. package/dist/esm/activities/chat/stream/processor.js.map +1 -1
  10. package/dist/esm/activities/chat/tools/tool-calls.d.ts +22 -21
  11. package/dist/esm/activities/chat/tools/tool-calls.js +40 -27
  12. package/dist/esm/activities/chat/tools/tool-calls.js.map +1 -1
  13. package/dist/esm/activities/generateImage/index.js +41 -1
  14. package/dist/esm/activities/generateImage/index.js.map +1 -1
  15. package/dist/esm/activities/generateSpeech/index.js +33 -1
  16. package/dist/esm/activities/generateSpeech/index.js.map +1 -1
  17. package/dist/esm/activities/generateTranscription/index.js +29 -1
  18. package/dist/esm/activities/generateTranscription/index.js.map +1 -1
  19. package/dist/esm/activities/generateVideo/index.js +50 -0
  20. package/dist/esm/activities/generateVideo/index.js.map +1 -1
  21. package/dist/esm/activities/summarize/index.js +9 -8
  22. package/dist/esm/activities/summarize/index.js.map +1 -1
  23. package/dist/esm/event-client.d.ts +378 -254
  24. package/dist/esm/event-client.js.map +1 -1
  25. package/dist/esm/index.d.ts +2 -2
  26. package/dist/esm/stream-to-response.d.ts +1 -1
  27. package/dist/esm/stream-to-response.js +5 -3
  28. package/dist/esm/stream-to-response.js.map +1 -1
  29. package/dist/esm/types.d.ts +162 -46
  30. package/package.json +1 -1
  31. package/src/activities/chat/index.ts +362 -216
  32. package/src/activities/chat/messages.ts +40 -1
  33. package/src/activities/chat/stream/index.ts +1 -5
  34. package/src/activities/chat/stream/processor.ts +142 -270
  35. package/src/activities/chat/tools/tool-calls.ts +55 -49
  36. package/src/activities/generateImage/index.ts +47 -2
  37. package/src/activities/generateSpeech/index.ts +37 -1
  38. package/src/activities/generateTranscription/index.ts +33 -1
  39. package/src/activities/generateVideo/index.ts +54 -0
  40. package/src/activities/summarize/index.ts +10 -9
  41. package/src/event-client.ts +460 -256
  42. package/src/index.ts +2 -3
  43. package/src/stream-to-response.ts +8 -6
  44. package/src/types.ts +209 -65
@@ -39,6 +39,7 @@ import type {
39
39
  StreamChunk,
40
40
  ToolCall,
41
41
  ToolCallPart,
42
+ ToolResultPart,
42
43
  UIMessage,
43
44
  } from '../../../types'
44
45
 
@@ -78,67 +79,13 @@ export interface StreamProcessorEvents {
78
79
  onThinkingUpdate?: (messageId: string, content: string) => void
79
80
  }
80
81
 
81
- /**
82
- * Legacy handlers for backward compatibility
83
- * These are the old callback-style handlers
84
- */
85
- export interface StreamProcessorHandlers {
86
- onTextUpdate?: (content: string) => void
87
- onThinkingUpdate?: (content: string) => void
88
-
89
- // Tool call lifecycle handlers
90
- onToolCallStart?: (index: number, id: string, name: string) => void
91
- onToolCallDelta?: (index: number, args: string) => void
92
- onToolCallComplete?: (
93
- index: number,
94
- id: string,
95
- name: string,
96
- args: string,
97
- ) => void
98
- onToolCallStateChange?: (
99
- index: number,
100
- id: string,
101
- name: string,
102
- state: ToolCallState,
103
- args: string,
104
- parsedArgs?: any,
105
- ) => void
106
-
107
- // Tool result handlers
108
- onToolResultStateChange?: (
109
- toolCallId: string,
110
- content: string,
111
- state: ToolResultState,
112
- error?: string,
113
- ) => void
114
-
115
- // Approval/client tool handlers
116
- onApprovalRequested?: (
117
- toolCallId: string,
118
- toolName: string,
119
- input: any,
120
- approvalId: string,
121
- ) => void
122
- onToolInputAvailable?: (
123
- toolCallId: string,
124
- toolName: string,
125
- input: any,
126
- ) => void
127
-
128
- // Stream lifecycle
129
- onStreamEnd?: (content: string, toolCalls?: Array<ToolCall>) => void
130
- onError?: (error: { message: string; code?: string }) => void
131
- }
132
-
133
82
  /**
134
83
  * Options for StreamProcessor
135
84
  */
136
85
  export interface StreamProcessorOptions {
137
86
  chunkStrategy?: ChunkStrategy
138
- /** New event-driven handlers */
87
+ /** Event-driven handlers */
139
88
  events?: StreamProcessorEvents
140
- /** Legacy callback handlers (for backward compatibility) */
141
- handlers?: StreamProcessorHandlers
142
89
  jsonParser?: {
143
90
  parse: (jsonString: string) => any
144
91
  }
@@ -168,7 +115,6 @@ export interface StreamProcessorOptions {
168
115
  export class StreamProcessor {
169
116
  private chunkStrategy: ChunkStrategy
170
117
  private events: StreamProcessorEvents
171
- private handlers: StreamProcessorHandlers
172
118
  private jsonParser: { parse: (jsonString: string) => any }
173
119
  private recordingEnabled: boolean
174
120
 
@@ -197,7 +143,6 @@ export class StreamProcessor {
197
143
  constructor(options: StreamProcessorOptions = {}) {
198
144
  this.chunkStrategy = options.chunkStrategy || new ImmediateStrategy()
199
145
  this.events = options.events || {}
200
- this.handlers = options.handlers || {}
201
146
  this.jsonParser = options.jsonParser || defaultJSONParser
202
147
  this.recordingEnabled = options.recording ?? false
203
148
 
@@ -352,11 +297,23 @@ export class StreamProcessor {
352
297
 
353
298
  if (toolParts.length === 0) return true
354
299
 
300
+ // Get tool result parts to check for server tool completion
301
+ const toolResultIds = new Set(
302
+ lastAssistant.parts
303
+ .filter((p): p is ToolResultPart => p.type === 'tool-result')
304
+ .map((p) => p.toolCallId),
305
+ )
306
+
355
307
  // All tool calls must be in a terminal state
308
+ // A tool call is complete if:
309
+ // 1. It was approved/denied (approval-responded state)
310
+ // 2. It has an output field set (client tool completed via addToolResult)
311
+ // 3. It has a corresponding tool-result part (server tool completed)
356
312
  return toolParts.every(
357
313
  (part) =>
358
314
  part.state === 'approval-responded' ||
359
- (part.output !== undefined && !part.approval),
315
+ (part.output !== undefined && !part.approval) ||
316
+ toolResultIds.has(part.id),
360
317
  )
361
318
  }
362
319
 
@@ -423,49 +380,51 @@ export class StreamProcessor {
423
380
  }
424
381
 
425
382
  switch (chunk.type) {
426
- case 'content':
427
- this.handleContentChunk(chunk)
383
+ // AG-UI Events
384
+ case 'TEXT_MESSAGE_CONTENT':
385
+ this.handleTextMessageContentEvent(chunk)
428
386
  break
429
387
 
430
- case 'tool_call':
431
- this.handleToolCallChunk(chunk)
388
+ case 'TOOL_CALL_START':
389
+ this.handleToolCallStartEvent(chunk)
432
390
  break
433
391
 
434
- case 'tool_result':
435
- this.handleToolResultChunk(chunk)
392
+ case 'TOOL_CALL_ARGS':
393
+ this.handleToolCallArgsEvent(chunk)
436
394
  break
437
395
 
438
- case 'done':
439
- this.handleDoneChunk(chunk)
396
+ case 'TOOL_CALL_END':
397
+ this.handleToolCallEndEvent(chunk)
440
398
  break
441
399
 
442
- case 'error':
443
- this.handleErrorChunk(chunk)
400
+ case 'RUN_FINISHED':
401
+ this.handleRunFinishedEvent(chunk)
444
402
  break
445
403
 
446
- case 'thinking':
447
- this.handleThinkingChunk(chunk)
404
+ case 'RUN_ERROR':
405
+ this.handleRunErrorEvent(chunk)
448
406
  break
449
407
 
450
- case 'approval-requested':
451
- this.handleApprovalRequestedChunk(chunk)
408
+ case 'STEP_FINISHED':
409
+ this.handleStepFinishedEvent(chunk)
452
410
  break
453
411
 
454
- case 'tool-input-available':
455
- this.handleToolInputAvailableChunk(chunk)
412
+ case 'CUSTOM':
413
+ this.handleCustomEvent(chunk)
456
414
  break
457
415
 
458
416
  default:
459
- // Unknown chunk type - ignore
417
+ // RUN_STARTED, TEXT_MESSAGE_START, TEXT_MESSAGE_END, STEP_STARTED,
418
+ // STATE_SNAPSHOT, STATE_DELTA - no special handling needed
460
419
  break
461
420
  }
462
421
  }
463
422
 
464
423
  /**
465
- * Handle a content chunk
424
+ * Handle TEXT_MESSAGE_CONTENT event
466
425
  */
467
- private handleContentChunk(
468
- chunk: Extract<StreamChunk, { type: 'content' }>,
426
+ private handleTextMessageContentEvent(
427
+ chunk: Extract<StreamChunk, { type: 'TEXT_MESSAGE_CONTENT' }>,
469
428
  ): void {
470
429
  // Content arriving means all current tool calls are complete
471
430
  this.completeAllToolCalls()
@@ -495,7 +454,7 @@ export class StreamProcessor {
495
454
  // Prefer delta over content - delta is the incremental change
496
455
  if (chunk.delta !== '') {
497
456
  nextText = currentText + chunk.delta
498
- } else if (chunk.content !== '') {
457
+ } else if (chunk.content && chunk.content !== '') {
499
458
  // Fallback: use content if delta is not provided
500
459
  if (chunk.content.startsWith(currentText)) {
501
460
  nextText = chunk.content
@@ -512,8 +471,7 @@ export class StreamProcessor {
512
471
  this.totalTextContent += textDelta
513
472
 
514
473
  // Use delta for chunk strategy if available
515
- // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
516
- const chunkPortion = chunk.delta ?? chunk.content ?? ''
474
+ const chunkPortion = chunk.delta || chunk.content || ''
517
475
  const shouldEmit = this.chunkStrategy.shouldEmit(
518
476
  chunkPortion,
519
477
  this.currentSegmentText,
@@ -524,100 +482,75 @@ export class StreamProcessor {
524
482
  }
525
483
 
526
484
  /**
527
- * Handle a tool call chunk
485
+ * Handle TOOL_CALL_START event
528
486
  */
529
- private handleToolCallChunk(
530
- chunk: Extract<StreamChunk, { type: 'tool_call' }>,
487
+ private handleToolCallStartEvent(
488
+ chunk: Extract<StreamChunk, { type: 'TOOL_CALL_START' }>,
531
489
  ): void {
532
490
  // Mark that we've seen tool calls since the last text segment
533
491
  this.hasToolCallsSinceTextStart = true
534
492
 
535
- const toolCallId = chunk.toolCall.id
493
+ const toolCallId = chunk.toolCallId
536
494
  const existingToolCall = this.toolCalls.get(toolCallId)
537
495
 
538
496
  if (!existingToolCall) {
539
497
  // New tool call starting
540
- const initialState: ToolCallState = chunk.toolCall.function.arguments
541
- ? 'input-streaming'
542
- : 'awaiting-input'
498
+ const initialState: ToolCallState = 'awaiting-input'
543
499
 
544
500
  const newToolCall: InternalToolCallState = {
545
- id: chunk.toolCall.id,
546
- name: chunk.toolCall.function.name,
547
- arguments: chunk.toolCall.function.arguments || '',
501
+ id: chunk.toolCallId,
502
+ name: chunk.toolName,
503
+ arguments: '',
548
504
  state: initialState,
549
505
  parsedArguments: undefined,
550
- index: chunk.index,
551
- }
552
-
553
- // Try to parse the arguments
554
- if (chunk.toolCall.function.arguments) {
555
- newToolCall.parsedArguments = this.jsonParser.parse(
556
- chunk.toolCall.function.arguments,
557
- )
506
+ index: chunk.index ?? this.toolCalls.size,
558
507
  }
559
508
 
560
509
  this.toolCalls.set(toolCallId, newToolCall)
561
510
  this.toolCallOrder.push(toolCallId)
562
511
 
563
- // Get actual index for this tool call (based on order)
564
- const actualIndex = this.toolCallOrder.indexOf(toolCallId)
565
-
566
- // Emit legacy lifecycle event
567
- this.handlers.onToolCallStart?.(
568
- actualIndex,
569
- chunk.toolCall.id,
570
- chunk.toolCall.function.name,
571
- )
572
-
573
- // Emit legacy state change event
574
- this.handlers.onToolCallStateChange?.(
575
- actualIndex,
576
- chunk.toolCall.id,
577
- chunk.toolCall.function.name,
578
- initialState,
579
- chunk.toolCall.function.arguments || '',
580
- newToolCall.parsedArguments,
581
- )
582
-
583
- // Emit initial delta
584
- if (chunk.toolCall.function.arguments) {
585
- this.handlers.onToolCallDelta?.(
586
- actualIndex,
587
- chunk.toolCall.function.arguments,
588
- )
589
- }
590
-
591
512
  // Update UIMessage
592
513
  if (this.currentAssistantMessageId) {
593
514
  this.messages = updateToolCallPart(
594
515
  this.messages,
595
516
  this.currentAssistantMessageId,
596
517
  {
597
- id: chunk.toolCall.id,
598
- name: chunk.toolCall.function.name,
599
- arguments: chunk.toolCall.function.arguments || '',
518
+ id: chunk.toolCallId,
519
+ name: chunk.toolName,
520
+ arguments: '',
600
521
  state: initialState,
601
522
  },
602
523
  )
603
524
  this.emitMessagesChange()
604
525
 
605
- // Emit new granular event
526
+ // Emit granular event
606
527
  this.events.onToolCallStateChange?.(
607
528
  this.currentAssistantMessageId,
608
- chunk.toolCall.id,
529
+ chunk.toolCallId,
609
530
  initialState,
610
- chunk.toolCall.function.arguments || '',
531
+ '',
611
532
  )
612
533
  }
613
- } else {
614
- // Continuing existing tool call
534
+ }
535
+ }
536
+
537
+ /**
538
+ * Handle TOOL_CALL_ARGS event
539
+ */
540
+ private handleToolCallArgsEvent(
541
+ chunk: Extract<StreamChunk, { type: 'TOOL_CALL_ARGS' }>,
542
+ ): void {
543
+ const toolCallId = chunk.toolCallId
544
+ const existingToolCall = this.toolCalls.get(toolCallId)
545
+
546
+ if (existingToolCall) {
615
547
  const wasAwaitingInput = existingToolCall.state === 'awaiting-input'
616
548
 
617
- existingToolCall.arguments += chunk.toolCall.function.arguments || ''
549
+ // Accumulate arguments from delta
550
+ existingToolCall.arguments += chunk.delta || ''
618
551
 
619
552
  // Update state
620
- if (wasAwaitingInput && chunk.toolCall.function.arguments) {
553
+ if (wasAwaitingInput && chunk.delta) {
621
554
  existingToolCall.state = 'input-streaming'
622
555
  }
623
556
 
@@ -626,27 +559,6 @@ export class StreamProcessor {
626
559
  existingToolCall.arguments,
627
560
  )
628
561
 
629
- // Get actual index for this tool call
630
- const actualIndex = this.toolCallOrder.indexOf(toolCallId)
631
-
632
- // Emit legacy state change event
633
- this.handlers.onToolCallStateChange?.(
634
- actualIndex,
635
- existingToolCall.id,
636
- existingToolCall.name,
637
- existingToolCall.state,
638
- existingToolCall.arguments,
639
- existingToolCall.parsedArguments,
640
- )
641
-
642
- // Emit delta
643
- if (chunk.toolCall.function.arguments) {
644
- this.handlers.onToolCallDelta?.(
645
- actualIndex,
646
- chunk.toolCall.function.arguments,
647
- )
648
- }
649
-
650
562
  // Update UIMessage
651
563
  if (this.currentAssistantMessageId) {
652
564
  this.messages = updateToolCallPart(
@@ -661,7 +573,7 @@ export class StreamProcessor {
661
573
  )
662
574
  this.emitMessagesChange()
663
575
 
664
- // Emit new granular event
576
+ // Emit granular event
665
577
  this.events.onToolCallStateChange?.(
666
578
  this.currentAssistantMessageId,
667
579
  existingToolCall.id,
@@ -673,27 +585,20 @@ export class StreamProcessor {
673
585
  }
674
586
 
675
587
  /**
676
- * Handle a tool result chunk
588
+ * Handle TOOL_CALL_END event
677
589
  */
678
- private handleToolResultChunk(
679
- chunk: Extract<StreamChunk, { type: 'tool_result' }>,
590
+ private handleToolCallEndEvent(
591
+ chunk: Extract<StreamChunk, { type: 'TOOL_CALL_END' }>,
680
592
  ): void {
681
593
  const state: ToolResultState = 'complete'
682
594
 
683
- // Emit legacy handler
684
- this.handlers.onToolResultStateChange?.(
685
- chunk.toolCallId,
686
- chunk.content,
687
- state,
688
- )
689
-
690
595
  // Update UIMessage if we have a current assistant message
691
- if (this.currentAssistantMessageId) {
596
+ if (this.currentAssistantMessageId && chunk.result) {
692
597
  this.messages = updateToolResultPart(
693
598
  this.messages,
694
599
  this.currentAssistantMessageId,
695
600
  chunk.toolCallId,
696
- chunk.content,
601
+ chunk.result,
697
602
  state,
698
603
  )
699
604
  this.emitMessagesChange()
@@ -701,32 +606,31 @@ export class StreamProcessor {
701
606
  }
702
607
 
703
608
  /**
704
- * Handle a done chunk
609
+ * Handle RUN_FINISHED event
705
610
  */
706
- private handleDoneChunk(chunk: Extract<StreamChunk, { type: 'done' }>): void {
611
+ private handleRunFinishedEvent(
612
+ chunk: Extract<StreamChunk, { type: 'RUN_FINISHED' }>,
613
+ ): void {
707
614
  this.finishReason = chunk.finishReason
708
615
  this.isDone = true
709
616
  this.completeAllToolCalls()
710
617
  }
711
618
 
712
619
  /**
713
- * Handle an error chunk
620
+ * Handle RUN_ERROR event
714
621
  */
715
- private handleErrorChunk(
716
- chunk: Extract<StreamChunk, { type: 'error' }>,
622
+ private handleRunErrorEvent(
623
+ chunk: Extract<StreamChunk, { type: 'RUN_ERROR' }>,
717
624
  ): void {
718
- // Emit legacy handler
719
- this.handlers.onError?.(chunk.error)
720
-
721
- // Emit new event
722
- this.events.onError?.(new Error(chunk.error.message))
625
+ // Emit error event
626
+ this.events.onError?.(new Error(chunk.error.message || 'An error occurred'))
723
627
  }
724
628
 
725
629
  /**
726
- * Handle a thinking chunk
630
+ * Handle STEP_FINISHED event (for thinking/reasoning content)
727
631
  */
728
- private handleThinkingChunk(
729
- chunk: Extract<StreamChunk, { type: 'thinking' }>,
632
+ private handleStepFinishedEvent(
633
+ chunk: Extract<StreamChunk, { type: 'STEP_FINISHED' }>,
730
634
  ): void {
731
635
  const previous = this.thinkingContent
732
636
  let nextThinking = previous
@@ -734,7 +638,7 @@ export class StreamProcessor {
734
638
  // Prefer delta over content
735
639
  if (chunk.delta && chunk.delta !== '') {
736
640
  nextThinking = previous + chunk.delta
737
- } else if (chunk.content !== '') {
641
+ } else if (chunk.content && chunk.content !== '') {
738
642
  if (chunk.content.startsWith(previous)) {
739
643
  nextThinking = chunk.content
740
644
  } else if (previous.startsWith(chunk.content)) {
@@ -746,9 +650,6 @@ export class StreamProcessor {
746
650
 
747
651
  this.thinkingContent = nextThinking
748
652
 
749
- // Emit legacy handler
750
- this.handlers.onThinkingUpdate?.(this.thinkingContent)
751
-
752
653
  // Update UIMessage
753
654
  if (this.currentAssistantMessageId) {
754
655
  this.messages = updateThinkingPart(
@@ -758,7 +659,7 @@ export class StreamProcessor {
758
659
  )
759
660
  this.emitMessagesChange()
760
661
 
761
- // Emit new granular event
662
+ // Emit granular event
762
663
  this.events.onThinkingUpdate?.(
763
664
  this.currentAssistantMessageId,
764
665
  this.thinkingContent,
@@ -767,69 +668,68 @@ export class StreamProcessor {
767
668
  }
768
669
 
769
670
  /**
770
- * Handle an approval-requested chunk
671
+ * Handle CUSTOM event
672
+ * Handles special custom events like 'tool-input-available' for client-side tool execution
673
+ * and 'approval-requested' for tool approval flows
771
674
  */
772
- private handleApprovalRequestedChunk(
773
- chunk: Extract<StreamChunk, { type: 'approval-requested' }>,
675
+ private handleCustomEvent(
676
+ chunk: Extract<StreamChunk, { type: 'CUSTOM' }>,
774
677
  ): void {
775
- // Emit legacy handler
776
- this.handlers.onApprovalRequested?.(
777
- chunk.toolCallId,
778
- chunk.toolName,
779
- chunk.input,
780
- chunk.approval.id,
781
- )
678
+ // Handle client tool input availability - trigger client-side execution
679
+ if (chunk.name === 'tool-input-available' && chunk.data) {
680
+ const { toolCallId, toolName, input } = chunk.data as {
681
+ toolCallId: string
682
+ toolName: string
683
+ input: any
684
+ }
782
685
 
783
- // Update UIMessage with approval metadata
784
- if (this.currentAssistantMessageId) {
785
- this.messages = updateToolCallApproval(
786
- this.messages,
787
- this.currentAssistantMessageId,
788
- chunk.toolCallId,
789
- chunk.approval.id,
790
- )
791
- this.emitMessagesChange()
686
+ // Emit onToolCall event for the client to execute the tool
687
+ this.events.onToolCall?.({
688
+ toolCallId,
689
+ toolName,
690
+ input,
691
+ })
792
692
  }
793
693
 
794
- // Emit new event
795
- this.events.onApprovalRequest?.({
796
- toolCallId: chunk.toolCallId,
797
- toolName: chunk.toolName,
798
- input: chunk.input,
799
- approvalId: chunk.approval.id,
800
- })
801
- }
694
+ // Handle approval requests
695
+ if (chunk.name === 'approval-requested' && chunk.data) {
696
+ const { toolCallId, toolName, input, approval } = chunk.data as {
697
+ toolCallId: string
698
+ toolName: string
699
+ input: any
700
+ approval: { id: string; needsApproval: boolean }
701
+ }
802
702
 
803
- /**
804
- * Handle a tool-input-available chunk
805
- */
806
- private handleToolInputAvailableChunk(
807
- chunk: Extract<StreamChunk, { type: 'tool-input-available' }>,
808
- ): void {
809
- // Emit legacy handler
810
- this.handlers.onToolInputAvailable?.(
811
- chunk.toolCallId,
812
- chunk.toolName,
813
- chunk.input,
814
- )
703
+ // Update the tool call part with approval state
704
+ if (this.currentAssistantMessageId) {
705
+ this.messages = updateToolCallApproval(
706
+ this.messages,
707
+ this.currentAssistantMessageId,
708
+ toolCallId,
709
+ approval.id,
710
+ )
711
+ this.emitMessagesChange()
712
+ }
815
713
 
816
- // Emit new event
817
- this.events.onToolCall?.({
818
- toolCallId: chunk.toolCallId,
819
- toolName: chunk.toolName,
820
- input: chunk.input,
821
- })
714
+ // Emit approval request event
715
+ this.events.onApprovalRequest?.({
716
+ toolCallId,
717
+ toolName,
718
+ input,
719
+ approvalId: approval.id,
720
+ })
721
+ }
822
722
  }
823
723
 
824
724
  /**
825
725
  * Detect if an incoming content chunk represents a NEW text segment
826
726
  */
827
727
  private isNewTextSegment(
828
- chunk: Extract<StreamChunk, { type: 'content' }>,
728
+ chunk: Extract<StreamChunk, { type: 'TEXT_MESSAGE_CONTENT' }>,
829
729
  previous: string,
830
730
  ): boolean {
831
- // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
832
- if (chunk.delta !== undefined && chunk.content !== undefined) {
731
+ // Check if content is present (delta is always defined but may be empty string)
732
+ if (chunk.content !== undefined) {
833
733
  if (chunk.content.length < previous.length) {
834
734
  return true
835
735
  }
@@ -859,7 +759,7 @@ export class StreamProcessor {
859
759
  * Mark a tool call as complete and emit event
860
760
  */
861
761
  private completeToolCall(
862
- index: number,
762
+ _index: number,
863
763
  toolCall: InternalToolCallState,
864
764
  ): void {
865
765
  toolCall.state = 'input-complete'
@@ -867,24 +767,6 @@ export class StreamProcessor {
867
767
  // Try final parse
868
768
  toolCall.parsedArguments = this.jsonParser.parse(toolCall.arguments)
869
769
 
870
- // Emit legacy state change event
871
- this.handlers.onToolCallStateChange?.(
872
- index,
873
- toolCall.id,
874
- toolCall.name,
875
- 'input-complete',
876
- toolCall.arguments,
877
- toolCall.parsedArguments,
878
- )
879
-
880
- // Emit legacy complete event
881
- this.handlers.onToolCallComplete?.(
882
- index,
883
- toolCall.id,
884
- toolCall.name,
885
- toolCall.arguments,
886
- )
887
-
888
770
  // Update UIMessage
889
771
  if (this.currentAssistantMessageId) {
890
772
  this.messages = updateToolCallPart(
@@ -899,7 +781,7 @@ export class StreamProcessor {
899
781
  )
900
782
  this.emitMessagesChange()
901
783
 
902
- // Emit new granular event
784
+ // Emit granular event
903
785
  this.events.onToolCallStateChange?.(
904
786
  this.currentAssistantMessageId,
905
787
  toolCall.id,
@@ -915,9 +797,6 @@ export class StreamProcessor {
915
797
  private emitTextUpdate(): void {
916
798
  this.lastEmittedText = this.currentSegmentText
917
799
 
918
- // Emit legacy handler
919
- this.handlers.onTextUpdate?.(this.currentSegmentText)
920
-
921
800
  // Update UIMessage
922
801
  if (this.currentAssistantMessageId) {
923
802
  this.messages = updateTextPart(
@@ -927,7 +806,7 @@ export class StreamProcessor {
927
806
  )
928
807
  this.emitMessagesChange()
929
808
 
930
- // Emit new granular event
809
+ // Emit granular event
931
810
  this.events.onTextUpdate?.(
932
811
  this.currentAssistantMessageId,
933
812
  this.currentSegmentText,
@@ -954,14 +833,7 @@ export class StreamProcessor {
954
833
  this.emitTextUpdate()
955
834
  }
956
835
 
957
- // Emit legacy stream end with total accumulated content
958
- const toolCalls = this.getCompletedToolCalls()
959
- this.handlers.onStreamEnd?.(
960
- this.totalTextContent,
961
- toolCalls.length > 0 ? toolCalls : undefined,
962
- )
963
-
964
- // Emit new stream end event
836
+ // Emit stream end event
965
837
  if (this.currentAssistantMessageId) {
966
838
  const assistantMessage = this.messages.find(
967
839
  (m) => m.id === this.currentAssistantMessageId,
@@ -1002,7 +874,7 @@ export class StreamProcessor {
1002
874
  }
1003
875
 
1004
876
  /**
1005
- * Get current processor state (legacy)
877
+ * Get current processor state
1006
878
  */
1007
879
  getState(): ProcessorState {
1008
880
  return {