@tanstack/ai 0.57.0 → 0.59.0

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 (95) hide show
  1. package/dist/esm/activities/chat/agents/define-agent.d.ts +81 -0
  2. package/dist/esm/activities/chat/agents/define-agent.js +34 -0
  3. package/dist/esm/activities/chat/agents/define-agent.js.map +1 -0
  4. package/dist/esm/activities/chat/agents/route.d.ts +53 -0
  5. package/dist/esm/activities/chat/agents/route.js +59 -0
  6. package/dist/esm/activities/chat/agents/route.js.map +1 -0
  7. package/dist/esm/activities/chat/agents/spawn.d.ts +124 -0
  8. package/dist/esm/activities/chat/agents/spawn.js +490 -0
  9. package/dist/esm/activities/chat/agents/spawn.js.map +1 -0
  10. package/dist/esm/activities/chat/agents/turn.d.ts +36 -0
  11. package/dist/esm/activities/chat/agents/turn.js +78 -0
  12. package/dist/esm/activities/chat/agents/turn.js.map +1 -0
  13. package/dist/esm/activities/chat/index.d.ts +13 -3
  14. package/dist/esm/activities/chat/index.js +345 -19
  15. package/dist/esm/activities/chat/index.js.map +1 -1
  16. package/dist/esm/activities/chat/messages.d.ts +7 -1
  17. package/dist/esm/activities/chat/messages.js +94 -18
  18. package/dist/esm/activities/chat/messages.js.map +1 -1
  19. package/dist/esm/activities/chat/middleware/run-store.d.ts +43 -7
  20. package/dist/esm/activities/chat/middleware/run-store.js +8 -1
  21. package/dist/esm/activities/chat/middleware/run-store.js.map +1 -1
  22. package/dist/esm/activities/chat/middleware/types.d.ts +50 -3
  23. package/dist/esm/activities/chat/middleware/types.js.map +1 -1
  24. package/dist/esm/activities/chat/stream/processor.d.ts +46 -0
  25. package/dist/esm/activities/chat/stream/processor.js +280 -7
  26. package/dist/esm/activities/chat/stream/processor.js.map +1 -1
  27. package/dist/esm/activities/chat/tools/tool-calls.d.ts +17 -3
  28. package/dist/esm/activities/chat/tools/tool-calls.js +56 -7
  29. package/dist/esm/activities/chat/tools/tool-calls.js.map +1 -1
  30. package/dist/esm/activities/generateAudio/index.js +1 -1
  31. package/dist/esm/activities/generateImage/index.js +1 -1
  32. package/dist/esm/activities/generateLiveVideo/index.js +1 -1
  33. package/dist/esm/activities/generateSpeech/index.js +1 -1
  34. package/dist/esm/activities/generateTranscription/index.js +1 -1
  35. package/dist/esm/activities/generateVoice/index.js +1 -1
  36. package/dist/esm/activities/generateWorld/index.js +1 -1
  37. package/dist/esm/activities/index.d.ts +3 -0
  38. package/dist/esm/activities/index.js +5 -3
  39. package/dist/esm/activities/summarize/index.js +1 -1
  40. package/dist/esm/client.d.ts +5 -36
  41. package/dist/esm/client.js +4 -37
  42. package/dist/esm/client.js.map +1 -1
  43. package/dist/esm/index.d.ts +5 -0
  44. package/dist/esm/index.js +7 -4
  45. package/dist/esm/middlewares/content-guard.js.map +1 -1
  46. package/dist/esm/stream-to-response.js +12 -6
  47. package/dist/esm/stream-to-response.js.map +1 -1
  48. package/dist/esm/strip-to-spec-middleware.js +2 -1
  49. package/dist/esm/strip-to-spec-middleware.js.map +1 -1
  50. package/dist/esm/types.d.ts +128 -89
  51. package/dist/esm/utilities/adapter-yield-chunk.d.ts +5 -1
  52. package/dist/esm/utilities/ag-ui-usage.d.ts +9 -9
  53. package/dist/esm/utilities/ag-ui-usage.js +66 -3
  54. package/dist/esm/utilities/ag-ui-usage.js.map +1 -1
  55. package/dist/esm/utilities/ag-ui-wire.js +56 -0
  56. package/dist/esm/utilities/ag-ui-wire.js.map +1 -1
  57. package/dist/esm/utilities/durability-batch.d.ts +8 -0
  58. package/dist/esm/utilities/durability-batch.js +45 -0
  59. package/dist/esm/utilities/durability-batch.js.map +1 -0
  60. package/dist/esm/utilities/normalize-stream-chunk.js +7 -2
  61. package/dist/esm/utilities/normalize-stream-chunk.js.map +1 -1
  62. package/dist/esm/utilities/spec-event-keys.js +13 -8
  63. package/dist/esm/utilities/spec-event-keys.js.map +1 -1
  64. package/dist/esm/utilities/subagent-wire.d.ts +36 -0
  65. package/dist/esm/utilities/subagent-wire.js +131 -0
  66. package/dist/esm/utilities/subagent-wire.js.map +1 -0
  67. package/package.json +4 -4
  68. package/skills/ai-core/adapter-configuration/references/grok-adapter.md +1 -1
  69. package/skills/ai-core/media-generation/SKILL.md +2 -2
  70. package/skills/ai-core/middleware/SKILL.md +7 -4
  71. package/src/activities/chat/agents/define-agent.ts +121 -0
  72. package/src/activities/chat/agents/route.ts +115 -0
  73. package/src/activities/chat/agents/spawn.ts +806 -0
  74. package/src/activities/chat/agents/turn.ts +151 -0
  75. package/src/activities/chat/index.ts +540 -16
  76. package/src/activities/chat/messages.ts +125 -15
  77. package/src/activities/chat/middleware/run-store.ts +56 -7
  78. package/src/activities/chat/middleware/types.ts +55 -2
  79. package/src/activities/chat/stream/processor.ts +428 -8
  80. package/src/activities/chat/tools/tool-calls.ts +92 -16
  81. package/src/activities/index.ts +15 -0
  82. package/src/client.ts +22 -35
  83. package/src/index.ts +23 -0
  84. package/src/middlewares/content-guard.ts +7 -5
  85. package/src/stream-to-response.ts +16 -6
  86. package/src/strip-to-spec-middleware.ts +2 -1
  87. package/src/types.ts +177 -95
  88. package/src/utilities/adapter-yield-chunk.ts +10 -2
  89. package/src/utilities/ag-ui-usage.test.ts +38 -0
  90. package/src/utilities/ag-ui-usage.ts +98 -11
  91. package/src/utilities/ag-ui-wire.ts +74 -0
  92. package/src/utilities/durability-batch.ts +48 -0
  93. package/src/utilities/normalize-stream-chunk.ts +10 -2
  94. package/src/utilities/spec-event-keys.ts +34 -7
  95. package/src/utilities/subagent-wire.ts +184 -0
@@ -1,9 +1,11 @@
1
+ import { EventType } from "../../../types.js";
1
2
  import { mergeMetadata, tanstackMetadata } from "../../../utilities/merge-metadata.js";
2
3
  import { runErrorEventToError } from "../../../utilities/errors.js";
3
4
  import { INTERRUPT_BINDING_METADATA_KEY } from "../../../interrupt-resume.js";
4
5
  import { normalizeToolResult } from "../../../utilities/tool-result.js";
6
+ import { splitSubagentWire } from "../../../utilities/subagent-wire.js";
5
7
  import { isProviderExecutedToolCall } from "../../../utilities/provider-executed.js";
6
- import { aguiSnapshotMessageToUIMessage, coerceCreatedAt, generateMessageId, uiMessageToModelMessages } from "../messages.js";
8
+ import { aguiContentToContentParts, aguiSnapshotMessageToUIMessage, coerceCreatedAt, generateMessageId, uiMessageToModelMessages } from "../messages.js";
7
9
  import { getChunkRunId } from "../../../utilities/chunk-ids.js";
8
10
  import { defaultJSONParser } from "./json-parser.js";
9
11
  import { appendStructuredOutputDelta, completeStructuredOutputPart, errorStructuredOutputPart, updateTextPart, updateThinkingPart, updateToolCallApproval, updateToolCallApprovalResponse, updateToolCallPart, updateToolCallWithOutput, updateToolResultPart } from "./message-updaters.js";
@@ -67,13 +69,17 @@ var StreamProcessor = class StreamProcessor {
67
69
  structuredMessageIds = /* @__PURE__ */ new Set();
68
70
  structuredOutputUpdateBatches = /* @__PURE__ */ new Map();
69
71
  activeRuns = /* @__PURE__ */ new Set();
72
+ childProcessors = /* @__PURE__ */ new Map();
73
+ childToolCalls = /* @__PURE__ */ new Map();
70
74
  finishReason = null;
71
75
  hasError = false;
72
76
  isDone = false;
73
77
  streamEndEmitted = false;
74
78
  recording = null;
75
79
  recordingStartTime = 0;
80
+ subagentRunId;
76
81
  constructor(options = {}) {
82
+ this.subagentRunId = options.subagentRunId;
77
83
  this.chunkStrategy = options.chunkStrategy || new ImmediateStrategy();
78
84
  this.events = options.events || {};
79
85
  this.jsonParser = options.jsonParser || defaultJSONParser;
@@ -192,6 +198,11 @@ var StreamProcessor = class StreamProcessor {
192
198
  addToolResult(toolCallId, output, error) {
193
199
  const messageWithToolCall = this.messages.find((msg) => msg.parts.some((p) => p.type === "tool-call" && p.id === toolCallId));
194
200
  if (!messageWithToolCall) {
201
+ const owner = this.childOwningToolCall((part) => part.id === toolCallId);
202
+ if (owner !== void 0) {
203
+ this.updateChild(owner, (child) => child.addToolResult(toolCallId, output, error));
204
+ return;
205
+ }
195
206
  console.warn(`[StreamProcessor] Could not find message with tool call ${toolCallId}`);
196
207
  return;
197
208
  }
@@ -206,6 +217,11 @@ var StreamProcessor = class StreamProcessor {
206
217
  * Add an approval response (called by client after handling onApprovalRequest)
207
218
  */
208
219
  addToolApprovalResponse(approvalId, approved) {
220
+ const owner = this.childOwningToolCall((part) => part.approval?.id === approvalId);
221
+ if (owner !== void 0) {
222
+ this.updateChild(owner, (child) => child.addToolApprovalResponse(approvalId, approved));
223
+ return;
224
+ }
209
225
  this.messages = updateToolCallApprovalResponse(this.messages, approvalId, approved);
210
226
  this.emitMessagesChange();
211
227
  }
@@ -259,6 +275,8 @@ var StreamProcessor = class StreamProcessor {
259
275
  this.structuredMessageIds.clear();
260
276
  this.structuredOutputUpdateBatches.clear();
261
277
  this.pendingManualMessageId = null;
278
+ this.childProcessors.clear();
279
+ this.childToolCalls.clear();
262
280
  this.emitMessagesChange();
263
281
  }
264
282
  /**
@@ -287,7 +305,17 @@ var StreamProcessor = class StreamProcessor {
287
305
  timestamp: Date.now(),
288
306
  index: this.recording.chunks.length
289
307
  });
308
+ if (this.routeToChild(chunk)) return;
290
309
  switch (chunk.type) {
310
+ case "SUBAGENT_STARTED":
311
+ this.handleSubagentStartedEvent(chunk);
312
+ break;
313
+ case "SUBAGENT_FINISHED":
314
+ this.handleSubagentFinishedEvent(chunk);
315
+ break;
316
+ case "SUBAGENT_ERROR":
317
+ this.handleSubagentErrorEvent(chunk);
318
+ break;
291
319
  case "TEXT_MESSAGE_START":
292
320
  this.handleTextMessageStartEvent(chunk);
293
321
  break;
@@ -497,6 +525,164 @@ var StreamProcessor = class StreamProcessor {
497
525
  } : msg);
498
526
  this.emitMessagesChange();
499
527
  }
528
+ findSubagentPart(subagentRunId) {
529
+ for (const message of this.messages) for (const part of message.parts) if (part.type === "subagent" && part.subagent.id === subagentRunId) return {
530
+ message,
531
+ part
532
+ };
533
+ }
534
+ /**
535
+ * The direct child whose card holds `id`: the child itself, or a nested
536
+ * child inside its messages.
537
+ */
538
+ childOwning(id) {
539
+ const holds = (messages) => messages.some((message) => message.parts.some((part) => part.type === "subagent" && (part.subagent.id === id || holds(part.subagent.messages))));
540
+ for (const message of this.messages) for (const part of message.parts) {
541
+ if (part.type !== "subagent") continue;
542
+ if (part.subagent.id === id || holds(part.subagent.messages)) return part.subagent.id;
543
+ }
544
+ }
545
+ /** The direct child whose messages hold this tool call. */
546
+ childOwningToolCall(matches) {
547
+ const holds = (messages) => messages.some((message) => message.parts.some((part) => part.type === "tool-call" && matches(part) || part.type === "subagent" && holds(part.subagent.messages)));
548
+ for (const message of this.messages) for (const part of message.parts) if (part.type === "subagent" && holds(part.subagent.messages)) return part.subagent.id;
549
+ }
550
+ /**
551
+ * The processor for a direct child. The card's messages are the source of
552
+ * truth. The processor re-reads them when something else replaced them.
553
+ */
554
+ childProcessor(subagentRunId) {
555
+ const found = this.findSubagentPart(subagentRunId);
556
+ if (!found) return void 0;
557
+ const messages = found.part.subagent.messages;
558
+ const existing = this.childProcessors.get(subagentRunId);
559
+ if (existing) {
560
+ if (existing.getMessages() !== messages) existing.messages = messages;
561
+ return existing;
562
+ }
563
+ const child = new StreamProcessor({
564
+ subagentRunId,
565
+ initialMessages: messages,
566
+ events: {
567
+ onToolCall: (args) => this.events.onToolCall?.(args),
568
+ onApprovalRequest: (args) => this.events.onApprovalRequest?.(args),
569
+ onCustomEvent: (name, value, context) => this.events.onCustomEvent?.(name, value, context)
570
+ }
571
+ });
572
+ this.childProcessors.set(subagentRunId, child);
573
+ return child;
574
+ }
575
+ /** Run `update` on a direct child, then copy its messages onto the card. */
576
+ updateChild(subagentRunId, update) {
577
+ const child = this.childProcessor(subagentRunId);
578
+ if (!child) return;
579
+ update(child);
580
+ const messages = child.getMessages();
581
+ this.patchSubagent(subagentRunId, (subagent) => {
582
+ subagent.messages = messages;
583
+ });
584
+ }
585
+ handleSubagentStartedEvent(chunk) {
586
+ if (chunk.type !== "SUBAGENT_STARTED") return;
587
+ if (this.findSubagentPart(chunk.subagentRunId)) {
588
+ this.childProcessors.delete(chunk.subagentRunId);
589
+ this.patchSubagent(chunk.subagentRunId, (subagent) => {
590
+ subagent.status = "running";
591
+ delete subagent.interruptIds;
592
+ delete subagent.error;
593
+ if (chunk.metadata !== void 0) subagent.metadata = chunk.metadata;
594
+ });
595
+ return;
596
+ }
597
+ const { messageId } = this.ensureAssistantMessage();
598
+ const part = {
599
+ type: "subagent",
600
+ subagent: {
601
+ id: chunk.subagentRunId,
602
+ name: chunk.name,
603
+ ...chunk.description !== void 0 && { description: chunk.description },
604
+ status: "running",
605
+ ...chunk.parentSubagentRunId !== void 0 && { parentSubagentRunId: chunk.parentSubagentRunId },
606
+ ...chunk.parentToolCallId !== void 0 && { parentToolCallId: chunk.parentToolCallId },
607
+ ...chunk.metadata !== void 0 && { metadata: chunk.metadata },
608
+ messages: []
609
+ }
610
+ };
611
+ this.messages = this.messages.map((message) => message.id === messageId ? {
612
+ ...message,
613
+ parts: [...message.parts, part]
614
+ } : message);
615
+ this.emitMessagesChange();
616
+ }
617
+ handleSubagentFinishedEvent(chunk) {
618
+ if (chunk.type !== "SUBAGENT_FINISHED") return;
619
+ this.updateChild(chunk.subagentRunId, (child) => child.finalizeStream());
620
+ this.patchSubagent(chunk.subagentRunId, (subagent) => {
621
+ if (subagent.status === "error") return;
622
+ if (chunk.outcome?.type === "suspended") {
623
+ subagent.status = "suspended";
624
+ subagent.interruptIds = chunk.outcome.interruptIds ?? [];
625
+ return;
626
+ }
627
+ subagent.status = "finished";
628
+ });
629
+ }
630
+ handleSubagentErrorEvent(chunk) {
631
+ if (chunk.type !== "SUBAGENT_ERROR") return;
632
+ this.updateChild(chunk.subagentRunId, (child) => child.finalizeStream());
633
+ this.patchSubagent(chunk.subagentRunId, (subagent) => {
634
+ subagent.status = "error";
635
+ subagent.error = {
636
+ message: chunk.message,
637
+ ...chunk.code !== void 0 && { code: chunk.code }
638
+ };
639
+ });
640
+ }
641
+ patchSubagent(subagentRunId, patch) {
642
+ this.messages = this.messages.map((message) => {
643
+ const index = message.parts.findIndex((part) => part.type === "subagent" && part.subagent.id === subagentRunId);
644
+ if (index === -1) return message;
645
+ const part = message.parts[index];
646
+ if (!part || part.type !== "subagent") return message;
647
+ const next = {
648
+ type: "subagent",
649
+ subagent: { ...part.subagent }
650
+ };
651
+ patch(next.subagent);
652
+ const parts = [...message.parts];
653
+ parts[index] = next;
654
+ return {
655
+ ...message,
656
+ parts
657
+ };
658
+ });
659
+ this.emitMessagesChange();
660
+ }
661
+ /**
662
+ * Send a child's chunk to that child's processor, so the card keeps text,
663
+ * reasoning, tool calls, results, and nested children. A tool event without
664
+ * `subagentRunId` follows its `TOOL_CALL_START`.
665
+ */
666
+ routeToChild(chunk) {
667
+ let id;
668
+ if (chunk.type === "SUBAGENT_STARTED") id = chunk.parentSubagentRunId;
669
+ else if (chunk.type === "SUBAGENT_FINISHED" || chunk.type === "SUBAGENT_ERROR") {
670
+ if (this.findSubagentPart(chunk.subagentRunId)) return false;
671
+ id = chunk.subagentRunId;
672
+ } else if ("subagentRunId" in chunk && chunk.subagentRunId) id = chunk.subagentRunId;
673
+ else if ("toolCallId" in chunk && typeof chunk.toolCallId === "string") id = this.childToolCalls.get(chunk.toolCallId);
674
+ if (id === void 0) return false;
675
+ const owner = this.childOwning(id);
676
+ if (owner === void 0) {
677
+ if (id === this.subagentRunId) return false;
678
+ console.warn(`[StreamProcessor] Dropped a chunk for unknown subagent ${id}`);
679
+ return true;
680
+ }
681
+ if (this.findSubagentPart(owner)?.part.subagent.status === "error") return true;
682
+ if (chunk.type === "TOOL_CALL_START") this.childToolCalls.set(chunk.toolCallId, owner);
683
+ this.updateChild(owner, (child) => child.processChunk(chunk));
684
+ return true;
685
+ }
500
686
  /**
501
687
  * Handle TEXT_MESSAGE_START event
502
688
  */
@@ -579,8 +765,9 @@ var StreamProcessor = class StreamProcessor {
579
765
  this.resetStreamState();
580
766
  const prevMessages = this.messages;
581
767
  const prevById = new Map(prevMessages.map((msg) => [msg.id, msg]));
582
- const normalized = this.mergeReasoningFanOut(chunk.messages.map(aguiSnapshotMessageToUIMessage));
583
- this.messages = this.reconcileSnapshotToolCalls(normalized, prevMessages).map((msg) => {
768
+ const { top, groups } = splitSubagentWire(chunk.messages);
769
+ const normalized = this.mergeReasoningFanOut(top.map(aguiSnapshotMessageToUIMessage));
770
+ const reconciled = this.reconcileSnapshotToolCalls(normalized, prevMessages).map((msg) => {
584
771
  if (msg.metadata != null) return msg;
585
772
  const prev = prevById.get(msg.id);
586
773
  if (prev?.metadata == null) return msg;
@@ -589,9 +776,76 @@ var StreamProcessor = class StreamProcessor {
589
776
  metadata: prev.metadata
590
777
  };
591
778
  });
779
+ this.messages = this.attachSnapshotSubagents(reconciled, top, groups, prevMessages);
592
780
  this.emitMessagesChange();
593
781
  }
594
782
  /**
783
+ * Put subagent cards back on a snapshot. Child wire messages (tagged with
784
+ * `subagentRunId`) become a card on the nearest assistant message before
785
+ * them, or a new assistant message when there is none. A card that the
786
+ * snapshot does not carry stays on its message, because a server snapshot
787
+ * can hold the parent history only.
788
+ */
789
+ attachSnapshotSubagents(messages, top, groups, prevMessages) {
790
+ const cards = [];
791
+ const children = /* @__PURE__ */ new Map();
792
+ for (const group of groups) {
793
+ const child = new StreamProcessor({ subagentRunId: group.id });
794
+ child.processChunk({
795
+ type: EventType.MESSAGES_SNAPSHOT,
796
+ messages: group.messages,
797
+ timestamp: Date.now()
798
+ });
799
+ children.set(group.id, child);
800
+ const { placeholder: _placeholder, ...info } = group.info;
801
+ cards.push({
802
+ hostId: top.slice(0, group.hostIndex + 1).findLast((message) => message.role === "assistant")?.id,
803
+ part: {
804
+ type: "subagent",
805
+ subagent: {
806
+ ...info,
807
+ id: group.id,
808
+ messages: child.getMessages()
809
+ }
810
+ },
811
+ carried: false
812
+ });
813
+ }
814
+ const inSnapshot = new Set(groups.map((group) => group.id));
815
+ for (const message of prevMessages) for (const part of message.parts) {
816
+ if (part.type !== "subagent" || inSnapshot.has(part.subagent.id)) continue;
817
+ cards.push({
818
+ hostId: message.id,
819
+ ...part.subagent.parentToolCallId !== void 0 && { toolCallId: part.subagent.parentToolCallId },
820
+ part,
821
+ carried: true
822
+ });
823
+ const kept = this.childProcessors.get(part.subagent.id);
824
+ if (kept) children.set(part.subagent.id, kept);
825
+ }
826
+ this.childProcessors.clear();
827
+ for (const [id, child] of children) this.childProcessors.set(id, child);
828
+ let out = messages;
829
+ for (const card of cards) {
830
+ let index = out.findIndex((message) => message.id === card.hostId);
831
+ if (index === -1 && card.toolCallId !== void 0) index = out.findIndex((message) => message.parts.some((part) => part.type === "tool-call" && part.id === card.toolCallId));
832
+ if (index === -1 && !card.carried) index = out.findLastIndex((message) => message.role === "assistant");
833
+ if (index === -1) {
834
+ if (!card.carried) out = [...out, {
835
+ id: generateMessageId(),
836
+ role: "assistant",
837
+ parts: [card.part]
838
+ }];
839
+ continue;
840
+ }
841
+ out = out.map((message, position) => position !== index || message.parts.some((part) => part.type === "subagent" && part.subagent.id === card.part.subagent.id) ? message : {
842
+ ...message,
843
+ parts: [...message.parts, card.part]
844
+ });
845
+ }
846
+ return out;
847
+ }
848
+ /**
595
849
  * Reconcile a freshly normalized snapshot with the pre-snapshot message
596
850
  * state so unreconstructable tool-call metadata is preserved.
597
851
  *
@@ -901,13 +1155,13 @@ var StreamProcessor = class StreamProcessor {
901
1155
  const isOutputError = chunk.state === "output-error" || tanstackMetadata(chunk)?.state === "output-error";
902
1156
  let output;
903
1157
  try {
904
- output = JSON.parse(chunk.content);
1158
+ output = typeof chunk.content === "string" ? JSON.parse(chunk.content) : chunk.content;
905
1159
  } catch {
906
1160
  output = chunk.content;
907
1161
  }
908
1162
  this.messages = updateToolCallWithOutput(this.messages, chunk.toolCallId, output, isOutputError ? "error" : void 0);
909
1163
  const resultState = isOutputError ? "error" : "complete";
910
- this.messages = updateToolResultPart(this.messages, messageId, chunk.toolCallId, chunk.content, resultState, resultState === "error" ? this.extractToolResultError(output) : void 0);
1164
+ this.messages = updateToolResultPart(this.messages, messageId, chunk.toolCallId, aguiContentToContentParts(chunk.content), resultState, resultState === "error" ? this.extractToolResultError(output) : void 0);
911
1165
  this.emitMessagesChange();
912
1166
  }
913
1167
  /**
@@ -941,21 +1195,38 @@ var StreamProcessor = class StreamProcessor {
941
1195
  this.finalizeStream();
942
1196
  }
943
1197
  }
944
- handleInterrupts(interrupts) {
1198
+ /**
1199
+ * Apply interrupt state to tool-call parts, then fire the client events.
1200
+ * A child's interrupt updates the child's card. `emit` is false there, so
1201
+ * the events fire once, from the top processor.
1202
+ */
1203
+ handleInterrupts(interrupts, emit = true) {
945
1204
  const hasGeneric = interruptBatchHasGeneric(interrupts);
946
1205
  for (const interrupt of interrupts) {
947
1206
  const metadata = interrupt.metadata && typeof interrupt.metadata === "object" ? interrupt.metadata : {};
948
1207
  const kind = typeof metadata.kind === "string" ? metadata.kind : void 0;
949
1208
  const toolCallId = interrupt.toolCallId;
950
1209
  if (!toolCallId) continue;
1210
+ const owner = interrupt.subagentRunId !== void 0 ? this.childOwning(interrupt.subagentRunId) : this.childOwningToolCall((part) => part.id === toolCallId);
1211
+ if (owner !== void 0) this.updateChild(owner, (child) => child.handleInterrupts([interrupt], false));
951
1212
  const toolName = typeof metadata.toolName === "string" ? metadata.toolName : this.findToolCallName(toolCallId);
952
1213
  const input = Object.hasOwn(metadata, "input") ? metadata.input : {};
953
1214
  if (kind === "approval" || interrupt.reason === "approval_required") {
1215
+ if (owner !== void 0) {
1216
+ if (emit) this.events.onApprovalRequest?.({
1217
+ toolCallId,
1218
+ toolName,
1219
+ input,
1220
+ approvalId: interrupt.id
1221
+ });
1222
+ continue;
1223
+ }
954
1224
  const resolvedMessageId = this.getActiveAssistantMessageId() ?? this.toolCallToMessage.get(toolCallId) ?? this.messages.find((m) => m.role === "assistant" && m.parts.some((p) => p.type === "tool-call" && p.id === toolCallId))?.id;
955
1225
  if (resolvedMessageId) {
956
1226
  this.messages = updateToolCallApproval(this.messages, resolvedMessageId, toolCallId, interrupt.id);
957
1227
  this.emitMessagesChange();
958
1228
  }
1229
+ if (!emit) continue;
959
1230
  this.events.onApprovalRequest?.({
960
1231
  toolCallId,
961
1232
  toolName,
@@ -965,7 +1236,7 @@ var StreamProcessor = class StreamProcessor {
965
1236
  continue;
966
1237
  }
967
1238
  if (kind === "client_tool" || interrupt.reason === "client_tool_input") {
968
- if (hasGeneric) continue;
1239
+ if (hasGeneric || !emit) continue;
969
1240
  this.events.onToolCall?.({
970
1241
  toolCallId,
971
1242
  toolName,
@@ -1473,6 +1744,8 @@ var StreamProcessor = class StreamProcessor {
1473
1744
  reset() {
1474
1745
  this.resetStreamState();
1475
1746
  this.messages = [];
1747
+ this.childProcessors.clear();
1748
+ this.childToolCalls.clear();
1476
1749
  }
1477
1750
  /**
1478
1751
  * Check if a message contains only whitespace text and no other meaningful parts