@assistant-ui/react-ag-ui 0.1.1 → 0.1.3

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 (43) hide show
  1. package/README.md +2 -0
  2. package/dist/runtime/AgUiThreadController.d.ts +11 -0
  3. package/dist/runtime/AgUiThreadController.d.ts.map +1 -1
  4. package/dist/runtime/AgUiThreadController.js +242 -25
  5. package/dist/runtime/AgUiThreadController.js.map +1 -1
  6. package/dist/runtime/adapter/conversions.d.ts +9 -1
  7. package/dist/runtime/adapter/conversions.d.ts.map +1 -1
  8. package/dist/runtime/adapter/conversions.js +184 -9
  9. package/dist/runtime/adapter/conversions.js.map +1 -1
  10. package/dist/runtime/adapter/run-aggregator.d.ts +2 -0
  11. package/dist/runtime/adapter/run-aggregator.d.ts.map +1 -1
  12. package/dist/runtime/adapter/run-aggregator.js +42 -12
  13. package/dist/runtime/adapter/run-aggregator.js.map +1 -1
  14. package/dist/runtime/adapter/subscriber.d.ts +0 -1
  15. package/dist/runtime/adapter/subscriber.d.ts.map +1 -1
  16. package/dist/runtime/adapter/subscriber.js +1 -8
  17. package/dist/runtime/adapter/subscriber.js.map +1 -1
  18. package/dist/runtime/event-parser.d.ts.map +1 -1
  19. package/dist/runtime/event-parser.js +1 -0
  20. package/dist/runtime/event-parser.js.map +1 -1
  21. package/dist/runtime/types.d.ts +2 -0
  22. package/dist/runtime/types.d.ts.map +1 -1
  23. package/dist/runtime/types.js +2 -1
  24. package/dist/runtime/types.js.map +1 -1
  25. package/package.json +6 -5
  26. package/src/runtime/AgUiThreadController.disconnect.test.ts +914 -0
  27. package/src/runtime/AgUiThreadController.interrupt-history.test.ts +392 -0
  28. package/src/runtime/AgUiThreadController.replay.test.ts +8 -1
  29. package/src/runtime/AgUiThreadController.snapshot-head-move.test.ts +346 -0
  30. package/src/runtime/AgUiThreadController.subagent-snapshot.test.ts +854 -0
  31. package/src/runtime/AgUiThreadController.test.ts +173 -1
  32. package/src/runtime/AgUiThreadController.ts +528 -33
  33. package/src/runtime/adapter/conversions.content.test.ts +129 -0
  34. package/src/runtime/adapter/conversions.subagent-messages.test.ts +550 -0
  35. package/src/runtime/adapter/conversions.ts +331 -20
  36. package/src/runtime/adapter/run-aggregator.metadata.test.ts +151 -0
  37. package/src/runtime/adapter/run-aggregator.ts +63 -9
  38. package/src/runtime/adapter/subscriber.test.ts +1 -1
  39. package/src/runtime/adapter/subscriber.ts +1 -6
  40. package/src/runtime/event-parser.ts +3 -0
  41. package/src/runtime/types.ts +5 -0
  42. package/src/useAgUiRuntime.cloud-run-leases.test.tsx +384 -0
  43. package/src/useAgUiRuntime.state.test.tsx +87 -0
package/README.md CHANGED
@@ -37,6 +37,8 @@ export function Provider({ children }: { children: React.ReactNode }) {
37
37
 
38
38
  An AG-UI backend that runs subagents (the agents-as-tools pattern) emits `SUBAGENT_STARTED` / `SUBAGENT_FINISHED` / `SUBAGENT_ERROR` plus a `subagentRunId` on the events each subagent produces. This adapter groups that activity into one nested assistant message per subagent run and attaches it to the spawning tool call as `ToolCallMessagePart.messages`, joined on `SUBAGENT_STARTED.parentToolCallId`. `PartPrimitive.Messages` renders it without extra wiring, and the subagent's name, description, result, and error code ride on that message's `agui` metadata.
39
39
 
40
+ A `MESSAGES_SNAPSHOT` whose messages carry `subagentRunId` adds to the same nested messages instead of rendering them a second time in the parent thread: a run's messages attach to the tool call its `SUBAGENT_STARTED.parentToolCallId` named in this session, merged with what streamed live. A run that was never announced this session, and its messages, stay in the parent thread. The live grouping only reaches spawning calls of the current run, so a subagent resumed in a later run still streams into the parent thread, and after that run's snapshot can show both nested and in the parent thread.
41
+
40
42
  A subagent that names no reachable spawning call (`parentToolCallId` is optional, may name a call this run never saw, or two runs may name each other) has nowhere to nest, so its output renders in the parent thread instead of being dropped. This matches the downgrade the protocol's own pre-subagent compatibility middleware performs.
41
43
 
42
44
  A subagent's frontend-executed tool calls work the same way the root agent's do: a call nested on `ToolCallMessagePart.messages` is reachable by `getPendingToolCalls()`, resolves through `addToolResult`, and its result rides the resume as a `tool` record on the spawning assistant record, the same flattened wire shape these calls had before subagent attribution.
@@ -44,6 +44,7 @@ export declare class AgUiThreadController {
44
44
  private activeRunAgent;
45
45
  private history;
46
46
  private readonly assistantHistoryParents;
47
+ private readonly subagentParentToolCallIds;
47
48
  private readonly snapshotHistoryIds;
48
49
  private readonly persistedHistoryIds;
49
50
  private readonly historyWrites;
@@ -156,6 +157,15 @@ export declare class AgUiThreadController {
156
157
  private preserveToolInteractions;
157
158
  private mergeSnapshotAssistantContent;
158
159
  private reconcileSnapshotNestedContent;
160
+ /**
161
+ * Merges one tool call's nested transcripts message by message. The incoming
162
+ * copy is the base for tool calls and reasoning; resolved results and calls
163
+ * only the prior copy has carry over, and the prior text wins when it extends
164
+ * the incoming text. Importing a snapshot keeps prior messages it lacks, a
165
+ * prior status that is running or incomplete, and the prior status when the
166
+ * snapshot waits on calls the merged content has already resolved.
167
+ */
168
+ private mergeNestedTranscripts;
159
169
  private mergeAssistantMetadata;
160
170
  private handleEvent;
161
171
  private updateActiveAssistantState;
@@ -167,6 +177,7 @@ export declare class AgUiThreadController {
167
177
  private isPersistableStatus;
168
178
  private recordHistoryEntry;
169
179
  private markPendingAssistantHistory;
180
+ private persistSettledAssistant;
170
181
  private persistAssistantHistory;
171
182
  private appendHistoryItem;
172
183
  }
@@ -1 +1 @@
1
- {"version":3,"file":"AgUiThreadController.d.ts","sourceRoot":"","sources":["../../src/runtime/AgUiThreadController.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"AgUiThreadController.d.ts","sourceRoot":"","sources":["../../src/runtime/AgUiThreadController.ts"],"names":[],"mappings":"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"}
@@ -3,14 +3,22 @@ import { createAgUiThreadState, reduceAgUiThreadState } from "./agUiThreadState.
3
3
  import { readMcpAppResourceUri, toolContentText } from "./mcp-tool-result.js";
4
4
  import { AG_UI_METADATA_NAMESPACE } from "./types.js";
5
5
  import { buildToolApprovalResume, preserveToolApprovalDecision, projectAgUiToolApprovals, withSettledToolApprovals, withToolApprovalDecision } from "./adapter/tool-approval.js";
6
+ import { fromAgUiSnapshotMessages, toAgUiMessages, toAgUiTools, withAgUiPartMessageMetadata } from "./adapter/conversions.js";
6
7
  import { RunAggregator, isPlainObject, tryParseJSON } from "./adapter/run-aggregator.js";
7
- import { fromAgUiMessages, toAgUiMessages, toAgUiTools } from "./adapter/conversions.js";
8
8
  import { createAgUiSubscriber } from "./adapter/subscriber.js";
9
9
  import { createReplayContentMerger, groupReplayToolContent, indexReplayToolOwners, preserveToolResult } from "./adapter/replay-content.js";
10
10
  import { fromThreadMessageLike, generateId, isMcpAppUri } from "@assistant-ui/core";
11
11
  import { appendToolInteraction, createHistoryWriteChain, createMessageRepositorySession, invokeUserCallback, iterateToolCallParts, mapToolCallPartsDeep } from "@assistant-ui/core/internal";
12
12
  import jsonpatch from "fast-json-patch";
13
13
  // src/runtime/AgUiThreadController.ts
14
+ const agentInitialStates = /* @__PURE__ */ new WeakMap();
15
+ const getAgentInitialState = (agent) => {
16
+ if (!agentInitialStates.has(agent)) {
17
+ const state = agent.state;
18
+ agentInitialStates.set(agent, state === void 0 || isPlainObject(state) && Object.keys(state).length === 0 ? void 0 : structuredClone(state));
19
+ }
20
+ return agentInitialStates.get(agent);
21
+ };
14
22
  const optimisticPrefix = "__optimistic__";
15
23
  const generateOptimisticId = () => `${optimisticPrefix}${generateId()}`;
16
24
  const isOptimisticId = (id) => id.startsWith(optimisticPrefix);
@@ -57,6 +65,42 @@ const FALLBACK_USER_STATUS = {
57
65
  type: "complete",
58
66
  reason: "unknown"
59
67
  };
68
+ const textOf = (content) => content.flatMap((part) => part.type === "text" ? [part.text] : []).join("");
69
+ /**
70
+ * Swaps `content`'s text parts, in order, for `source`'s. A text part `source`
71
+ * has beyond `content`'s text slots goes after the nearest earlier part of
72
+ * `source` that `content` also has: a tool call by id, another part by its
73
+ * type and position among parts of that type. With none, it goes first.
74
+ */
75
+ const replaceTextParts = (content, source) => {
76
+ const texts = source.filter((part) => part.type === "text");
77
+ const result = [];
78
+ let used = 0;
79
+ for (const part of content) if (part.type !== "text") result.push(part);
80
+ else if (used < texts.length) result.push(texts[used++]);
81
+ const positionIn = (parts, part) => parts.filter((other) => other.type === part.type).indexOf(part);
82
+ for (const text of texts.slice(used)) {
83
+ let anchor = -1;
84
+ for (let index = source.indexOf(text) - 1; index >= 0 && anchor < 0; index--) {
85
+ const before = source[index];
86
+ if (before.type === "tool-call") anchor = result.findIndex((part) => part.type === "tool-call" && part.toolCallId === before.toolCallId);
87
+ else if (before.type === "text") anchor = result.indexOf(before);
88
+ else {
89
+ const match = result.filter((part) => part.type === before.type)[positionIn(source, before)];
90
+ anchor = match ? result.indexOf(match) : -1;
91
+ }
92
+ }
93
+ result.splice(anchor + 1, 0, text);
94
+ }
95
+ return result;
96
+ };
97
+ const settleNestedContent = (content, status) => mapToolCallPartsDeep(content, (part) => part.messages ? {
98
+ ...part,
99
+ messages: part.messages.map((message) => message.role === "assistant" && message.status?.type === "running" ? {
100
+ ...message,
101
+ status
102
+ } : message)
103
+ } : part).content;
60
104
  const invokeRuntimeCallback = (name, callback, ...args) => {
61
105
  invokeUserCallback("react-ag-ui", name, callback, ...args);
62
106
  };
@@ -107,6 +151,7 @@ var AgUiThreadController = class {
107
151
  activeRunAgent = null;
108
152
  history;
109
153
  assistantHistoryParents = /* @__PURE__ */ new Map();
154
+ subagentParentToolCallIds = /* @__PURE__ */ new Map();
110
155
  snapshotHistoryIds = /* @__PURE__ */ new Set();
111
156
  persistedHistoryIds = /* @__PURE__ */ new Set();
112
157
  historyWrites = createHistoryWriteChain();
@@ -114,6 +159,11 @@ var AgUiThreadController = class {
114
159
  _loadPromise;
115
160
  constructor(options) {
116
161
  this.agent = options.agent;
162
+ const initialState = getAgentInitialState(this.agent);
163
+ if (initialState !== void 0) this.dispatch({
164
+ type: "stateSnapshot",
165
+ snapshot: initialState
166
+ });
117
167
  this.logger = options.logger;
118
168
  this.showThinking = options.showThinking;
119
169
  this.joinStrategy = options.joinStrategy;
@@ -143,7 +193,18 @@ var AgUiThreadController = class {
143
193
  for (const listener of this.listeners) listener();
144
194
  }
145
195
  updateOptions(options) {
196
+ const agentChanged = this.agent !== options.agent;
146
197
  this.agent = options.agent;
198
+ if (agentChanged) {
199
+ const initialState = getAgentInitialState(this.agent);
200
+ if (this.state.stateSnapshot === void 0 && initialState !== void 0) {
201
+ this.dispatch({
202
+ type: "stateSnapshot",
203
+ snapshot: initialState
204
+ });
205
+ this.publishUpdate();
206
+ }
207
+ }
147
208
  this.logger = options.logger;
148
209
  this.showThinking = options.showThinking;
149
210
  this.joinStrategy = options.joinStrategy;
@@ -188,6 +249,7 @@ var AgUiThreadController = class {
188
249
  if (!repo) return;
189
250
  this.session.applyExternalMessageRepository(repo);
190
251
  this.assistantHistoryParents.clear();
252
+ this.subagentParentToolCallIds.clear();
191
253
  this.snapshotHistoryIds.clear();
192
254
  this.persistedHistoryIds.clear();
193
255
  for (const { message } of repo.messages) this.persistedHistoryIds.add(message.id);
@@ -555,7 +617,10 @@ var AgUiThreadController = class {
555
617
  custom: newCustom
556
618
  }
557
619
  };
558
- })) this.publishUpdate();
620
+ })) {
621
+ this.publishUpdate();
622
+ this.persistSettledAssistant(messageId);
623
+ }
559
624
  }
560
625
  findMessageIdForToolCall(toolCallId) {
561
626
  let fallbackMessageId;
@@ -609,9 +674,31 @@ var AgUiThreadController = class {
609
674
  };
610
675
  });
611
676
  if (!matchedToolCall) return message;
677
+ const settled = mapToolCallPartsDeep(content, (part) => {
678
+ if (!part.messages) return part;
679
+ let changed = false;
680
+ const messages = part.messages.map((nested) => {
681
+ if (nested.role !== "assistant" || nested.status.type !== "requires-action" || nested.status.reason !== "tool-calls") return nested;
682
+ const listed = listedPendingToolCallIds(nested);
683
+ if (Array.from(iterateToolCallParts(nested.content)).some((call) => !isResolvedToolCall(call) && (!listed || listed.has(call.toolCallId)))) return nested;
684
+ changed = true;
685
+ return {
686
+ ...nested,
687
+ status: {
688
+ type: "complete",
689
+ reason: "unknown"
690
+ },
691
+ metadata: withoutPendingToolCallIds(nested.metadata)
692
+ };
693
+ });
694
+ return changed ? {
695
+ ...part,
696
+ messages
697
+ } : part;
698
+ }).content;
612
699
  return {
613
700
  ...assistant,
614
- content
701
+ content: settled
615
702
  };
616
703
  })) return;
617
704
  this.publishUpdate();
@@ -724,7 +811,8 @@ var AgUiThreadController = class {
724
811
  metadata: withoutPendingToolCallIds(current.metadata)
725
812
  } : current)) return false;
726
813
  this.publishUpdate();
727
- this.persistAssistantHistory(messageId);
814
+ if (this.history?.update) this.persistSettledAssistant(messageId);
815
+ else this.persistAssistantHistory(messageId);
728
816
  return true;
729
817
  }
730
818
  startResumeRun(messageId) {
@@ -827,6 +915,7 @@ var AgUiThreadController = class {
827
915
  const controller = this.abortController;
828
916
  const activeRunAgent = this.activeRunAgent;
829
917
  this.dispatch({ type: "resetThread" });
918
+ this.subagentParentToolCallIds.clear();
830
919
  if (controller) {
831
920
  this.abortController = null;
832
921
  this.activeRunAgent = null;
@@ -892,6 +981,8 @@ var AgUiThreadController = class {
892
981
  let frameOwnerIds = /* @__PURE__ */ new Set();
893
982
  let restrictedPendingToolCallIds;
894
983
  let settlingRun = false;
984
+ let latestSnapshot;
985
+ let snapshotToolCallIds = /* @__PURE__ */ new Set();
895
986
  const ensureAssistant = (allowRecreate = false) => {
896
987
  const cached = assistantMessageId;
897
988
  if (cached !== void 0 && this.session.tryGetMessage(cached)) return cached;
@@ -1097,7 +1188,45 @@ var AgUiThreadController = class {
1097
1188
  showThinking: this.showThinking,
1098
1189
  joinStrategy,
1099
1190
  logger: this.logger,
1100
- emit: applyUpdate,
1191
+ emit: (update) => {
1192
+ const currentSnapshotAssistant = assistantMessageId === void 0 ? void 0 : this.session.tryGetMessage(assistantMessageId)?.message;
1193
+ const listedSnapshotToolCallIds = currentSnapshotAssistant?.role === "assistant" ? listedPendingToolCallIds(currentSnapshotAssistant) : void 0;
1194
+ const hasPendingSnapshotToolCall = latestSnapshot?.id === assistantMessageId && currentSnapshotAssistant?.role === "assistant" && Array.from(iterateToolCallParts(currentSnapshotAssistant.content)).some((part) => !isResolvedToolCall(part) && (!listedSnapshotToolCallIds || listedSnapshotToolCallIds.has(part.toolCallId)) && (!restrictedPendingToolCallIds || restrictedPendingToolCallIds.has(part.toolCallId)));
1195
+ const reconciledUpdate = settlingRun && update.status?.type === "complete" && hasPendingSnapshotToolCall ? {
1196
+ ...update,
1197
+ status: {
1198
+ type: "requires-action",
1199
+ reason: "tool-calls"
1200
+ },
1201
+ ...restrictedPendingToolCallIds ? { metadata: {
1202
+ ...update.metadata,
1203
+ custom: {
1204
+ ...update.metadata?.custom,
1205
+ [AG_UI_METADATA_NAMESPACE]: {
1206
+ ...update.metadata?.custom?.[AG_UI_METADATA_NAMESPACE],
1207
+ pendingToolCallIds: [...restrictedPendingToolCallIds]
1208
+ }
1209
+ }
1210
+ } } : {}
1211
+ } : update;
1212
+ if (settlingRun && latestSnapshot && latestSnapshot.id === assistantMessageId && reconciledUpdate.status && reconciledUpdate.status.type !== "running" && reconciledUpdate.status.type !== "requires-action") {
1213
+ const status = reconciledUpdate.status;
1214
+ const current = this.session.tryGetMessage(assistantMessageId)?.message;
1215
+ const content = settleNestedContent(current?.role === "assistant" ? current.content : latestSnapshot.content, status);
1216
+ applyUpdate({
1217
+ ...reconciledUpdate,
1218
+ content
1219
+ });
1220
+ } else if (latestSnapshot && latestSnapshot.id === assistantMessageId && reconciledUpdate.content !== void 0) {
1221
+ const content = this.mergeSnapshotAssistantContent(latestSnapshot.content, reconciledUpdate.content, reconciledUpdate.content.some((part) => part.type === "reasoning"), reconciledUpdate.content.some(isActivityPart), snapshotToolCallIds);
1222
+ applyUpdate({
1223
+ ...reconciledUpdate,
1224
+ content
1225
+ });
1226
+ } else applyUpdate(reconciledUpdate);
1227
+ const current = assistantMessageId ? this.session.tryGetMessage(assistantMessageId)?.message : void 0;
1228
+ latestSnapshot = !settlingRun && current?.role === "assistant" && current.id === latestSnapshot?.id ? current : void 0;
1229
+ },
1101
1230
  onServerMessageId: (serverId) => {
1102
1231
  adoptServerMessageId(serverId, false);
1103
1232
  },
@@ -1147,6 +1276,10 @@ var AgUiThreadController = class {
1147
1276
  }
1148
1277
  }
1149
1278
  assistantMessageId = nextAssistantMessageId;
1279
+ const imported = assistantMessageId ? this.session.tryGetMessage(assistantMessageId)?.message : void 0;
1280
+ const source = event.messages.find((message) => message?.role === "assistant" && message.id === imported?.id);
1281
+ latestSnapshot = imported?.role === "assistant" && source ? imported : void 0;
1282
+ snapshotToolCallIds = new Set(Array.isArray(source?.toolCalls) ? source.toolCalls.map((call) => call.id).filter((id) => typeof id === "string") : []);
1150
1283
  if (!replayEnded && assistantMessageId === void 0) {
1151
1284
  const tail = this.session.getMessages().at(-1);
1152
1285
  snapshotAssistantId = tail?.role === "assistant" ? tail.id : void 0;
@@ -1194,7 +1327,6 @@ var AgUiThreadController = class {
1194
1327
  const input = this.buildRunInput(runId, normalizedRunConfig, historicalMessages, resume);
1195
1328
  const subscriber = createAgUiSubscriber({
1196
1329
  dispatch,
1197
- runId,
1198
1330
  logger: this.logger,
1199
1331
  onRunFailed: (error) => {
1200
1332
  if (abortSignal.aborted) return;
@@ -1205,7 +1337,7 @@ var AgUiThreadController = class {
1205
1337
  try {
1206
1338
  runAgentInstance.messages = input.messages;
1207
1339
  runAgentInstance.threadId = input.threadId;
1208
- runAgentInstance.state = input.state ?? null;
1340
+ runAgentInstance.state = input.state;
1209
1341
  } catch {}
1210
1342
  await runAgentInstance.runAgent.bind(runAgentInstance)(input, subscriber, { signal: abortSignal });
1211
1343
  }
@@ -1220,6 +1352,19 @@ var AgUiThreadController = class {
1220
1352
  pendingError = err;
1221
1353
  }
1222
1354
  } finally {
1355
+ const current = assistantMessageId ? this.session.tryGetMessage(assistantMessageId)?.message : void 0;
1356
+ if (this.abortController === abortController && current?.role === "assistant" && current.status?.type === "running") {
1357
+ const status = {
1358
+ type: "incomplete",
1359
+ reason: "error",
1360
+ error: "The connection ended before the answer finished."
1361
+ };
1362
+ settleHeldMessages(status);
1363
+ applyUpdate({
1364
+ status,
1365
+ content: settleNestedContent(current.content, status)
1366
+ });
1367
+ }
1223
1368
  this.finishRun(abortController);
1224
1369
  }
1225
1370
  if (pendingError) {
@@ -1342,7 +1487,7 @@ var AgUiThreadController = class {
1342
1487
  return {
1343
1488
  threadId,
1344
1489
  runId,
1345
- state: this.state.stateSnapshot ?? null,
1490
+ state: this.state.stateSnapshot === void 0 ? {} : this.state.stateSnapshot,
1346
1491
  messages,
1347
1492
  tools: toAgUiTools(context?.tools),
1348
1493
  context: [...context?.system ? [{
@@ -1452,13 +1597,13 @@ var AgUiThreadController = class {
1452
1597
  const mergedMetadata = update.metadata ? this.mergeAssistantMetadata(assistant.metadata, update.metadata) : assistant.metadata;
1453
1598
  latestStatus = update.status ?? assistant.status;
1454
1599
  let content = update.content !== void 0 ? replay ? replay.merge(assistant.content, update.content) : this.preserveToolResults(assistant.content, update.content) : assistant.content;
1455
- const calls = replay ? Array.from(iterateToolCallParts(content)) : [];
1600
+ const calls = Array.from(iterateToolCallParts(content));
1456
1601
  const pending = pendingToolCallIds ?? (replay ? /* @__PURE__ */ new Set([...replay.admittedToolCallIds, ...Array.from(iterateToolCallParts(update.content ?? [])).filter((part) => !isResolvedToolCall(part)).map((part) => part.toolCallId)]) : void 0);
1457
1602
  if (replay && latestStatus?.type !== "running" && pending) replay.admittedToolCallIds = pending;
1458
- if (replay) {
1603
+ if (replay || latestStatus?.type === "requires-action" && latestStatus.reason === "interrupt") {
1459
1604
  const interrupts = latestStatus?.type === "requires-action" && latestStatus.reason === "interrupt" ? mergedMetadata.custom["agui"]?.interrupts : void 0;
1460
1605
  const approvals = projectAgUiToolApprovals(interrupts, new Set(calls.map((part) => part.toolCallId)));
1461
- const originalCalls = new Map(Array.from(iterateToolCallParts(replay.originalContent), (part) => [part.toolCallId, part]));
1606
+ const originalCalls = new Map(Array.from(iterateToolCallParts(replay?.originalContent ?? assistant.content), (part) => [part.toolCallId, part]));
1462
1607
  content = mapToolCallPartsDeep(content, (part) => {
1463
1608
  const approval = approvals.get(part.toolCallId);
1464
1609
  if (approval) return {
@@ -1535,10 +1680,10 @@ var AgUiThreadController = class {
1535
1680
  });
1536
1681
  return changed ? content : next;
1537
1682
  }
1538
- mergeSnapshotAssistantContent(previous, next, snapshotHasReasoning, snapshotHasActivity) {
1539
- const shouldKeep = (part) => part.type === "reasoning" ? this.showThinking && !snapshotHasReasoning : isActivityPart(part) && !snapshotHasActivity;
1683
+ mergeSnapshotAssistantContent(previous, next, snapshotHasReasoning, snapshotHasActivity, keepMissingToolCalls = /* @__PURE__ */ new Set(), keepPriorTranscripts = false) {
1684
+ const shouldKeep = (part) => part.type === "tool-call" ? keepMissingToolCalls.has(part.toolCallId) && !next.some((incoming) => incoming.type === "tool-call" && incoming.toolCallId === part.toolCallId) : part.type === "reasoning" ? this.showThinking && !snapshotHasReasoning : part.type === "data" && !isActivityPart(part) ? !next.some((incoming) => incoming.type === "data" && incoming.name === part.name) : isActivityPart(part) && !snapshotHasActivity;
1540
1685
  const kept = previous.filter(shouldKeep);
1541
- const merged = this.reconcileSnapshotNestedContent(previous, this.preserveToolInteractions(previous, next));
1686
+ const merged = this.reconcileSnapshotNestedContent(previous, this.preserveToolInteractions(previous, next), keepPriorTranscripts);
1542
1687
  if (kept.length === 0) return merged;
1543
1688
  const mergedByType = /* @__PURE__ */ new Map();
1544
1689
  const mergedTools = /* @__PURE__ */ new Map();
@@ -1570,7 +1715,7 @@ var AgUiThreadController = class {
1570
1715
  result.push(...insertions[merged.length]);
1571
1716
  return result;
1572
1717
  }
1573
- reconcileSnapshotNestedContent(previous, next) {
1718
+ reconcileSnapshotNestedContent(previous, next, keepPriorTranscripts) {
1574
1719
  const nestedIds = /* @__PURE__ */ new Set();
1575
1720
  for (const part of previous) {
1576
1721
  if (part.type !== "tool-call" || !part.messages) continue;
@@ -1600,11 +1745,49 @@ var AgUiThreadController = class {
1600
1745
  if (part.type === "tool-call" && flattened.has(part.toolCallId)) return [];
1601
1746
  if (part.type !== "tool-call") return [part];
1602
1747
  const prior = previousCalls.get(part.toolCallId);
1603
- return prior?.messages === void 0 ? [part] : [{
1748
+ if (prior?.messages === void 0) return [part];
1749
+ if (part.messages === void 0) return [{
1604
1750
  ...part,
1605
1751
  messages: prior.messages
1606
1752
  }];
1753
+ if (part.messages.length === 0) return [part];
1754
+ return [{
1755
+ ...part,
1756
+ messages: this.mergeNestedTranscripts(prior.messages, part.messages, keepPriorTranscripts)
1757
+ }];
1758
+ });
1759
+ }
1760
+ /**
1761
+ * Merges one tool call's nested transcripts message by message. The incoming
1762
+ * copy is the base for tool calls and reasoning; resolved results and calls
1763
+ * only the prior copy has carry over, and the prior text wins when it extends
1764
+ * the incoming text. Importing a snapshot keeps prior messages it lacks, a
1765
+ * prior status that is running or incomplete, and the prior status when the
1766
+ * snapshot waits on calls the merged content has already resolved.
1767
+ */
1768
+ mergeNestedTranscripts(prior, next, importingSnapshot) {
1769
+ const priorById = new Map(prior.map((message) => [message.id, message]));
1770
+ const merged = next.map((message) => {
1771
+ const previous = priorById.get(message.id);
1772
+ if (previous?.role !== "assistant" || message.role !== "assistant") return message;
1773
+ let content = this.mergeSnapshotAssistantContent(previous.content, this.preserveToolResults(previous.content, message.content), message.content.some((part) => part.type === "reasoning"), message.content.some(isActivityPart), new Set(Array.from(iterateToolCallParts(previous.content), (call) => call.toolCallId)), importingSnapshot);
1774
+ const priorText = textOf(previous.content);
1775
+ const incomingText = textOf(message.content);
1776
+ if (priorText.length > incomingText.length && priorText.startsWith(incomingText)) content = replaceTextParts(content, previous.content);
1777
+ const status = !importingSnapshot ? message.status : previous.status.type === "running" || previous.status.type === "incomplete" || message.status.type === "requires-action" && message.status.reason === "tool-calls" && !Array.from(iterateToolCallParts(content)).some((call) => !isResolvedToolCall(call)) ? previous.status : message.status;
1778
+ return {
1779
+ ...message,
1780
+ status,
1781
+ content,
1782
+ metadata: {
1783
+ ...message.metadata,
1784
+ custom: mergeAgUiNamespace(previous.metadata.custom, message.metadata.custom)
1785
+ }
1786
+ };
1607
1787
  });
1788
+ if (!importingSnapshot) return merged;
1789
+ const mergedById = new Map(merged.map((message) => [message.id, message]));
1790
+ return [...prior.map((message) => mergedById.get(message.id) ?? message), ...merged.filter((message) => !priorById.has(message.id))];
1608
1791
  }
1609
1792
  mergeAssistantMetadata(current, incoming) {
1610
1793
  const annotations = incoming.unstable_annotations ? [...current.unstable_annotations, ...incoming.unstable_annotations] : current.unstable_annotations;
@@ -1621,6 +1804,7 @@ var AgUiThreadController = class {
1621
1804
  };
1622
1805
  }
1623
1806
  handleEvent(aggregator, event, activeAssistantId, joinStrategy, runStartMessageIds, hasRunUserBoundary, reconcileSnapshot) {
1807
+ if (event.type === "SUBAGENT_STARTED" && !this.subagentParentToolCallIds.has(event.subagentRunId)) this.subagentParentToolCallIds.set(event.subagentRunId, event.parentToolCallId);
1624
1808
  switch (event.type) {
1625
1809
  case "STATE_SNAPSHOT":
1626
1810
  this.dispatch({
@@ -1643,12 +1827,27 @@ var AgUiThreadController = class {
1643
1827
  }
1644
1828
  break;
1645
1829
  case "MESSAGES_SNAPSHOT": {
1830
+ aggregator.handle(event);
1646
1831
  const previousHeadId = this.session.headId;
1647
1832
  const activeAssistant = activeAssistantId === void 0 ? void 0 : this.session.tryGetMessage(activeAssistantId)?.message;
1648
1833
  const hasActiveText = activeAssistant?.role === "assistant" && activeAssistant.content.some((part) => part.type === "text");
1649
1834
  this.importMessagesSnapshot(event.messages, activeAssistantId, joinStrategy, runStartMessageIds, hasRunUserBoundary, reconcileSnapshot);
1650
1835
  const headId = this.session.headId;
1651
- if (hasActiveText && headId !== previousHeadId) activeAssistantId = (headId === null ? void 0 : this.session.tryGetMessage(headId)?.message)?.role === "assistant" ? headId ?? void 0 : void 0;
1836
+ const head = headId === null ? void 0 : this.session.tryGetMessage(headId)?.message;
1837
+ const hasNewPendingToolCallAnswer = head?.role === "assistant" && !runStartMessageIds.has(head.id) && head.status?.type === "requires-action" && head.status.reason === "tool-calls";
1838
+ if (headId !== previousHeadId && (hasActiveText || hasNewPendingToolCallAnswer)) {
1839
+ const nextActiveAssistantId = head?.role === "assistant" ? headId ?? void 0 : void 0;
1840
+ if (activeAssistantId !== nextActiveAssistantId && activeAssistantId) {
1841
+ const previous = this.session.tryGetMessage(activeAssistantId)?.message;
1842
+ if (previous?.role === "assistant" && previous.status?.type === "running") this.updateAssistantMessage(activeAssistantId, {
1843
+ status: FALLBACK_USER_STATUS,
1844
+ content: settleNestedContent(previous.content, FALLBACK_USER_STATUS)
1845
+ });
1846
+ }
1847
+ activeAssistantId = nextActiveAssistantId;
1848
+ if (activeAssistantId && joinStrategy !== "concat-content") aggregator.rebaseMessage(activeAssistantId);
1849
+ if (hasNewPendingToolCallAnswer && head) this.updateAssistantMessage(head.id, { status: { type: "running" } });
1850
+ }
1652
1851
  this.updateActiveAssistantState(activeAssistantId);
1653
1852
  break;
1654
1853
  }
@@ -1710,7 +1909,8 @@ var AgUiThreadController = class {
1710
1909
  text: modelText
1711
1910
  }] } : {},
1712
1911
  ...part.isError === void 0 ? typeof event.mcpResult?.isError === "boolean" ? { isError: event.mcpResult.isError } : event.role === "tool" ? { isError: false } : {} : {},
1713
- ...event.messageId ? { unstable_toolMessageId: event.messageId } : {}
1912
+ ...event.messageId ? { unstable_toolMessageId: event.messageId } : {},
1913
+ ...event.metadata !== void 0 ? { providerMetadata: withAgUiPartMessageMetadata(part.providerMetadata, event.metadata) } : {}
1714
1914
  };
1715
1915
  const { modelContent: _modelContent, ...partWithoutModelContent } = part;
1716
1916
  return {
@@ -1722,7 +1922,8 @@ var AgUiThreadController = class {
1722
1922
  }] } : {},
1723
1923
  ...typeof event.mcpResult?.isError === "boolean" ? { isError: event.mcpResult.isError } : event.role === "tool" ? { isError: false } : {},
1724
1924
  ...part.mcp === void 0 && mcpAppUri !== void 0 ? { mcp: { app: { resourceUri: mcpAppUri } } } : {},
1725
- ...event.messageId ? { unstable_toolMessageId: event.messageId } : {}
1925
+ ...event.messageId ? { unstable_toolMessageId: event.messageId } : {},
1926
+ ...event.metadata !== void 0 ? { providerMetadata: withAgUiPartMessageMetadata(part.providerMetadata, event.metadata) } : {}
1726
1927
  };
1727
1928
  });
1728
1929
  if (!matchedToolCall) return message;
@@ -1732,7 +1933,8 @@ var AgUiThreadController = class {
1732
1933
  };
1733
1934
  })) return;
1734
1935
  this.publishUpdate();
1735
- this.maybeCompleteAfterToolResults(messageId);
1936
+ if (this.maybeCompleteAfterToolResults(messageId)) return;
1937
+ this.persistSettledAssistant(messageId);
1736
1938
  }
1737
1939
  applyCrossRunActivitySnapshot(messageId, toolCallId, event) {
1738
1940
  const resourceUri = event.content["resourceUri"];
@@ -1771,7 +1973,8 @@ var AgUiThreadController = class {
1771
1973
  };
1772
1974
  })) return;
1773
1975
  this.publishUpdate();
1774
- this.maybeCompleteAfterToolResults(messageId);
1976
+ if (this.maybeCompleteAfterToolResults(messageId)) return;
1977
+ this.persistSettledAssistant(messageId);
1775
1978
  }
1776
1979
  importMessagesSnapshot(rawMessages, activeAssistantId, joinStrategy, runStartMessageIds, hasRunUserBoundary, reconcileSnapshot) {
1777
1980
  try {
@@ -1779,10 +1982,12 @@ var AgUiThreadController = class {
1779
1982
  const activeMessage = activeAssistantItem?.message;
1780
1983
  const activeAssistantParentId = activeAssistantItem?.parentId;
1781
1984
  const activeAssistant = activeMessage?.role === "assistant" ? activeMessage : void 0;
1782
- const normalized = fromAgUiMessages(rawMessages, { showThinking: this.showThinking });
1985
+ const normalized = fromAgUiSnapshotMessages(rawMessages, { showThinking: this.showThinking }, this.subagentParentToolCallIds);
1783
1986
  const converted = [];
1784
1987
  for (const message of normalized) try {
1785
- converted.push(fromThreadMessageLike(message, generateId(), FALLBACK_USER_STATUS));
1988
+ const previous = message.id ? this.session.tryGetMessage(message.id)?.message : void 0;
1989
+ const fallbackStatus = previous?.role === "assistant" && previous.status?.type === "running" ? previous.status : FALLBACK_USER_STATUS;
1990
+ converted.push(fromThreadMessageLike(message, generateId(), fallbackStatus));
1786
1991
  } catch (error) {
1787
1992
  this.logger.error?.("[agui] failed to import message from snapshot", error);
1788
1993
  }
@@ -1794,9 +1999,13 @@ var AgUiThreadController = class {
1794
1999
  if (convertedMessage.role === "assistant") {
1795
2000
  const previous = existing?.role === "assistant" ? existing : void 0;
1796
2001
  const snapshot = reconcileSnapshot?.(convertedMessage, previous) ?? convertedMessage;
2002
+ const preservesRunningStatus = convertedMessage.id === activeAssistantId && previous?.status?.type === "running" && snapshot.status?.type === "requires-action" && snapshot.status.reason === "tool-calls";
2003
+ const previousToolCallIds = new Set(Array.from(iterateToolCallParts(previous?.content ?? []), (call) => call.toolCallId));
2004
+ const preservesSettledStatus = convertedMessage.id !== activeAssistantId && this.isTerminalStatus(previous?.status) && snapshot.status?.type === "requires-action" && snapshot.status.reason === "tool-calls" && Array.from(iterateToolCallParts(snapshot.content)).every((call) => isResolvedToolCall(call) || previousToolCallIds.has(call.toolCallId));
1797
2005
  converted[index] = previous ? {
1798
2006
  ...snapshot,
1799
- content: this.mergeSnapshotAssistantContent(previous.content, snapshot.content, snapshotHasReasoning, snapshotHasActivity)
2007
+ ...preservesRunningStatus || preservesSettledStatus ? { status: previous.status } : {},
2008
+ content: this.mergeSnapshotAssistantContent(previous.content, snapshot.content, snapshotHasReasoning, snapshotHasActivity, /* @__PURE__ */ new Set(), true)
1800
2009
  } : snapshot;
1801
2010
  }
1802
2011
  }
@@ -1881,6 +2090,14 @@ var AgUiThreadController = class {
1881
2090
  if (!this.history) return;
1882
2091
  this.assistantHistoryParents.set(messageId, parentId);
1883
2092
  }
2093
+ persistSettledAssistant(messageId) {
2094
+ const item = this.session.tryGetMessage(messageId);
2095
+ if (!this.history?.update || !item) return;
2096
+ if (!this.isTerminalStatus(item.message.status)) return;
2097
+ if (!this.persistedHistoryIds.has(messageId) && this.historyWrites.pending(messageId) === void 0) return;
2098
+ this.markPendingAssistantHistory(messageId, item.parentId);
2099
+ this.persistAssistantHistory(messageId);
2100
+ }
1884
2101
  persistAssistantHistory(messageId) {
1885
2102
  const history = this.history;
1886
2103
  if (!history) return;
@@ -1891,7 +2108,7 @@ var AgUiThreadController = class {
1891
2108
  if (!this.isPersistableStatus(message.status)) return;
1892
2109
  const wasPersisted = this.persistedHistoryIds.has(messageId);
1893
2110
  const update = history.update;
1894
- if (update !== void 0 && (wasPersisted || this.snapshotHistoryIds.has(messageId))) {
2111
+ if (update !== void 0 && (wasPersisted || this.snapshotHistoryIds.has(messageId) || this.historyWrites.pending(messageId) !== void 0)) {
1895
2112
  const write = this.historyWrites.chain(messageId, () => update.call(history, {
1896
2113
  parentId,
1897
2114
  message