@assistant-ui/react-ag-ui 0.0.60 → 0.0.61

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 (45) hide show
  1. package/dist/agUiExtras.d.ts +1 -3
  2. package/dist/agUiExtras.d.ts.map +1 -1
  3. package/dist/hooks.d.ts +3 -5
  4. package/dist/hooks.d.ts.map +1 -1
  5. package/dist/index.d.ts +7 -5
  6. package/dist/index.d.ts.map +1 -0
  7. package/dist/runtime/AgUiThreadRuntimeCore.d.ts +152 -144
  8. package/dist/runtime/AgUiThreadRuntimeCore.d.ts.map +1 -1
  9. package/dist/runtime/AgUiThreadRuntimeCore.js +141 -23
  10. package/dist/runtime/AgUiThreadRuntimeCore.js.map +1 -1
  11. package/dist/runtime/adapter/conversions.d.ts +51 -53
  12. package/dist/runtime/adapter/conversions.d.ts.map +1 -1
  13. package/dist/runtime/adapter/conversions.js +65 -21
  14. package/dist/runtime/adapter/conversions.js.map +1 -1
  15. package/dist/runtime/adapter/run-aggregator.d.ts +89 -88
  16. package/dist/runtime/adapter/run-aggregator.d.ts.map +1 -1
  17. package/dist/runtime/adapter/run-aggregator.js +35 -2
  18. package/dist/runtime/adapter/run-aggregator.js.map +1 -1
  19. package/dist/runtime/adapter/subscriber.d.ts +107 -108
  20. package/dist/runtime/adapter/subscriber.d.ts.map +1 -1
  21. package/dist/runtime/adapter/tool-approval.d.ts +3 -4
  22. package/dist/runtime/adapter/tool-approval.d.ts.map +1 -1
  23. package/dist/runtime/event-parser.d.ts +3 -5
  24. package/dist/runtime/event-parser.d.ts.map +1 -1
  25. package/dist/runtime/interrupt-internals.d.ts +1 -3
  26. package/dist/runtime/interrupt-internals.d.ts.map +1 -1
  27. package/dist/runtime/logger.d.ts +2 -4
  28. package/dist/runtime/logger.d.ts.map +1 -1
  29. package/dist/runtime/mcp-tool-result.d.ts +4 -6
  30. package/dist/runtime/mcp-tool-result.d.ts.map +1 -1
  31. package/dist/runtime/types.d.ts +237 -238
  32. package/dist/runtime/types.d.ts.map +1 -1
  33. package/dist/useAgUiRuntime.d.ts +13 -15
  34. package/dist/useAgUiRuntime.d.ts.map +1 -1
  35. package/dist/useAgUiRuntime.js +9 -2
  36. package/dist/useAgUiRuntime.js.map +1 -1
  37. package/package.json +6 -6
  38. package/src/runtime/AgUiThreadRuntimeCore.ts +239 -32
  39. package/src/runtime/adapter/conversions.content.test.ts +56 -0
  40. package/src/runtime/adapter/conversions.ts +112 -44
  41. package/src/runtime/adapter/run-aggregator.ts +53 -2
  42. package/src/useAgUiRuntime.history.test.tsx +93 -2
  43. package/src/useAgUiRuntime.thread-switch.test.tsx +84 -4
  44. package/src/useAgUiRuntime.ts +12 -3
  45. package/src/useAgUiRuntime.voice.test.tsx +402 -0
@@ -5,7 +5,7 @@ import { AG_UI_METADATA_NAMESPACE, RunAggregator, isPlainObject, tryParseJSON }
5
5
  import { fromAgUiMessages, toAgUiMessages, toAgUiTools } from "./adapter/conversions.js";
6
6
  import { createAgUiSubscriber } from "./adapter/subscriber.js";
7
7
  import { fromThreadMessageLike, generateId, isMcpAppUri } from "@assistant-ui/core";
8
- import { createMessageRepositorySession, invokeUserCallback, iterateToolCallParts, mapToolCallPartsDeep } from "@assistant-ui/core/internal";
8
+ import { appendToolInteraction, createMessageRepositorySession, invokeUserCallback, iterateToolCallParts, mapToolCallPartsDeep } from "@assistant-ui/core/internal";
9
9
  import jsonpatch from "fast-json-patch";
10
10
  //#region src/runtime/AgUiThreadRuntimeCore.ts
11
11
  const optimisticPrefix = "__optimistic__";
@@ -162,6 +162,13 @@ var AgUiThreadRuntimeCore = class {
162
162
  if (!startRun || !ownsThread) return;
163
163
  await this.startRun(threadMessageId, message.runConfig);
164
164
  }
165
+ appendVoiceTranscript(message) {
166
+ const parentId = this.session.headId;
167
+ this.session.addOrUpdateMessage(parentId, message);
168
+ this.session.switchToBranch(message.id);
169
+ this.notifyUpdate();
170
+ this.recordHistoryEntry(parentId, message);
171
+ }
165
172
  maybeAutoCancelPendingToolCalls() {
166
173
  if (this.autoCancelPendingToolCalls === false) return;
167
174
  const pending = this.getPendingToolCalls();
@@ -484,6 +491,38 @@ var AgUiThreadRuntimeCore = class {
484
491
  this.notifyUpdate();
485
492
  this.maybeResumeAfterToolResults(sessionMessageId);
486
493
  }
494
+ async recordToolInteraction(options) {
495
+ const sessionMessageId = this.session.tryGetMessage(options.messageId) ? options.messageId : this.findMessageIdForToolCall(options.toolCallId);
496
+ if (sessionMessageId === void 0) throw new Error(`[agui] recordToolInteraction: message "${options.messageId}" was not found`);
497
+ const item = this.session.tryGetMessage(sessionMessageId);
498
+ if (!item) throw new Error(`[agui] recordToolInteraction: message "${sessionMessageId}" was not found`);
499
+ let recorded = false;
500
+ const updated = this.session.updateMessage(sessionMessageId, (message) => {
501
+ if (message.role !== "assistant") return message;
502
+ const assistant = message;
503
+ const { content } = mapToolCallPartsDeep(assistant.content, (part) => {
504
+ if (part.toolCallId !== options.toolCallId) return part;
505
+ recorded = true;
506
+ return {
507
+ ...part,
508
+ unstable_interactions: appendToolInteraction(part.unstable_interactions, options.interaction)
509
+ };
510
+ });
511
+ return recorded ? {
512
+ ...assistant,
513
+ content
514
+ } : assistant;
515
+ });
516
+ if (!recorded || !updated) throw new Error(`[agui] recordToolInteraction: tool call "${options.toolCallId}" was not found on message "${options.messageId}"`);
517
+ this.notifyUpdate();
518
+ const message = this.session.tryGetMessage(sessionMessageId)?.message;
519
+ const history = this.history;
520
+ if (!message || !history?.update || !this.isPersistableStatus(message.status)) return;
521
+ await this.chainHistoryWrite(sessionMessageId, () => history.update({
522
+ parentId: item.parentId,
523
+ message
524
+ }));
525
+ }
487
526
  sendA2uiAction(action) {
488
527
  this.assertNoPendingInterrupts();
489
528
  this.maybeAutoCancelPendingToolCalls();
@@ -543,6 +582,18 @@ var AgUiThreadRuntimeCore = class {
543
582
  });
544
583
  }
545
584
  applyExternalMessages(messages) {
585
+ messages = messages.map((message) => {
586
+ if (message.role === "system" || message.metadata.modality !== void 0) return message;
587
+ const modality = this.session.tryGetMessage(message.id)?.message.metadata.modality;
588
+ if (modality === void 0) return message;
589
+ return {
590
+ ...message,
591
+ metadata: {
592
+ ...message.metadata,
593
+ modality
594
+ }
595
+ };
596
+ });
546
597
  this.pendingA2uiResumeOwner = null;
547
598
  this.pendingA2uiAction = void 0;
548
599
  this.assistantHistoryParents.clear();
@@ -597,6 +648,26 @@ var AgUiThreadRuntimeCore = class {
597
648
  this.stateSnapshot = void 0;
598
649
  this.notifyUpdate();
599
650
  }
651
+ resetThreadState() {
652
+ const controller = this.abortController;
653
+ const activeRunAgent = this.activeRunAgent;
654
+ this.stateSnapshot = void 0;
655
+ this.pendingResume = null;
656
+ this.pendingA2uiResumeOwner = null;
657
+ this.pendingA2uiAction = void 0;
658
+ if (controller) {
659
+ this.abortController = null;
660
+ this.activeRunAgent = null;
661
+ this.setRunning(false);
662
+ try {
663
+ (activeRunAgent ?? this.agent).abortRun();
664
+ } catch (error) {
665
+ this.logger.error?.("[agui] agent abortRun failed", error);
666
+ } finally {
667
+ controller.abort();
668
+ }
669
+ } else this.notifyUpdate();
670
+ }
600
671
  async startRun(parentId, runConfig, resume, resumeStream) {
601
672
  if (!this.supersedeActiveRun()) return;
602
673
  const normalizedRunConfig = runConfig ?? {};
@@ -652,6 +723,8 @@ var AgUiThreadRuntimeCore = class {
652
723
  }
653
724
  } else assistantCollided = true;
654
725
  };
726
+ const abortController = new AbortController();
727
+ const abortSignal = abortController.signal;
655
728
  const aggregator = new RunAggregator({
656
729
  showThinking: this.showThinking,
657
730
  logger: this.logger,
@@ -661,9 +734,11 @@ var AgUiThreadRuntimeCore = class {
661
734
  },
662
735
  onTextMessageStart: (serverId) => adoptServerMessageId(serverId, true)
663
736
  });
664
- const dispatch = (event) => this.handleEvent(aggregator, event, assistantMessageId);
665
- const abortController = new AbortController();
666
- const abortSignal = abortController.signal;
737
+ const dispatch = (event) => {
738
+ if (this.abortController !== abortController) return;
739
+ const nextAssistantMessageId = this.handleEvent(aggregator, event, assistantMessageId);
740
+ if (event.type === "MESSAGES_SNAPSHOT") assistantMessageId = nextAssistantMessageId;
741
+ };
667
742
  this.abortController = abortController;
668
743
  const runAgentInstance = this.agent;
669
744
  this.activeRunAgent = runAgentInstance;
@@ -948,22 +1023,36 @@ var AgUiThreadRuntimeCore = class {
948
1023
  return resolvedMessageId;
949
1024
  }
950
1025
  preserveToolResults(previous, next) {
951
- const resolved = /* @__PURE__ */ new Map();
952
- for (const part of iterateToolCallParts(previous)) if (isResolvedToolCall(part)) resolved.set(part.toolCallId, part);
953
- if (resolved.size === 0) return next;
1026
+ const preserved = /* @__PURE__ */ new Map();
1027
+ for (const part of iterateToolCallParts(previous)) if (isResolvedToolCall(part)) preserved.set(part.toolCallId, part);
1028
+ if (preserved.size === 0) return this.preserveToolInteractions(previous, next);
954
1029
  const { content: merged, changed } = mapToolCallPartsDeep(next, (part) => {
955
- if (isResolvedToolCall(part)) return part;
956
- const prior = resolved.get(part.toolCallId);
1030
+ const prior = preserved.get(part.toolCallId);
957
1031
  if (!prior) return part;
958
- return {
1032
+ if (!isResolvedToolCall(part) && isResolvedToolCall(prior)) return {
959
1033
  ...part,
960
1034
  result: prior.result,
961
1035
  ...prior.artifact !== void 0 ? { artifact: prior.artifact } : {},
962
1036
  ...prior.isError !== void 0 ? { isError: prior.isError } : {},
963
1037
  ...prior.modelContent !== void 0 ? { modelContent: prior.modelContent } : {}
964
1038
  };
1039
+ return part;
965
1040
  });
966
- return changed ? merged : next;
1041
+ return this.preserveToolInteractions(previous, changed ? merged : next);
1042
+ }
1043
+ preserveToolInteractions(previous, next) {
1044
+ const interactions = /* @__PURE__ */ new Map();
1045
+ for (const part of iterateToolCallParts(previous)) if (part.unstable_interactions !== void 0) interactions.set(part.toolCallId, part.unstable_interactions);
1046
+ if (interactions.size === 0) return next;
1047
+ const { content, changed } = mapToolCallPartsDeep(next, (part) => {
1048
+ const interaction = interactions.get(part.toolCallId);
1049
+ if (interaction === void 0 || part.unstable_interactions !== void 0) return part;
1050
+ return {
1051
+ ...part,
1052
+ unstable_interactions: interaction
1053
+ };
1054
+ });
1055
+ return changed ? content : next;
967
1056
  }
968
1057
  mergeAssistantMetadata(current, incoming) {
969
1058
  const annotations = incoming.unstable_annotations ? [...current.unstable_annotations, ...incoming.unstable_annotations] : current.unstable_annotations;
@@ -983,46 +1072,69 @@ var AgUiThreadRuntimeCore = class {
983
1072
  switch (event.type) {
984
1073
  case "STATE_SNAPSHOT":
985
1074
  this.stateSnapshot = event.snapshot;
986
- this.notifyUpdate();
987
- return;
1075
+ this.updateActiveAssistantState(activeAssistantId);
1076
+ break;
988
1077
  case "STATE_DELTA":
989
- if (event.delta.length === 0) return;
1078
+ if (event.delta.length === 0) break;
990
1079
  try {
991
1080
  const state = this.stateSnapshot ?? {};
992
1081
  const result = jsonpatch.applyPatch(state, event.delta, true, false);
993
1082
  this.stateSnapshot = result.newDocument;
994
- this.notifyUpdate();
1083
+ this.updateActiveAssistantState(activeAssistantId);
995
1084
  } catch (error) {
996
1085
  this.logger.error?.("[agui] failed to apply state delta", error);
997
1086
  }
998
- return;
999
- case "MESSAGES_SNAPSHOT":
1087
+ break;
1088
+ case "MESSAGES_SNAPSHOT": {
1089
+ const previousHeadId = this.session.headId;
1090
+ const activeAssistant = activeAssistantId === void 0 ? void 0 : this.session.tryGetMessage(activeAssistantId)?.message;
1091
+ const hasActiveText = activeAssistant?.role === "assistant" && activeAssistant.content.some((part) => part.type === "text");
1000
1092
  this.importMessagesSnapshot(event.messages, activeAssistantId);
1001
- return;
1093
+ const headId = this.session.headId;
1094
+ if (hasActiveText && headId !== previousHeadId) activeAssistantId = (headId === null ? void 0 : this.session.tryGetMessage(headId)?.message)?.role === "assistant" ? headId ?? void 0 : void 0;
1095
+ this.updateActiveAssistantState(activeAssistantId);
1096
+ break;
1097
+ }
1002
1098
  case "TOOL_CALL_RESULT":
1003
1099
  if (!aggregator.hasToolCall(event.toolCallId)) {
1004
1100
  const messageId = this.findMessageIdForToolCall(event.toolCallId);
1005
1101
  if (messageId !== void 0) {
1006
1102
  this.applyCrossRunToolResult(messageId, event);
1007
- return;
1103
+ break;
1008
1104
  }
1009
1105
  }
1010
1106
  aggregator.handle(event);
1011
- return;
1107
+ break;
1012
1108
  case "ACTIVITY_SNAPSHOT": {
1013
1109
  const toolCallId = event.content["toolCallId"];
1014
1110
  if (event.activityType === "mcp-apps" && typeof toolCallId === "string" && !aggregator.hasToolCall(toolCallId)) {
1015
1111
  const messageId = this.findMessageIdForToolCall(toolCallId);
1016
1112
  if (messageId !== void 0) {
1017
1113
  this.applyCrossRunActivitySnapshot(messageId, toolCallId, event);
1018
- return;
1114
+ break;
1019
1115
  }
1020
1116
  }
1021
1117
  aggregator.handle(event);
1022
- return;
1118
+ break;
1023
1119
  }
1024
1120
  default: aggregator.handle(event);
1025
1121
  }
1122
+ return activeAssistantId;
1123
+ }
1124
+ updateActiveAssistantState(messageId) {
1125
+ const activeAssistantId = (messageId === void 0 ? void 0 : this.session.tryGetMessage(messageId)?.message)?.role === "assistant" && this.session.headId === messageId ? messageId : this.session.headId;
1126
+ if (activeAssistantId !== null && activeAssistantId !== void 0) this.session.updateMessage(activeAssistantId, (message) => {
1127
+ if (message.role !== "assistant") return message;
1128
+ const assistant = message;
1129
+ return {
1130
+ ...assistant,
1131
+ metadata: {
1132
+ ...assistant.metadata,
1133
+ unstable_state: this.stateSnapshot ?? null
1134
+ }
1135
+ };
1136
+ });
1137
+ this.notifyUpdate();
1026
1138
  }
1027
1139
  applyCrossRunToolResult(messageId, event) {
1028
1140
  if (!this.session.updateMessage(messageId, (message) => {
@@ -1109,7 +1221,13 @@ var AgUiThreadRuntimeCore = class {
1109
1221
  const normalized = fromAgUiMessages(rawMessages, { showThinking: this.showThinking });
1110
1222
  const converted = [];
1111
1223
  for (const message of normalized) try {
1112
- converted.push(fromThreadMessageLike(message, generateId(), FALLBACK_USER_STATUS));
1224
+ const convertedMessage = fromThreadMessageLike(message, generateId(), FALLBACK_USER_STATUS);
1225
+ const existing = this.session.tryGetMessage(convertedMessage.id)?.message;
1226
+ if (convertedMessage.role === "assistant" && existing?.role === "assistant") converted.push({
1227
+ ...convertedMessage,
1228
+ content: this.preserveToolInteractions(existing.content, convertedMessage.content)
1229
+ });
1230
+ else converted.push(convertedMessage);
1113
1231
  } catch (error) {
1114
1232
  this.logger.error?.("[agui] failed to import message from snapshot", error);
1115
1233
  }