@assistant-ui/react-ag-ui 0.0.57 → 0.0.59

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 (46) hide show
  1. package/README.md +16 -0
  2. package/dist/index.d.ts +2 -2
  3. package/dist/runtime/AgUiThreadRuntimeCore.d.ts +19 -8
  4. package/dist/runtime/AgUiThreadRuntimeCore.d.ts.map +1 -1
  5. package/dist/runtime/AgUiThreadRuntimeCore.js +118 -86
  6. package/dist/runtime/AgUiThreadRuntimeCore.js.map +1 -1
  7. package/dist/runtime/adapter/conversions.d.ts.map +1 -1
  8. package/dist/runtime/adapter/conversions.js +34 -11
  9. package/dist/runtime/adapter/conversions.js.map +1 -1
  10. package/dist/runtime/adapter/run-aggregator.d.ts +13 -3
  11. package/dist/runtime/adapter/run-aggregator.d.ts.map +1 -1
  12. package/dist/runtime/adapter/run-aggregator.js +385 -119
  13. package/dist/runtime/adapter/run-aggregator.js.map +1 -1
  14. package/dist/runtime/adapter/subscriber.d.ts +9 -0
  15. package/dist/runtime/adapter/subscriber.d.ts.map +1 -1
  16. package/dist/runtime/adapter/subscriber.js +4 -1
  17. package/dist/runtime/adapter/subscriber.js.map +1 -1
  18. package/dist/runtime/adapter/tool-approval.d.ts +3 -1
  19. package/dist/runtime/adapter/tool-approval.d.ts.map +1 -1
  20. package/dist/runtime/adapter/tool-approval.js +11 -12
  21. package/dist/runtime/adapter/tool-approval.js.map +1 -1
  22. package/dist/runtime/event-parser.d.ts.map +1 -1
  23. package/dist/runtime/event-parser.js +176 -45
  24. package/dist/runtime/event-parser.js.map +1 -1
  25. package/dist/runtime/types.d.ts +67 -1
  26. package/dist/runtime/types.d.ts.map +1 -1
  27. package/dist/useAgUiRuntime.d.ts.map +1 -1
  28. package/dist/useAgUiRuntime.js +15 -5
  29. package/dist/useAgUiRuntime.js.map +1 -1
  30. package/package.json +9 -9
  31. package/src/index.ts +1 -0
  32. package/src/runtime/AgUiThreadRuntimeCore.test.ts +18 -0
  33. package/src/runtime/AgUiThreadRuntimeCore.ts +204 -120
  34. package/src/runtime/adapter/conversions.ts +87 -17
  35. package/src/runtime/adapter/run-aggregator.approval.test.ts +128 -0
  36. package/src/runtime/adapter/run-aggregator.ts +595 -139
  37. package/src/runtime/adapter/subscriber.ts +10 -1
  38. package/src/runtime/adapter/tool-approval.test.ts +75 -0
  39. package/src/runtime/adapter/tool-approval.ts +17 -12
  40. package/src/runtime/event-parser.ts +216 -36
  41. package/src/runtime/types.ts +96 -12
  42. package/src/useAgUiRuntime.approval.test.tsx +34 -24
  43. package/src/useAgUiRuntime.queue.test.tsx +54 -0
  44. package/src/useAgUiRuntime.thread-switch.test.tsx +199 -0
  45. package/src/useAgUiRuntime.toolDeferral.test.tsx +186 -0
  46. package/src/useAgUiRuntime.ts +15 -3
@@ -5,6 +5,62 @@ import { isMcpAppUri } from "@assistant-ui/core";
5
5
  import { applyA2uiOperations, convertSurfaceToUISpec } from "@assistant-ui/react-generative-ui/a2ui";
6
6
  //#region src/runtime/adapter/run-aggregator.ts
7
7
  const AG_UI_METADATA_NAMESPACE = "agui";
8
+ const ROOT_SCOPE = "";
9
+ const MAX_SUBAGENT_DEPTH = 16;
10
+ const createJSONContainerScanner = () => ({
11
+ state: "pending",
12
+ stack: [],
13
+ inString: false,
14
+ escaped: false
15
+ });
16
+ const isJSONWhitespace = (char) => char === " " || char === " " || char === "\n" || char === "\r";
17
+ const scanJSONContainerDelta = (scanner, delta) => {
18
+ let completed = false;
19
+ for (const char of delta) {
20
+ if (scanner.state === "invalid") continue;
21
+ if (scanner.state === "complete") {
22
+ if (isJSONWhitespace(char)) continue;
23
+ scanner.state = "invalid";
24
+ continue;
25
+ }
26
+ if (scanner.state === "pending") {
27
+ if (isJSONWhitespace(char)) continue;
28
+ if (char !== "{" && char !== "[") {
29
+ scanner.state = "invalid";
30
+ continue;
31
+ }
32
+ scanner.state = "open";
33
+ scanner.stack.push(char);
34
+ continue;
35
+ }
36
+ if (scanner.inString) {
37
+ if (scanner.escaped) scanner.escaped = false;
38
+ else if (char === "\\") scanner.escaped = true;
39
+ else if (char === "\"") scanner.inString = false;
40
+ continue;
41
+ }
42
+ if (char === "\"") {
43
+ scanner.inString = true;
44
+ continue;
45
+ }
46
+ if (char === "{" || char === "[") {
47
+ scanner.stack.push(char);
48
+ continue;
49
+ }
50
+ if (char === "}" || char === "]") {
51
+ const expected = char === "}" ? "{" : "[";
52
+ if (scanner.stack.pop() !== expected) {
53
+ scanner.state = "invalid";
54
+ continue;
55
+ }
56
+ if (scanner.stack.length === 0) {
57
+ scanner.state = "complete";
58
+ completed = true;
59
+ }
60
+ }
61
+ }
62
+ return completed && scanner.state === "complete";
63
+ };
8
64
  const MCP_APPS_ACTIVITY_TYPE = "mcp-apps";
9
65
  const A2UI_SURFACE_ACTIVITY_TYPE = "a2ui-surface";
10
66
  const isPlainObject = (value) => !!value && typeof value === "object" && !Array.isArray(value);
@@ -27,7 +83,7 @@ var RunAggregator = class {
27
83
  status;
28
84
  interrupts;
29
85
  textParts = /* @__PURE__ */ new Map();
30
- activeTextMessageId;
86
+ activeTextMessageIdByScope = /* @__PURE__ */ new Map();
31
87
  reasoningParts = /* @__PURE__ */ new Map();
32
88
  reasoningSignatures = /* @__PURE__ */ new Map();
33
89
  reasoningSignatureIds = /* @__PURE__ */ new Map();
@@ -41,12 +97,14 @@ var RunAggregator = class {
41
97
  loggedDroppedOpaqueIds = /* @__PURE__ */ new Set();
42
98
  reasoningMessageIds = /* @__PURE__ */ new Map();
43
99
  anonymousReasoningKeys = /* @__PURE__ */ new Set();
44
- activeReasoningKey;
100
+ activeReasoningKeyByScope = /* @__PURE__ */ new Map();
101
+ subagentRuns = /* @__PURE__ */ new Map();
102
+ nestingMemo;
45
103
  reasoningPartCounter = 0;
46
104
  toolCalls = /* @__PURE__ */ new Map();
47
105
  a2uiBuckets = /* @__PURE__ */ new Map();
48
106
  a2uiToolCallIds = /* @__PURE__ */ new Set();
49
- lastResolvedToolCallId;
107
+ lastResolvedToolCallIdByScope = /* @__PURE__ */ new Map();
50
108
  partOrder = [];
51
109
  textPartCounter = 0;
52
110
  serverMessageIdReported = false;
@@ -91,13 +149,14 @@ var RunAggregator = class {
91
149
  }
92
150
  this.interrupts = void 0;
93
151
  const hasUnresolvedToolCalls = Array.from(this.toolCalls.values()).some((tc) => tc.result === void 0);
94
- this.status = event.outcome?.type === "success" || !hasUnresolvedToolCalls ? {
95
- type: "complete",
96
- reason: "unknown"
97
- } : {
152
+ this.status = hasUnresolvedToolCalls ? {
98
153
  type: "requires-action",
99
154
  reason: "tool-calls"
155
+ } : {
156
+ type: "complete",
157
+ reason: "unknown"
100
158
  };
159
+ this.closeOpenSubagentRuns(this.status);
101
160
  this.emit();
102
161
  break;
103
162
  }
@@ -107,6 +166,7 @@ var RunAggregator = class {
107
166
  reason: "error",
108
167
  ...event.message !== void 0 ? { error: event.message } : {}
109
168
  };
169
+ this.closeOpenSubagentRuns(this.status);
110
170
  this.emit();
111
171
  break;
112
172
  case "RUN_CANCELLED":
@@ -114,36 +174,46 @@ var RunAggregator = class {
114
174
  type: "incomplete",
115
175
  reason: "cancelled"
116
176
  };
177
+ this.closeOpenSubagentRuns(this.status);
117
178
  this.emit();
118
179
  break;
119
- case "TEXT_MESSAGE_START":
120
- this.beginDistinctTextMessage(event.messageId);
121
- this.reportServerMessageId(event.messageId);
122
- this.startTextMessage(event.messageId);
123
- if (event.messageId) this.lastTextMessageId = event.messageId;
180
+ case "TEXT_MESSAGE_START": {
181
+ const scope = this.scopeOf(event);
182
+ if (scope === ROOT_SCOPE) {
183
+ this.beginDistinctTextMessage(event.messageId);
184
+ this.reportServerMessageId(event.messageId);
185
+ if (event.messageId) this.lastTextMessageId = event.messageId;
186
+ }
187
+ this.startTextMessage(scope, event.messageId);
124
188
  this.emit();
125
189
  break;
190
+ }
126
191
  case "TEXT_MESSAGE_CONTENT":
127
192
  case "TEXT_MESSAGE_CHUNK": {
128
193
  const incomingId = "messageId" in event ? event.messageId : void 0;
129
- this.beginDistinctTextMessage(incomingId);
130
- this.reportServerMessageId(incomingId);
131
- if (incomingId) this.lastTextMessageId = incomingId;
194
+ const scope = this.scopeOf(event);
195
+ if (scope === ROOT_SCOPE) {
196
+ this.beginDistinctTextMessage(incomingId);
197
+ this.reportServerMessageId(incomingId);
198
+ if (incomingId) this.lastTextMessageId = incomingId;
199
+ }
132
200
  if (!event.delta) break;
133
201
  this.recordFirstToken();
134
- const id = this.resolveTextMessageId(incomingId);
202
+ const id = this.resolveTextMessageId(scope, incomingId);
135
203
  this.appendText(id, event.delta);
136
204
  this.totalChunks++;
137
205
  this.emit();
138
206
  break;
139
207
  }
140
- case "TEXT_MESSAGE_END":
141
- this.reportServerMessageId(event.messageId);
142
- if (event.messageId && this.activeTextMessageId === event.messageId) this.activeTextMessageId = void 0;
208
+ case "TEXT_MESSAGE_END": {
209
+ const scope = this.scopeOf(event);
210
+ if (scope === ROOT_SCOPE) this.reportServerMessageId(event.messageId);
211
+ if (event.messageId && this.activeTextMessageIdByScope.get(scope) === this.partKey(scope, event.messageId)) this.activeTextMessageIdByScope.delete(scope);
143
212
  this.emit();
144
213
  break;
214
+ }
145
215
  case "CUSTOM":
146
- this.activeTextMessageId = void 0;
216
+ this.activeTextMessageIdByScope.delete(ROOT_SCOPE);
147
217
  this.partOrder.push({
148
218
  kind: "data",
149
219
  name: event.name,
@@ -155,17 +225,19 @@ var RunAggregator = class {
155
225
  case "THINKING_TEXT_MESSAGE_START":
156
226
  case "REASONING_START":
157
227
  case "REASONING_MESSAGE_START":
158
- this.handleReasoningStart("messageId" in event ? event.messageId : void 0, event.type === "REASONING_MESSAGE_START");
228
+ this.handleReasoningStart(this.scopeOf("subagentRunId" in event ? event : {}), "messageId" in event ? event.messageId : void 0, event.type === "REASONING_MESSAGE_START");
159
229
  break;
160
230
  case "REASONING_ENCRYPTED_VALUE":
161
231
  if (event.subtype === "message") {
162
- const active = this.activeReasoningKey;
163
- const key = this.showThinking ? this.reasoningParts.has(event.entityId) ? event.entityId : active !== void 0 && this.anonymousReasoningKeys.has(active) ? active : void 0 : void 0;
232
+ const scope = this.scopeOf(event);
233
+ const active = this.activeReasoningKeyByScope.get(scope);
234
+ const entityKey = this.partKey(scope, event.entityId);
235
+ const key = this.showThinking ? this.reasoningParts.has(entityKey) ? entityKey : active !== void 0 && this.anonymousReasoningKeys.has(active) ? active : void 0 : void 0;
164
236
  if (key !== void 0) {
165
237
  this.reasoningSignatures.set(key, event.encryptedValue);
166
238
  this.reasoningSignatureIds.set(key, event.entityId);
167
239
  this.emit();
168
- } else if (!this.showThinking && event.entityId.trim().length > 0 && event.encryptedValue.trim().length > 0 && (this.hiddenReasoningIds.has(event.entityId) || this.hiddenActiveReasoning === "anonymous")) {
240
+ } else if (!this.showThinking && scope === ROOT_SCOPE && event.entityId.trim().length > 0 && event.encryptedValue.trim().length > 0 && (this.hiddenReasoningIds.has(event.entityId) || this.hiddenActiveReasoning === "anonymous")) {
169
241
  this.hiddenSignatures.set(event.entityId, event.encryptedValue);
170
242
  this.hiddenSignatureAnchors.set(event.entityId, this.hiddenBlockAnchors.get(event.entityId) ?? this.hiddenAnonymousAnchor ?? this.partOrder.length);
171
243
  this.emit();
@@ -173,29 +245,33 @@ var RunAggregator = class {
173
245
  }
174
246
  break;
175
247
  case "THINKING_TEXT_MESSAGE_CONTENT":
176
- this.handleReasoningContent(event.delta);
248
+ this.handleReasoningContent(ROOT_SCOPE, event.delta);
177
249
  this.totalChunks++;
178
250
  this.recordFirstToken();
179
251
  break;
180
252
  case "REASONING_MESSAGE_CONTENT":
181
- this.handleReasoningContent(event.delta, "messageId" in event ? event.messageId : void 0, true);
253
+ this.handleReasoningContent(this.scopeOf(event), event.delta, "messageId" in event ? event.messageId : void 0, true);
182
254
  this.totalChunks++;
183
255
  this.recordFirstToken();
184
256
  break;
185
257
  case "THINKING_TEXT_MESSAGE_END":
186
258
  case "THINKING_END":
259
+ this.handleReasoningEnd(ROOT_SCOPE);
260
+ break;
187
261
  case "REASONING_MESSAGE_END":
188
262
  case "REASONING_END":
189
- this.handleReasoningEnd();
263
+ this.handleReasoningEnd(this.scopeOf(event));
190
264
  break;
191
- case "TOOL_CALL_START":
192
- this.reportServerMessageId(event.parentMessageId);
193
- this.startToolCall(event.toolCallId, event.toolCallName, event.parentMessageId);
265
+ case "TOOL_CALL_START": {
266
+ const scope = this.scopeOf(event);
267
+ if (scope === ROOT_SCOPE) this.reportServerMessageId(event.parentMessageId);
268
+ this.startToolCall(scope, event.toolCallId, event.toolCallName, event.parentMessageId);
194
269
  this.emit();
195
270
  break;
271
+ }
196
272
  case "TOOL_CALL_ARGS":
197
273
  case "TOOL_CALL_CHUNK":
198
- if (event.type === "TOOL_CALL_CHUNK") this.reportServerMessageId(event.parentMessageId);
274
+ if (event.type === "TOOL_CALL_CHUNK" && this.scopeOf(event) === ROOT_SCOPE) this.reportServerMessageId(event.parentMessageId);
199
275
  if (!event.delta) break;
200
276
  this.appendToolArgs(event.toolCallId, event.delta);
201
277
  this.emit();
@@ -204,7 +280,7 @@ var RunAggregator = class {
204
280
  this.emit();
205
281
  break;
206
282
  case "TOOL_CALL_RESULT":
207
- this.finishToolCall(event.toolCallId, event.content ?? "", typeof event.mcpResult?.isError === "boolean" ? event.mcpResult.isError : event.role === "tool" ? false : void 0, event.messageId, event.mcpResult);
283
+ this.finishToolCall(this.scopeOf(event), event.toolCallId, event.content ?? "", typeof event.mcpResult?.isError === "boolean" ? event.mcpResult.isError : event.role === "tool" ? false : void 0, event.messageId, event.mcpResult);
208
284
  this.emit();
209
285
  break;
210
286
  case "ACTIVITY_SNAPSHOT": {
@@ -213,8 +289,10 @@ var RunAggregator = class {
213
289
  break;
214
290
  }
215
291
  if (event.activityType !== "mcp-apps") break;
292
+ const activityScope = this.scopeOf(event);
216
293
  const toolCallId = event.content.toolCallId;
217
- const entry = typeof toolCallId === "string" ? this.toolCalls.get(toolCallId) : this.lastResolvedToolCallId ? this.toolCalls.get(this.lastResolvedToolCallId) : void 0;
294
+ const fallbackId = this.lastResolvedToolCallIdByScope.get(activityScope);
295
+ const entry = typeof toolCallId === "string" ? this.toolCalls.get(toolCallId) : fallbackId ? this.toolCalls.get(fallbackId) : void 0;
218
296
  const resourceUri = event.content.resourceUri;
219
297
  if (entry && typeof resourceUri === "string" && isMcpAppUri(resourceUri)) {
220
298
  entry.mcpAppResourceUri = resourceUri;
@@ -236,6 +314,58 @@ var RunAggregator = class {
236
314
  }
237
315
  break;
238
316
  }
317
+ case "SUBAGENT_STARTED": {
318
+ const existing = this.subagentRuns.get(event.subagentRunId);
319
+ if (existing) {
320
+ existing.name = event.name;
321
+ if (event.description !== void 0) existing.description = event.description;
322
+ existing.status = { type: "running" };
323
+ this.emit();
324
+ break;
325
+ }
326
+ this.nestingMemo = void 0;
327
+ this.subagentRuns.set(event.subagentRunId, {
328
+ subagentRunId: event.subagentRunId,
329
+ name: event.name,
330
+ ...event.description !== void 0 ? { description: event.description } : {},
331
+ ...event.parentSubagentRunId !== void 0 ? { parentSubagentRunId: event.parentSubagentRunId } : {},
332
+ ...event.parentToolCallId !== void 0 ? { parentToolCallId: event.parentToolCallId } : {},
333
+ ...event.parentMessageId !== void 0 ? { parentMessageId: event.parentMessageId } : {},
334
+ createdAt: /* @__PURE__ */ new Date(),
335
+ status: { type: "running" }
336
+ });
337
+ this.emit();
338
+ break;
339
+ }
340
+ case "SUBAGENT_FINISHED": {
341
+ const run = this.subagentRuns.get(event.subagentRunId);
342
+ if (run) {
343
+ run.status = event.outcome?.type === "suspended" ? {
344
+ type: "requires-action",
345
+ reason: "interrupt"
346
+ } : {
347
+ type: "complete",
348
+ reason: "unknown"
349
+ };
350
+ if (event.result !== void 0) run.result = event.result;
351
+ if (event.outcome?.type === "suspended" && event.outcome.interruptIds) run.interruptIds = event.outcome.interruptIds;
352
+ }
353
+ this.emit();
354
+ break;
355
+ }
356
+ case "SUBAGENT_ERROR": {
357
+ const run = this.subagentRuns.get(event.subagentRunId);
358
+ if (run) {
359
+ run.status = {
360
+ type: "incomplete",
361
+ reason: "error",
362
+ ...event.message !== void 0 ? { error: event.message } : {}
363
+ };
364
+ if (event.code !== void 0) run.errorCode = event.code;
365
+ }
366
+ this.emit();
367
+ break;
368
+ }
239
369
  default: this.logger.debug?.("[agui] aggregator ignored event", event);
240
370
  }
241
371
  }
@@ -274,11 +404,13 @@ var RunAggregator = class {
274
404
  kind: "tool-call",
275
405
  toolCallId
276
406
  });
407
+ this.nestingMemo = void 0;
277
408
  this.toolCalls.set(toolCallId, entry);
278
409
  this.a2uiToolCallIds.add(toolCallId);
279
410
  }
280
411
  for (const toolCallId of this.a2uiToolCallIds) {
281
412
  if (activeToolCallIds.has(toolCallId)) continue;
413
+ this.nestingMemo = void 0;
282
414
  this.toolCalls.delete(toolCallId);
283
415
  const partIndex = this.partOrder.findIndex((part) => part.kind === "tool-call" && part.toolCallId === toolCallId);
284
416
  if (partIndex !== -1) this.partOrder.splice(partIndex, 1);
@@ -310,17 +442,28 @@ var RunAggregator = class {
310
442
  this.hiddenActiveReasoning = "none";
311
443
  this.loggedDroppedOpaqueIds.clear();
312
444
  this.anonymousReasoningKeys.clear();
313
- this.activeReasoningKey = void 0;
445
+ this.activeReasoningKeyByScope.clear();
446
+ this.nestingMemo = void 0;
447
+ this.subagentRuns.clear();
314
448
  this.reasoningPartCounter = 0;
315
449
  this.toolCalls.clear();
316
450
  this.a2uiBuckets.clear();
317
451
  this.a2uiToolCallIds.clear();
318
- this.lastResolvedToolCallId = void 0;
452
+ this.lastResolvedToolCallIdByScope.clear();
319
453
  this.partOrder.length = 0;
320
454
  this.textPartCounter = 0;
321
- this.activeTextMessageId = void 0;
455
+ this.activeTextMessageIdByScope.clear();
322
456
  this.reportedServerMessageId = void 0;
323
457
  }
458
+ closeOpenSubagentRuns(status) {
459
+ for (const run of this.subagentRuns.values()) if (run.status.type === "running") run.status = status;
460
+ }
461
+ scopeOf(event) {
462
+ return event.subagentRunId ?? ROOT_SCOPE;
463
+ }
464
+ partKey(scope, id) {
465
+ return scope === ROOT_SCOPE ? id : `${scope}\u0000${id}`;
466
+ }
324
467
  beginDistinctTextMessage(messageId) {
325
468
  if (!messageId || this.lastTextMessageId === void 0 || this.lastTextMessageId === messageId || !this.onTextMessageStart) return;
326
469
  this.resetMessageParts();
@@ -330,43 +473,48 @@ var RunAggregator = class {
330
473
  this.textPartCounter += 1;
331
474
  return `text-${this.textPartCounter}`;
332
475
  }
333
- startTextMessage(messageId) {
476
+ startTextMessage(scope, messageId) {
334
477
  const id = messageId ?? this.generateTextKey();
335
- this.ensureTextPart(id);
336
- this.activeTextMessageId = id;
337
- return id;
478
+ const key = this.ensureTextPart(scope, id);
479
+ this.activeTextMessageIdByScope.set(scope, key);
480
+ return key;
338
481
  }
339
- resolveTextMessageId(messageId) {
482
+ resolveTextMessageId(scope, messageId) {
340
483
  if (messageId) {
341
- this.ensureTextPart(messageId);
342
- this.activeTextMessageId = messageId;
343
- return messageId;
484
+ const key = this.ensureTextPart(scope, messageId);
485
+ this.activeTextMessageIdByScope.set(scope, key);
486
+ return key;
344
487
  }
345
- if (this.activeTextMessageId) return this.activeTextMessageId;
346
- const generated = this.generateTextKey();
347
- this.ensureTextPart(generated);
348
- this.activeTextMessageId = generated;
349
- return generated;
350
- }
351
- ensureTextPart(id) {
352
- if (!this.textParts.has(id)) {
353
- this.textParts.set(id, { buffer: "" });
354
- if (!this.partOrder.some((part) => part.kind === "text" && part.key === id)) this.partOrder.push({
488
+ const active = this.activeTextMessageIdByScope.get(scope);
489
+ if (active) return active;
490
+ const key = this.ensureTextPart(scope, this.generateTextKey());
491
+ this.activeTextMessageIdByScope.set(scope, key);
492
+ return key;
493
+ }
494
+ ensureTextPart(scope, id) {
495
+ const key = this.partKey(scope, id);
496
+ if (!this.textParts.has(key)) {
497
+ this.textParts.set(key, { buffer: "" });
498
+ if (!this.partOrder.some((part) => part.kind === "text" && part.key === key)) this.partOrder.push(scope === ROOT_SCOPE ? {
499
+ kind: "text",
500
+ key
501
+ } : {
355
502
  kind: "text",
356
- key: id
503
+ key,
504
+ subagentRunId: scope
357
505
  });
358
506
  }
507
+ return key;
359
508
  }
360
509
  appendText(id, delta) {
361
- this.ensureTextPart(id);
362
510
  const entry = this.textParts.get(id);
363
511
  if (!entry) return;
364
512
  entry.buffer += delta;
365
513
  }
366
- startToolCall(id, name, parentMessageId) {
514
+ startToolCall(scope, id, name, parentMessageId) {
367
515
  if (!id) return;
368
- this.activeTextMessageId = void 0;
369
- if (!this.partOrder.some((part) => part.kind === "tool-call" && part.toolCallId === id)) this.insertToolPart(id, parentMessageId);
516
+ this.activeTextMessageIdByScope.delete(scope);
517
+ if (!this.partOrder.some((part) => part.kind === "tool-call" && part.toolCallId === id)) this.insertToolPart(scope, id, parentMessageId);
370
518
  const state = {
371
519
  toolCallId: id,
372
520
  toolCallName: name ?? "tool",
@@ -377,15 +525,18 @@ var RunAggregator = class {
377
525
  snapshotResultApplied: false
378
526
  };
379
527
  if (parentMessageId) state.parentMessageId = parentMessageId;
528
+ if (scope !== ROOT_SCOPE) state.subagentRunId = scope;
529
+ this.nestingMemo = void 0;
380
530
  this.toolCalls.set(id, state);
381
531
  }
382
- insertToolPart(id, parentMessageId) {
532
+ insertToolPart(scope, id, parentMessageId) {
383
533
  const entry = {
384
534
  kind: "tool-call",
385
535
  toolCallId: id
386
536
  };
387
537
  if (parentMessageId) {
388
- const parentIndex = this.partOrder.findIndex((part) => part.kind === "text" && part.key === parentMessageId);
538
+ const parentKey = this.partKey(scope, parentMessageId);
539
+ const parentIndex = this.partOrder.findIndex((part) => part.kind === "text" && part.key === parentKey);
389
540
  if (parentIndex !== -1) {
390
541
  let insertAt = parentIndex + 1;
391
542
  while (insertAt < this.partOrder.length) {
@@ -405,7 +556,12 @@ var RunAggregator = class {
405
556
  appendToolArgs(id, delta) {
406
557
  const entry = id ? this.toolCalls.get(id) : void 0;
407
558
  if (!entry) return;
559
+ if (!entry.argsTextScanner) entry.argsTextScanner = createJSONContainerScanner();
408
560
  entry.argsText += delta;
561
+ if (!scanJSONContainerDelta(entry.argsTextScanner, delta)) {
562
+ if (entry.argsTextScanner.state === "invalid") entry.parsedArgs = void 0;
563
+ return;
564
+ }
409
565
  try {
410
566
  const parsed = JSON.parse(entry.argsText);
411
567
  if (parsed && typeof parsed === "object") entry.parsedArgs = parsed;
@@ -414,7 +570,7 @@ var RunAggregator = class {
414
570
  entry.parsedArgs = void 0;
415
571
  }
416
572
  }
417
- finishToolCall(id, content, isError, toolMessageId, mcpResult) {
573
+ finishToolCall(scope, id, content, isError, toolMessageId, mcpResult) {
418
574
  if (!id) return;
419
575
  let entry = this.toolCalls.get(id);
420
576
  if (!entry) {
@@ -425,8 +581,10 @@ var RunAggregator = class {
425
581
  parsedArgs: void 0,
426
582
  result: void 0,
427
583
  isError: void 0,
428
- snapshotResultApplied: false
584
+ snapshotResultApplied: false,
585
+ ...scope !== ROOT_SCOPE ? { subagentRunId: scope } : {}
429
586
  };
587
+ this.nestingMemo = void 0;
430
588
  this.toolCalls.set(id, entry);
431
589
  }
432
590
  if (!this.partOrder.some((part) => part.kind === "tool-call" && part.toolCallId === id)) this.partOrder.push({
@@ -456,43 +614,126 @@ var RunAggregator = class {
456
614
  entry.isError = isError;
457
615
  }
458
616
  if (toolMessageId) entry.toolMessageId = toolMessageId;
459
- this.lastResolvedToolCallId = id;
617
+ this.lastResolvedToolCallIdByScope.set(scope, id);
460
618
  }
461
- emit() {
462
- const snapshot = [];
463
- const approvals = projectAgUiToolApprovals(this.status?.type === "requires-action" ? this.interrupts : void 0, new Set(this.toolCalls.keys()));
464
- const opaqueCandidates = Array.from(this.hiddenSignatures, ([id, encryptedValue]) => ({
465
- id,
466
- encryptedValue,
467
- anchor: this.hiddenSignatureAnchors.get(id)
468
- }));
469
- let currentIndex = -1;
470
- let lastMaterializedIndex = -1;
471
- const pushSnapshotPart = (part) => {
472
- snapshot.push(part);
473
- lastMaterializedIndex = currentIndex;
619
+ nesting() {
620
+ if (!this.nestingMemo) {
621
+ const subagentsByParentToolCallId = this.subagentsByParentToolCallId();
622
+ this.nestingMemo = {
623
+ subagentsByParentToolCallId,
624
+ reachable: this.reachableSubagentRunIds(subagentsByParentToolCallId)
625
+ };
626
+ }
627
+ return this.nestingMemo;
628
+ }
629
+ subagentsByParentToolCallId() {
630
+ const map = /* @__PURE__ */ new Map();
631
+ for (const run of this.subagentRuns.values()) {
632
+ if (!run.parentToolCallId) continue;
633
+ const list = map.get(run.parentToolCallId) ?? [];
634
+ list.push(run.subagentRunId);
635
+ map.set(run.parentToolCallId, list);
636
+ }
637
+ return map;
638
+ }
639
+ reachableSubagentRunIds(subagentsByParentToolCallId) {
640
+ const reachable = /* @__PURE__ */ new Set();
641
+ const walk = (scope, depth) => {
642
+ if (depth > MAX_SUBAGENT_DEPTH) return;
643
+ for (const entry of this.toolCalls.values()) {
644
+ if ((entry.subagentRunId ?? ROOT_SCOPE) !== scope) continue;
645
+ for (const id of subagentsByParentToolCallId.get(entry.toolCallId) ?? []) {
646
+ if (reachable.has(id)) continue;
647
+ reachable.add(id);
648
+ walk(id, depth + 1);
649
+ }
650
+ }
474
651
  };
475
- for (const [index, part] of this.partOrder.entries()) {
476
- currentIndex = index;
652
+ walk(ROOT_SCOPE, 0);
653
+ return reachable;
654
+ }
655
+ materializeSubagentMessage(subagentRunId, depth, ctx) {
656
+ const run = this.subagentRuns.get(subagentRunId);
657
+ if (!run || depth > MAX_SUBAGENT_DEPTH) return void 0;
658
+ const content = this.buildParts(subagentRunId, depth + 1, ctx);
659
+ return {
660
+ id: run.subagentRunId,
661
+ role: "assistant",
662
+ createdAt: run.createdAt,
663
+ status: run.status,
664
+ content,
665
+ metadata: {
666
+ unstable_state: null,
667
+ unstable_annotations: [],
668
+ unstable_data: [],
669
+ steps: [],
670
+ custom: { [AG_UI_METADATA_NAMESPACE]: {
671
+ name: run.name,
672
+ ...run.description !== void 0 ? { description: run.description } : {},
673
+ ...run.parentSubagentRunId !== void 0 ? { parentSubagentRunId: run.parentSubagentRunId } : {},
674
+ ...run.result !== void 0 ? { result: run.result } : {},
675
+ ...run.interruptIds !== void 0 ? { interruptIds: run.interruptIds } : {},
676
+ ...run.errorCode !== void 0 ? { errorCode: run.errorCode } : {}
677
+ } }
678
+ }
679
+ };
680
+ }
681
+ buildParts(scope, depth, ctx) {
682
+ const snapshot = [];
683
+ const isRoot = scope === ROOT_SCOPE;
684
+ for (const { index, part } of ctx.partsByScope.get(scope) ?? []) {
685
+ const materialized = () => {
686
+ if (isRoot && ctx.root) ctx.root.lastMaterializedIndex = index;
687
+ };
688
+ if (part.kind === "tool-call") {
689
+ const entry = this.toolCalls.get(part.toolCallId);
690
+ if (!entry) continue;
691
+ const approval = ctx.approvals.get(entry.toolCallId);
692
+ const nestedMessages = (ctx.subagentsByParentToolCallId.get(entry.toolCallId) ?? []).filter((id) => ctx.reachable.has(id)).map((id) => this.materializeSubagentMessage(id, depth, ctx)).filter((m) => m !== void 0);
693
+ const toolPart = {
694
+ type: "tool-call",
695
+ toolCallId: entry.toolCallId,
696
+ toolName: entry.toolCallName,
697
+ args: entry.parsedArgs ?? {},
698
+ argsText: entry.argsText,
699
+ ...approval ? { approval } : {},
700
+ ...entry.result !== void 0 ? { result: entry.result } : {},
701
+ ...entry.modelContent !== void 0 ? { modelContent: entry.modelContent } : {},
702
+ ...entry.isError !== void 0 ? { isError: entry.isError } : {},
703
+ ...entry.mcpAppResourceUri ? { mcp: { app: {
704
+ resourceUri: entry.mcpAppResourceUri,
705
+ ...entry.mcpAppServerId ? { serverId: entry.mcpAppServerId } : {}
706
+ } } } : {},
707
+ ...entry.parentMessageId ? { parentId: entry.parentMessageId } : {},
708
+ ...entry.toolMessageId ? { unstable_toolMessageId: entry.toolMessageId } : {},
709
+ ...nestedMessages.length > 0 ? { messages: nestedMessages } : {}
710
+ };
711
+ snapshot.push(toolPart);
712
+ materialized();
713
+ continue;
714
+ }
715
+ const rawScope = "subagentRunId" in part ? part.subagentRunId ?? ROOT_SCOPE : ROOT_SCOPE;
477
716
  if (part.kind === "reasoning") {
478
717
  if (this.showThinking) {
479
718
  const buffer = this.reasoningParts.get(part.key) ?? "";
480
- if (buffer.length > 0 || this.activeReasoningKey === part.key) {
719
+ const isActive = this.activeReasoningKeyByScope.get(rawScope) === part.key;
720
+ if (buffer.length > 0 || isActive) {
481
721
  const encryptedValue = this.reasoningSignatures.get(part.key);
482
722
  const reasoningId = this.reasoningMessageIds.get(part.key);
483
723
  const meta = {
484
724
  ...reasoningId !== void 0 ? { reasoningId } : {},
485
725
  ...encryptedValue !== void 0 ? { encryptedValue } : {}
486
726
  };
487
- pushSnapshotPart({
727
+ snapshot.push({
488
728
  type: "reasoning",
489
729
  text: buffer,
490
730
  ...Object.keys(meta).length > 0 ? { providerMetadata: { [AG_UI_METADATA_NAMESPACE]: meta } } : {}
491
731
  });
492
- } else {
732
+ materialized();
733
+ } else if (isRoot && ctx.root) {
493
734
  const encryptedValue = this.reasoningSignatures.get(part.key);
494
735
  const id = this.reasoningSignatureIds.get(part.key);
495
- if (id?.trim() && encryptedValue?.trim()) opaqueCandidates.push({
736
+ if (id?.trim() && encryptedValue?.trim()) ctx.root.opaqueCandidates.push({
496
737
  id,
497
738
  encryptedValue,
498
739
  anchor: index + 1
@@ -503,41 +744,60 @@ var RunAggregator = class {
503
744
  }
504
745
  if (part.kind === "text") {
505
746
  const entry = this.textParts.get(part.key);
506
- if (entry && entry.buffer.trim().length > 0) pushSnapshotPart({
507
- type: "text",
508
- text: entry.buffer
509
- });
747
+ if (entry && entry.buffer.trim().length > 0) {
748
+ snapshot.push({
749
+ type: "text",
750
+ text: entry.buffer
751
+ });
752
+ materialized();
753
+ }
510
754
  continue;
511
755
  }
512
756
  if (part.kind === "data") {
513
- pushSnapshotPart({
757
+ snapshot.push({
514
758
  type: "data",
515
759
  name: part.name,
516
760
  data: part.value
517
761
  });
762
+ materialized();
518
763
  continue;
519
764
  }
520
- const entry = this.toolCalls.get(part.toolCallId);
521
- if (!entry) continue;
522
- const approval = approvals.get(entry.toolCallId);
523
- pushSnapshotPart({
524
- type: "tool-call",
525
- toolCallId: entry.toolCallId,
526
- toolName: entry.toolCallName,
527
- args: entry.parsedArgs ?? {},
528
- argsText: entry.argsText,
529
- ...approval ? { approval } : {},
530
- ...entry.result !== void 0 ? { result: entry.result } : {},
531
- ...entry.modelContent !== void 0 ? { modelContent: entry.modelContent } : {},
532
- ...entry.isError !== void 0 ? { isError: entry.isError } : {},
533
- ...entry.mcpAppResourceUri ? { mcp: { app: {
534
- resourceUri: entry.mcpAppResourceUri,
535
- ...entry.mcpAppServerId ? { serverId: entry.mcpAppServerId } : {}
536
- } } } : {},
537
- ...entry.parentMessageId ? { parentId: entry.parentMessageId } : {},
538
- ...entry.toolMessageId ? { unstable_toolMessageId: entry.toolMessageId } : {}
765
+ }
766
+ return snapshot;
767
+ }
768
+ emit() {
769
+ const { subagentsByParentToolCallId, reachable } = this.nesting();
770
+ const root = {
771
+ opaqueCandidates: Array.from(this.hiddenSignatures, ([id, encryptedValue]) => ({
772
+ id,
773
+ encryptedValue,
774
+ anchor: this.hiddenSignatureAnchors.get(id)
775
+ })),
776
+ lastMaterializedIndex: -1
777
+ };
778
+ const partsByScope = /* @__PURE__ */ new Map();
779
+ for (const [index, part] of this.partOrder.entries()) {
780
+ const rawScope = part.kind === "tool-call" ? this.toolCalls.get(part.toolCallId)?.subagentRunId ?? ROOT_SCOPE : "subagentRunId" in part ? part.subagentRunId ?? ROOT_SCOPE : ROOT_SCOPE;
781
+ const bucketScope = rawScope === ROOT_SCOPE || reachable.has(rawScope) ? rawScope : ROOT_SCOPE;
782
+ const bucket = partsByScope.get(bucketScope);
783
+ if (bucket) bucket.push({
784
+ index,
785
+ part
539
786
  });
787
+ else partsByScope.set(bucketScope, [{
788
+ index,
789
+ part
790
+ }]);
540
791
  }
792
+ const ctx = {
793
+ subagentsByParentToolCallId,
794
+ reachable,
795
+ partsByScope,
796
+ approvals: projectAgUiToolApprovals(this.status?.type === "requires-action" ? this.interrupts : void 0, new Set(Array.from(this.toolCalls.values()).map((entry) => entry.toolCallId))),
797
+ root
798
+ };
799
+ const snapshot = this.buildParts(ROOT_SCOPE, 0, ctx);
800
+ const { opaqueCandidates, lastMaterializedIndex } = root;
541
801
  const toolMessageIds = /* @__PURE__ */ new Set();
542
802
  for (const call of this.toolCalls.values()) if (call.toolMessageId) toolMessageIds.add(call.toolMessageId);
543
803
  opaqueCandidates.sort((a, b) => a.anchor - b.anchor);
@@ -587,8 +847,9 @@ var RunAggregator = class {
587
847
  toolCallCount: this.toolCalls.size
588
848
  };
589
849
  }
590
- handleReasoningStart(messageId, isMessageId = false) {
850
+ handleReasoningStart(scope, messageId, isMessageId = false) {
591
851
  if (!this.showThinking) {
852
+ if (scope !== ROOT_SCOPE) return;
592
853
  const anchor = this.partOrder.length;
593
854
  if (messageId === void 0) {
594
855
  this.hiddenActiveReasoning = "anonymous";
@@ -600,38 +861,43 @@ var RunAggregator = class {
600
861
  }
601
862
  return;
602
863
  }
603
- this.activeTextMessageId = void 0;
604
- const key = messageId ?? `__auto-reasoning-${++this.reasoningPartCounter}`;
864
+ this.activeTextMessageIdByScope.delete(scope);
865
+ const key = this.partKey(scope, messageId ?? `__auto-reasoning-${++this.reasoningPartCounter}`);
605
866
  if (messageId === void 0) this.anonymousReasoningKeys.add(key);
606
867
  else if (isMessageId) this.reasoningMessageIds.set(key, messageId);
607
868
  if (!this.reasoningParts.has(key)) {
608
869
  this.reasoningParts.set(key, "");
609
- this.partOrder.push({
870
+ this.partOrder.push(scope === ROOT_SCOPE ? {
610
871
  kind: "reasoning",
611
872
  key
873
+ } : {
874
+ kind: "reasoning",
875
+ key,
876
+ subagentRunId: scope
612
877
  });
613
878
  }
614
- this.activeReasoningKey = key;
879
+ this.activeReasoningKeyByScope.set(scope, key);
615
880
  this.emit();
616
881
  }
617
- handleReasoningContent(delta, messageId, isMessageId = false) {
882
+ handleReasoningContent(scope, delta, messageId, isMessageId = false) {
618
883
  if (!delta) return;
619
884
  if (!this.showThinking) {
620
- if (this.hiddenActiveReasoning === "none") this.handleReasoningStart(messageId, isMessageId);
885
+ if (scope !== ROOT_SCOPE) return;
886
+ if (this.hiddenActiveReasoning === "none") this.handleReasoningStart(scope, messageId, isMessageId);
621
887
  return;
622
888
  }
623
- if (!this.activeReasoningKey) this.handleReasoningStart(messageId, isMessageId);
624
- const key = this.activeReasoningKey;
889
+ if (!this.activeReasoningKeyByScope.has(scope)) this.handleReasoningStart(scope, messageId, isMessageId);
890
+ const key = this.activeReasoningKeyByScope.get(scope);
625
891
  if (!key) return;
626
892
  this.reasoningParts.set(key, (this.reasoningParts.get(key) ?? "") + delta);
627
893
  this.emit();
628
894
  }
629
- handleReasoningEnd() {
895
+ handleReasoningEnd(scope) {
630
896
  if (!this.showThinking) {
631
- this.hiddenActiveReasoning = "none";
897
+ if (scope === ROOT_SCOPE) this.hiddenActiveReasoning = "none";
632
898
  return;
633
899
  }
634
- this.activeReasoningKey = void 0;
900
+ this.activeReasoningKeyByScope.delete(scope);
635
901
  this.emit();
636
902
  }
637
903
  };