@assistant-ui/react-ag-ui 0.0.56 → 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 (42) hide show
  1. package/README.md +16 -0
  2. package/dist/runtime/AgUiThreadRuntimeCore.d.ts +5 -17
  3. package/dist/runtime/AgUiThreadRuntimeCore.d.ts.map +1 -1
  4. package/dist/runtime/AgUiThreadRuntimeCore.js +133 -234
  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 +54 -25
  8. package/dist/runtime/adapter/conversions.js.map +1 -1
  9. package/dist/runtime/adapter/run-aggregator.d.ts +31 -4
  10. package/dist/runtime/adapter/run-aggregator.d.ts.map +1 -1
  11. package/dist/runtime/adapter/run-aggregator.js +419 -123
  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 +12 -9
  27. package/dist/useAgUiRuntime.js.map +1 -1
  28. package/package.json +10 -10
  29. package/src/runtime/AgUiThreadRuntimeCore.test.ts +105 -0
  30. package/src/runtime/AgUiThreadRuntimeCore.ts +190 -319
  31. package/src/runtime/adapter/conversions.ts +128 -36
  32. package/src/runtime/adapter/run-aggregator.approval.test.ts +128 -0
  33. package/src/runtime/adapter/run-aggregator.ts +670 -148
  34. package/src/runtime/adapter/subscriber.ts +10 -1
  35. package/src/runtime/adapter/tool-approval.test.ts +75 -0
  36. package/src/runtime/adapter/tool-approval.ts +17 -12
  37. package/src/runtime/event-parser.ts +216 -36
  38. package/src/runtime/types.ts +70 -12
  39. package/src/useAgUiRuntime.approval.test.tsx +34 -24
  40. package/src/useAgUiRuntime.history.test.tsx +76 -0
  41. package/src/useAgUiRuntime.toolDeferral.test.tsx +186 -0
  42. package/src/useAgUiRuntime.ts +12 -9
@@ -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);
@@ -23,23 +25,37 @@ var RunAggregator = class {
23
25
  showThinking;
24
26
  logger;
25
27
  onServerMessageId;
28
+ onTextMessageStart;
26
29
  status;
27
30
  interrupts;
28
31
  textParts = /* @__PURE__ */ new Map();
29
- activeTextMessageId;
32
+ activeTextMessageIdByScope = /* @__PURE__ */ new Map();
30
33
  reasoningParts = /* @__PURE__ */ new Map();
31
34
  reasoningSignatures = /* @__PURE__ */ new Map();
35
+ reasoningSignatureIds = /* @__PURE__ */ new Map();
36
+ hiddenSignatures = /* @__PURE__ */ new Map();
37
+ hiddenSignatureAnchors = /* @__PURE__ */ new Map();
38
+ hiddenBlockAnchors = /* @__PURE__ */ new Map();
39
+ hiddenAnonymousAnchor;
40
+ hiddenReasoningIds = /* @__PURE__ */ new Set();
41
+ hiddenActiveReasoning = "none";
42
+ hasEmittedOpaqueReasoning = false;
43
+ loggedDroppedOpaqueIds = /* @__PURE__ */ new Set();
32
44
  reasoningMessageIds = /* @__PURE__ */ new Map();
33
45
  anonymousReasoningKeys = /* @__PURE__ */ new Set();
34
- activeReasoningKey;
46
+ activeReasoningKeyByScope = /* @__PURE__ */ new Map();
47
+ subagentRuns = /* @__PURE__ */ new Map();
48
+ nestingMemo;
35
49
  reasoningPartCounter = 0;
36
50
  toolCalls = /* @__PURE__ */ new Map();
37
51
  a2uiBuckets = /* @__PURE__ */ new Map();
38
52
  a2uiToolCallIds = /* @__PURE__ */ new Set();
39
- lastResolvedToolCallId;
53
+ lastResolvedToolCallIdByScope = /* @__PURE__ */ new Map();
40
54
  partOrder = [];
41
55
  textPartCounter = 0;
42
56
  serverMessageIdReported = false;
57
+ reportedServerMessageId;
58
+ lastTextMessageId;
43
59
  streamStartTime;
44
60
  firstTokenTime;
45
61
  totalChunks = 0;
@@ -48,6 +64,7 @@ var RunAggregator = class {
48
64
  this.showThinking = options.showThinking;
49
65
  this.logger = options.logger;
50
66
  this.onServerMessageId = options.onServerMessageId;
67
+ this.onTextMessageStart = options.onTextMessageStart;
51
68
  }
52
69
  hasToolCall(toolCallId) {
53
70
  return this.toolCalls.has(toolCallId);
@@ -55,22 +72,11 @@ var RunAggregator = class {
55
72
  handle(event) {
56
73
  switch (event.type) {
57
74
  case "RUN_STARTED":
58
- this.clearTextParts();
59
- this.reasoningParts.clear();
60
- this.reasoningSignatures.clear();
61
- this.reasoningMessageIds.clear();
62
- this.anonymousReasoningKeys.clear();
63
- this.activeReasoningKey = void 0;
64
- this.reasoningPartCounter = 0;
65
- this.toolCalls.clear();
66
- this.a2uiBuckets.clear();
67
- this.a2uiToolCallIds.clear();
68
- this.lastResolvedToolCallId = void 0;
69
- this.partOrder.length = 0;
70
- this.textPartCounter = 0;
71
- this.activeTextMessageId = void 0;
75
+ this.resetMessageParts();
72
76
  this.interrupts = void 0;
73
77
  this.serverMessageIdReported = false;
78
+ this.lastTextMessageId = void 0;
79
+ this.reportedServerMessageId = void 0;
74
80
  this.streamStartTime = Date.now();
75
81
  this.firstTokenTime = void 0;
76
82
  this.totalChunks = 0;
@@ -89,13 +95,14 @@ var RunAggregator = class {
89
95
  }
90
96
  this.interrupts = void 0;
91
97
  const hasUnresolvedToolCalls = Array.from(this.toolCalls.values()).some((tc) => tc.result === void 0);
92
- this.status = event.outcome?.type === "success" || !hasUnresolvedToolCalls ? {
93
- type: "complete",
94
- reason: "unknown"
95
- } : {
98
+ this.status = hasUnresolvedToolCalls ? {
96
99
  type: "requires-action",
97
100
  reason: "tool-calls"
101
+ } : {
102
+ type: "complete",
103
+ reason: "unknown"
98
104
  };
105
+ this.closeOpenSubagentRuns(this.status);
99
106
  this.emit();
100
107
  break;
101
108
  }
@@ -105,6 +112,7 @@ var RunAggregator = class {
105
112
  reason: "error",
106
113
  ...event.message !== void 0 ? { error: event.message } : {}
107
114
  };
115
+ this.closeOpenSubagentRuns(this.status);
108
116
  this.emit();
109
117
  break;
110
118
  case "RUN_CANCELLED":
@@ -112,34 +120,46 @@ var RunAggregator = class {
112
120
  type: "incomplete",
113
121
  reason: "cancelled"
114
122
  };
123
+ this.closeOpenSubagentRuns(this.status);
115
124
  this.emit();
116
125
  break;
117
126
  case "TEXT_MESSAGE_START": {
118
- this.reportServerMessageId(event.messageId);
119
- const id = this.startTextMessage(event.messageId);
120
- if (id) this.markTextPartTouched(id);
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);
121
134
  this.emit();
122
135
  break;
123
136
  }
124
137
  case "TEXT_MESSAGE_CONTENT":
125
138
  case "TEXT_MESSAGE_CHUNK": {
126
139
  const incomingId = "messageId" in event ? event.messageId : void 0;
127
- this.reportServerMessageId(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
+ }
128
146
  if (!event.delta) break;
129
147
  this.recordFirstToken();
130
- const id = this.resolveTextMessageId(incomingId);
148
+ const id = this.resolveTextMessageId(scope, incomingId);
131
149
  this.appendText(id, event.delta);
132
150
  this.totalChunks++;
133
151
  this.emit();
134
152
  break;
135
153
  }
136
- case "TEXT_MESSAGE_END":
137
- this.reportServerMessageId(event.messageId);
138
- 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);
139
158
  this.emit();
140
159
  break;
160
+ }
141
161
  case "CUSTOM":
142
- this.activeTextMessageId = void 0;
162
+ this.activeTextMessageIdByScope.delete(ROOT_SCOPE);
143
163
  this.partOrder.push({
144
164
  kind: "data",
145
165
  name: event.name,
@@ -151,42 +171,53 @@ var RunAggregator = class {
151
171
  case "THINKING_TEXT_MESSAGE_START":
152
172
  case "REASONING_START":
153
173
  case "REASONING_MESSAGE_START":
154
- 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");
155
175
  break;
156
176
  case "REASONING_ENCRYPTED_VALUE":
157
177
  if (event.subtype === "message") {
158
- const active = this.activeReasoningKey;
159
- const key = this.reasoningParts.has(event.entityId) ? event.entityId : active !== void 0 && this.anonymousReasoningKeys.has(active) ? active : 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;
160
182
  if (key !== void 0) {
161
183
  this.reasoningSignatures.set(key, event.encryptedValue);
184
+ this.reasoningSignatureIds.set(key, event.entityId);
185
+ this.emit();
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")) {
187
+ this.hiddenSignatures.set(event.entityId, event.encryptedValue);
188
+ this.hiddenSignatureAnchors.set(event.entityId, this.hiddenBlockAnchors.get(event.entityId) ?? this.hiddenAnonymousAnchor ?? this.partOrder.length);
162
189
  this.emit();
163
190
  }
164
191
  }
165
192
  break;
166
193
  case "THINKING_TEXT_MESSAGE_CONTENT":
167
- this.handleReasoningContent(event.delta);
194
+ this.handleReasoningContent(ROOT_SCOPE, event.delta);
168
195
  this.totalChunks++;
169
196
  this.recordFirstToken();
170
197
  break;
171
198
  case "REASONING_MESSAGE_CONTENT":
172
- 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);
173
200
  this.totalChunks++;
174
201
  this.recordFirstToken();
175
202
  break;
176
203
  case "THINKING_TEXT_MESSAGE_END":
177
204
  case "THINKING_END":
205
+ this.handleReasoningEnd(ROOT_SCOPE);
206
+ break;
178
207
  case "REASONING_MESSAGE_END":
179
208
  case "REASONING_END":
180
- this.handleReasoningEnd();
209
+ this.handleReasoningEnd(this.scopeOf(event));
181
210
  break;
182
- case "TOOL_CALL_START":
183
- this.reportServerMessageId(event.parentMessageId);
184
- 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);
185
215
  this.emit();
186
216
  break;
217
+ }
187
218
  case "TOOL_CALL_ARGS":
188
219
  case "TOOL_CALL_CHUNK":
189
- 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);
190
221
  if (!event.delta) break;
191
222
  this.appendToolArgs(event.toolCallId, event.delta);
192
223
  this.emit();
@@ -195,7 +226,7 @@ var RunAggregator = class {
195
226
  this.emit();
196
227
  break;
197
228
  case "TOOL_CALL_RESULT":
198
- 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);
199
230
  this.emit();
200
231
  break;
201
232
  case "ACTIVITY_SNAPSHOT": {
@@ -204,8 +235,10 @@ var RunAggregator = class {
204
235
  break;
205
236
  }
206
237
  if (event.activityType !== "mcp-apps") break;
238
+ const activityScope = this.scopeOf(event);
207
239
  const toolCallId = event.content.toolCallId;
208
- 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;
209
242
  const resourceUri = event.content.resourceUri;
210
243
  if (entry && typeof resourceUri === "string" && isMcpAppUri(resourceUri)) {
211
244
  entry.mcpAppResourceUri = resourceUri;
@@ -227,6 +260,58 @@ var RunAggregator = class {
227
260
  }
228
261
  break;
229
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
+ }
230
315
  default: this.logger.debug?.("[agui] aggregator ignored event", event);
231
316
  }
232
317
  }
@@ -265,11 +350,13 @@ var RunAggregator = class {
265
350
  kind: "tool-call",
266
351
  toolCallId
267
352
  });
353
+ this.nestingMemo = void 0;
268
354
  this.toolCalls.set(toolCallId, entry);
269
355
  this.a2uiToolCallIds.add(toolCallId);
270
356
  }
271
357
  for (const toolCallId of this.a2uiToolCallIds) {
272
358
  if (activeToolCallIds.has(toolCallId)) continue;
359
+ this.nestingMemo = void 0;
273
360
  this.toolCalls.delete(toolCallId);
274
361
  const partIndex = this.partOrder.findIndex((part) => part.kind === "tool-call" && part.toolCallId === toolCallId);
275
362
  if (partIndex !== -1) this.partOrder.splice(partIndex, 1);
@@ -277,63 +364,103 @@ var RunAggregator = class {
277
364
  }
278
365
  }
279
366
  reportServerMessageId(messageId) {
280
- if (this.serverMessageIdReported || !messageId) return;
367
+ if (!messageId) return;
368
+ if (this.lastTextMessageId === void 0) this.lastTextMessageId = messageId;
369
+ if (this.serverMessageIdReported) return;
281
370
  this.serverMessageIdReported = true;
371
+ this.reportedServerMessageId = messageId;
282
372
  this.onServerMessageId?.(messageId);
283
373
  }
284
374
  clearTextParts() {
285
375
  this.textParts.clear();
286
376
  }
377
+ resetMessageParts() {
378
+ this.clearTextParts();
379
+ this.reasoningParts.clear();
380
+ this.reasoningSignatures.clear();
381
+ this.reasoningSignatureIds.clear();
382
+ this.reasoningMessageIds.clear();
383
+ this.hiddenSignatures.clear();
384
+ this.hiddenSignatureAnchors.clear();
385
+ this.hiddenBlockAnchors.clear();
386
+ this.hiddenAnonymousAnchor = void 0;
387
+ this.hiddenReasoningIds.clear();
388
+ this.hiddenActiveReasoning = "none";
389
+ this.loggedDroppedOpaqueIds.clear();
390
+ this.anonymousReasoningKeys.clear();
391
+ this.activeReasoningKeyByScope.clear();
392
+ this.nestingMemo = void 0;
393
+ this.subagentRuns.clear();
394
+ this.reasoningPartCounter = 0;
395
+ this.toolCalls.clear();
396
+ this.a2uiBuckets.clear();
397
+ this.a2uiToolCallIds.clear();
398
+ this.lastResolvedToolCallIdByScope.clear();
399
+ this.partOrder.length = 0;
400
+ this.textPartCounter = 0;
401
+ this.activeTextMessageIdByScope.clear();
402
+ this.reportedServerMessageId = void 0;
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
+ }
413
+ beginDistinctTextMessage(messageId) {
414
+ if (!messageId || this.lastTextMessageId === void 0 || this.lastTextMessageId === messageId || !this.onTextMessageStart) return;
415
+ this.resetMessageParts();
416
+ this.onTextMessageStart(messageId);
417
+ }
287
418
  generateTextKey() {
288
419
  this.textPartCounter += 1;
289
420
  return `text-${this.textPartCounter}`;
290
421
  }
291
- startTextMessage(messageId) {
422
+ startTextMessage(scope, messageId) {
292
423
  const id = messageId ?? this.generateTextKey();
293
- this.ensureTextPart(id);
294
- this.activeTextMessageId = id;
295
- return id;
424
+ const key = this.ensureTextPart(scope, id);
425
+ this.activeTextMessageIdByScope.set(scope, key);
426
+ return key;
296
427
  }
297
- resolveTextMessageId(messageId) {
428
+ resolveTextMessageId(scope, messageId) {
298
429
  if (messageId) {
299
- this.ensureTextPart(messageId);
300
- this.activeTextMessageId = messageId;
301
- return messageId;
430
+ const key = this.ensureTextPart(scope, messageId);
431
+ this.activeTextMessageIdByScope.set(scope, key);
432
+ return key;
302
433
  }
303
- if (this.activeTextMessageId) return this.activeTextMessageId;
304
- const generated = this.generateTextKey();
305
- this.ensureTextPart(generated);
306
- this.activeTextMessageId = generated;
307
- return generated;
308
- }
309
- ensureTextPart(id) {
310
- if (!this.textParts.has(id)) {
311
- this.textParts.set(id, {
312
- buffer: "",
313
- touched: false
314
- });
315
- 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 ? {
445
+ kind: "text",
446
+ key
447
+ } : {
316
448
  kind: "text",
317
- key: id
449
+ key,
450
+ subagentRunId: scope
318
451
  });
319
452
  }
320
- }
321
- markTextPartTouched(id) {
322
- const entry = this.textParts.get(id);
323
- if (!entry) return;
324
- entry.touched = true;
453
+ return key;
325
454
  }
326
455
  appendText(id, delta) {
327
- this.ensureTextPart(id);
328
456
  const entry = this.textParts.get(id);
329
457
  if (!entry) return;
330
458
  entry.buffer += delta;
331
- entry.touched = true;
332
459
  }
333
- startToolCall(id, name, parentMessageId) {
460
+ startToolCall(scope, id, name, parentMessageId) {
334
461
  if (!id) return;
335
- this.activeTextMessageId = void 0;
336
- 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);
337
464
  const state = {
338
465
  toolCallId: id,
339
466
  toolCallName: name ?? "tool",
@@ -344,15 +471,18 @@ var RunAggregator = class {
344
471
  snapshotResultApplied: false
345
472
  };
346
473
  if (parentMessageId) state.parentMessageId = parentMessageId;
474
+ if (scope !== ROOT_SCOPE) state.subagentRunId = scope;
475
+ this.nestingMemo = void 0;
347
476
  this.toolCalls.set(id, state);
348
477
  }
349
- insertToolPart(id, parentMessageId) {
478
+ insertToolPart(scope, id, parentMessageId) {
350
479
  const entry = {
351
480
  kind: "tool-call",
352
481
  toolCallId: id
353
482
  };
354
483
  if (parentMessageId) {
355
- 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);
356
486
  if (parentIndex !== -1) {
357
487
  let insertAt = parentIndex + 1;
358
488
  while (insertAt < this.partOrder.length) {
@@ -381,7 +511,7 @@ var RunAggregator = class {
381
511
  entry.parsedArgs = void 0;
382
512
  }
383
513
  }
384
- finishToolCall(id, content, isError, toolMessageId, mcpResult) {
514
+ finishToolCall(scope, id, content, isError, toolMessageId, mcpResult) {
385
515
  if (!id) return;
386
516
  let entry = this.toolCalls.get(id);
387
517
  if (!entry) {
@@ -392,8 +522,10 @@ var RunAggregator = class {
392
522
  parsedArgs: void 0,
393
523
  result: void 0,
394
524
  isError: void 0,
395
- snapshotResultApplied: false
525
+ snapshotResultApplied: false,
526
+ ...scope !== ROOT_SCOPE ? { subagentRunId: scope } : {}
396
527
  };
528
+ this.nestingMemo = void 0;
397
529
  this.toolCalls.set(id, entry);
398
530
  }
399
531
  if (!this.partOrder.some((part) => part.kind === "tool-call" && part.toolCallId === id)) this.partOrder.push({
@@ -423,16 +555,110 @@ var RunAggregator = class {
423
555
  entry.isError = isError;
424
556
  }
425
557
  if (toolMessageId) entry.toolMessageId = toolMessageId;
426
- this.lastResolvedToolCallId = id;
558
+ this.lastResolvedToolCallIdByScope.set(scope, id);
427
559
  }
428
- emit() {
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
+ }
592
+ };
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) {
429
623
  const snapshot = [];
430
- const approvals = projectAgUiToolApprovals(this.status?.type === "requires-action" ? this.interrupts : void 0, new Set(this.toolCalls.keys()));
431
- for (const part of this.partOrder) {
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;
432
657
  if (part.kind === "reasoning") {
433
658
  if (this.showThinking) {
434
659
  const buffer = this.reasoningParts.get(part.key) ?? "";
435
- if (buffer.length > 0 || this.activeReasoningKey === part.key) {
660
+ const isActive = this.activeReasoningKeyByScope.get(rawScope) === part.key;
661
+ if (buffer.length > 0 || isActive) {
436
662
  const encryptedValue = this.reasoningSignatures.get(part.key);
437
663
  const reasoningId = this.reasoningMessageIds.get(part.key);
438
664
  const meta = {
@@ -444,16 +670,28 @@ var RunAggregator = class {
444
670
  text: buffer,
445
671
  ...Object.keys(meta).length > 0 ? { providerMetadata: { [AG_UI_METADATA_NAMESPACE]: meta } } : {}
446
672
  });
673
+ materialized();
674
+ } else if (isRoot && ctx.root) {
675
+ const encryptedValue = this.reasoningSignatures.get(part.key);
676
+ const id = this.reasoningSignatureIds.get(part.key);
677
+ if (id?.trim() && encryptedValue?.trim()) ctx.root.opaqueCandidates.push({
678
+ id,
679
+ encryptedValue,
680
+ anchor: index + 1
681
+ });
447
682
  }
448
683
  }
449
684
  continue;
450
685
  }
451
686
  if (part.kind === "text") {
452
687
  const entry = this.textParts.get(part.key);
453
- if (entry?.touched) snapshot.push({
454
- type: "text",
455
- text: entry.buffer
456
- });
688
+ if (entry && entry.buffer.trim().length > 0) {
689
+ snapshot.push({
690
+ type: "text",
691
+ text: entry.buffer
692
+ });
693
+ materialized();
694
+ }
457
695
  continue;
458
696
  }
459
697
  if (part.kind === "data") {
@@ -462,34 +700,68 @@ var RunAggregator = class {
462
700
  name: part.name,
463
701
  data: part.value
464
702
  });
703
+ materialized();
465
704
  continue;
466
705
  }
467
- const entry = this.toolCalls.get(part.toolCallId);
468
- if (!entry) continue;
469
- const approval = approvals.get(entry.toolCallId);
470
- const toolPart = {
471
- type: "tool-call",
472
- toolCallId: entry.toolCallId,
473
- toolName: entry.toolCallName,
474
- args: entry.parsedArgs ?? {},
475
- argsText: entry.argsText,
476
- ...approval ? { approval } : {},
477
- ...entry.result !== void 0 ? { result: entry.result } : {},
478
- ...entry.modelContent !== void 0 ? { modelContent: entry.modelContent } : {},
479
- ...entry.isError !== void 0 ? { isError: entry.isError } : {},
480
- ...entry.mcpAppResourceUri ? { mcp: { app: {
481
- resourceUri: entry.mcpAppResourceUri,
482
- ...entry.mcpAppServerId ? { serverId: entry.mcpAppServerId } : {}
483
- } } } : {},
484
- ...entry.parentMessageId ? { parentId: entry.parentMessageId } : {},
485
- ...entry.toolMessageId ? { unstable_toolMessageId: entry.toolMessageId } : {}
486
- };
487
- snapshot.push(toolPart);
488
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
727
+ });
728
+ else partsByScope.set(bucketScope, [{
729
+ index,
730
+ part
731
+ }]);
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;
742
+ const toolMessageIds = /* @__PURE__ */ new Set();
743
+ for (const call of this.toolCalls.values()) if (call.toolMessageId) toolMessageIds.add(call.toolMessageId);
744
+ opaqueCandidates.sort((a, b) => a.anchor - b.anchor);
745
+ const publishableOpaqueReasoning = opaqueCandidates.filter((entry) => {
746
+ const collides = this.textParts.has(entry.id) || entry.id === this.reportedServerMessageId || toolMessageIds.has(entry.id);
747
+ if (collides && !this.loggedDroppedOpaqueIds.has(entry.id)) {
748
+ this.loggedDroppedOpaqueIds.add(entry.id);
749
+ this.logger.debug?.("[agui] aggregator dropped opaque reasoning signature: id collides with a message id", entry.id);
750
+ }
751
+ return !collides;
752
+ }).map(({ anchor, ...entry }) => lastMaterializedIndex < anchor ? {
753
+ ...entry,
754
+ after: true
755
+ } : entry);
756
+ if (publishableOpaqueReasoning.length > 0) this.hasEmittedOpaqueReasoning = true;
757
+ const includeOpaque = this.hasEmittedOpaqueReasoning;
489
758
  const timing = this.getTiming();
490
759
  const metadata = {
491
760
  ...timing ? { timing } : {},
492
- ...this.interrupts ? { custom: { [AG_UI_METADATA_NAMESPACE]: { interrupts: this.interrupts } } } : {}
761
+ ...this.interrupts || includeOpaque ? { custom: { [AG_UI_METADATA_NAMESPACE]: {
762
+ ...this.interrupts ? { interrupts: this.interrupts } : {},
763
+ ...includeOpaque ? { opaqueReasoning: publishableOpaqueReasoning } : {}
764
+ } } } : {}
493
765
  };
494
766
  const result = {
495
767
  content: snapshot,
@@ -516,33 +788,57 @@ var RunAggregator = class {
516
788
  toolCallCount: this.toolCalls.size
517
789
  };
518
790
  }
519
- handleReasoningStart(messageId, isMessageId = false) {
520
- if (!this.showThinking) return;
521
- this.activeTextMessageId = void 0;
522
- const key = messageId ?? `__auto-reasoning-${++this.reasoningPartCounter}`;
791
+ handleReasoningStart(scope, messageId, isMessageId = false) {
792
+ if (!this.showThinking) {
793
+ if (scope !== ROOT_SCOPE) return;
794
+ const anchor = this.partOrder.length;
795
+ if (messageId === void 0) {
796
+ this.hiddenActiveReasoning = "anonymous";
797
+ this.hiddenAnonymousAnchor = anchor;
798
+ } else {
799
+ this.hiddenReasoningIds.add(messageId);
800
+ this.hiddenBlockAnchors.set(messageId, anchor);
801
+ this.hiddenActiveReasoning = "identified";
802
+ }
803
+ return;
804
+ }
805
+ this.activeTextMessageIdByScope.delete(scope);
806
+ const key = this.partKey(scope, messageId ?? `__auto-reasoning-${++this.reasoningPartCounter}`);
523
807
  if (messageId === void 0) this.anonymousReasoningKeys.add(key);
524
808
  else if (isMessageId) this.reasoningMessageIds.set(key, messageId);
525
809
  if (!this.reasoningParts.has(key)) {
526
810
  this.reasoningParts.set(key, "");
527
- this.partOrder.push({
811
+ this.partOrder.push(scope === ROOT_SCOPE ? {
528
812
  kind: "reasoning",
529
813
  key
814
+ } : {
815
+ kind: "reasoning",
816
+ key,
817
+ subagentRunId: scope
530
818
  });
531
819
  }
532
- this.activeReasoningKey = key;
820
+ this.activeReasoningKeyByScope.set(scope, key);
533
821
  this.emit();
534
822
  }
535
- handleReasoningContent(delta, messageId, isMessageId = false) {
536
- if (!this.showThinking || !delta) return;
537
- if (!this.activeReasoningKey) this.handleReasoningStart(messageId, isMessageId);
538
- const key = this.activeReasoningKey;
823
+ handleReasoningContent(scope, delta, messageId, isMessageId = false) {
824
+ if (!delta) return;
825
+ if (!this.showThinking) {
826
+ if (scope !== ROOT_SCOPE) return;
827
+ if (this.hiddenActiveReasoning === "none") this.handleReasoningStart(scope, messageId, isMessageId);
828
+ return;
829
+ }
830
+ if (!this.activeReasoningKeyByScope.has(scope)) this.handleReasoningStart(scope, messageId, isMessageId);
831
+ const key = this.activeReasoningKeyByScope.get(scope);
539
832
  if (!key) return;
540
833
  this.reasoningParts.set(key, (this.reasoningParts.get(key) ?? "") + delta);
541
834
  this.emit();
542
835
  }
543
- handleReasoningEnd() {
544
- if (!this.showThinking) return;
545
- this.activeReasoningKey = void 0;
836
+ handleReasoningEnd(scope) {
837
+ if (!this.showThinking) {
838
+ if (scope === ROOT_SCOPE) this.hiddenActiveReasoning = "none";
839
+ return;
840
+ }
841
+ this.activeReasoningKeyByScope.delete(scope);
546
842
  this.emit();
547
843
  }
548
844
  };