@assistant-ui/react-ag-ui 0.0.70 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/README.md +1 -1
  2. package/dist/runtime/{AgUiThreadRuntimeCore.d.ts → AgUiThreadController.d.ts} +8 -12
  3. package/dist/runtime/AgUiThreadController.d.ts.map +1 -0
  4. package/dist/runtime/{AgUiThreadRuntimeCore.js → AgUiThreadController.js} +302 -162
  5. package/dist/runtime/AgUiThreadController.js.map +1 -0
  6. package/dist/runtime/adapter/conversions.d.ts +4 -4
  7. package/dist/runtime/adapter/conversions.d.ts.map +1 -1
  8. package/dist/runtime/adapter/conversions.js +111 -33
  9. package/dist/runtime/adapter/conversions.js.map +1 -1
  10. package/dist/runtime/adapter/run-aggregator.d.ts +6 -5
  11. package/dist/runtime/adapter/run-aggregator.d.ts.map +1 -1
  12. package/dist/runtime/adapter/run-aggregator.js +50 -62
  13. package/dist/runtime/adapter/run-aggregator.js.map +1 -1
  14. package/dist/runtime/adapter/subscriber.d.ts +0 -24
  15. package/dist/runtime/adapter/subscriber.d.ts.map +1 -1
  16. package/dist/runtime/adapter/subscriber.js +1 -22
  17. package/dist/runtime/adapter/subscriber.js.map +1 -1
  18. package/dist/runtime/agUiThreadState.d.ts +60 -0
  19. package/dist/runtime/agUiThreadState.d.ts.map +1 -0
  20. package/dist/runtime/agUiThreadState.js +95 -0
  21. package/dist/runtime/agUiThreadState.js.map +1 -0
  22. package/dist/runtime/event-parser.d.ts.map +1 -1
  23. package/dist/runtime/event-parser.js +45 -48
  24. package/dist/runtime/event-parser.js.map +1 -1
  25. package/dist/runtime/mcp-tool-result.d.ts +3 -1
  26. package/dist/runtime/mcp-tool-result.d.ts.map +1 -1
  27. package/dist/runtime/mcp-tool-result.js +6 -3
  28. package/dist/runtime/mcp-tool-result.js.map +1 -1
  29. package/dist/runtime/types.d.ts +13 -35
  30. package/dist/runtime/types.d.ts.map +1 -1
  31. package/dist/runtime/types.js.map +1 -1
  32. package/dist/useAgUiRuntime.d.ts.map +1 -1
  33. package/dist/useAgUiRuntime.js +4 -2
  34. package/dist/useAgUiRuntime.js.map +1 -1
  35. package/package.json +13 -12
  36. package/src/runtime/{AgUiThreadRuntimeCore.interrupt-cancel.test.ts → AgUiThreadController.interrupt-cancel.test.ts} +4 -4
  37. package/src/runtime/{AgUiThreadRuntimeCore.test.ts → AgUiThreadController.test.ts} +35 -6
  38. package/src/runtime/{AgUiThreadRuntimeCore.ts → AgUiThreadController.ts} +471 -169
  39. package/src/runtime/adapter/conversions.content.test.ts +37 -5
  40. package/src/runtime/adapter/conversions.tools.test.ts +2 -1
  41. package/src/runtime/adapter/conversions.ts +235 -58
  42. package/src/runtime/adapter/run-aggregator.ts +84 -114
  43. package/src/runtime/adapter/subscriber.ts +1 -44
  44. package/src/runtime/agUiThreadState.test.ts +205 -0
  45. package/src/runtime/agUiThreadState.ts +130 -0
  46. package/src/runtime/event-parser.ts +40 -58
  47. package/src/runtime/mcp-tool-result.ts +16 -2
  48. package/src/runtime/types.ts +18 -28
  49. package/src/useAgUiRuntime.external-store.test.tsx +39 -0
  50. package/src/useAgUiRuntime.fast-refresh.test.tsx +3 -3
  51. package/src/useAgUiRuntime.thread-switch.test.tsx +4 -4
  52. package/src/useAgUiRuntime.ts +5 -3
  53. package/dist/runtime/AgUiThreadRuntimeCore.d.ts.map +0 -1
  54. package/dist/runtime/AgUiThreadRuntimeCore.js.map +0 -1
@@ -1,5 +1,5 @@
1
1
  "use client";
2
- import { readMcpAppResourceUri } from "../mcp-tool-result.js";
2
+ import { readMcpAppResourceUri, toolContentText } from "../mcp-tool-result.js";
3
3
  import { AG_UI_METADATA_NAMESPACE } from "../types.js";
4
4
  import { projectAgUiToolApprovals } from "./tool-approval.js";
5
5
  import { isMcpAppUri } from "@assistant-ui/core";
@@ -76,11 +76,13 @@ const isPlainObject = (value) => !!value && typeof value === "object" && !Array.
76
76
  var RunAggregator = class {
77
77
  emitUpdate;
78
78
  showThinking;
79
+ joinStrategy;
79
80
  logger;
80
81
  onServerMessageId;
81
82
  onTextMessageStart;
82
83
  status;
83
84
  interrupts;
85
+ pendingToolCallIds;
84
86
  textParts = /* @__PURE__ */ new Map();
85
87
  activeTextMessageIdByScope = /* @__PURE__ */ new Map();
86
88
  reasoningParts = /* @__PURE__ */ new Map();
@@ -89,17 +91,14 @@ var RunAggregator = class {
89
91
  hiddenSignatures = /* @__PURE__ */ new Map();
90
92
  hiddenSignatureAnchors = /* @__PURE__ */ new Map();
91
93
  hiddenBlockAnchors = /* @__PURE__ */ new Map();
92
- hiddenAnonymousAnchor;
93
94
  hiddenReasoningIds = /* @__PURE__ */ new Set();
94
- hiddenActiveReasoning = "none";
95
+ hiddenActiveReasoningId;
95
96
  hasEmittedOpaqueReasoning = false;
96
97
  loggedDroppedOpaqueIds = /* @__PURE__ */ new Set();
97
98
  reasoningMessageIds = /* @__PURE__ */ new Map();
98
- anonymousReasoningKeys = /* @__PURE__ */ new Set();
99
99
  activeReasoningKeyByScope = /* @__PURE__ */ new Map();
100
100
  subagentRuns = /* @__PURE__ */ new Map();
101
101
  nestingMemo;
102
- reasoningPartCounter = 0;
103
102
  toolCalls = /* @__PURE__ */ new Map();
104
103
  a2uiBuckets = /* @__PURE__ */ new Map();
105
104
  a2uiToolCallIds = /* @__PURE__ */ new Set();
@@ -117,6 +116,7 @@ var RunAggregator = class {
117
116
  constructor(options) {
118
117
  this.emitUpdate = options.emit;
119
118
  this.showThinking = options.showThinking;
119
+ this.joinStrategy = options.joinStrategy;
120
120
  this.logger = options.logger;
121
121
  this.onServerMessageId = options.onServerMessageId;
122
122
  this.onTextMessageStart = options.onTextMessageStart;
@@ -139,6 +139,16 @@ var RunAggregator = class {
139
139
  this.emit();
140
140
  break;
141
141
  case "RUN_FINISHED": {
142
+ if (event.outcome?.type === "cancelled") {
143
+ this.interrupts = void 0;
144
+ this.status = {
145
+ type: "incomplete",
146
+ reason: "cancelled"
147
+ };
148
+ this.closeOpenSubagentRuns(this.status);
149
+ this.emit();
150
+ break;
151
+ }
142
152
  if (event.outcome?.type === "interrupt") {
143
153
  this.interrupts = event.outcome.interrupts;
144
154
  this.status = {
@@ -149,7 +159,11 @@ var RunAggregator = class {
149
159
  break;
150
160
  }
151
161
  this.interrupts = void 0;
152
- const hasUnresolvedToolCalls = Array.from(this.toolCalls.values()).some((tc) => tc.result === void 0);
162
+ const pendingToolCallIds = event.outcome?.type === "success" ? event.outcome.pendingToolCallIds : void 0;
163
+ const hasUnresolvedToolCalls = pendingToolCallIds && pendingToolCallIds.length > 0 ? pendingToolCallIds.some((id) => {
164
+ const call = this.toolCalls.get(id);
165
+ return call !== void 0 && call.result === void 0;
166
+ }) : Array.from(this.toolCalls.values()).some((tc) => tc.result === void 0);
153
167
  this.status = hasUnresolvedToolCalls ? {
154
168
  type: "requires-action",
155
169
  reason: "tool-calls"
@@ -157,6 +171,7 @@ var RunAggregator = class {
157
171
  type: "complete",
158
172
  reason: "unknown"
159
173
  };
174
+ this.pendingToolCallIds = hasUnresolvedToolCalls && pendingToolCallIds?.length ? pendingToolCallIds : void 0;
160
175
  this.closeOpenSubagentRuns(this.status);
161
176
  this.emit();
162
177
  break;
@@ -190,9 +205,8 @@ var RunAggregator = class {
190
205
  this.emit();
191
206
  break;
192
207
  }
193
- case "TEXT_MESSAGE_CONTENT":
194
- case "TEXT_MESSAGE_CHUNK": {
195
- const incomingId = "messageId" in event ? event.messageId : void 0;
208
+ case "TEXT_MESSAGE_CONTENT": {
209
+ const incomingId = event.messageId;
196
210
  const scope = this.scopeOf(event);
197
211
  if (scope === ROOT_SCOPE) {
198
212
  this.beginDistinctTextMessage(incomingId);
@@ -223,43 +237,31 @@ var RunAggregator = class {
223
237
  });
224
238
  this.emit();
225
239
  break;
226
- case "THINKING_START":
227
- case "THINKING_TEXT_MESSAGE_START":
228
240
  case "REASONING_START":
229
241
  case "REASONING_MESSAGE_START":
230
- this.handleReasoningStart(this.scopeOf("subagentRunId" in event ? event : {}), "messageId" in event ? event.messageId : void 0, event.type === "REASONING_MESSAGE_START", event.timestamp);
242
+ this.handleReasoningStart(this.scopeOf(event), event.messageId, event.type === "REASONING_MESSAGE_START", event.timestamp);
231
243
  break;
232
244
  case "REASONING_ENCRYPTED_VALUE":
233
245
  if (event.subtype === "message") {
234
246
  const scope = this.scopeOf(event);
235
- const active = this.activeReasoningKeyByScope.get(scope);
236
247
  const entityKey = this.partKey(scope, event.entityId);
237
- const key = this.showThinking ? this.reasoningParts.has(entityKey) ? entityKey : active !== void 0 && this.anonymousReasoningKeys.has(active) ? active : void 0 : void 0;
248
+ const key = this.showThinking && this.reasoningParts.has(entityKey) ? entityKey : void 0;
238
249
  if (key !== void 0) {
239
250
  this.reasoningSignatures.set(key, event.encryptedValue);
240
251
  this.reasoningSignatureIds.set(key, event.entityId);
241
252
  this.emit();
242
- } 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")) {
253
+ } else if (!this.showThinking && scope === ROOT_SCOPE && event.entityId.trim().length > 0 && event.encryptedValue.trim().length > 0 && this.hiddenReasoningIds.has(event.entityId)) {
243
254
  this.hiddenSignatures.set(event.entityId, event.encryptedValue);
244
- this.hiddenSignatureAnchors.set(event.entityId, this.hiddenBlockAnchors.get(event.entityId) ?? this.hiddenAnonymousAnchor ?? this.partOrder.length);
255
+ this.hiddenSignatureAnchors.set(event.entityId, this.hiddenBlockAnchors.get(event.entityId) ?? this.partOrder.length);
245
256
  this.emit();
246
257
  }
247
258
  }
248
259
  break;
249
- case "THINKING_TEXT_MESSAGE_CONTENT":
250
- this.handleReasoningContent(ROOT_SCOPE, event.delta);
251
- this.totalChunks++;
252
- this.recordFirstToken();
253
- break;
254
260
  case "REASONING_MESSAGE_CONTENT":
255
- this.handleReasoningContent(this.scopeOf(event), event.delta, "messageId" in event ? event.messageId : void 0, true);
261
+ this.handleReasoningContent(this.scopeOf(event), event.delta, event.messageId, true);
256
262
  this.totalChunks++;
257
263
  this.recordFirstToken();
258
264
  break;
259
- case "THINKING_TEXT_MESSAGE_END":
260
- case "THINKING_END":
261
- this.handleReasoningEnd(ROOT_SCOPE, event.timestamp);
262
- break;
263
265
  case "REASONING_MESSAGE_END":
264
266
  case "REASONING_END":
265
267
  this.handleReasoningEnd(this.scopeOf(event), event.timestamp);
@@ -272,8 +274,6 @@ var RunAggregator = class {
272
274
  break;
273
275
  }
274
276
  case "TOOL_CALL_ARGS":
275
- case "TOOL_CALL_CHUNK":
276
- if (event.type === "TOOL_CALL_CHUNK" && this.scopeOf(event) === ROOT_SCOPE) this.reportServerMessageId(event.parentMessageId);
277
277
  if (!event.delta) break;
278
278
  this.appendToolArgs(event.toolCallId, event.delta);
279
279
  this.emit();
@@ -492,15 +492,12 @@ var RunAggregator = class {
492
492
  this.hiddenSignatures.clear();
493
493
  this.hiddenSignatureAnchors.clear();
494
494
  this.hiddenBlockAnchors.clear();
495
- this.hiddenAnonymousAnchor = void 0;
496
495
  this.hiddenReasoningIds.clear();
497
- this.hiddenActiveReasoning = "none";
496
+ this.hiddenActiveReasoningId = void 0;
498
497
  this.loggedDroppedOpaqueIds.clear();
499
- this.anonymousReasoningKeys.clear();
500
498
  this.activeReasoningKeyByScope.clear();
501
499
  this.nestingMemo = void 0;
502
500
  this.subagentRuns.clear();
503
- this.reasoningPartCounter = 0;
504
501
  this.toolCalls.clear();
505
502
  this.a2uiBuckets.clear();
506
503
  this.a2uiToolCallIds.clear();
@@ -522,13 +519,12 @@ var RunAggregator = class {
522
519
  return scope === ROOT_SCOPE ? id : `${scope}\u0000${id}`;
523
520
  }
524
521
  beginDistinctTextMessage(messageId) {
525
- if (!messageId || this.lastTextMessageId === void 0 || this.lastTextMessageId === messageId || !this.onTextMessageStart) return;
522
+ if (this.joinStrategy === "concat-content" || !messageId || this.lastTextMessageId === void 0 || this.lastTextMessageId === messageId || !this.onTextMessageStart) return;
526
523
  const carried = this.takeTrailingReasoning();
527
524
  if (carried.parts.length > 0 || carried.hiddenSignatures.length > 0) this.emit();
528
- const reasoningPartCounter = this.reasoningPartCounter;
525
+ const openToolCallIds = Array.from(this.toolCalls.values()).filter((call) => call.result === void 0).map((call) => call.toolCallId);
529
526
  this.resetMessageParts();
530
- this.onTextMessageStart(messageId);
531
- this.reasoningPartCounter = reasoningPartCounter;
527
+ this.onTextMessageStart(messageId, openToolCallIds);
532
528
  this.restoreReasoning(carried);
533
529
  }
534
530
  takeTrailingReasoning() {
@@ -541,8 +537,7 @@ var RunAggregator = class {
541
537
  entry: this.reasoningParts.get(last.key) ?? { buffer: "" },
542
538
  signature: this.reasoningSignatures.get(last.key),
543
539
  signatureId: this.reasoningSignatureIds.get(last.key),
544
- reasoningId: this.reasoningMessageIds.get(last.key),
545
- anonymous: this.anonymousReasoningKeys.has(last.key)
540
+ reasoningId: this.reasoningMessageIds.get(last.key)
546
541
  });
547
542
  last = this.partOrder.at(-1);
548
543
  }
@@ -553,15 +548,13 @@ var RunAggregator = class {
553
548
  this.hiddenSignatureAnchors.delete(id);
554
549
  }
555
550
  const hiddenBlockIds = Array.from(this.hiddenBlockAnchors).filter(([, anchor]) => anchor >= cut).map(([id]) => id);
556
- const hiddenAnonymous = this.hiddenAnonymousAnchor !== void 0 && this.hiddenAnonymousAnchor >= cut;
557
- const activeCarried = this.hiddenActiveReasoning === "identified" && hiddenBlockIds.length > 0 || this.hiddenActiveReasoning === "anonymous" && hiddenAnonymous;
551
+ const hiddenActiveReasoningId = this.hiddenActiveReasoningId;
558
552
  return {
559
553
  parts,
560
554
  activeKey: this.activeReasoningKeyByScope.get(ROOT_SCOPE),
561
555
  hiddenSignatures,
562
556
  hiddenBlockIds,
563
- hiddenAnonymous,
564
- hiddenActiveReasoning: activeCarried ? this.hiddenActiveReasoning : "none"
557
+ hiddenActiveReasoningId: hiddenActiveReasoningId !== void 0 && hiddenBlockIds.includes(hiddenActiveReasoningId) ? hiddenActiveReasoningId : void 0
565
558
  };
566
559
  }
567
560
  restoreReasoning(carried) {
@@ -574,7 +567,6 @@ var RunAggregator = class {
574
567
  if (part.signature !== void 0) this.reasoningSignatures.set(part.key, part.signature);
575
568
  if (part.signatureId !== void 0) this.reasoningSignatureIds.set(part.key, part.signatureId);
576
569
  if (part.reasoningId !== void 0) this.reasoningMessageIds.set(part.key, part.reasoningId);
577
- if (part.anonymous) this.anonymousReasoningKeys.add(part.key);
578
570
  }
579
571
  if (carried.activeKey !== void 0 && this.reasoningParts.has(carried.activeKey)) this.activeReasoningKeyByScope.set(ROOT_SCOPE, carried.activeKey);
580
572
  for (const [id, encryptedValue] of carried.hiddenSignatures) {
@@ -585,8 +577,7 @@ var RunAggregator = class {
585
577
  this.hiddenReasoningIds.add(id);
586
578
  this.hiddenBlockAnchors.set(id, 0);
587
579
  }
588
- if (carried.hiddenAnonymous) this.hiddenAnonymousAnchor = 0;
589
- this.hiddenActiveReasoning = carried.hiddenActiveReasoning;
580
+ this.hiddenActiveReasoningId = carried.hiddenActiveReasoningId;
590
581
  }
591
582
  generateTextKey() {
592
583
  this.textPartCounter += 1;
@@ -694,6 +685,7 @@ var RunAggregator = class {
694
685
  }
695
686
  finishToolCall(scope, id, content, isError, toolMessageId, mcpResult) {
696
687
  if (!id) return;
688
+ const modelText = toolContentText(content);
697
689
  let entry = this.toolCalls.get(id);
698
690
  if (!entry) {
699
691
  entry = {
@@ -717,7 +709,7 @@ var RunAggregator = class {
717
709
  entry.result = mcpResult;
718
710
  entry.modelContent = [{
719
711
  type: "text",
720
- text: content
712
+ text: modelText
721
713
  }];
722
714
  entry.snapshotResultApplied = true;
723
715
  entry.isError = isError;
@@ -728,11 +720,12 @@ var RunAggregator = class {
728
720
  } else if (entry.snapshotResultApplied) {
729
721
  if (entry.modelContent === void 0) entry.modelContent = [{
730
722
  type: "text",
731
- text: content
723
+ text: modelText
732
724
  }];
733
725
  if (entry.isError === void 0) entry.isError = isError;
734
726
  } else {
735
- entry.result = tryParseJSON(content);
727
+ entry.result = Array.isArray(content) ? content : tryParseJSON(content);
728
+ if (Array.isArray(content)) delete entry.modelContent;
736
729
  entry.isError = isError;
737
730
  }
738
731
  if (toolMessageId) entry.toolMessageId = toolMessageId;
@@ -942,8 +935,9 @@ var RunAggregator = class {
942
935
  const timing = this.getTiming();
943
936
  const metadata = {
944
937
  ...timing ? { timing } : {},
945
- ...this.interrupts || includeOpaque ? { custom: { [AG_UI_METADATA_NAMESPACE]: {
938
+ ...this.interrupts || this.status?.type === "requires-action" && this.status.reason === "tool-calls" && this.pendingToolCallIds || includeOpaque ? { custom: { [AG_UI_METADATA_NAMESPACE]: {
946
939
  ...this.interrupts ? { interrupts: this.interrupts } : {},
940
+ ...this.status?.type === "requires-action" && this.status.reason === "tool-calls" && this.pendingToolCallIds ? { pendingToolCallIds: this.pendingToolCallIds } : {},
947
941
  ...includeOpaque ? { opaqueReasoning: publishableOpaqueReasoning } : {}
948
942
  } } } : {}
949
943
  };
@@ -976,20 +970,14 @@ var RunAggregator = class {
976
970
  if (!this.showThinking) {
977
971
  if (scope !== ROOT_SCOPE) return;
978
972
  const anchor = this.partOrder.length;
979
- if (messageId === void 0) {
980
- this.hiddenActiveReasoning = "anonymous";
981
- this.hiddenAnonymousAnchor = anchor;
982
- } else {
983
- this.hiddenReasoningIds.add(messageId);
984
- this.hiddenBlockAnchors.set(messageId, anchor);
985
- this.hiddenActiveReasoning = "identified";
986
- }
973
+ this.hiddenReasoningIds.add(messageId);
974
+ this.hiddenBlockAnchors.set(messageId, anchor);
975
+ this.hiddenActiveReasoningId = messageId;
987
976
  return;
988
977
  }
989
978
  this.activeTextMessageIdByScope.delete(scope);
990
- const key = this.partKey(scope, messageId ?? `__auto-reasoning-${++this.reasoningPartCounter}`);
991
- if (messageId === void 0) this.anonymousReasoningKeys.add(key);
992
- else if (isMessageId) this.reasoningMessageIds.set(key, messageId);
979
+ const key = this.partKey(scope, messageId);
980
+ if (isMessageId) this.reasoningMessageIds.set(key, messageId);
993
981
  if (!this.reasoningParts.has(key)) {
994
982
  this.reasoningParts.set(key, { buffer: "" });
995
983
  this.partOrder.push(scope === ROOT_SCOPE ? {
@@ -1011,7 +999,7 @@ var RunAggregator = class {
1011
999
  if (!delta) return;
1012
1000
  if (!this.showThinking) {
1013
1001
  if (scope !== ROOT_SCOPE) return;
1014
- if (this.hiddenActiveReasoning === "none") this.handleReasoningStart(scope, messageId, isMessageId);
1002
+ if (this.hiddenActiveReasoningId === void 0) this.handleReasoningStart(scope, messageId, isMessageId);
1015
1003
  return;
1016
1004
  }
1017
1005
  if (!this.activeReasoningKeyByScope.has(scope)) this.handleReasoningStart(scope, messageId, isMessageId);
@@ -1023,7 +1011,7 @@ var RunAggregator = class {
1023
1011
  }
1024
1012
  handleReasoningEnd(scope, timestamp) {
1025
1013
  if (!this.showThinking) {
1026
- if (scope === ROOT_SCOPE) this.hiddenActiveReasoning = "none";
1014
+ if (scope === ROOT_SCOPE) this.hiddenActiveReasoningId = void 0;
1027
1015
  return;
1028
1016
  }
1029
1017
  const key = this.activeReasoningKeyByScope.get(scope);