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

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 (40) hide show
  1. package/README.md +16 -0
  2. package/dist/runtime/AgUiThreadRuntimeCore.d.ts +3 -4
  3. package/dist/runtime/AgUiThreadRuntimeCore.d.ts.map +1 -1
  4. package/dist/runtime/AgUiThreadRuntimeCore.js +29 -43
  5. package/dist/runtime/AgUiThreadRuntimeCore.js.map +1 -1
  6. package/dist/runtime/adapter/conversions.d.ts.map +1 -1
  7. package/dist/runtime/adapter/conversions.js +34 -11
  8. package/dist/runtime/adapter/conversions.js.map +1 -1
  9. package/dist/runtime/adapter/run-aggregator.d.ts +13 -3
  10. package/dist/runtime/adapter/run-aggregator.d.ts.map +1 -1
  11. package/dist/runtime/adapter/run-aggregator.js +326 -119
  12. package/dist/runtime/adapter/run-aggregator.js.map +1 -1
  13. package/dist/runtime/adapter/subscriber.d.ts +9 -0
  14. package/dist/runtime/adapter/subscriber.d.ts.map +1 -1
  15. package/dist/runtime/adapter/subscriber.js +4 -1
  16. package/dist/runtime/adapter/subscriber.js.map +1 -1
  17. package/dist/runtime/adapter/tool-approval.d.ts +3 -1
  18. package/dist/runtime/adapter/tool-approval.d.ts.map +1 -1
  19. package/dist/runtime/adapter/tool-approval.js +11 -12
  20. package/dist/runtime/adapter/tool-approval.js.map +1 -1
  21. package/dist/runtime/event-parser.d.ts.map +1 -1
  22. package/dist/runtime/event-parser.js +176 -45
  23. package/dist/runtime/event-parser.js.map +1 -1
  24. package/dist/runtime/types.d.ts +42 -1
  25. package/dist/runtime/types.d.ts.map +1 -1
  26. package/dist/useAgUiRuntime.js +1 -0
  27. package/dist/useAgUiRuntime.js.map +1 -1
  28. package/package.json +9 -9
  29. package/src/runtime/AgUiThreadRuntimeCore.ts +40 -60
  30. package/src/runtime/adapter/conversions.ts +87 -17
  31. package/src/runtime/adapter/run-aggregator.approval.test.ts +128 -0
  32. package/src/runtime/adapter/run-aggregator.ts +510 -139
  33. package/src/runtime/adapter/subscriber.ts +10 -1
  34. package/src/runtime/adapter/tool-approval.test.ts +75 -0
  35. package/src/runtime/adapter/tool-approval.ts +17 -12
  36. package/src/runtime/event-parser.ts +216 -36
  37. package/src/runtime/types.ts +70 -12
  38. package/src/useAgUiRuntime.approval.test.tsx +34 -24
  39. package/src/useAgUiRuntime.toolDeferral.test.tsx +186 -0
  40. package/src/useAgUiRuntime.ts +1 -0
@@ -5,6 +5,8 @@ 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;
8
10
  const MCP_APPS_ACTIVITY_TYPE = "mcp-apps";
9
11
  const A2UI_SURFACE_ACTIVITY_TYPE = "a2ui-surface";
10
12
  const isPlainObject = (value) => !!value && typeof value === "object" && !Array.isArray(value);
@@ -27,7 +29,7 @@ var RunAggregator = class {
27
29
  status;
28
30
  interrupts;
29
31
  textParts = /* @__PURE__ */ new Map();
30
- activeTextMessageId;
32
+ activeTextMessageIdByScope = /* @__PURE__ */ new Map();
31
33
  reasoningParts = /* @__PURE__ */ new Map();
32
34
  reasoningSignatures = /* @__PURE__ */ new Map();
33
35
  reasoningSignatureIds = /* @__PURE__ */ new Map();
@@ -41,12 +43,14 @@ var RunAggregator = class {
41
43
  loggedDroppedOpaqueIds = /* @__PURE__ */ new Set();
42
44
  reasoningMessageIds = /* @__PURE__ */ new Map();
43
45
  anonymousReasoningKeys = /* @__PURE__ */ new Set();
44
- activeReasoningKey;
46
+ activeReasoningKeyByScope = /* @__PURE__ */ new Map();
47
+ subagentRuns = /* @__PURE__ */ new Map();
48
+ nestingMemo;
45
49
  reasoningPartCounter = 0;
46
50
  toolCalls = /* @__PURE__ */ new Map();
47
51
  a2uiBuckets = /* @__PURE__ */ new Map();
48
52
  a2uiToolCallIds = /* @__PURE__ */ new Set();
49
- lastResolvedToolCallId;
53
+ lastResolvedToolCallIdByScope = /* @__PURE__ */ new Map();
50
54
  partOrder = [];
51
55
  textPartCounter = 0;
52
56
  serverMessageIdReported = false;
@@ -91,13 +95,14 @@ var RunAggregator = class {
91
95
  }
92
96
  this.interrupts = void 0;
93
97
  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
- } : {
98
+ this.status = hasUnresolvedToolCalls ? {
98
99
  type: "requires-action",
99
100
  reason: "tool-calls"
101
+ } : {
102
+ type: "complete",
103
+ reason: "unknown"
100
104
  };
105
+ this.closeOpenSubagentRuns(this.status);
101
106
  this.emit();
102
107
  break;
103
108
  }
@@ -107,6 +112,7 @@ var RunAggregator = class {
107
112
  reason: "error",
108
113
  ...event.message !== void 0 ? { error: event.message } : {}
109
114
  };
115
+ this.closeOpenSubagentRuns(this.status);
110
116
  this.emit();
111
117
  break;
112
118
  case "RUN_CANCELLED":
@@ -114,36 +120,46 @@ var RunAggregator = class {
114
120
  type: "incomplete",
115
121
  reason: "cancelled"
116
122
  };
123
+ this.closeOpenSubagentRuns(this.status);
117
124
  this.emit();
118
125
  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;
126
+ case "TEXT_MESSAGE_START": {
127
+ const scope = this.scopeOf(event);
128
+ if (scope === ROOT_SCOPE) {
129
+ this.beginDistinctTextMessage(event.messageId);
130
+ this.reportServerMessageId(event.messageId);
131
+ if (event.messageId) this.lastTextMessageId = event.messageId;
132
+ }
133
+ this.startTextMessage(scope, event.messageId);
124
134
  this.emit();
125
135
  break;
136
+ }
126
137
  case "TEXT_MESSAGE_CONTENT":
127
138
  case "TEXT_MESSAGE_CHUNK": {
128
139
  const incomingId = "messageId" in event ? event.messageId : void 0;
129
- this.beginDistinctTextMessage(incomingId);
130
- this.reportServerMessageId(incomingId);
131
- if (incomingId) this.lastTextMessageId = incomingId;
140
+ const scope = this.scopeOf(event);
141
+ if (scope === ROOT_SCOPE) {
142
+ this.beginDistinctTextMessage(incomingId);
143
+ this.reportServerMessageId(incomingId);
144
+ if (incomingId) this.lastTextMessageId = incomingId;
145
+ }
132
146
  if (!event.delta) break;
133
147
  this.recordFirstToken();
134
- const id = this.resolveTextMessageId(incomingId);
148
+ const id = this.resolveTextMessageId(scope, incomingId);
135
149
  this.appendText(id, event.delta);
136
150
  this.totalChunks++;
137
151
  this.emit();
138
152
  break;
139
153
  }
140
- case "TEXT_MESSAGE_END":
141
- this.reportServerMessageId(event.messageId);
142
- if (event.messageId && this.activeTextMessageId === event.messageId) this.activeTextMessageId = void 0;
154
+ case "TEXT_MESSAGE_END": {
155
+ const scope = this.scopeOf(event);
156
+ if (scope === ROOT_SCOPE) this.reportServerMessageId(event.messageId);
157
+ if (event.messageId && this.activeTextMessageIdByScope.get(scope) === this.partKey(scope, event.messageId)) this.activeTextMessageIdByScope.delete(scope);
143
158
  this.emit();
144
159
  break;
160
+ }
145
161
  case "CUSTOM":
146
- this.activeTextMessageId = void 0;
162
+ this.activeTextMessageIdByScope.delete(ROOT_SCOPE);
147
163
  this.partOrder.push({
148
164
  kind: "data",
149
165
  name: event.name,
@@ -155,17 +171,19 @@ var RunAggregator = class {
155
171
  case "THINKING_TEXT_MESSAGE_START":
156
172
  case "REASONING_START":
157
173
  case "REASONING_MESSAGE_START":
158
- this.handleReasoningStart("messageId" in event ? event.messageId : void 0, event.type === "REASONING_MESSAGE_START");
174
+ this.handleReasoningStart(this.scopeOf("subagentRunId" in event ? event : {}), "messageId" in event ? event.messageId : void 0, event.type === "REASONING_MESSAGE_START");
159
175
  break;
160
176
  case "REASONING_ENCRYPTED_VALUE":
161
177
  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;
178
+ const scope = this.scopeOf(event);
179
+ const active = this.activeReasoningKeyByScope.get(scope);
180
+ const entityKey = this.partKey(scope, event.entityId);
181
+ const key = this.showThinking ? this.reasoningParts.has(entityKey) ? entityKey : active !== void 0 && this.anonymousReasoningKeys.has(active) ? active : void 0 : void 0;
164
182
  if (key !== void 0) {
165
183
  this.reasoningSignatures.set(key, event.encryptedValue);
166
184
  this.reasoningSignatureIds.set(key, event.entityId);
167
185
  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")) {
186
+ } 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
187
  this.hiddenSignatures.set(event.entityId, event.encryptedValue);
170
188
  this.hiddenSignatureAnchors.set(event.entityId, this.hiddenBlockAnchors.get(event.entityId) ?? this.hiddenAnonymousAnchor ?? this.partOrder.length);
171
189
  this.emit();
@@ -173,29 +191,33 @@ var RunAggregator = class {
173
191
  }
174
192
  break;
175
193
  case "THINKING_TEXT_MESSAGE_CONTENT":
176
- this.handleReasoningContent(event.delta);
194
+ this.handleReasoningContent(ROOT_SCOPE, event.delta);
177
195
  this.totalChunks++;
178
196
  this.recordFirstToken();
179
197
  break;
180
198
  case "REASONING_MESSAGE_CONTENT":
181
- this.handleReasoningContent(event.delta, "messageId" in event ? event.messageId : void 0, true);
199
+ this.handleReasoningContent(this.scopeOf(event), event.delta, "messageId" in event ? event.messageId : void 0, true);
182
200
  this.totalChunks++;
183
201
  this.recordFirstToken();
184
202
  break;
185
203
  case "THINKING_TEXT_MESSAGE_END":
186
204
  case "THINKING_END":
205
+ this.handleReasoningEnd(ROOT_SCOPE);
206
+ break;
187
207
  case "REASONING_MESSAGE_END":
188
208
  case "REASONING_END":
189
- this.handleReasoningEnd();
209
+ this.handleReasoningEnd(this.scopeOf(event));
190
210
  break;
191
- case "TOOL_CALL_START":
192
- this.reportServerMessageId(event.parentMessageId);
193
- this.startToolCall(event.toolCallId, event.toolCallName, event.parentMessageId);
211
+ case "TOOL_CALL_START": {
212
+ const scope = this.scopeOf(event);
213
+ if (scope === ROOT_SCOPE) this.reportServerMessageId(event.parentMessageId);
214
+ this.startToolCall(scope, event.toolCallId, event.toolCallName, event.parentMessageId);
194
215
  this.emit();
195
216
  break;
217
+ }
196
218
  case "TOOL_CALL_ARGS":
197
219
  case "TOOL_CALL_CHUNK":
198
- if (event.type === "TOOL_CALL_CHUNK") this.reportServerMessageId(event.parentMessageId);
220
+ if (event.type === "TOOL_CALL_CHUNK" && this.scopeOf(event) === ROOT_SCOPE) this.reportServerMessageId(event.parentMessageId);
199
221
  if (!event.delta) break;
200
222
  this.appendToolArgs(event.toolCallId, event.delta);
201
223
  this.emit();
@@ -204,7 +226,7 @@ var RunAggregator = class {
204
226
  this.emit();
205
227
  break;
206
228
  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);
229
+ 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
230
  this.emit();
209
231
  break;
210
232
  case "ACTIVITY_SNAPSHOT": {
@@ -213,8 +235,10 @@ var RunAggregator = class {
213
235
  break;
214
236
  }
215
237
  if (event.activityType !== "mcp-apps") break;
238
+ const activityScope = this.scopeOf(event);
216
239
  const toolCallId = event.content.toolCallId;
217
- const entry = typeof toolCallId === "string" ? this.toolCalls.get(toolCallId) : this.lastResolvedToolCallId ? this.toolCalls.get(this.lastResolvedToolCallId) : void 0;
240
+ const fallbackId = this.lastResolvedToolCallIdByScope.get(activityScope);
241
+ const entry = typeof toolCallId === "string" ? this.toolCalls.get(toolCallId) : fallbackId ? this.toolCalls.get(fallbackId) : void 0;
218
242
  const resourceUri = event.content.resourceUri;
219
243
  if (entry && typeof resourceUri === "string" && isMcpAppUri(resourceUri)) {
220
244
  entry.mcpAppResourceUri = resourceUri;
@@ -236,6 +260,58 @@ var RunAggregator = class {
236
260
  }
237
261
  break;
238
262
  }
263
+ case "SUBAGENT_STARTED": {
264
+ const existing = this.subagentRuns.get(event.subagentRunId);
265
+ if (existing) {
266
+ existing.name = event.name;
267
+ if (event.description !== void 0) existing.description = event.description;
268
+ existing.status = { type: "running" };
269
+ this.emit();
270
+ break;
271
+ }
272
+ this.nestingMemo = void 0;
273
+ this.subagentRuns.set(event.subagentRunId, {
274
+ subagentRunId: event.subagentRunId,
275
+ name: event.name,
276
+ ...event.description !== void 0 ? { description: event.description } : {},
277
+ ...event.parentSubagentRunId !== void 0 ? { parentSubagentRunId: event.parentSubagentRunId } : {},
278
+ ...event.parentToolCallId !== void 0 ? { parentToolCallId: event.parentToolCallId } : {},
279
+ ...event.parentMessageId !== void 0 ? { parentMessageId: event.parentMessageId } : {},
280
+ createdAt: /* @__PURE__ */ new Date(),
281
+ status: { type: "running" }
282
+ });
283
+ this.emit();
284
+ break;
285
+ }
286
+ case "SUBAGENT_FINISHED": {
287
+ const run = this.subagentRuns.get(event.subagentRunId);
288
+ if (run) {
289
+ run.status = event.outcome?.type === "suspended" ? {
290
+ type: "requires-action",
291
+ reason: "interrupt"
292
+ } : {
293
+ type: "complete",
294
+ reason: "unknown"
295
+ };
296
+ if (event.result !== void 0) run.result = event.result;
297
+ if (event.outcome?.type === "suspended" && event.outcome.interruptIds) run.interruptIds = event.outcome.interruptIds;
298
+ }
299
+ this.emit();
300
+ break;
301
+ }
302
+ case "SUBAGENT_ERROR": {
303
+ const run = this.subagentRuns.get(event.subagentRunId);
304
+ if (run) {
305
+ run.status = {
306
+ type: "incomplete",
307
+ reason: "error",
308
+ ...event.message !== void 0 ? { error: event.message } : {}
309
+ };
310
+ if (event.code !== void 0) run.errorCode = event.code;
311
+ }
312
+ this.emit();
313
+ break;
314
+ }
239
315
  default: this.logger.debug?.("[agui] aggregator ignored event", event);
240
316
  }
241
317
  }
@@ -274,11 +350,13 @@ var RunAggregator = class {
274
350
  kind: "tool-call",
275
351
  toolCallId
276
352
  });
353
+ this.nestingMemo = void 0;
277
354
  this.toolCalls.set(toolCallId, entry);
278
355
  this.a2uiToolCallIds.add(toolCallId);
279
356
  }
280
357
  for (const toolCallId of this.a2uiToolCallIds) {
281
358
  if (activeToolCallIds.has(toolCallId)) continue;
359
+ this.nestingMemo = void 0;
282
360
  this.toolCalls.delete(toolCallId);
283
361
  const partIndex = this.partOrder.findIndex((part) => part.kind === "tool-call" && part.toolCallId === toolCallId);
284
362
  if (partIndex !== -1) this.partOrder.splice(partIndex, 1);
@@ -310,17 +388,28 @@ var RunAggregator = class {
310
388
  this.hiddenActiveReasoning = "none";
311
389
  this.loggedDroppedOpaqueIds.clear();
312
390
  this.anonymousReasoningKeys.clear();
313
- this.activeReasoningKey = void 0;
391
+ this.activeReasoningKeyByScope.clear();
392
+ this.nestingMemo = void 0;
393
+ this.subagentRuns.clear();
314
394
  this.reasoningPartCounter = 0;
315
395
  this.toolCalls.clear();
316
396
  this.a2uiBuckets.clear();
317
397
  this.a2uiToolCallIds.clear();
318
- this.lastResolvedToolCallId = void 0;
398
+ this.lastResolvedToolCallIdByScope.clear();
319
399
  this.partOrder.length = 0;
320
400
  this.textPartCounter = 0;
321
- this.activeTextMessageId = void 0;
401
+ this.activeTextMessageIdByScope.clear();
322
402
  this.reportedServerMessageId = void 0;
323
403
  }
404
+ closeOpenSubagentRuns(status) {
405
+ for (const run of this.subagentRuns.values()) if (run.status.type === "running") run.status = status;
406
+ }
407
+ scopeOf(event) {
408
+ return event.subagentRunId ?? ROOT_SCOPE;
409
+ }
410
+ partKey(scope, id) {
411
+ return scope === ROOT_SCOPE ? id : `${scope}\u0000${id}`;
412
+ }
324
413
  beginDistinctTextMessage(messageId) {
325
414
  if (!messageId || this.lastTextMessageId === void 0 || this.lastTextMessageId === messageId || !this.onTextMessageStart) return;
326
415
  this.resetMessageParts();
@@ -330,43 +419,48 @@ var RunAggregator = class {
330
419
  this.textPartCounter += 1;
331
420
  return `text-${this.textPartCounter}`;
332
421
  }
333
- startTextMessage(messageId) {
422
+ startTextMessage(scope, messageId) {
334
423
  const id = messageId ?? this.generateTextKey();
335
- this.ensureTextPart(id);
336
- this.activeTextMessageId = id;
337
- return id;
424
+ const key = this.ensureTextPart(scope, id);
425
+ this.activeTextMessageIdByScope.set(scope, key);
426
+ return key;
338
427
  }
339
- resolveTextMessageId(messageId) {
428
+ resolveTextMessageId(scope, messageId) {
340
429
  if (messageId) {
341
- this.ensureTextPart(messageId);
342
- this.activeTextMessageId = messageId;
343
- return messageId;
430
+ const key = this.ensureTextPart(scope, messageId);
431
+ this.activeTextMessageIdByScope.set(scope, key);
432
+ return key;
344
433
  }
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({
434
+ const active = this.activeTextMessageIdByScope.get(scope);
435
+ if (active) return active;
436
+ const key = this.ensureTextPart(scope, this.generateTextKey());
437
+ this.activeTextMessageIdByScope.set(scope, key);
438
+ return key;
439
+ }
440
+ ensureTextPart(scope, id) {
441
+ const key = this.partKey(scope, id);
442
+ if (!this.textParts.has(key)) {
443
+ this.textParts.set(key, { buffer: "" });
444
+ if (!this.partOrder.some((part) => part.kind === "text" && part.key === key)) this.partOrder.push(scope === ROOT_SCOPE ? {
355
445
  kind: "text",
356
- key: id
446
+ key
447
+ } : {
448
+ kind: "text",
449
+ key,
450
+ subagentRunId: scope
357
451
  });
358
452
  }
453
+ return key;
359
454
  }
360
455
  appendText(id, delta) {
361
- this.ensureTextPart(id);
362
456
  const entry = this.textParts.get(id);
363
457
  if (!entry) return;
364
458
  entry.buffer += delta;
365
459
  }
366
- startToolCall(id, name, parentMessageId) {
460
+ startToolCall(scope, id, name, parentMessageId) {
367
461
  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);
462
+ this.activeTextMessageIdByScope.delete(scope);
463
+ if (!this.partOrder.some((part) => part.kind === "tool-call" && part.toolCallId === id)) this.insertToolPart(scope, id, parentMessageId);
370
464
  const state = {
371
465
  toolCallId: id,
372
466
  toolCallName: name ?? "tool",
@@ -377,15 +471,18 @@ var RunAggregator = class {
377
471
  snapshotResultApplied: false
378
472
  };
379
473
  if (parentMessageId) state.parentMessageId = parentMessageId;
474
+ if (scope !== ROOT_SCOPE) state.subagentRunId = scope;
475
+ this.nestingMemo = void 0;
380
476
  this.toolCalls.set(id, state);
381
477
  }
382
- insertToolPart(id, parentMessageId) {
478
+ insertToolPart(scope, id, parentMessageId) {
383
479
  const entry = {
384
480
  kind: "tool-call",
385
481
  toolCallId: id
386
482
  };
387
483
  if (parentMessageId) {
388
- const parentIndex = this.partOrder.findIndex((part) => part.kind === "text" && part.key === parentMessageId);
484
+ const parentKey = this.partKey(scope, parentMessageId);
485
+ const parentIndex = this.partOrder.findIndex((part) => part.kind === "text" && part.key === parentKey);
389
486
  if (parentIndex !== -1) {
390
487
  let insertAt = parentIndex + 1;
391
488
  while (insertAt < this.partOrder.length) {
@@ -414,7 +511,7 @@ var RunAggregator = class {
414
511
  entry.parsedArgs = void 0;
415
512
  }
416
513
  }
417
- finishToolCall(id, content, isError, toolMessageId, mcpResult) {
514
+ finishToolCall(scope, id, content, isError, toolMessageId, mcpResult) {
418
515
  if (!id) return;
419
516
  let entry = this.toolCalls.get(id);
420
517
  if (!entry) {
@@ -425,8 +522,10 @@ var RunAggregator = class {
425
522
  parsedArgs: void 0,
426
523
  result: void 0,
427
524
  isError: void 0,
428
- snapshotResultApplied: false
525
+ snapshotResultApplied: false,
526
+ ...scope !== ROOT_SCOPE ? { subagentRunId: scope } : {}
429
527
  };
528
+ this.nestingMemo = void 0;
430
529
  this.toolCalls.set(id, entry);
431
530
  }
432
531
  if (!this.partOrder.some((part) => part.kind === "tool-call" && part.toolCallId === id)) this.partOrder.push({
@@ -456,43 +555,126 @@ var RunAggregator = class {
456
555
  entry.isError = isError;
457
556
  }
458
557
  if (toolMessageId) entry.toolMessageId = toolMessageId;
459
- this.lastResolvedToolCallId = id;
558
+ this.lastResolvedToolCallIdByScope.set(scope, id);
460
559
  }
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;
560
+ nesting() {
561
+ if (!this.nestingMemo) {
562
+ const subagentsByParentToolCallId = this.subagentsByParentToolCallId();
563
+ this.nestingMemo = {
564
+ subagentsByParentToolCallId,
565
+ reachable: this.reachableSubagentRunIds(subagentsByParentToolCallId)
566
+ };
567
+ }
568
+ return this.nestingMemo;
569
+ }
570
+ subagentsByParentToolCallId() {
571
+ const map = /* @__PURE__ */ new Map();
572
+ for (const run of this.subagentRuns.values()) {
573
+ if (!run.parentToolCallId) continue;
574
+ const list = map.get(run.parentToolCallId) ?? [];
575
+ list.push(run.subagentRunId);
576
+ map.set(run.parentToolCallId, list);
577
+ }
578
+ return map;
579
+ }
580
+ reachableSubagentRunIds(subagentsByParentToolCallId) {
581
+ const reachable = /* @__PURE__ */ new Set();
582
+ const walk = (scope, depth) => {
583
+ if (depth > MAX_SUBAGENT_DEPTH) return;
584
+ for (const entry of this.toolCalls.values()) {
585
+ if ((entry.subagentRunId ?? ROOT_SCOPE) !== scope) continue;
586
+ for (const id of subagentsByParentToolCallId.get(entry.toolCallId) ?? []) {
587
+ if (reachable.has(id)) continue;
588
+ reachable.add(id);
589
+ walk(id, depth + 1);
590
+ }
591
+ }
474
592
  };
475
- for (const [index, part] of this.partOrder.entries()) {
476
- currentIndex = index;
593
+ walk(ROOT_SCOPE, 0);
594
+ return reachable;
595
+ }
596
+ materializeSubagentMessage(subagentRunId, depth, ctx) {
597
+ const run = this.subagentRuns.get(subagentRunId);
598
+ if (!run || depth > MAX_SUBAGENT_DEPTH) return void 0;
599
+ const content = this.buildParts(subagentRunId, depth + 1, ctx);
600
+ return {
601
+ id: run.subagentRunId,
602
+ role: "assistant",
603
+ createdAt: run.createdAt,
604
+ status: run.status,
605
+ content,
606
+ metadata: {
607
+ unstable_state: null,
608
+ unstable_annotations: [],
609
+ unstable_data: [],
610
+ steps: [],
611
+ custom: { [AG_UI_METADATA_NAMESPACE]: {
612
+ name: run.name,
613
+ ...run.description !== void 0 ? { description: run.description } : {},
614
+ ...run.parentSubagentRunId !== void 0 ? { parentSubagentRunId: run.parentSubagentRunId } : {},
615
+ ...run.result !== void 0 ? { result: run.result } : {},
616
+ ...run.interruptIds !== void 0 ? { interruptIds: run.interruptIds } : {},
617
+ ...run.errorCode !== void 0 ? { errorCode: run.errorCode } : {}
618
+ } }
619
+ }
620
+ };
621
+ }
622
+ buildParts(scope, depth, ctx) {
623
+ const snapshot = [];
624
+ const isRoot = scope === ROOT_SCOPE;
625
+ for (const { index, part } of ctx.partsByScope.get(scope) ?? []) {
626
+ const materialized = () => {
627
+ if (isRoot && ctx.root) ctx.root.lastMaterializedIndex = index;
628
+ };
629
+ if (part.kind === "tool-call") {
630
+ const entry = this.toolCalls.get(part.toolCallId);
631
+ if (!entry) continue;
632
+ const approval = ctx.approvals.get(entry.toolCallId);
633
+ 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);
634
+ const toolPart = {
635
+ type: "tool-call",
636
+ toolCallId: entry.toolCallId,
637
+ toolName: entry.toolCallName,
638
+ args: entry.parsedArgs ?? {},
639
+ argsText: entry.argsText,
640
+ ...approval ? { approval } : {},
641
+ ...entry.result !== void 0 ? { result: entry.result } : {},
642
+ ...entry.modelContent !== void 0 ? { modelContent: entry.modelContent } : {},
643
+ ...entry.isError !== void 0 ? { isError: entry.isError } : {},
644
+ ...entry.mcpAppResourceUri ? { mcp: { app: {
645
+ resourceUri: entry.mcpAppResourceUri,
646
+ ...entry.mcpAppServerId ? { serverId: entry.mcpAppServerId } : {}
647
+ } } } : {},
648
+ ...entry.parentMessageId ? { parentId: entry.parentMessageId } : {},
649
+ ...entry.toolMessageId ? { unstable_toolMessageId: entry.toolMessageId } : {},
650
+ ...nestedMessages.length > 0 ? { messages: nestedMessages } : {}
651
+ };
652
+ snapshot.push(toolPart);
653
+ materialized();
654
+ continue;
655
+ }
656
+ const rawScope = "subagentRunId" in part ? part.subagentRunId ?? ROOT_SCOPE : ROOT_SCOPE;
477
657
  if (part.kind === "reasoning") {
478
658
  if (this.showThinking) {
479
659
  const buffer = this.reasoningParts.get(part.key) ?? "";
480
- if (buffer.length > 0 || this.activeReasoningKey === part.key) {
660
+ const isActive = this.activeReasoningKeyByScope.get(rawScope) === part.key;
661
+ if (buffer.length > 0 || isActive) {
481
662
  const encryptedValue = this.reasoningSignatures.get(part.key);
482
663
  const reasoningId = this.reasoningMessageIds.get(part.key);
483
664
  const meta = {
484
665
  ...reasoningId !== void 0 ? { reasoningId } : {},
485
666
  ...encryptedValue !== void 0 ? { encryptedValue } : {}
486
667
  };
487
- pushSnapshotPart({
668
+ snapshot.push({
488
669
  type: "reasoning",
489
670
  text: buffer,
490
671
  ...Object.keys(meta).length > 0 ? { providerMetadata: { [AG_UI_METADATA_NAMESPACE]: meta } } : {}
491
672
  });
492
- } else {
673
+ materialized();
674
+ } else if (isRoot && ctx.root) {
493
675
  const encryptedValue = this.reasoningSignatures.get(part.key);
494
676
  const id = this.reasoningSignatureIds.get(part.key);
495
- if (id?.trim() && encryptedValue?.trim()) opaqueCandidates.push({
677
+ if (id?.trim() && encryptedValue?.trim()) ctx.root.opaqueCandidates.push({
496
678
  id,
497
679
  encryptedValue,
498
680
  anchor: index + 1
@@ -503,41 +685,60 @@ var RunAggregator = class {
503
685
  }
504
686
  if (part.kind === "text") {
505
687
  const entry = this.textParts.get(part.key);
506
- if (entry && entry.buffer.trim().length > 0) pushSnapshotPart({
507
- type: "text",
508
- text: entry.buffer
509
- });
688
+ if (entry && entry.buffer.trim().length > 0) {
689
+ snapshot.push({
690
+ type: "text",
691
+ text: entry.buffer
692
+ });
693
+ materialized();
694
+ }
510
695
  continue;
511
696
  }
512
697
  if (part.kind === "data") {
513
- pushSnapshotPart({
698
+ snapshot.push({
514
699
  type: "data",
515
700
  name: part.name,
516
701
  data: part.value
517
702
  });
703
+ materialized();
518
704
  continue;
519
705
  }
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 } : {}
706
+ }
707
+ return snapshot;
708
+ }
709
+ emit() {
710
+ const { subagentsByParentToolCallId, reachable } = this.nesting();
711
+ const root = {
712
+ opaqueCandidates: Array.from(this.hiddenSignatures, ([id, encryptedValue]) => ({
713
+ id,
714
+ encryptedValue,
715
+ anchor: this.hiddenSignatureAnchors.get(id)
716
+ })),
717
+ lastMaterializedIndex: -1
718
+ };
719
+ const partsByScope = /* @__PURE__ */ new Map();
720
+ for (const [index, part] of this.partOrder.entries()) {
721
+ const rawScope = part.kind === "tool-call" ? this.toolCalls.get(part.toolCallId)?.subagentRunId ?? ROOT_SCOPE : "subagentRunId" in part ? part.subagentRunId ?? ROOT_SCOPE : ROOT_SCOPE;
722
+ const bucketScope = rawScope === ROOT_SCOPE || reachable.has(rawScope) ? rawScope : ROOT_SCOPE;
723
+ const bucket = partsByScope.get(bucketScope);
724
+ if (bucket) bucket.push({
725
+ index,
726
+ part
539
727
  });
728
+ else partsByScope.set(bucketScope, [{
729
+ index,
730
+ part
731
+ }]);
540
732
  }
733
+ const ctx = {
734
+ subagentsByParentToolCallId,
735
+ reachable,
736
+ partsByScope,
737
+ approvals: projectAgUiToolApprovals(this.status?.type === "requires-action" ? this.interrupts : void 0, new Set(Array.from(this.toolCalls.values()).map((entry) => entry.toolCallId))),
738
+ root
739
+ };
740
+ const snapshot = this.buildParts(ROOT_SCOPE, 0, ctx);
741
+ const { opaqueCandidates, lastMaterializedIndex } = root;
541
742
  const toolMessageIds = /* @__PURE__ */ new Set();
542
743
  for (const call of this.toolCalls.values()) if (call.toolMessageId) toolMessageIds.add(call.toolMessageId);
543
744
  opaqueCandidates.sort((a, b) => a.anchor - b.anchor);
@@ -587,8 +788,9 @@ var RunAggregator = class {
587
788
  toolCallCount: this.toolCalls.size
588
789
  };
589
790
  }
590
- handleReasoningStart(messageId, isMessageId = false) {
791
+ handleReasoningStart(scope, messageId, isMessageId = false) {
591
792
  if (!this.showThinking) {
793
+ if (scope !== ROOT_SCOPE) return;
592
794
  const anchor = this.partOrder.length;
593
795
  if (messageId === void 0) {
594
796
  this.hiddenActiveReasoning = "anonymous";
@@ -600,38 +802,43 @@ var RunAggregator = class {
600
802
  }
601
803
  return;
602
804
  }
603
- this.activeTextMessageId = void 0;
604
- const key = messageId ?? `__auto-reasoning-${++this.reasoningPartCounter}`;
805
+ this.activeTextMessageIdByScope.delete(scope);
806
+ const key = this.partKey(scope, messageId ?? `__auto-reasoning-${++this.reasoningPartCounter}`);
605
807
  if (messageId === void 0) this.anonymousReasoningKeys.add(key);
606
808
  else if (isMessageId) this.reasoningMessageIds.set(key, messageId);
607
809
  if (!this.reasoningParts.has(key)) {
608
810
  this.reasoningParts.set(key, "");
609
- this.partOrder.push({
811
+ this.partOrder.push(scope === ROOT_SCOPE ? {
610
812
  kind: "reasoning",
611
813
  key
814
+ } : {
815
+ kind: "reasoning",
816
+ key,
817
+ subagentRunId: scope
612
818
  });
613
819
  }
614
- this.activeReasoningKey = key;
820
+ this.activeReasoningKeyByScope.set(scope, key);
615
821
  this.emit();
616
822
  }
617
- handleReasoningContent(delta, messageId, isMessageId = false) {
823
+ handleReasoningContent(scope, delta, messageId, isMessageId = false) {
618
824
  if (!delta) return;
619
825
  if (!this.showThinking) {
620
- if (this.hiddenActiveReasoning === "none") this.handleReasoningStart(messageId, isMessageId);
826
+ if (scope !== ROOT_SCOPE) return;
827
+ if (this.hiddenActiveReasoning === "none") this.handleReasoningStart(scope, messageId, isMessageId);
621
828
  return;
622
829
  }
623
- if (!this.activeReasoningKey) this.handleReasoningStart(messageId, isMessageId);
624
- const key = this.activeReasoningKey;
830
+ if (!this.activeReasoningKeyByScope.has(scope)) this.handleReasoningStart(scope, messageId, isMessageId);
831
+ const key = this.activeReasoningKeyByScope.get(scope);
625
832
  if (!key) return;
626
833
  this.reasoningParts.set(key, (this.reasoningParts.get(key) ?? "") + delta);
627
834
  this.emit();
628
835
  }
629
- handleReasoningEnd() {
836
+ handleReasoningEnd(scope) {
630
837
  if (!this.showThinking) {
631
- this.hiddenActiveReasoning = "none";
838
+ if (scope === ROOT_SCOPE) this.hiddenActiveReasoning = "none";
632
839
  return;
633
840
  }
634
- this.activeReasoningKey = void 0;
841
+ this.activeReasoningKeyByScope.delete(scope);
635
842
  this.emit();
636
843
  }
637
844
  };