@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,31 +5,19 @@ 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 { MessageRepository } from "@assistant-ui/core/internal";
8
+ import { 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__";
12
12
  const generateOptimisticId = () => `${optimisticPrefix}${generateId()}`;
13
13
  const isOptimisticId = (id) => id.startsWith(optimisticPrefix);
14
14
  const isResolvedToolCall = (part) => part.type === "tool-call" && "result" in part && part.result !== void 0;
15
- const symbolResumeShim = Symbol("agui-resume-shim");
16
15
  const FALLBACK_USER_STATUS = {
17
16
  type: "complete",
18
17
  reason: "unknown"
19
18
  };
20
- const reportCallbackError = (name, error) => {
21
- console.error(`[react-ag-ui] ${name} callback threw an error`, error);
22
- };
23
19
  const invokeRuntimeCallback = (name, callback, ...args) => {
24
- if (!callback) return;
25
- try {
26
- const result = callback(...args);
27
- if (result !== null && (typeof result === "object" || typeof result === "function") && "then" in result && typeof result.then === "function") Promise.resolve(result).catch((error) => {
28
- reportCallbackError(name, error);
29
- });
30
- } catch (error) {
31
- reportCallbackError(name, error);
32
- }
20
+ invokeUserCallback("react-ag-ui", name, callback, ...args);
33
21
  };
34
22
  const mergeAgUiNamespace = (current, incoming) => {
35
23
  const merged = {
@@ -54,8 +42,20 @@ var AgUiThreadRuntimeCore = class {
54
42
  notifyUpdate;
55
43
  reportedErrors = /* @__PURE__ */ new WeakSet();
56
44
  runtime;
57
- repository = new MessageRepository();
58
- exportedRepository;
45
+ session = createMessageRepositorySession({ decorateExport: (exported, repository) => {
46
+ let parentId = null;
47
+ for (const message of repository.getMessages()) {
48
+ if (message.metadata.isOptimistic) exported.messages.push({
49
+ parentId,
50
+ message
51
+ });
52
+ parentId = message.id;
53
+ }
54
+ return {
55
+ ...exported,
56
+ headId: repository.headId
57
+ };
58
+ } });
59
59
  isRunningFlag = false;
60
60
  abortController = null;
61
61
  activeRunAgent = null;
@@ -69,6 +69,7 @@ var AgUiThreadRuntimeCore = class {
69
69
  historyWrites = /* @__PURE__ */ new Map();
70
70
  _isLoading = false;
71
71
  _loadPromise;
72
+ _loadRequested = false;
72
73
  pendingResumeMessageId = null;
73
74
  pendingA2uiResume = false;
74
75
  pendingA2uiAction;
@@ -81,7 +82,6 @@ var AgUiThreadRuntimeCore = class {
81
82
  this.onCancel = options.onCancel;
82
83
  this.history = options.history;
83
84
  this.notifyUpdate = options.notifyUpdate;
84
- this.installResumeShim();
85
85
  }
86
86
  updateOptions(options) {
87
87
  this.agent = options.agent;
@@ -90,8 +90,9 @@ var AgUiThreadRuntimeCore = class {
90
90
  this.autoCancelPendingToolCalls = options.autoCancelPendingToolCalls;
91
91
  this.onError = options.onError;
92
92
  this.onCancel = options.onCancel;
93
+ const previousHistory = this.history;
93
94
  this.history = options.history;
94
- this.installResumeShim();
95
+ if (this._loadRequested && !this._loadPromise && !previousHistory && options.history && this.session.getMessages().length === 0) this.__internal_load();
95
96
  }
96
97
  attachRuntime(runtime) {
97
98
  this.runtime = runtime;
@@ -103,74 +104,10 @@ var AgUiThreadRuntimeCore = class {
103
104
  });
104
105
  }
105
106
  getMessages() {
106
- return this.repository.getMessages();
107
+ return this.session.getMessages();
107
108
  }
108
109
  getMessageRepository() {
109
- if (this.exportedRepository) return this.exportedRepository;
110
- const exportedRepository = this.repository.export();
111
- let parentId = null;
112
- for (const message of this.repository.getMessages()) {
113
- if (message.metadata.isOptimistic) exportedRepository.messages.push({
114
- parentId,
115
- message
116
- });
117
- parentId = message.id;
118
- }
119
- this.exportedRepository = {
120
- ...exportedRepository,
121
- headId: this.repository.headId
122
- };
123
- return this.exportedRepository;
124
- }
125
- tryGetMessage(messageId) {
126
- try {
127
- return this.repository.getMessage(messageId);
128
- } catch {
129
- return;
130
- }
131
- }
132
- tryGetMessages(messageId) {
133
- try {
134
- return this.repository.getMessages(messageId);
135
- } catch {
136
- return;
137
- }
138
- }
139
- hasMessage(messageId) {
140
- return this.tryGetMessage(messageId) !== void 0;
141
- }
142
- addOrUpdateMessage(parentId, message) {
143
- this.repository.addOrUpdateMessage(parentId, message);
144
- this.exportedRepository = void 0;
145
- }
146
- deleteMessage(messageId, replacementId) {
147
- this.repository.deleteMessage(messageId, replacementId);
148
- this.exportedRepository = void 0;
149
- }
150
- tryDeleteMessage(messageId) {
151
- if (!this.hasMessage(messageId)) return false;
152
- this.deleteMessage(messageId);
153
- return true;
154
- }
155
- switchToBranch(messageId) {
156
- this.repository.switchToBranch(messageId);
157
- this.exportedRepository = void 0;
158
- }
159
- resetRepositoryHead(messageId) {
160
- this.repository.resetHead(messageId);
161
- this.exportedRepository = void 0;
162
- }
163
- clearRepository() {
164
- this.repository.clear();
165
- this.exportedRepository = void 0;
166
- }
167
- updateMessage(messageId, updater) {
168
- const item = this.tryGetMessage(messageId);
169
- if (!item) return false;
170
- const message = updater(item.message);
171
- if (message === item.message) return false;
172
- this.addOrUpdateMessage(item.parentId, message);
173
- return true;
110
+ return this.session.export();
174
111
  }
175
112
  getState() {
176
113
  return this.stateSnapshot;
@@ -182,15 +119,22 @@ var AgUiThreadRuntimeCore = class {
182
119
  return this._isLoading;
183
120
  }
184
121
  __internal_load() {
122
+ this._loadRequested = true;
185
123
  if (this._loadPromise) return this._loadPromise;
186
- const promise = this.history?.load() ?? Promise.resolve(null);
124
+ if (!this.history) return Promise.resolve();
125
+ const promise = this.history.load();
187
126
  this._isLoading = true;
188
127
  this._loadPromise = promise.then(async (repo) => {
189
128
  if (!repo) return;
190
- this.applyExternalMessageRepository(repo);
129
+ this.session.applyExternalMessageRepository(repo);
130
+ this.assistantHistoryParents.clear();
131
+ this.snapshotHistoryIds.clear();
132
+ this.persistedHistoryIds.clear();
133
+ for (const { message } of repo.messages) this.persistedHistoryIds.add(message.id);
134
+ this.notifyUpdate();
191
135
  if (repo.state !== void 0) this.loadExternalState(repo.state);
192
136
  if (repo.unstable_resume) {
193
- const parentId = repo.headId ?? this.repository.headId;
137
+ const parentId = repo.headId ?? this.session.headId;
194
138
  const resumeStream = this.history?.resume?.bind(this.history);
195
139
  await this.startRun(parentId, this.lastRunConfig, void 0, resumeStream);
196
140
  }
@@ -222,11 +166,11 @@ var AgUiThreadRuntimeCore = class {
222
166
  this.maybeCompleteAfterToolResults(pending.messageId);
223
167
  }
224
168
  appendEntry(message) {
225
- if (message.sourceId) this.tryDeleteMessage(message.sourceId);
169
+ if (message.sourceId) this.session.tryDeleteMessage(message.sourceId);
226
170
  const threadMessage = this.toThreadMessage(message);
227
- const parentId = message.parentId === null ? null : message.parentId && this.hasMessage(message.parentId) ? message.parentId : this.repository.headId;
228
- this.addOrUpdateMessage(parentId, threadMessage);
229
- this.switchToBranch(threadMessage.id);
171
+ const parentId = message.parentId === null ? null : message.parentId && this.session.hasMessage(message.parentId) ? message.parentId : this.session.headId;
172
+ this.session.addOrUpdateMessage(parentId, threadMessage);
173
+ this.session.switchToBranch(threadMessage.id);
230
174
  this.notifyUpdate();
231
175
  this.recordHistoryEntry(parentId, threadMessage);
232
176
  return threadMessage.id;
@@ -287,8 +231,7 @@ var AgUiThreadRuntimeCore = class {
287
231
  const assistant = this.findRequiresActionAssistant("tool-calls");
288
232
  if (!assistant) return null;
289
233
  const toolCallIds = [];
290
- for (const part of assistant.content) {
291
- if (part.type !== "tool-call") continue;
234
+ for (const part of iterateToolCallParts(assistant.content)) {
292
235
  if (isResolvedToolCall(part)) continue;
293
236
  toolCallIds.push(part.toolCallId);
294
237
  }
@@ -319,9 +262,9 @@ var AgUiThreadRuntimeCore = class {
319
262
  await this.startRun(messageId, this.lastRunConfig, resume);
320
263
  }
321
264
  /**
322
- * The core seams that reach `respondToToolApproval` discard the promise they
323
- * receive, so a rejected decision would otherwise surface only as an
324
- * unhandled rejection. A failure raised by the resumed run itself was already
265
+ * A rejected decision reaches the caller through the approval seam, and
266
+ * `onError` in addition, because a consumer watching only the runtime would
267
+ * otherwise miss it. A failure raised by the resumed run itself was already
325
268
  * reported by `startRun` before it rethrew, so reporting it here again would
326
269
  * give the consumer two notifications for one failure.
327
270
  */
@@ -345,10 +288,10 @@ var AgUiThreadRuntimeCore = class {
345
288
  async respondToToolApproval(options) {
346
289
  const pending = this.getPendingInterrupts();
347
290
  if (!pending) throw new Error("[agui] respondToToolApproval: no pending interrupts on this thread");
348
- const gatedMessage = this.tryGetMessage(pending.messageId)?.message;
349
- if (![...projectAgUiToolApprovals(pending.interrupts, new Set((gatedMessage?.content ?? []).filter((part) => part.type === "tool-call").map((part) => part.toolCallId))).values()].some((approval) => approval.id === options.approvalId)) throw new Error(`[agui] respondToToolApproval: no pending tool-call interrupt for approval id "${options.approvalId}"`);
291
+ const gatedMessage = this.session.tryGetMessage(pending.messageId)?.message;
292
+ if (![...projectAgUiToolApprovals(pending.interrupts, new Set(Array.from(iterateToolCallParts(gatedMessage?.content ?? [])).map((part) => part.toolCallId))).values()].some((approval) => approval.id === options.approvalId)) throw new Error(`[agui] respondToToolApproval: no pending tool-call interrupt for approval id "${options.approvalId}"`);
350
293
  this.assertInterruptsAnswerable("respondToToolApproval", pending.interrupts);
351
- if (!this.updateMessage(pending.messageId, (message) => {
294
+ if (!this.session.updateMessage(pending.messageId, (message) => {
352
295
  if (message.role !== "assistant") return message;
353
296
  const assistant = message;
354
297
  const content = withToolApprovalDecision(assistant.content, options);
@@ -359,7 +302,7 @@ var AgUiThreadRuntimeCore = class {
359
302
  };
360
303
  })) throw new Error(`[agui] respondToToolApproval: approval "${options.approvalId}" is already decided`);
361
304
  this.notifyUpdate();
362
- const assistant = this.tryGetMessage(pending.messageId)?.message;
305
+ const assistant = this.session.tryGetMessage(pending.messageId)?.message;
363
306
  if (!assistant) return;
364
307
  const resume = buildToolApprovalResume(assistant.content, pending.interrupts);
365
308
  if (!resume) return;
@@ -412,7 +355,7 @@ var AgUiThreadRuntimeCore = class {
412
355
  toAppendMessage(message) {
413
356
  if (typeof message === "string") return {
414
357
  createdAt: /* @__PURE__ */ new Date(),
415
- parentId: this.repository.headId,
358
+ parentId: this.session.headId,
416
359
  sourceId: null,
417
360
  runConfig: {},
418
361
  role: "user",
@@ -425,7 +368,7 @@ var AgUiThreadRuntimeCore = class {
425
368
  };
426
369
  return {
427
370
  createdAt: message.createdAt ?? /* @__PURE__ */ new Date(),
428
- parentId: message.parentId ?? this.repository.headId,
371
+ parentId: message.parentId ?? this.session.headId,
429
372
  sourceId: message.sourceId ?? null,
430
373
  role: message.role ?? "user",
431
374
  content: message.content,
@@ -436,7 +379,7 @@ var AgUiThreadRuntimeCore = class {
436
379
  };
437
380
  }
438
381
  clearPendingInterrupts(messageId, resume) {
439
- if (this.updateMessage(messageId, (message) => {
382
+ if (this.session.updateMessage(messageId, (message) => {
440
383
  if (message.role !== "assistant") return message;
441
384
  const assistant = message;
442
385
  if (assistant.status?.type !== "requires-action" || assistant.status.reason !== "interrupt") return assistant;
@@ -464,8 +407,8 @@ var AgUiThreadRuntimeCore = class {
464
407
  for (let index = messages.length - 1; index >= 0; index--) {
465
408
  const message = messages[index];
466
409
  if (!message || message.role !== "assistant") continue;
467
- for (const part of message.content) {
468
- if (part.type !== "tool-call" || part.toolCallId !== toolCallId) continue;
410
+ for (const part of iterateToolCallParts(message.content)) {
411
+ if (part.toolCallId !== toolCallId) continue;
469
412
  if (!isResolvedToolCall(part)) return message.id;
470
413
  fallbackMessageId ??= message.id;
471
414
  }
@@ -473,11 +416,11 @@ var AgUiThreadRuntimeCore = class {
473
416
  return fallbackMessageId;
474
417
  }
475
418
  cancelUnresolvedToolCalls(messageId) {
476
- if (this.updateMessage(messageId, (message) => {
419
+ if (this.session.updateMessage(messageId, (message) => {
477
420
  if (message.role !== "assistant") return message;
478
421
  const assistant = message;
479
- const content = assistant.content.map((part) => {
480
- if (part.type !== "tool-call" || isResolvedToolCall(part)) return part;
422
+ const { content } = mapToolCallPartsDeep(assistant.content, (part) => {
423
+ if (isResolvedToolCall(part)) return part;
481
424
  return {
482
425
  ...part,
483
426
  result: { error: "Tool call cancelled by user" },
@@ -491,12 +434,14 @@ var AgUiThreadRuntimeCore = class {
491
434
  })) this.notifyUpdate();
492
435
  }
493
436
  addToolResult(options) {
494
- if (!this.updateMessage(options.messageId, (message) => {
437
+ const sessionMessageId = this.session.tryGetMessage(options.messageId) ? options.messageId : this.findMessageIdForToolCall(options.toolCallId);
438
+ if (sessionMessageId === void 0) return;
439
+ if (!this.session.updateMessage(sessionMessageId, (message) => {
495
440
  if (message.role !== "assistant") return message;
496
441
  const assistant = message;
497
442
  let matchedToolCall = false;
498
- const content = assistant.content.map((part) => {
499
- if (part.type !== "tool-call" || part.toolCallId !== options.toolCallId) return part;
443
+ const { content } = mapToolCallPartsDeep(assistant.content, (part) => {
444
+ if (part.toolCallId !== options.toolCallId) return part;
500
445
  matchedToolCall = true;
501
446
  return {
502
447
  ...part,
@@ -512,12 +457,12 @@ var AgUiThreadRuntimeCore = class {
512
457
  };
513
458
  })) return;
514
459
  this.notifyUpdate();
515
- this.maybeResumeAfterToolResults(options.messageId);
460
+ this.maybeResumeAfterToolResults(sessionMessageId);
516
461
  }
517
462
  sendA2uiAction(action) {
518
463
  this.assertNoPendingInterrupts();
519
464
  this.maybeAutoCancelPendingToolCalls();
520
- const parentId = this.repository.headId;
465
+ const parentId = this.session.headId;
521
466
  if (parentId === null) {
522
467
  this.logger.debug("[agui] sendA2uiAction: no messages to resume, dropping action");
523
468
  return;
@@ -541,12 +486,17 @@ var AgUiThreadRuntimeCore = class {
541
486
  this.startResumeRun(messageId);
542
487
  }
543
488
  maybeCompleteAfterToolResults(messageId) {
544
- const message = this.tryGetMessage(messageId)?.message;
489
+ const message = this.session.tryGetMessage(messageId)?.message;
545
490
  if (!message || message.role !== "assistant") return false;
546
491
  const assistant = message;
547
492
  if (assistant.status?.type !== "requires-action" || assistant.status.reason !== "tool-calls") return false;
548
- if (!assistant.content.every((part) => part.type !== "tool-call" || isResolvedToolCall(part))) return false;
549
- if (!this.updateMessage(messageId, (current) => current.role === "assistant" ? {
493
+ let allResolved = true;
494
+ for (const part of iterateToolCallParts(assistant.content)) if (!isResolvedToolCall(part)) {
495
+ allResolved = false;
496
+ break;
497
+ }
498
+ if (!allResolved) return false;
499
+ if (!this.session.updateMessage(messageId, (current) => current.role === "assistant" ? {
550
500
  ...current,
551
501
  status: {
552
502
  type: "complete",
@@ -566,7 +516,7 @@ var AgUiThreadRuntimeCore = class {
566
516
  this.pendingA2uiResume = false;
567
517
  this.pendingA2uiAction = void 0;
568
518
  this.assistantHistoryParents.clear();
569
- if (messages.length === 0) this.clearRepository();
519
+ if (messages.length === 0) this.session.clear();
570
520
  else {
571
521
  let expectedParentId = null;
572
522
  let lastAppliedId = null;
@@ -575,85 +525,34 @@ var AgUiThreadRuntimeCore = class {
575
525
  for (const message of messages) {
576
526
  if (seen.has(message.id)) continue;
577
527
  seen.add(message.id);
578
- const existing = this.tryGetMessage(message.id);
528
+ const existing = this.session.tryGetMessage(message.id);
579
529
  if (existing && existing.parentId !== expectedParentId) {
580
530
  hardReplace = true;
581
531
  break;
582
532
  }
583
- this.addOrUpdateMessage(expectedParentId, message);
533
+ this.session.addOrUpdateMessage(expectedParentId, message);
584
534
  expectedParentId = message.id;
585
535
  lastAppliedId = message.id;
586
536
  }
587
537
  if (hardReplace) {
588
- this.clearRepository();
538
+ this.session.clear();
589
539
  expectedParentId = null;
590
540
  lastAppliedId = null;
591
541
  seen.clear();
592
542
  for (const message of messages) {
593
543
  if (seen.has(message.id)) continue;
594
544
  seen.add(message.id);
595
- this.addOrUpdateMessage(expectedParentId, message);
545
+ this.session.addOrUpdateMessage(expectedParentId, message);
596
546
  expectedParentId = message.id;
597
547
  lastAppliedId = message.id;
598
548
  }
599
549
  }
600
- this.resetRepositoryHead(lastAppliedId);
550
+ this.session.resetHead(lastAppliedId);
601
551
  }
602
552
  this.snapshotHistoryIds.clear();
603
553
  for (const { message } of this.getMessageRepository().messages) this.snapshotHistoryIds.add(message.id);
604
554
  this.notifyUpdate();
605
555
  }
606
- applyExternalMessageRepository(loaded) {
607
- const headId = loaded.headId ?? loaded.messages.at(-1)?.message.id ?? null;
608
- const ids = /* @__PURE__ */ new Set();
609
- let degenerate = false;
610
- for (const { message } of loaded.messages) {
611
- if (ids.has(message.id)) {
612
- degenerate = true;
613
- break;
614
- }
615
- ids.add(message.id);
616
- }
617
- if (headId !== null && !ids.has(headId)) degenerate = true;
618
- if (!degenerate) {
619
- this.clearRepository();
620
- let pending = [...loaded.messages];
621
- const importedIds = /* @__PURE__ */ new Set();
622
- while (pending.length > 0) {
623
- const unresolved = [];
624
- let progressed = false;
625
- for (const item of pending) {
626
- if (item.parentId !== null && !importedIds.has(item.parentId)) {
627
- unresolved.push(item);
628
- continue;
629
- }
630
- this.addOrUpdateMessage(item.parentId, item.message);
631
- importedIds.add(item.message.id);
632
- progressed = true;
633
- }
634
- if (!progressed) {
635
- degenerate = true;
636
- break;
637
- }
638
- pending = unresolved;
639
- }
640
- }
641
- if (degenerate) {
642
- this.clearRepository();
643
- let previousId = null;
644
- for (const { message } of loaded.messages) {
645
- const existing = this.tryGetMessage(message.id);
646
- this.addOrUpdateMessage(existing ? existing.parentId : previousId, message);
647
- previousId = message.id;
648
- }
649
- this.resetRepositoryHead(previousId);
650
- } else this.resetRepositoryHead(headId);
651
- this.assistantHistoryParents.clear();
652
- this.snapshotHistoryIds.clear();
653
- this.persistedHistoryIds.clear();
654
- for (const { message } of loaded.messages) this.persistedHistoryIds.add(message.id);
655
- this.notifyUpdate();
656
- }
657
556
  loadExternalState(state) {
658
557
  this.stateSnapshot = state;
659
558
  this.notifyUpdate();
@@ -669,20 +568,20 @@ var AgUiThreadRuntimeCore = class {
669
568
  async startRun(parentId, runConfig, resume, resumeStream) {
670
569
  const normalizedRunConfig = runConfig ?? {};
671
570
  this.lastRunConfig = normalizedRunConfig;
672
- const parent = parentId === null ? void 0 : this.tryGetMessage(parentId);
673
- const shouldEagerlyInsertAssistant = parentId !== null && parent !== void 0 && parentId !== this.repository.headId;
674
- const historicalMessages = [...shouldEagerlyInsertAssistant ? this.tryGetMessages(parentId) ?? this.repository.getMessages() : this.repository.getMessages()];
571
+ const parent = parentId === null ? void 0 : this.session.tryGetMessage(parentId);
572
+ const shouldEagerlyInsertAssistant = parentId !== null && parent !== void 0 && parentId !== this.session.headId;
573
+ const historicalMessages = [...shouldEagerlyInsertAssistant ? this.session.tryGetMessages(parentId) ?? this.session.getMessages() : this.session.getMessages()];
675
574
  const runStartMessageIds = new Set(this.getMessageRepository().messages.map(({ message }) => message.id));
676
575
  this.pendingError = null;
677
- const assistantParentId = parent ? parentId : this.repository.headId;
576
+ const assistantParentId = parent ? parentId : this.session.headId;
678
577
  let assistantMessageId;
679
578
  let assistantCollided = false;
680
579
  const ensureAssistant = (allowRecreate = false) => {
681
580
  const cached = assistantMessageId;
682
- if (cached !== void 0 && this.tryGetMessage(cached)) return cached;
581
+ if (cached !== void 0 && this.session.tryGetMessage(cached)) return cached;
683
582
  if (cached !== void 0 && (assistantCollided || !allowRecreate)) return cached;
684
- const repositoryHeadId = this.repository.headId;
685
- const parentId = (cached === void 0 || shouldEagerlyInsertAssistant && (repositoryHeadId === null || runStartMessageIds.has(repositoryHeadId))) && assistantParentId && this.hasMessage(assistantParentId) ? assistantParentId : repositoryHeadId;
583
+ const repositoryHeadId = this.session.headId;
584
+ const parentId = (cached === void 0 || shouldEagerlyInsertAssistant && (repositoryHeadId === null || runStartMessageIds.has(repositoryHeadId))) && assistantParentId && this.session.hasMessage(assistantParentId) ? assistantParentId : repositoryHeadId;
686
585
  const created = this.insertAssistantPlaceholder(parentId, cached);
687
586
  assistantMessageId = created;
688
587
  this.markPendingAssistantHistory(created, parentId);
@@ -694,23 +593,40 @@ var AgUiThreadRuntimeCore = class {
694
593
  const resolved = this.updateAssistantMessage(ensureAssistant(hasStreamContent), update);
695
594
  if (resolved !== assistantMessageId) assistantMessageId = resolved;
696
595
  };
596
+ const adoptServerMessageId = (serverId, startNewMessage) => {
597
+ if (startNewMessage && assistantMessageId !== void 0) {
598
+ applyUpdate({ status: {
599
+ type: "complete",
600
+ reason: "unknown"
601
+ } });
602
+ const previousId = assistantMessageId;
603
+ assistantMessageId = void 0;
604
+ if (this.session.tryGetMessage(previousId)) {
605
+ const created = this.insertAssistantPlaceholder(previousId);
606
+ assistantMessageId = created;
607
+ this.markPendingAssistantHistory(created, previousId);
608
+ }
609
+ }
610
+ const placeholder = ensureAssistant(true);
611
+ if (placeholder === serverId) return;
612
+ const reassigned = this.reassignAssistantId(placeholder, serverId);
613
+ const adoptsVisibleCollision = !reassigned && !runStartMessageIds.has(serverId) && this.session.headId === serverId;
614
+ if (reassigned || adoptsVisibleCollision) {
615
+ assistantMessageId = serverId;
616
+ if (adoptsVisibleCollision) {
617
+ const parentId = this.session.tryGetMessage(serverId)?.parentId ?? null;
618
+ this.markPendingAssistantHistory(serverId, parentId);
619
+ }
620
+ } else assistantCollided = true;
621
+ };
697
622
  const aggregator = new RunAggregator({
698
623
  showThinking: this.showThinking,
699
624
  logger: this.logger,
700
625
  emit: applyUpdate,
701
626
  onServerMessageId: (serverId) => {
702
- const placeholder = ensureAssistant(true);
703
- if (placeholder === serverId) return;
704
- const reassigned = this.reassignAssistantId(placeholder, serverId);
705
- const adoptsVisibleCollision = !reassigned && !runStartMessageIds.has(serverId) && this.repository.headId === serverId;
706
- if (reassigned || adoptsVisibleCollision) {
707
- assistantMessageId = serverId;
708
- if (adoptsVisibleCollision) {
709
- const parentId = this.tryGetMessage(serverId)?.parentId ?? null;
710
- this.markPendingAssistantHistory(serverId, parentId);
711
- }
712
- } else assistantCollided = true;
713
- }
627
+ adoptServerMessageId(serverId, false);
628
+ },
629
+ onTextMessageStart: (serverId) => adoptServerMessageId(serverId, true)
714
630
  });
715
631
  const dispatch = (event) => this.handleEvent(aggregator, event, assistantMessageId);
716
632
  const abortController = new AbortController();
@@ -802,7 +718,7 @@ var AgUiThreadRuntimeCore = class {
802
718
  return;
803
719
  }
804
720
  this.maybeAutoCancelPendingToolCalls();
805
- const parentId = this.repository.headId;
721
+ const parentId = this.session.headId;
806
722
  if (parentId !== null) this.startResumeRun(parentId);
807
723
  else {
808
724
  this.pendingA2uiAction = void 0;
@@ -825,7 +741,7 @@ var AgUiThreadRuntimeCore = class {
825
741
  unstable_threadId: ctx.threadId,
826
742
  unstable_parentId: ctx.parentId,
827
743
  unstable_getMessage: () => {
828
- const message = this.tryGetMessage(currentId())?.message;
744
+ const message = this.session.tryGetMessage(currentId())?.message;
829
745
  if (!message) throw new Error("[agui] resume stream requested the assistant message before it existed");
830
746
  return message;
831
747
  }
@@ -848,7 +764,7 @@ var AgUiThreadRuntimeCore = class {
848
764
  return;
849
765
  }
850
766
  if (ctx.abortSignal.aborted) return;
851
- const current = this.tryGetMessage(currentId())?.message;
767
+ const current = this.session.tryGetMessage(currentId())?.message;
852
768
  if (!current || current.status?.type === "running") ctx.applyUpdate({ status: {
853
769
  type: "complete",
854
770
  reason: "unknown"
@@ -856,7 +772,7 @@ var AgUiThreadRuntimeCore = class {
856
772
  }
857
773
  buildRunInput(runId, runConfig, historyMessages, resume) {
858
774
  const threadId = this.agent.threadId || "main";
859
- const messages = toAgUiMessages(historyMessages ?? this.repository.getMessages());
775
+ const messages = toAgUiMessages(historyMessages ?? this.session.getMessages());
860
776
  const context = this.runtime?.thread.getModelContext();
861
777
  const input = {
862
778
  threadId,
@@ -879,21 +795,6 @@ var AgUiThreadRuntimeCore = class {
879
795
  this.pendingA2uiAction = void 0;
880
796
  return input;
881
797
  }
882
- installResumeShim() {
883
- const agent = this.agent;
884
- if (agent[symbolResumeShim]) return;
885
- agent[symbolResumeShim] = true;
886
- const original = Object.hasOwn(agent, "prepareRunAgentInput") ? agent.prepareRunAgentInput : Object.getPrototypeOf(agent)?.prepareRunAgentInput;
887
- if (typeof original !== "function") return;
888
- agent.prepareRunAgentInput = function(params) {
889
- const input = original.call(this, params);
890
- if (params?.resume !== void 0 && input && typeof input === "object") return {
891
- ...input,
892
- resume: params.resume
893
- };
894
- return input;
895
- };
896
- }
897
798
  setRunning(running) {
898
799
  this.isRunningFlag = running;
899
800
  this.notifyUpdate();
@@ -921,31 +822,31 @@ var AgUiThreadRuntimeCore = class {
921
822
  custom: {}
922
823
  }
923
824
  };
924
- this.addOrUpdateMessage(parentId ?? this.repository.headId, assistant);
925
- this.switchToBranch(id);
825
+ this.session.addOrUpdateMessage(parentId ?? this.session.headId, assistant);
826
+ this.session.switchToBranch(id);
926
827
  this.notifyUpdate();
927
828
  return id;
928
829
  }
929
830
  reassignAssistantId(oldId, newId) {
930
831
  if (oldId === newId) return true;
931
- const oldItem = this.tryGetMessage(oldId);
832
+ const oldItem = this.session.tryGetMessage(oldId);
932
833
  if (!oldItem) return false;
933
- const collidesWithExisting = this.hasMessage(newId);
834
+ const collidesWithExisting = this.session.hasMessage(newId);
934
835
  if (collidesWithExisting) {
935
836
  this.logger.debug?.("[agui] reassignAssistantId: server id already present in messages or repository, dropping placeholder", {
936
837
  oldId,
937
838
  newId
938
839
  });
939
- this.deleteMessage(oldId, oldItem.parentId ?? null);
840
+ this.session.deleteMessage(oldId, oldItem.parentId ?? null);
940
841
  } else {
941
842
  const { isOptimistic: _, ...metadata } = oldItem.message.metadata;
942
- this.addOrUpdateMessage(oldItem.parentId, {
843
+ this.session.addOrUpdateMessage(oldItem.parentId, {
943
844
  ...oldItem.message,
944
845
  id: newId,
945
846
  metadata
946
847
  });
947
- this.switchToBranch(newId);
948
- this.tryDeleteMessage(oldId);
848
+ this.session.switchToBranch(newId);
849
+ this.session.tryDeleteMessage(oldId);
949
850
  }
950
851
  const pendingParent = this.assistantHistoryParents.get(oldId);
951
852
  if (pendingParent !== void 0) {
@@ -976,7 +877,7 @@ var AgUiThreadRuntimeCore = class {
976
877
  }
977
878
  updateAssistantMessage(messageId, update) {
978
879
  let latestStatus;
979
- if (!this.updateMessage(messageId, (message) => {
880
+ if (!this.session.updateMessage(messageId, (message) => {
980
881
  if (message.role !== "assistant") return message;
981
882
  const assistant = message;
982
883
  const metadata = update.metadata ? this.mergeAssistantMetadata(assistant.metadata, update.metadata) : assistant.metadata;
@@ -1001,14 +902,12 @@ var AgUiThreadRuntimeCore = class {
1001
902
  }
1002
903
  preserveToolResults(previous, next) {
1003
904
  const resolved = /* @__PURE__ */ new Map();
1004
- for (const part of previous) if (part.type === "tool-call" && isResolvedToolCall(part)) resolved.set(part.toolCallId, part);
905
+ for (const part of iterateToolCallParts(previous)) if (isResolvedToolCall(part)) resolved.set(part.toolCallId, part);
1005
906
  if (resolved.size === 0) return next;
1006
- let changed = false;
1007
- const merged = next.map((part) => {
1008
- if (part.type !== "tool-call" || isResolvedToolCall(part)) return part;
907
+ const { content: merged, changed } = mapToolCallPartsDeep(next, (part) => {
908
+ if (isResolvedToolCall(part)) return part;
1009
909
  const prior = resolved.get(part.toolCallId);
1010
910
  if (!prior) return part;
1011
- changed = true;
1012
911
  return {
1013
912
  ...part,
1014
913
  result: prior.result,
@@ -1078,13 +977,13 @@ var AgUiThreadRuntimeCore = class {
1078
977
  }
1079
978
  }
1080
979
  applyCrossRunToolResult(messageId, event) {
1081
- if (!this.updateMessage(messageId, (message) => {
980
+ if (!this.session.updateMessage(messageId, (message) => {
1082
981
  if (message.role !== "assistant") return message;
1083
982
  const assistant = message;
1084
983
  const mcpAppUri = readMcpAppResourceUri(event.mcpResult?._meta);
1085
984
  let matchedToolCall = false;
1086
- const content = assistant.content.map((part) => {
1087
- if (part.type !== "tool-call" || part.toolCallId !== event.toolCallId) return part;
985
+ const { content } = mapToolCallPartsDeep(assistant.content, (part) => {
986
+ if (part.toolCallId !== event.toolCallId) return part;
1088
987
  matchedToolCall = true;
1089
988
  if (part.mcp?.app !== void 0 && isPlainObject(part.result) && Array.isArray(part.result["content"])) return {
1090
989
  ...part,
@@ -1123,12 +1022,12 @@ var AgUiThreadRuntimeCore = class {
1123
1022
  const serverHash = event.content["serverHash"];
1124
1023
  const appServerId = typeof serverId === "string" && serverId.length > 0 ? serverId : typeof serverHash === "string" && serverHash.length > 0 ? serverHash : void 0;
1125
1024
  const result = event.content["result"];
1126
- if (!this.updateMessage(messageId, (message) => {
1025
+ if (!this.session.updateMessage(messageId, (message) => {
1127
1026
  if (message.role !== "assistant") return message;
1128
1027
  const assistant = message;
1129
1028
  let matchedToolCall = false;
1130
- const content = assistant.content.map((part) => {
1131
- if (part.type !== "tool-call" || part.toolCallId !== toolCallId) return part;
1029
+ const { content } = mapToolCallPartsDeep(assistant.content, (part) => {
1030
+ if (part.toolCallId !== toolCallId) return part;
1132
1031
  matchedToolCall = true;
1133
1032
  return {
1134
1033
  ...part,
@@ -1157,7 +1056,7 @@ var AgUiThreadRuntimeCore = class {
1157
1056
  }
1158
1057
  importMessagesSnapshot(rawMessages, activeAssistantId) {
1159
1058
  try {
1160
- const activeMessage = activeAssistantId ? this.tryGetMessage(activeAssistantId)?.message : void 0;
1059
+ const activeMessage = activeAssistantId ? this.session.tryGetMessage(activeAssistantId)?.message : void 0;
1161
1060
  const activeAssistant = activeMessage?.role === "assistant" ? activeMessage : void 0;
1162
1061
  const normalized = fromAgUiMessages(rawMessages, { showThinking: this.showThinking });
1163
1062
  const converted = [];
@@ -1171,7 +1070,7 @@ var AgUiThreadRuntimeCore = class {
1171
1070
  if (preservesActiveAssistant) converted.push(activeAssistant);
1172
1071
  this.applyExternalMessages(converted);
1173
1072
  if (activeAssistant !== void 0) {
1174
- const activeItem = this.tryGetMessage(activeAssistant.id);
1073
+ const activeItem = this.session.tryGetMessage(activeAssistant.id);
1175
1074
  if (activeItem) {
1176
1075
  if (preservesActiveAssistant) this.snapshotHistoryIds.delete(activeAssistant.id);
1177
1076
  this.markPendingAssistantHistory(activeAssistant.id, activeItem.parentId);
@@ -1205,7 +1104,7 @@ var AgUiThreadRuntimeCore = class {
1205
1104
  if (!history) return;
1206
1105
  const parentId = this.assistantHistoryParents.get(messageId);
1207
1106
  if (parentId === void 0) return;
1208
- const message = this.tryGetMessage(messageId)?.message;
1107
+ const message = this.session.tryGetMessage(messageId)?.message;
1209
1108
  if (!message || message.role !== "assistant") return;
1210
1109
  if (!this.isPersistableStatus(message.status)) return;
1211
1110
  const wasPersisted = this.persistedHistoryIds.has(messageId);