@assistant-ui/react-ag-ui 0.0.59 → 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 +2 -5
  2. package/dist/agUiExtras.d.ts.map +1 -1
  3. package/dist/hooks.d.ts +9 -12
  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 +153 -146
  8. package/dist/runtime/AgUiThreadRuntimeCore.d.ts.map +1 -1
  9. package/dist/runtime/AgUiThreadRuntimeCore.js +146 -26
  10. package/dist/runtime/AgUiThreadRuntimeCore.js.map +1 -1
  11. package/dist/runtime/adapter/conversions.d.ts +54 -56
  12. package/dist/runtime/adapter/conversions.d.ts.map +1 -1
  13. package/dist/runtime/adapter/conversions.js +164 -23
  14. package/dist/runtime/adapter/conversions.js.map +1 -1
  15. package/dist/runtime/adapter/run-aggregator.d.ts +98 -98
  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 +109 -111
  20. package/dist/runtime/adapter/subscriber.d.ts.map +1 -1
  21. package/dist/runtime/adapter/tool-approval.d.ts +7 -9
  22. package/dist/runtime/adapter/tool-approval.d.ts.map +1 -1
  23. package/dist/runtime/event-parser.d.ts +5 -8
  24. package/dist/runtime/event-parser.d.ts.map +1 -1
  25. package/dist/runtime/interrupt-internals.d.ts +4 -7
  26. package/dist/runtime/interrupt-internals.d.ts.map +1 -1
  27. package/dist/runtime/logger.d.ts +4 -7
  28. package/dist/runtime/logger.d.ts.map +1 -1
  29. package/dist/runtime/mcp-tool-result.d.ts +7 -10
  30. package/dist/runtime/mcp-tool-result.d.ts.map +1 -1
  31. package/dist/runtime/types.d.ts +248 -250
  32. package/dist/runtime/types.d.ts.map +1 -1
  33. package/dist/useAgUiRuntime.d.ts +15 -18
  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 +12 -11
  38. package/src/runtime/AgUiThreadRuntimeCore.ts +246 -30
  39. package/src/runtime/adapter/conversions.content.test.ts +56 -0
  40. package/src/runtime/adapter/conversions.ts +278 -36
  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();
@@ -470,8 +477,9 @@ var AgUiThreadRuntimeCore = class {
470
477
  return {
471
478
  ...part,
472
479
  result: options.result,
473
- artifact: options.artifact,
474
- isError: options.isError
480
+ isError: options.isError,
481
+ ...options.artifact !== void 0 && { artifact: options.artifact },
482
+ ...options.modelContent !== void 0 && { modelContent: options.modelContent }
475
483
  };
476
484
  });
477
485
  if (!matchedToolCall) return message;
@@ -483,6 +491,38 @@ var AgUiThreadRuntimeCore = class {
483
491
  this.notifyUpdate();
484
492
  this.maybeResumeAfterToolResults(sessionMessageId);
485
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
+ }
486
526
  sendA2uiAction(action) {
487
527
  this.assertNoPendingInterrupts();
488
528
  this.maybeAutoCancelPendingToolCalls();
@@ -542,6 +582,18 @@ var AgUiThreadRuntimeCore = class {
542
582
  });
543
583
  }
544
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
+ });
545
597
  this.pendingA2uiResumeOwner = null;
546
598
  this.pendingA2uiAction = void 0;
547
599
  this.assistantHistoryParents.clear();
@@ -596,6 +648,26 @@ var AgUiThreadRuntimeCore = class {
596
648
  this.stateSnapshot = void 0;
597
649
  this.notifyUpdate();
598
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
+ }
599
671
  async startRun(parentId, runConfig, resume, resumeStream) {
600
672
  if (!this.supersedeActiveRun()) return;
601
673
  const normalizedRunConfig = runConfig ?? {};
@@ -651,6 +723,8 @@ var AgUiThreadRuntimeCore = class {
651
723
  }
652
724
  } else assistantCollided = true;
653
725
  };
726
+ const abortController = new AbortController();
727
+ const abortSignal = abortController.signal;
654
728
  const aggregator = new RunAggregator({
655
729
  showThinking: this.showThinking,
656
730
  logger: this.logger,
@@ -660,9 +734,11 @@ var AgUiThreadRuntimeCore = class {
660
734
  },
661
735
  onTextMessageStart: (serverId) => adoptServerMessageId(serverId, true)
662
736
  });
663
- const dispatch = (event) => this.handleEvent(aggregator, event, assistantMessageId);
664
- const abortController = new AbortController();
665
- 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
+ };
666
742
  this.abortController = abortController;
667
743
  const runAgentInstance = this.agent;
668
744
  this.activeRunAgent = runAgentInstance;
@@ -947,21 +1023,36 @@ var AgUiThreadRuntimeCore = class {
947
1023
  return resolvedMessageId;
948
1024
  }
949
1025
  preserveToolResults(previous, next) {
950
- const resolved = /* @__PURE__ */ new Map();
951
- for (const part of iterateToolCallParts(previous)) if (isResolvedToolCall(part)) resolved.set(part.toolCallId, part);
952
- 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);
953
1029
  const { content: merged, changed } = mapToolCallPartsDeep(next, (part) => {
954
- if (isResolvedToolCall(part)) return part;
955
- const prior = resolved.get(part.toolCallId);
1030
+ const prior = preserved.get(part.toolCallId);
956
1031
  if (!prior) return part;
957
- return {
1032
+ if (!isResolvedToolCall(part) && isResolvedToolCall(prior)) return {
958
1033
  ...part,
959
1034
  result: prior.result,
960
1035
  ...prior.artifact !== void 0 ? { artifact: prior.artifact } : {},
961
- ...prior.isError !== void 0 ? { isError: prior.isError } : {}
1036
+ ...prior.isError !== void 0 ? { isError: prior.isError } : {},
1037
+ ...prior.modelContent !== void 0 ? { modelContent: prior.modelContent } : {}
962
1038
  };
1039
+ return part;
963
1040
  });
964
- 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;
965
1056
  }
966
1057
  mergeAssistantMetadata(current, incoming) {
967
1058
  const annotations = incoming.unstable_annotations ? [...current.unstable_annotations, ...incoming.unstable_annotations] : current.unstable_annotations;
@@ -981,46 +1072,69 @@ var AgUiThreadRuntimeCore = class {
981
1072
  switch (event.type) {
982
1073
  case "STATE_SNAPSHOT":
983
1074
  this.stateSnapshot = event.snapshot;
984
- this.notifyUpdate();
985
- return;
1075
+ this.updateActiveAssistantState(activeAssistantId);
1076
+ break;
986
1077
  case "STATE_DELTA":
987
- if (event.delta.length === 0) return;
1078
+ if (event.delta.length === 0) break;
988
1079
  try {
989
1080
  const state = this.stateSnapshot ?? {};
990
1081
  const result = jsonpatch.applyPatch(state, event.delta, true, false);
991
1082
  this.stateSnapshot = result.newDocument;
992
- this.notifyUpdate();
1083
+ this.updateActiveAssistantState(activeAssistantId);
993
1084
  } catch (error) {
994
1085
  this.logger.error?.("[agui] failed to apply state delta", error);
995
1086
  }
996
- return;
997
- 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");
998
1092
  this.importMessagesSnapshot(event.messages, activeAssistantId);
999
- 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
+ }
1000
1098
  case "TOOL_CALL_RESULT":
1001
1099
  if (!aggregator.hasToolCall(event.toolCallId)) {
1002
1100
  const messageId = this.findMessageIdForToolCall(event.toolCallId);
1003
1101
  if (messageId !== void 0) {
1004
1102
  this.applyCrossRunToolResult(messageId, event);
1005
- return;
1103
+ break;
1006
1104
  }
1007
1105
  }
1008
1106
  aggregator.handle(event);
1009
- return;
1107
+ break;
1010
1108
  case "ACTIVITY_SNAPSHOT": {
1011
1109
  const toolCallId = event.content["toolCallId"];
1012
1110
  if (event.activityType === "mcp-apps" && typeof toolCallId === "string" && !aggregator.hasToolCall(toolCallId)) {
1013
1111
  const messageId = this.findMessageIdForToolCall(toolCallId);
1014
1112
  if (messageId !== void 0) {
1015
1113
  this.applyCrossRunActivitySnapshot(messageId, toolCallId, event);
1016
- return;
1114
+ break;
1017
1115
  }
1018
1116
  }
1019
1117
  aggregator.handle(event);
1020
- return;
1118
+ break;
1021
1119
  }
1022
1120
  default: aggregator.handle(event);
1023
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();
1024
1138
  }
1025
1139
  applyCrossRunToolResult(messageId, event) {
1026
1140
  if (!this.session.updateMessage(messageId, (message) => {
@@ -1107,7 +1221,13 @@ var AgUiThreadRuntimeCore = class {
1107
1221
  const normalized = fromAgUiMessages(rawMessages, { showThinking: this.showThinking });
1108
1222
  const converted = [];
1109
1223
  for (const message of normalized) try {
1110
- 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);
1111
1231
  } catch (error) {
1112
1232
  this.logger.error?.("[agui] failed to import message from snapshot", error);
1113
1233
  }