@assistant-ui/react-ag-ui 0.1.0 → 0.1.2
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.
- package/README.md +2 -0
- package/dist/agUiExtras.js +1 -2
- package/dist/hooks.js +1 -2
- package/dist/runtime/AgUiThreadController.d.ts +11 -0
- package/dist/runtime/AgUiThreadController.d.ts.map +1 -1
- package/dist/runtime/AgUiThreadController.js +565 -89
- package/dist/runtime/AgUiThreadController.js.map +1 -1
- package/dist/runtime/adapter/conversions.d.ts +9 -1
- package/dist/runtime/adapter/conversions.d.ts.map +1 -1
- package/dist/runtime/adapter/conversions.js +194 -12
- package/dist/runtime/adapter/conversions.js.map +1 -1
- package/dist/runtime/adapter/replay-content.d.ts +9 -0
- package/dist/runtime/adapter/replay-content.d.ts.map +1 -0
- package/dist/runtime/adapter/replay-content.js +185 -0
- package/dist/runtime/adapter/replay-content.js.map +1 -0
- package/dist/runtime/adapter/run-aggregator.d.ts +1 -0
- package/dist/runtime/adapter/run-aggregator.d.ts.map +1 -1
- package/dist/runtime/adapter/run-aggregator.js +35 -14
- package/dist/runtime/adapter/run-aggregator.js.map +1 -1
- package/dist/runtime/adapter/subscriber.d.ts +0 -1
- package/dist/runtime/adapter/subscriber.d.ts.map +1 -1
- package/dist/runtime/adapter/subscriber.js +2 -10
- package/dist/runtime/adapter/subscriber.js.map +1 -1
- package/dist/runtime/adapter/tool-approval.d.ts +2 -0
- package/dist/runtime/adapter/tool-approval.d.ts.map +1 -1
- package/dist/runtime/adapter/tool-approval.js +23 -6
- package/dist/runtime/adapter/tool-approval.js.map +1 -1
- package/dist/runtime/agUiThreadState.d.ts +1 -1
- package/dist/runtime/agUiThreadState.d.ts.map +1 -1
- package/dist/runtime/agUiThreadState.js +3 -4
- package/dist/runtime/agUiThreadState.js.map +1 -1
- package/dist/runtime/event-parser.d.ts.map +1 -1
- package/dist/runtime/event-parser.js +2 -2
- package/dist/runtime/event-parser.js.map +1 -1
- package/dist/runtime/interrupt-internals.js +1 -2
- package/dist/runtime/logger.js +1 -2
- package/dist/runtime/mcp-tool-result.js +1 -2
- package/dist/runtime/types.d.ts +2 -0
- package/dist/runtime/types.d.ts.map +1 -1
- package/dist/runtime/types.js +3 -3
- package/dist/runtime/types.js.map +1 -1
- package/dist/useAgUiRuntime.js +1 -2
- package/package.json +6 -6
- package/src/runtime/AgUiThreadController.deferred-results.test.ts +548 -0
- package/src/runtime/AgUiThreadController.disconnect.test.ts +914 -0
- package/src/runtime/AgUiThreadController.interrupt-history.test.ts +392 -0
- package/src/runtime/AgUiThreadController.replay.test.ts +3939 -0
- package/src/runtime/AgUiThreadController.snapshot-head-move.test.ts +167 -0
- package/src/runtime/AgUiThreadController.subagent-snapshot.test.ts +854 -0
- package/src/runtime/AgUiThreadController.test.ts +119 -0
- package/src/runtime/AgUiThreadController.ts +1184 -136
- package/src/runtime/adapter/conversions.content.test.ts +242 -1
- package/src/runtime/adapter/conversions.subagent-messages.test.ts +550 -0
- package/src/runtime/adapter/conversions.ts +349 -28
- package/src/runtime/adapter/replay-content.test.ts +235 -0
- package/src/runtime/adapter/replay-content.ts +306 -0
- package/src/runtime/adapter/run-aggregator.metadata.test.ts +151 -0
- package/src/runtime/adapter/run-aggregator.ts +52 -9
- package/src/runtime/adapter/subscriber.test.ts +1 -1
- package/src/runtime/adapter/subscriber.ts +1 -6
- package/src/runtime/adapter/tool-approval.ts +41 -3
- package/src/runtime/agUiThreadState.test.ts +21 -2
- package/src/runtime/agUiThreadState.ts +12 -3
- package/src/runtime/event-parser.ts +3 -0
- package/src/runtime/types.ts +5 -0
|
@@ -2,21 +2,27 @@
|
|
|
2
2
|
import { createAgUiThreadState, reduceAgUiThreadState } from "./agUiThreadState.js";
|
|
3
3
|
import { readMcpAppResourceUri, toolContentText } from "./mcp-tool-result.js";
|
|
4
4
|
import { AG_UI_METADATA_NAMESPACE } from "./types.js";
|
|
5
|
-
import { buildToolApprovalResume, projectAgUiToolApprovals, withSettledToolApprovals, withToolApprovalDecision } from "./adapter/tool-approval.js";
|
|
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
|
+
import { createReplayContentMerger, groupReplayToolContent, indexReplayToolOwners, preserveToolResult } from "./adapter/replay-content.js";
|
|
9
10
|
import { fromThreadMessageLike, generateId, isMcpAppUri } from "@assistant-ui/core";
|
|
10
11
|
import { appendToolInteraction, createHistoryWriteChain, createMessageRepositorySession, invokeUserCallback, iterateToolCallParts, mapToolCallPartsDeep } from "@assistant-ui/core/internal";
|
|
11
12
|
import jsonpatch from "fast-json-patch";
|
|
12
|
-
|
|
13
|
+
// src/runtime/AgUiThreadController.ts
|
|
13
14
|
const optimisticPrefix = "__optimistic__";
|
|
14
15
|
const generateOptimisticId = () => `${optimisticPrefix}${generateId()}`;
|
|
15
16
|
const isOptimisticId = (id) => id.startsWith(optimisticPrefix);
|
|
16
17
|
const isResolvedToolCall = (part) => part.type === "tool-call" && "result" in part && part.result !== void 0;
|
|
18
|
+
const hasResolvedToolCalls = (message) => {
|
|
19
|
+
if (message.role !== "assistant") return false;
|
|
20
|
+
for (const part of iterateToolCallParts(message.content)) if (isResolvedToolCall(part)) return true;
|
|
21
|
+
return false;
|
|
22
|
+
};
|
|
17
23
|
const listedPendingToolCallIds = (assistant) => {
|
|
18
24
|
const ids = assistant.metadata.custom[AG_UI_METADATA_NAMESPACE]?.pendingToolCallIds;
|
|
19
|
-
return ids
|
|
25
|
+
return ids === void 0 ? void 0 : new Set(ids);
|
|
20
26
|
};
|
|
21
27
|
const withoutPendingToolCallIds = (metadata) => {
|
|
22
28
|
const namespace = metadata.custom[AG_UI_METADATA_NAMESPACE];
|
|
@@ -31,11 +37,62 @@ const withoutPendingToolCallIds = (metadata) => {
|
|
|
31
37
|
}
|
|
32
38
|
};
|
|
33
39
|
};
|
|
40
|
+
const withPendingToolCallIds = (metadata, ids) => ({
|
|
41
|
+
...metadata,
|
|
42
|
+
custom: {
|
|
43
|
+
...metadata.custom,
|
|
44
|
+
[AG_UI_METADATA_NAMESPACE]: {
|
|
45
|
+
...metadata.custom[AG_UI_METADATA_NAMESPACE],
|
|
46
|
+
pendingToolCallIds: [...ids]
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
const actionableToolCallIds = (message) => {
|
|
51
|
+
if (message?.role !== "assistant" || message.status?.type !== "requires-action" || message.status.reason !== "tool-calls") return /* @__PURE__ */ new Set();
|
|
52
|
+
const permitted = listedPendingToolCallIds(message);
|
|
53
|
+
return new Set(Array.from(iterateToolCallParts(message.content)).filter((part) => !isResolvedToolCall(part) && (!permitted || permitted.has(part.toolCallId))).map((part) => part.toolCallId));
|
|
54
|
+
};
|
|
34
55
|
const isActivityPart = (part) => part.type === "data" && part.name.startsWith("agui-activity/");
|
|
35
56
|
const FALLBACK_USER_STATUS = {
|
|
36
57
|
type: "complete",
|
|
37
58
|
reason: "unknown"
|
|
38
59
|
};
|
|
60
|
+
const textOf = (content) => content.flatMap((part) => part.type === "text" ? [part.text] : []).join("");
|
|
61
|
+
/**
|
|
62
|
+
* Swaps `content`'s text parts, in order, for `source`'s. A text part `source`
|
|
63
|
+
* has beyond `content`'s text slots goes after the nearest earlier part of
|
|
64
|
+
* `source` that `content` also has: a tool call by id, another part by its
|
|
65
|
+
* type and position among parts of that type. With none, it goes first.
|
|
66
|
+
*/
|
|
67
|
+
const replaceTextParts = (content, source) => {
|
|
68
|
+
const texts = source.filter((part) => part.type === "text");
|
|
69
|
+
const result = [];
|
|
70
|
+
let used = 0;
|
|
71
|
+
for (const part of content) if (part.type !== "text") result.push(part);
|
|
72
|
+
else if (used < texts.length) result.push(texts[used++]);
|
|
73
|
+
const positionIn = (parts, part) => parts.filter((other) => other.type === part.type).indexOf(part);
|
|
74
|
+
for (const text of texts.slice(used)) {
|
|
75
|
+
let anchor = -1;
|
|
76
|
+
for (let index = source.indexOf(text) - 1; index >= 0 && anchor < 0; index--) {
|
|
77
|
+
const before = source[index];
|
|
78
|
+
if (before.type === "tool-call") anchor = result.findIndex((part) => part.type === "tool-call" && part.toolCallId === before.toolCallId);
|
|
79
|
+
else if (before.type === "text") anchor = result.indexOf(before);
|
|
80
|
+
else {
|
|
81
|
+
const match = result.filter((part) => part.type === before.type)[positionIn(source, before)];
|
|
82
|
+
anchor = match ? result.indexOf(match) : -1;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
result.splice(anchor + 1, 0, text);
|
|
86
|
+
}
|
|
87
|
+
return result;
|
|
88
|
+
};
|
|
89
|
+
const settleNestedContent = (content, status) => mapToolCallPartsDeep(content, (part) => part.messages ? {
|
|
90
|
+
...part,
|
|
91
|
+
messages: part.messages.map((message) => message.role === "assistant" && message.status?.type === "running" ? {
|
|
92
|
+
...message,
|
|
93
|
+
status
|
|
94
|
+
} : message)
|
|
95
|
+
} : part).content;
|
|
39
96
|
const invokeRuntimeCallback = (name, callback, ...args) => {
|
|
40
97
|
invokeUserCallback("react-ag-ui", name, callback, ...args);
|
|
41
98
|
};
|
|
@@ -86,6 +143,7 @@ var AgUiThreadController = class {
|
|
|
86
143
|
activeRunAgent = null;
|
|
87
144
|
history;
|
|
88
145
|
assistantHistoryParents = /* @__PURE__ */ new Map();
|
|
146
|
+
subagentParentToolCallIds = /* @__PURE__ */ new Map();
|
|
89
147
|
snapshotHistoryIds = /* @__PURE__ */ new Set();
|
|
90
148
|
persistedHistoryIds = /* @__PURE__ */ new Set();
|
|
91
149
|
historyWrites = createHistoryWriteChain();
|
|
@@ -167,6 +225,7 @@ var AgUiThreadController = class {
|
|
|
167
225
|
if (!repo) return;
|
|
168
226
|
this.session.applyExternalMessageRepository(repo);
|
|
169
227
|
this.assistantHistoryParents.clear();
|
|
228
|
+
this.subagentParentToolCallIds.clear();
|
|
170
229
|
this.snapshotHistoryIds.clear();
|
|
171
230
|
this.persistedHistoryIds.clear();
|
|
172
231
|
for (const { message } of repo.messages) this.persistedHistoryIds.add(message.id);
|
|
@@ -299,7 +358,7 @@ var AgUiThreadController = class {
|
|
|
299
358
|
const historyImport = this.waitForHistoryImport();
|
|
300
359
|
if (historyImport && !await historyImport) return;
|
|
301
360
|
this.assertNoPendingInterrupts();
|
|
302
|
-
await this.startRun(config.parentId, config.runConfig ?? this.state.lastRunConfig, void 0, config.stream);
|
|
361
|
+
await this.startRun(config.parentId, config.runConfig ?? this.state.lastRunConfig, void 0, config.stream, true);
|
|
303
362
|
}
|
|
304
363
|
async resumeInFlightRun(messages) {
|
|
305
364
|
const historyImport = this.waitForHistoryImport();
|
|
@@ -321,9 +380,7 @@ var AgUiThreadController = class {
|
|
|
321
380
|
throw new Error("[agui] cannot start a new run while interrupts are pending; resolve them with submitInterruptResponses()");
|
|
322
381
|
}
|
|
323
382
|
findRequiresActionAssistant(reason) {
|
|
324
|
-
|
|
325
|
-
if (!assistant || assistant.status?.type !== "requires-action" || assistant.status.reason !== reason) return null;
|
|
326
|
-
return assistant;
|
|
383
|
+
return this.getTrailingAssistantTurn().findLast((message) => message.status?.type === "requires-action" && message.status.reason === reason) ?? null;
|
|
327
384
|
}
|
|
328
385
|
getTrailingAssistantTurn() {
|
|
329
386
|
const messages = this.getMessages();
|
|
@@ -383,8 +440,9 @@ var AgUiThreadController = class {
|
|
|
383
440
|
* unretryable.
|
|
384
441
|
*/
|
|
385
442
|
async resumeWithResponses(messageId, resume) {
|
|
443
|
+
const parentId = this.session.headId ?? messageId;
|
|
386
444
|
this.clearPendingInterrupts(messageId, resume);
|
|
387
|
-
await this.startRun(
|
|
445
|
+
await this.startRun(parentId, this.state.lastRunConfig, {
|
|
388
446
|
entries: resume,
|
|
389
447
|
interruptedMessageId: messageId
|
|
390
448
|
});
|
|
@@ -535,7 +593,10 @@ var AgUiThreadController = class {
|
|
|
535
593
|
custom: newCustom
|
|
536
594
|
}
|
|
537
595
|
};
|
|
538
|
-
}))
|
|
596
|
+
})) {
|
|
597
|
+
this.publishUpdate();
|
|
598
|
+
this.persistSettledAssistant(messageId);
|
|
599
|
+
}
|
|
539
600
|
}
|
|
540
601
|
findMessageIdForToolCall(toolCallId) {
|
|
541
602
|
let fallbackMessageId;
|
|
@@ -589,9 +650,31 @@ var AgUiThreadController = class {
|
|
|
589
650
|
};
|
|
590
651
|
});
|
|
591
652
|
if (!matchedToolCall) return message;
|
|
653
|
+
const settled = mapToolCallPartsDeep(content, (part) => {
|
|
654
|
+
if (!part.messages) return part;
|
|
655
|
+
let changed = false;
|
|
656
|
+
const messages = part.messages.map((nested) => {
|
|
657
|
+
if (nested.role !== "assistant" || nested.status.type !== "requires-action" || nested.status.reason !== "tool-calls") return nested;
|
|
658
|
+
const listed = listedPendingToolCallIds(nested);
|
|
659
|
+
if (Array.from(iterateToolCallParts(nested.content)).some((call) => !isResolvedToolCall(call) && (!listed || listed.has(call.toolCallId)))) return nested;
|
|
660
|
+
changed = true;
|
|
661
|
+
return {
|
|
662
|
+
...nested,
|
|
663
|
+
status: {
|
|
664
|
+
type: "complete",
|
|
665
|
+
reason: "unknown"
|
|
666
|
+
},
|
|
667
|
+
metadata: withoutPendingToolCallIds(nested.metadata)
|
|
668
|
+
};
|
|
669
|
+
});
|
|
670
|
+
return changed ? {
|
|
671
|
+
...part,
|
|
672
|
+
messages
|
|
673
|
+
} : part;
|
|
674
|
+
}).content;
|
|
592
675
|
return {
|
|
593
676
|
...assistant,
|
|
594
|
-
content
|
|
677
|
+
content: settled
|
|
595
678
|
};
|
|
596
679
|
})) return;
|
|
597
680
|
this.publishUpdate();
|
|
@@ -657,23 +740,27 @@ var AgUiThreadController = class {
|
|
|
657
740
|
}
|
|
658
741
|
this.startResumeRun(parentId);
|
|
659
742
|
}
|
|
660
|
-
maybeResumeAfterToolResults(messageId) {
|
|
743
|
+
maybeResumeAfterToolResults(messageId, accountedResultIds) {
|
|
661
744
|
if (!this.maybeCompleteAfterToolResults(messageId)) return;
|
|
745
|
+
if (accountedResultIds) {
|
|
746
|
+
const message = this.session.tryGetMessage(messageId)?.message;
|
|
747
|
+
if (message?.role !== "assistant" || !Array.from(iterateToolCallParts(message.content)).some((part) => isResolvedToolCall(part) && !accountedResultIds.has(part.toolCallId))) return;
|
|
748
|
+
}
|
|
662
749
|
if (this.getPendingInterrupts()) return;
|
|
663
750
|
const turn = this.getTrailingAssistantTurn();
|
|
664
751
|
if (!turn.some((message) => message.id === messageId)) return;
|
|
665
|
-
if (this.getPendingToolCalls()) return;
|
|
666
|
-
const headId = turn.at(-1)?.id;
|
|
667
|
-
if (headId === void 0) return;
|
|
668
752
|
const owner = this.abortController;
|
|
669
753
|
if (owner) {
|
|
670
754
|
this.dispatch({
|
|
671
755
|
type: "resumeDeferred",
|
|
672
756
|
owner,
|
|
673
|
-
messageId
|
|
757
|
+
messageId
|
|
674
758
|
});
|
|
675
759
|
return;
|
|
676
760
|
}
|
|
761
|
+
if (this.getPendingToolCalls()) return;
|
|
762
|
+
const headId = turn.at(-1)?.id;
|
|
763
|
+
if (headId === void 0) return;
|
|
677
764
|
this.startResumeRun(headId);
|
|
678
765
|
}
|
|
679
766
|
maybeCompleteAfterToolResults(messageId) {
|
|
@@ -700,7 +787,8 @@ var AgUiThreadController = class {
|
|
|
700
787
|
metadata: withoutPendingToolCallIds(current.metadata)
|
|
701
788
|
} : current)) return false;
|
|
702
789
|
this.publishUpdate();
|
|
703
|
-
this.
|
|
790
|
+
if (this.history?.update) this.persistSettledAssistant(messageId);
|
|
791
|
+
else this.persistAssistantHistory(messageId);
|
|
704
792
|
return true;
|
|
705
793
|
}
|
|
706
794
|
startResumeRun(messageId) {
|
|
@@ -729,6 +817,10 @@ var AgUiThreadController = class {
|
|
|
729
817
|
let lastAppliedId = null;
|
|
730
818
|
let hardReplace = false;
|
|
731
819
|
const seen = /* @__PURE__ */ new Set();
|
|
820
|
+
const retainToolResults = (existing, message) => existing?.role === "assistant" && message.role === "assistant" ? {
|
|
821
|
+
...message,
|
|
822
|
+
content: this.preserveToolResults(existing.content, message.content)
|
|
823
|
+
} : message;
|
|
732
824
|
for (const message of messages) {
|
|
733
825
|
if (seen.has(message.id)) continue;
|
|
734
826
|
seen.add(message.id);
|
|
@@ -737,11 +829,12 @@ var AgUiThreadController = class {
|
|
|
737
829
|
hardReplace = true;
|
|
738
830
|
break;
|
|
739
831
|
}
|
|
740
|
-
this.session.addOrUpdateMessage(expectedParentId, message);
|
|
832
|
+
this.session.addOrUpdateMessage(expectedParentId, retainToolResults(existing?.message, message));
|
|
741
833
|
expectedParentId = message.id;
|
|
742
834
|
lastAppliedId = message.id;
|
|
743
835
|
}
|
|
744
836
|
if (hardReplace) {
|
|
837
|
+
const previous = new Map(this.getMessageRepository().messages.map(({ message }) => [message.id, message]));
|
|
745
838
|
this.session.clear();
|
|
746
839
|
expectedParentId = null;
|
|
747
840
|
lastAppliedId = null;
|
|
@@ -749,17 +842,29 @@ var AgUiThreadController = class {
|
|
|
749
842
|
for (const message of messages) {
|
|
750
843
|
if (seen.has(message.id)) continue;
|
|
751
844
|
seen.add(message.id);
|
|
752
|
-
this.session.addOrUpdateMessage(expectedParentId, message);
|
|
845
|
+
this.session.addOrUpdateMessage(expectedParentId, retainToolResults(previous.get(message.id), message));
|
|
753
846
|
expectedParentId = message.id;
|
|
754
847
|
lastAppliedId = message.id;
|
|
755
848
|
}
|
|
756
849
|
}
|
|
757
850
|
this.session.resetHead(lastAppliedId);
|
|
851
|
+
for (const messageId of seen) this.maybeCompleteAfterToolResults(messageId);
|
|
758
852
|
}
|
|
759
853
|
this.snapshotHistoryIds.clear();
|
|
760
854
|
for (const { message } of this.getMessageRepository().messages) this.snapshotHistoryIds.add(message.id);
|
|
761
|
-
const
|
|
762
|
-
if (
|
|
855
|
+
const pendingResume = this.state.pendingResume;
|
|
856
|
+
if (pendingResume) {
|
|
857
|
+
const selectedResultOwnerIds = new Set(this.session.getMessages().filter(hasResolvedToolCalls).map((message) => message.id));
|
|
858
|
+
const retained = pendingResume.messageIds.filter((id) => selectedResultOwnerIds.has(id));
|
|
859
|
+
if (retained.length !== pendingResume.messageIds.length) {
|
|
860
|
+
this.dispatch({ type: "resumeCleared" });
|
|
861
|
+
for (const messageId of retained) this.dispatch({
|
|
862
|
+
type: "resumeDeferred",
|
|
863
|
+
owner: pendingResume.owner,
|
|
864
|
+
messageId
|
|
865
|
+
});
|
|
866
|
+
}
|
|
867
|
+
}
|
|
763
868
|
this.publishUpdate();
|
|
764
869
|
}
|
|
765
870
|
loadExternalState(state) {
|
|
@@ -786,6 +891,7 @@ var AgUiThreadController = class {
|
|
|
786
891
|
const controller = this.abortController;
|
|
787
892
|
const activeRunAgent = this.activeRunAgent;
|
|
788
893
|
this.dispatch({ type: "resetThread" });
|
|
894
|
+
this.subagentParentToolCallIds.clear();
|
|
789
895
|
if (controller) {
|
|
790
896
|
this.abortController = null;
|
|
791
897
|
this.activeRunAgent = null;
|
|
@@ -799,9 +905,10 @@ var AgUiThreadController = class {
|
|
|
799
905
|
}
|
|
800
906
|
} else this.publishUpdate();
|
|
801
907
|
}
|
|
802
|
-
async startRun(parentId, runConfig, resume, resumeStream) {
|
|
908
|
+
async startRun(parentId, runConfig, resume, resumeStream, replayExistingMessages = false) {
|
|
803
909
|
if (this.isThreadSwitching?.()) return;
|
|
804
910
|
if (!this.supersedeActiveRun()) return;
|
|
911
|
+
const generation = this.state.historyMutationGeneration;
|
|
805
912
|
const normalizedRunConfig = runConfig ?? {};
|
|
806
913
|
this.dispatch({
|
|
807
914
|
type: "runConfigRecorded",
|
|
@@ -811,36 +918,79 @@ var AgUiThreadController = class {
|
|
|
811
918
|
const shouldEagerlyInsertAssistant = parentId !== null && parent !== void 0 && parentId !== this.session.headId;
|
|
812
919
|
const historicalMessages = [...shouldEagerlyInsertAssistant ? this.session.tryGetMessages(parentId) ?? this.session.getMessages() : this.session.getMessages()];
|
|
813
920
|
const runStartMessageIds = new Set(this.getMessageRepository().messages.map(({ message }) => message.id));
|
|
921
|
+
const selectedMessages = this.session.getMessages();
|
|
922
|
+
const parentIndex = selectedMessages.findIndex((message) => message.id === parentId);
|
|
923
|
+
let replayStart = parentIndex + 1;
|
|
924
|
+
if (selectedMessages[parentIndex]?.role === "assistant") {
|
|
925
|
+
replayStart = parentIndex;
|
|
926
|
+
while (replayStart > 0 && selectedMessages[replayStart - 1]?.role === "assistant") replayStart--;
|
|
927
|
+
}
|
|
928
|
+
const replayMessageIds = [];
|
|
929
|
+
if (replayExistingMessages && !resumeStream && parentIndex >= 0) for (const message of selectedMessages.slice(replayStart)) {
|
|
930
|
+
if (message.role !== "assistant") break;
|
|
931
|
+
replayMessageIds.push(message.id);
|
|
932
|
+
}
|
|
933
|
+
const accountedResultIds = new Set(historicalMessages.flatMap((message) => message.role === "assistant" ? Array.from(iterateToolCallParts(message.content)).filter(isResolvedToolCall).map((part) => part.toolCallId) : []));
|
|
934
|
+
const candidateContexts = /* @__PURE__ */ new Map();
|
|
935
|
+
for (const message of selectedMessages) {
|
|
936
|
+
if (message.role !== "assistant" || !replayMessageIds.includes(message.id)) continue;
|
|
937
|
+
candidateContexts.set(message.id, {
|
|
938
|
+
originalContent: message.content,
|
|
939
|
+
accountedResultIds,
|
|
940
|
+
admittedToolCallIds: actionableToolCallIds(message),
|
|
941
|
+
merge: createReplayContentMerger()
|
|
942
|
+
});
|
|
943
|
+
}
|
|
944
|
+
let replayContexts = /* @__PURE__ */ new Map();
|
|
945
|
+
let lastReplayedIndex = -1;
|
|
946
|
+
let replayEnded = replayMessageIds.length === 0;
|
|
947
|
+
const replayTail = replayMessageIds.at(-1);
|
|
814
948
|
const joinStrategy = this.joinStrategy;
|
|
815
949
|
const hasRunUserBoundary = parent?.message.role === "user" && !resume;
|
|
816
950
|
let pendingError = null;
|
|
817
951
|
const assistantParentId = parent ? parentId : this.session.headId;
|
|
818
952
|
let assistantMessageId;
|
|
953
|
+
let snapshotAssistantId;
|
|
954
|
+
let replayInterruptMessageId;
|
|
819
955
|
let assistantCollided = false;
|
|
820
956
|
const heldMessages = /* @__PURE__ */ new Map();
|
|
957
|
+
let frameOwnerIds = /* @__PURE__ */ new Set();
|
|
821
958
|
let restrictedPendingToolCallIds;
|
|
822
959
|
let settlingRun = false;
|
|
960
|
+
let latestSnapshot;
|
|
961
|
+
let snapshotToolCallIds = /* @__PURE__ */ new Set();
|
|
823
962
|
const ensureAssistant = (allowRecreate = false) => {
|
|
824
963
|
const cached = assistantMessageId;
|
|
825
964
|
if (cached !== void 0 && this.session.tryGetMessage(cached)) return cached;
|
|
826
965
|
if (cached !== void 0 && (assistantCollided || !allowRecreate)) return cached;
|
|
827
966
|
const repositoryHeadId = this.session.headId;
|
|
828
|
-
const parentId = (cached === void 0 || shouldEagerlyInsertAssistant && (repositoryHeadId === null || runStartMessageIds.has(repositoryHeadId))) && assistantParentId && this.session.hasMessage(assistantParentId) ? assistantParentId : repositoryHeadId;
|
|
967
|
+
const parentId = replayContexts.size === 0 && (cached === void 0 || shouldEagerlyInsertAssistant && (repositoryHeadId === null || runStartMessageIds.has(repositoryHeadId))) && (replayMessageIds.length === 0 || repositoryHeadId === null || runStartMessageIds.has(repositoryHeadId)) && assistantParentId && this.session.hasMessage(assistantParentId) ? assistantParentId : repositoryHeadId;
|
|
829
968
|
const created = this.insertAssistantPlaceholder(parentId, cached);
|
|
830
969
|
assistantMessageId = created;
|
|
831
970
|
this.markPendingAssistantHistory(created, parentId);
|
|
832
971
|
return created;
|
|
833
972
|
};
|
|
834
|
-
if (shouldEagerlyInsertAssistant) ensureAssistant();
|
|
973
|
+
if (shouldEagerlyInsertAssistant && replayEnded) ensureAssistant();
|
|
835
974
|
const settleHeldMessages = (terminalStatus) => {
|
|
836
975
|
const pendingMessages = [];
|
|
976
|
+
const producerPending = restrictedPendingToolCallIds;
|
|
977
|
+
const canWaitForTools = terminalStatus.type === "complete" || terminalStatus.type === "requires-action";
|
|
978
|
+
if (producerPending && canWaitForTools) for (const message of this.getTrailingAssistantTurn()) {
|
|
979
|
+
const replay = replayContexts.get(message.id);
|
|
980
|
+
if (message.id === assistantMessageId || heldMessages.has(message.id) || !replay || message.status?.type === "requires-action" && message.status.reason === "interrupt") continue;
|
|
981
|
+
if (replay.admittedToolCallIds.size > 0 || actionableToolCallIds(message).size > 0 || Array.from(iterateToolCallParts(message.content)).some((part) => producerPending.has(part.toolCallId) && !accountedResultIds.has(part.toolCallId))) heldMessages.set(message.id, /* @__PURE__ */ new Set());
|
|
982
|
+
}
|
|
837
983
|
for (const [messageId, openToolCallIds] of heldMessages) {
|
|
984
|
+
const existing = this.session.tryGetMessage(messageId);
|
|
985
|
+
if (existing) this.markPendingAssistantHistory(messageId, existing.parentId);
|
|
838
986
|
let requiresToolResult = false;
|
|
839
987
|
if (this.session.updateMessage(messageId, (message) => {
|
|
840
988
|
if (message.role !== "assistant") return message;
|
|
841
989
|
const assistant = message;
|
|
842
|
-
const pendingToolCallIds = Array.from(
|
|
843
|
-
const
|
|
990
|
+
const pendingToolCallIds = producerPending ? Array.from(iterateToolCallParts(assistant.content)).filter((part) => producerPending.has(part.toolCallId) && (!isResolvedToolCall(part) || openToolCallIds.has(part.toolCallId) || replayContexts.has(messageId) && !accountedResultIds.has(part.toolCallId))).map((part) => part.toolCallId) : Array.from(openToolCallIds);
|
|
991
|
+
const replay = replayContexts.get(messageId);
|
|
992
|
+
if (replay) replay.admittedToolCallIds = new Set(pendingToolCallIds);
|
|
993
|
+
const status = canWaitForTools ? pendingToolCallIds.length > 0 ? {
|
|
844
994
|
type: "requires-action",
|
|
845
995
|
reason: "tool-calls"
|
|
846
996
|
} : {
|
|
@@ -852,16 +1002,7 @@ var AgUiThreadController = class {
|
|
|
852
1002
|
return {
|
|
853
1003
|
...assistant,
|
|
854
1004
|
status,
|
|
855
|
-
metadata: status.type === "requires-action" && restrictedPendingToolCallIds ?
|
|
856
|
-
...metadata,
|
|
857
|
-
custom: {
|
|
858
|
-
...metadata.custom,
|
|
859
|
-
["agui"]: {
|
|
860
|
-
...metadata.custom["agui"],
|
|
861
|
-
pendingToolCallIds
|
|
862
|
-
}
|
|
863
|
-
}
|
|
864
|
-
} : metadata
|
|
1005
|
+
metadata: status.type === "requires-action" && (restrictedPendingToolCallIds || replayContexts.has(messageId)) ? withPendingToolCallIds(metadata, pendingToolCallIds) : metadata
|
|
865
1006
|
};
|
|
866
1007
|
})) {
|
|
867
1008
|
this.publishUpdate();
|
|
@@ -872,29 +1013,139 @@ var AgUiThreadController = class {
|
|
|
872
1013
|
heldMessages.clear();
|
|
873
1014
|
return pendingMessages;
|
|
874
1015
|
};
|
|
1016
|
+
const accountProducerResults = (content) => {
|
|
1017
|
+
for (const part of iterateToolCallParts(content)) if (isResolvedToolCall(part)) accountedResultIds.add(part.toolCallId);
|
|
1018
|
+
};
|
|
1019
|
+
const reconcileReplaySnapshot = (snapshot, previous) => {
|
|
1020
|
+
accountProducerResults(snapshot.content);
|
|
1021
|
+
const replay = candidateContexts.get(snapshot.id);
|
|
1022
|
+
if (!replay) return snapshot;
|
|
1023
|
+
replay.admittedToolCallIds = actionableToolCallIds(snapshot);
|
|
1024
|
+
if (heldMessages.has(snapshot.id)) heldMessages.set(snapshot.id, new Set(replay.admittedToolCallIds));
|
|
1025
|
+
if (!replayContexts.has(snapshot.id)) return snapshot;
|
|
1026
|
+
return previous ? {
|
|
1027
|
+
...snapshot,
|
|
1028
|
+
content: this.preserveToolResults(previous.content, snapshot.content, replay.originalContent)
|
|
1029
|
+
} : snapshot;
|
|
1030
|
+
};
|
|
875
1031
|
const applyUpdate = (update) => {
|
|
1032
|
+
if (!assistantCollided && replayContexts.size && update.content !== void 0) accountProducerResults(update.content);
|
|
1033
|
+
let ownerParts;
|
|
1034
|
+
let contentOwnerIds;
|
|
1035
|
+
const hasStreamContent = Array.isArray(update.content) && update.content.some((part) => part.type !== "data");
|
|
1036
|
+
if (!replayEnded && assistantMessageId === void 0) {
|
|
1037
|
+
if (!update.status || update.status.type === "running") return;
|
|
1038
|
+
replayEnded = true;
|
|
1039
|
+
if (replayContexts.size === 0 && snapshotAssistantId !== void 0 && this.session.headId === snapshotAssistantId && !hasStreamContent) {
|
|
1040
|
+
if (update.status.type !== "complete") this.updateAssistantMessage(snapshotAssistantId, {
|
|
1041
|
+
status: update.status,
|
|
1042
|
+
...update.metadata ? { metadata: update.metadata } : {}
|
|
1043
|
+
});
|
|
1044
|
+
return;
|
|
1045
|
+
}
|
|
1046
|
+
}
|
|
1047
|
+
const streamMessageId = assistantMessageId;
|
|
1048
|
+
let primaryMessageId = streamMessageId;
|
|
876
1049
|
const terminalStatus = update.status;
|
|
1050
|
+
const replayInterrupt = replayContexts.size > 0 && terminalStatus?.type === "requires-action" && terminalStatus.reason === "interrupt";
|
|
1051
|
+
if (streamMessageId !== void 0 && !assistantCollided && replayContexts.size > 0 && (update.content !== void 0 || replayInterrupt)) {
|
|
1052
|
+
const owners = this.getTrailingAssistantTurn().filter((message) => replayContexts.has(message.id));
|
|
1053
|
+
const grouped = update.content !== void 0 ? groupReplayToolContent(owners, update.content, streamMessageId) : /* @__PURE__ */ new Map([[streamMessageId, [...update.content ?? []]]]);
|
|
1054
|
+
if (update.content !== void 0) {
|
|
1055
|
+
const currentOwnerIds = new Set(grouped.keys());
|
|
1056
|
+
for (const ownerId of frameOwnerIds) grouped.set(ownerId, grouped.get(ownerId) ?? []);
|
|
1057
|
+
contentOwnerIds = new Set(grouped.keys());
|
|
1058
|
+
frameOwnerIds = currentOwnerIds;
|
|
1059
|
+
}
|
|
1060
|
+
ownerParts = grouped;
|
|
1061
|
+
if (replayInterrupt) {
|
|
1062
|
+
const interrupts = (update.metadata?.custom?.[AG_UI_METADATA_NAMESPACE])?.interrupts;
|
|
1063
|
+
const approvals = projectAgUiToolApprovals(interrupts);
|
|
1064
|
+
for (const owner of owners) if (Array.from(iterateToolCallParts(owner.content)).some((part) => approvals.has(part.toolCallId))) grouped.set(owner.id, grouped.get(owner.id) ?? []);
|
|
1065
|
+
const anchor = owners.find((message) => projectAgUiToolApprovals(interrupts, new Set(Array.from(iterateToolCallParts([...message.content, ...grouped.get(message.id) ?? []])).map((part) => part.toolCallId))).size > 0);
|
|
1066
|
+
if (anchor) {
|
|
1067
|
+
primaryMessageId = anchor.id;
|
|
1068
|
+
replayInterruptMessageId = anchor.id;
|
|
1069
|
+
grouped.set(anchor.id, grouped.get(anchor.id) ?? []);
|
|
1070
|
+
}
|
|
1071
|
+
}
|
|
1072
|
+
}
|
|
877
1073
|
const pendingMessages = settlingRun && terminalStatus && terminalStatus.type !== "running" ? settleHeldMessages(terminalStatus) : [];
|
|
878
|
-
const
|
|
879
|
-
const
|
|
880
|
-
|
|
881
|
-
|
|
1074
|
+
const frameMessageId = ensureAssistant(hasStreamContent);
|
|
1075
|
+
const messageId = primaryMessageId ?? frameMessageId;
|
|
1076
|
+
let primaryUpdate = update;
|
|
1077
|
+
if (ownerParts) {
|
|
1078
|
+
const interrupt = terminalStatus?.type === "requires-action" && terminalStatus.reason === "interrupt";
|
|
1079
|
+
const ownerStatus = terminalStatus?.type === "requires-action" ? {
|
|
1080
|
+
type: "complete",
|
|
1081
|
+
reason: "unknown"
|
|
1082
|
+
} : terminalStatus;
|
|
1083
|
+
for (const [ownerId, content] of ownerParts) {
|
|
1084
|
+
const existing = this.session.tryGetMessage(ownerId);
|
|
1085
|
+
if (existing) this.markPendingAssistantHistory(ownerId, existing.parentId);
|
|
1086
|
+
if (ownerId === messageId) continue;
|
|
1087
|
+
this.updateAssistantMessage(ownerId, {
|
|
1088
|
+
...contentOwnerIds?.has(ownerId) ? { content } : {},
|
|
1089
|
+
...ownerStatus ? { status: ownerStatus } : {}
|
|
1090
|
+
}, replayContexts.get(ownerId), interrupt ? /* @__PURE__ */ new Set() : restrictedPendingToolCallIds);
|
|
1091
|
+
}
|
|
1092
|
+
const { content: _content, ...controlUpdate } = update;
|
|
1093
|
+
const content = ownerParts.get(messageId) ?? [];
|
|
1094
|
+
primaryUpdate = {
|
|
1095
|
+
...controlUpdate,
|
|
1096
|
+
...contentOwnerIds?.has(messageId) ? { content } : {},
|
|
1097
|
+
...ownerStatus && !interrupt && replayContexts.has(messageId) ? { status: ownerStatus } : {}
|
|
1098
|
+
};
|
|
1099
|
+
}
|
|
1100
|
+
const resolved = this.updateAssistantMessage(messageId, primaryUpdate, replayContexts.get(messageId), restrictedPendingToolCallIds);
|
|
1101
|
+
if (messageId === frameMessageId && resolved !== assistantMessageId) assistantMessageId = resolved;
|
|
1102
|
+
for (const messageId of pendingMessages) this.maybeResumeAfterToolResults(messageId, replayMessageIds.includes(messageId) ? accountedResultIds : void 0);
|
|
882
1103
|
};
|
|
883
1104
|
const adoptServerMessageId = (serverId, startNewMessage, openToolCallIds = []) => {
|
|
1105
|
+
const existing = this.session.tryGetMessage(serverId);
|
|
1106
|
+
const replayIndex = replayMessageIds.indexOf(serverId);
|
|
1107
|
+
const adoptsReplay = !replayEnded && !assistantCollided && replayIndex > lastReplayedIndex && existing?.message.role === "assistant" && this.session.getMessages().some((message) => message.id === serverId);
|
|
1108
|
+
if (adoptsReplay && replayContexts.size === 0) {
|
|
1109
|
+
replayContexts = candidateContexts;
|
|
1110
|
+
const selected = this.session.getMessages();
|
|
1111
|
+
let tailId = serverId;
|
|
1112
|
+
for (const message of selected.slice(selected.findIndex((message) => message.id === serverId) + 1)) {
|
|
1113
|
+
if (message.role !== "assistant") break;
|
|
1114
|
+
tailId = message.id;
|
|
1115
|
+
}
|
|
1116
|
+
this.session.selectHead(tailId);
|
|
1117
|
+
}
|
|
884
1118
|
if (startNewMessage && assistantMessageId !== void 0) {
|
|
885
|
-
|
|
1119
|
+
let hasReplayCallsToHold = false;
|
|
1120
|
+
const replay = replayContexts.get(assistantMessageId);
|
|
1121
|
+
if (replay) {
|
|
1122
|
+
const current = this.session.tryGetMessage(assistantMessageId)?.message;
|
|
1123
|
+
if (current?.role === "assistant") {
|
|
1124
|
+
const retainedPending = Array.from(iterateToolCallParts(current.content)).filter((part) => !isResolvedToolCall(part) || replay.admittedToolCallIds.has(part.toolCallId) && !accountedResultIds.has(part.toolCallId));
|
|
1125
|
+
hasReplayCallsToHold = retainedPending.length > 0;
|
|
1126
|
+
openToolCallIds = [.../* @__PURE__ */ new Set([...openToolCallIds, ...retainedPending.filter((part) => replay.admittedToolCallIds.has(part.toolCallId)).map((part) => part.toolCallId)])];
|
|
1127
|
+
}
|
|
1128
|
+
}
|
|
1129
|
+
if (openToolCallIds.length > 0 || hasReplayCallsToHold) heldMessages.set(assistantMessageId, new Set(openToolCallIds));
|
|
886
1130
|
else applyUpdate({ status: {
|
|
887
1131
|
type: "complete",
|
|
888
1132
|
reason: "unknown"
|
|
889
1133
|
} });
|
|
890
1134
|
const previousId = assistantMessageId;
|
|
891
1135
|
assistantMessageId = void 0;
|
|
892
|
-
if (this.session.tryGetMessage(previousId)) {
|
|
1136
|
+
if (!adoptsReplay && this.session.tryGetMessage(previousId)) {
|
|
893
1137
|
const created = this.insertAssistantPlaceholder(previousId);
|
|
894
1138
|
assistantMessageId = created;
|
|
895
1139
|
this.markPendingAssistantHistory(created, previousId);
|
|
896
1140
|
}
|
|
897
1141
|
}
|
|
1142
|
+
if (adoptsReplay) {
|
|
1143
|
+
assistantMessageId = serverId;
|
|
1144
|
+
lastReplayedIndex = replayIndex;
|
|
1145
|
+
this.markPendingAssistantHistory(serverId, existing.parentId);
|
|
1146
|
+
return;
|
|
1147
|
+
}
|
|
1148
|
+
replayEnded = true;
|
|
898
1149
|
const placeholder = ensureAssistant(true);
|
|
899
1150
|
if (placeholder === serverId) return;
|
|
900
1151
|
const reassigned = this.reassignAssistantId(placeholder, serverId);
|
|
@@ -913,25 +1164,103 @@ var AgUiThreadController = class {
|
|
|
913
1164
|
showThinking: this.showThinking,
|
|
914
1165
|
joinStrategy,
|
|
915
1166
|
logger: this.logger,
|
|
916
|
-
emit:
|
|
1167
|
+
emit: (update) => {
|
|
1168
|
+
const currentSnapshotAssistant = assistantMessageId === void 0 ? void 0 : this.session.tryGetMessage(assistantMessageId)?.message;
|
|
1169
|
+
const listedSnapshotToolCallIds = currentSnapshotAssistant?.role === "assistant" ? listedPendingToolCallIds(currentSnapshotAssistant) : void 0;
|
|
1170
|
+
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)));
|
|
1171
|
+
const reconciledUpdate = settlingRun && update.status?.type === "complete" && hasPendingSnapshotToolCall ? {
|
|
1172
|
+
...update,
|
|
1173
|
+
status: {
|
|
1174
|
+
type: "requires-action",
|
|
1175
|
+
reason: "tool-calls"
|
|
1176
|
+
},
|
|
1177
|
+
...restrictedPendingToolCallIds ? { metadata: {
|
|
1178
|
+
...update.metadata,
|
|
1179
|
+
custom: {
|
|
1180
|
+
...update.metadata?.custom,
|
|
1181
|
+
[AG_UI_METADATA_NAMESPACE]: {
|
|
1182
|
+
...update.metadata?.custom?.[AG_UI_METADATA_NAMESPACE],
|
|
1183
|
+
pendingToolCallIds: [...restrictedPendingToolCallIds]
|
|
1184
|
+
}
|
|
1185
|
+
}
|
|
1186
|
+
} } : {}
|
|
1187
|
+
} : update;
|
|
1188
|
+
if (settlingRun && latestSnapshot && latestSnapshot.id === assistantMessageId && reconciledUpdate.status && reconciledUpdate.status.type !== "running" && reconciledUpdate.status.type !== "requires-action") {
|
|
1189
|
+
const status = reconciledUpdate.status;
|
|
1190
|
+
const current = this.session.tryGetMessage(assistantMessageId)?.message;
|
|
1191
|
+
const content = settleNestedContent(current?.role === "assistant" ? current.content : latestSnapshot.content, status);
|
|
1192
|
+
applyUpdate({
|
|
1193
|
+
...reconciledUpdate,
|
|
1194
|
+
content
|
|
1195
|
+
});
|
|
1196
|
+
} else if (latestSnapshot && latestSnapshot.id === assistantMessageId && reconciledUpdate.content !== void 0) {
|
|
1197
|
+
const content = this.mergeSnapshotAssistantContent(latestSnapshot.content, reconciledUpdate.content, reconciledUpdate.content.some((part) => part.type === "reasoning"), reconciledUpdate.content.some(isActivityPart), snapshotToolCallIds);
|
|
1198
|
+
applyUpdate({
|
|
1199
|
+
...reconciledUpdate,
|
|
1200
|
+
content
|
|
1201
|
+
});
|
|
1202
|
+
} else applyUpdate(reconciledUpdate);
|
|
1203
|
+
const current = assistantMessageId ? this.session.tryGetMessage(assistantMessageId)?.message : void 0;
|
|
1204
|
+
latestSnapshot = !settlingRun && current?.role === "assistant" && current.id === latestSnapshot?.id ? current : void 0;
|
|
1205
|
+
},
|
|
917
1206
|
onServerMessageId: (serverId) => {
|
|
918
1207
|
adoptServerMessageId(serverId, false);
|
|
919
1208
|
},
|
|
920
|
-
onTextMessageStart: (serverId, openToolCallIds) =>
|
|
1209
|
+
onTextMessageStart: (serverId, openToolCallIds) => {
|
|
1210
|
+
const owners = this.getTrailingAssistantTurn().filter((message) => replayContexts.has(message.id));
|
|
1211
|
+
const toolOwners = indexReplayToolOwners(owners);
|
|
1212
|
+
const frameOpenIds = openToolCallIds.filter((id) => {
|
|
1213
|
+
const ownerId = toolOwners.get(id);
|
|
1214
|
+
if (!ownerId || ownerId === assistantMessageId || assistantCollided) return true;
|
|
1215
|
+
const held = heldMessages.get(ownerId) ?? /* @__PURE__ */ new Set();
|
|
1216
|
+
held.add(id);
|
|
1217
|
+
heldMessages.set(ownerId, held);
|
|
1218
|
+
return false;
|
|
1219
|
+
});
|
|
1220
|
+
for (const ownerId of frameOwnerIds) {
|
|
1221
|
+
const replay = replayContexts.get(ownerId);
|
|
1222
|
+
if (replay) replay.merge = createReplayContentMerger();
|
|
1223
|
+
}
|
|
1224
|
+
frameOwnerIds.clear();
|
|
1225
|
+
adoptServerMessageId(serverId, true, frameOpenIds);
|
|
1226
|
+
}
|
|
921
1227
|
});
|
|
922
1228
|
let runFinished = false;
|
|
923
1229
|
const dispatch = (event) => {
|
|
924
1230
|
if (this.abortController !== abortController) return;
|
|
925
1231
|
if (event.type === "RUN_FINISHED") {
|
|
926
1232
|
runFinished = true;
|
|
1233
|
+
if (event.outcome?.type === "interrupt") replayInterruptMessageId = assistantMessageId ?? replayTail;
|
|
927
1234
|
const ids = event.outcome?.type === "success" ? event.outcome.pendingToolCallIds : void 0;
|
|
928
|
-
restrictedPendingToolCallIds = ids
|
|
1235
|
+
restrictedPendingToolCallIds = ids === void 0 ? void 0 : new Set(ids);
|
|
1236
|
+
}
|
|
1237
|
+
if (event.type === "TOOL_CALL_RESULT") {
|
|
1238
|
+
if (!assistantCollided && candidateContexts.size) accountedResultIds.add(event.toolCallId);
|
|
1239
|
+
for (const openToolCallIds of heldMessages.values()) openToolCallIds.delete(event.toolCallId);
|
|
929
1240
|
}
|
|
930
|
-
if (event.type === "TOOL_CALL_RESULT") for (const openToolCallIds of heldMessages.values()) openToolCallIds.delete(event.toolCallId);
|
|
931
1241
|
settlingRun = event.type === "RUN_FINISHED" || event.type === "RUN_ERROR" || event.type === "RUN_CANCELLED";
|
|
1242
|
+
const beforeSnapshot = event.type === "MESSAGES_SNAPSHOT" ? this.session.getMessages() : [];
|
|
932
1243
|
try {
|
|
933
|
-
const nextAssistantMessageId = this.handleEvent(aggregator, event, assistantMessageId, joinStrategy, runStartMessageIds, hasRunUserBoundary);
|
|
934
|
-
if (event.type === "MESSAGES_SNAPSHOT")
|
|
1244
|
+
const nextAssistantMessageId = this.handleEvent(aggregator, event, assistantMessageId, joinStrategy, runStartMessageIds, hasRunUserBoundary, candidateContexts.size ? reconcileReplaySnapshot : void 0);
|
|
1245
|
+
if (event.type === "MESSAGES_SNAPSHOT") {
|
|
1246
|
+
for (const message of beforeSnapshot) {
|
|
1247
|
+
const context = replayContexts.get(message.id);
|
|
1248
|
+
const current = this.session.tryGetMessage(message.id)?.message;
|
|
1249
|
+
if (context && current?.content !== message.content) {
|
|
1250
|
+
context.merge = createReplayContentMerger();
|
|
1251
|
+
frameOwnerIds.delete(message.id);
|
|
1252
|
+
}
|
|
1253
|
+
}
|
|
1254
|
+
assistantMessageId = nextAssistantMessageId;
|
|
1255
|
+
const imported = assistantMessageId ? this.session.tryGetMessage(assistantMessageId)?.message : void 0;
|
|
1256
|
+
const source = event.messages.find((message) => message?.role === "assistant" && message.id === imported?.id);
|
|
1257
|
+
latestSnapshot = imported?.role === "assistant" && source ? imported : void 0;
|
|
1258
|
+
snapshotToolCallIds = new Set(Array.isArray(source?.toolCalls) ? source.toolCalls.map((call) => call.id).filter((id) => typeof id === "string") : []);
|
|
1259
|
+
if (!replayEnded && assistantMessageId === void 0) {
|
|
1260
|
+
const tail = this.session.getMessages().at(-1);
|
|
1261
|
+
snapshotAssistantId = tail?.role === "assistant" ? tail.id : void 0;
|
|
1262
|
+
}
|
|
1263
|
+
}
|
|
935
1264
|
} finally {
|
|
936
1265
|
settlingRun = false;
|
|
937
1266
|
}
|
|
@@ -974,7 +1303,6 @@ var AgUiThreadController = class {
|
|
|
974
1303
|
const input = this.buildRunInput(runId, normalizedRunConfig, historicalMessages, resume);
|
|
975
1304
|
const subscriber = createAgUiSubscriber({
|
|
976
1305
|
dispatch,
|
|
977
|
-
runId,
|
|
978
1306
|
logger: this.logger,
|
|
979
1307
|
onRunFailed: (error) => {
|
|
980
1308
|
if (abortSignal.aborted) return;
|
|
@@ -985,7 +1313,7 @@ var AgUiThreadController = class {
|
|
|
985
1313
|
try {
|
|
986
1314
|
runAgentInstance.messages = input.messages;
|
|
987
1315
|
runAgentInstance.threadId = input.threadId;
|
|
988
|
-
runAgentInstance.state = input.state
|
|
1316
|
+
runAgentInstance.state = input.state;
|
|
989
1317
|
} catch {}
|
|
990
1318
|
await runAgentInstance.runAgent.bind(runAgentInstance)(input, subscriber, { signal: abortSignal });
|
|
991
1319
|
}
|
|
@@ -1000,6 +1328,19 @@ var AgUiThreadController = class {
|
|
|
1000
1328
|
pendingError = err;
|
|
1001
1329
|
}
|
|
1002
1330
|
} finally {
|
|
1331
|
+
const current = assistantMessageId ? this.session.tryGetMessage(assistantMessageId)?.message : void 0;
|
|
1332
|
+
if (this.abortController === abortController && current?.role === "assistant" && current.status?.type === "running") {
|
|
1333
|
+
const status = {
|
|
1334
|
+
type: "incomplete",
|
|
1335
|
+
reason: "error",
|
|
1336
|
+
error: "The connection ended before the answer finished."
|
|
1337
|
+
};
|
|
1338
|
+
settleHeldMessages(status);
|
|
1339
|
+
applyUpdate({
|
|
1340
|
+
status,
|
|
1341
|
+
content: settleNestedContent(current.content, status)
|
|
1342
|
+
});
|
|
1343
|
+
}
|
|
1003
1344
|
this.finishRun(abortController);
|
|
1004
1345
|
}
|
|
1005
1346
|
if (pendingError) {
|
|
@@ -1008,10 +1349,40 @@ var AgUiThreadController = class {
|
|
|
1008
1349
|
this.clearDeferredContinuations(abortController);
|
|
1009
1350
|
throw err;
|
|
1010
1351
|
}
|
|
1352
|
+
const pending = replayInterruptMessageId ? this.getPendingInterrupts() : null;
|
|
1353
|
+
if (pending && pending.messageId === replayInterruptMessageId && replayContexts.has(pending.messageId) && !abortSignal.aborted && this.abortController === null && generation === this.state.historyMutationGeneration && !this.isThreadSwitching?.()) {
|
|
1354
|
+
const message = this.session.tryGetMessage(pending.messageId)?.message;
|
|
1355
|
+
const responses = message?.role === "assistant" ? buildToolApprovalResume(message.content, pending.interrupts) : null;
|
|
1356
|
+
if (responses) {
|
|
1357
|
+
this.clearDeferredContinuations(abortController);
|
|
1358
|
+
try {
|
|
1359
|
+
await this.submitInterruptResponses(responses);
|
|
1360
|
+
} catch (error) {
|
|
1361
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
1362
|
+
if (!this.reportedErrors.has(err)) {
|
|
1363
|
+
this.reportError(err);
|
|
1364
|
+
this.reportedErrors.add(err);
|
|
1365
|
+
}
|
|
1366
|
+
throw err;
|
|
1367
|
+
}
|
|
1368
|
+
return;
|
|
1369
|
+
}
|
|
1370
|
+
}
|
|
1011
1371
|
if (this.state.pendingResume?.owner === abortController) {
|
|
1012
|
-
const {
|
|
1372
|
+
const { messageIds } = this.state.pendingResume;
|
|
1013
1373
|
this.dispatch({ type: "resumeCleared" });
|
|
1014
|
-
if (!abortSignal.aborted) this.
|
|
1374
|
+
if (!abortSignal.aborted && !this.getPendingInterrupts() && !this.getPendingToolCalls()) {
|
|
1375
|
+
const turn = this.getTrailingAssistantTurn();
|
|
1376
|
+
if (turn.some((message) => {
|
|
1377
|
+
if (!messageIds.includes(message.id)) return false;
|
|
1378
|
+
const replay = replayContexts.get(message.id);
|
|
1379
|
+
if (!replay) return hasResolvedToolCalls(message);
|
|
1380
|
+
return Array.from(iterateToolCallParts(message.content)).some((part) => isResolvedToolCall(part) && replay.admittedToolCallIds.has(part.toolCallId) && !accountedResultIds.has(part.toolCallId));
|
|
1381
|
+
})) {
|
|
1382
|
+
const headId = turn.at(-1)?.id;
|
|
1383
|
+
if (headId !== void 0) this.startResumeRun(headId);
|
|
1384
|
+
}
|
|
1385
|
+
}
|
|
1015
1386
|
}
|
|
1016
1387
|
if (this.state.pendingA2uiResumeOwner === abortController) {
|
|
1017
1388
|
this.dispatch({ type: "a2uiResumeCleared" });
|
|
@@ -1092,7 +1463,7 @@ var AgUiThreadController = class {
|
|
|
1092
1463
|
return {
|
|
1093
1464
|
threadId,
|
|
1094
1465
|
runId,
|
|
1095
|
-
state: this.state.stateSnapshot
|
|
1466
|
+
state: this.state.stateSnapshot === void 0 ? {} : this.state.stateSnapshot,
|
|
1096
1467
|
messages,
|
|
1097
1468
|
tools: toAgUiTools(context?.tools),
|
|
1098
1469
|
context: [...context?.system ? [{
|
|
@@ -1194,15 +1565,47 @@ var AgUiThreadController = class {
|
|
|
1194
1565
|
this.publishUpdate();
|
|
1195
1566
|
return !collidesWithExisting;
|
|
1196
1567
|
}
|
|
1197
|
-
updateAssistantMessage(messageId, update) {
|
|
1568
|
+
updateAssistantMessage(messageId, update, replay, pendingToolCallIds) {
|
|
1198
1569
|
let latestStatus;
|
|
1199
1570
|
if (!this.session.updateMessage(messageId, (message) => {
|
|
1200
1571
|
if (message.role !== "assistant") return message;
|
|
1201
1572
|
const assistant = message;
|
|
1202
1573
|
const mergedMetadata = update.metadata ? this.mergeAssistantMetadata(assistant.metadata, update.metadata) : assistant.metadata;
|
|
1203
1574
|
latestStatus = update.status ?? assistant.status;
|
|
1204
|
-
|
|
1205
|
-
const
|
|
1575
|
+
let content = update.content !== void 0 ? replay ? replay.merge(assistant.content, update.content) : this.preserveToolResults(assistant.content, update.content) : assistant.content;
|
|
1576
|
+
const calls = replay ? Array.from(iterateToolCallParts(content)) : [];
|
|
1577
|
+
const pending = pendingToolCallIds ?? (replay ? /* @__PURE__ */ new Set([...replay.admittedToolCallIds, ...Array.from(iterateToolCallParts(update.content ?? [])).filter((part) => !isResolvedToolCall(part)).map((part) => part.toolCallId)]) : void 0);
|
|
1578
|
+
if (replay && latestStatus?.type !== "running" && pending) replay.admittedToolCallIds = pending;
|
|
1579
|
+
if (replay) {
|
|
1580
|
+
const interrupts = latestStatus?.type === "requires-action" && latestStatus.reason === "interrupt" ? mergedMetadata.custom["agui"]?.interrupts : void 0;
|
|
1581
|
+
const approvals = projectAgUiToolApprovals(interrupts, new Set(calls.map((part) => part.toolCallId)));
|
|
1582
|
+
const originalCalls = new Map(Array.from(iterateToolCallParts(replay.originalContent), (part) => [part.toolCallId, part]));
|
|
1583
|
+
content = mapToolCallPartsDeep(content, (part) => {
|
|
1584
|
+
const approval = approvals.get(part.toolCallId);
|
|
1585
|
+
if (approval) return {
|
|
1586
|
+
...part,
|
|
1587
|
+
approval: preserveToolApprovalDecision(originalCalls.get(part.toolCallId), {
|
|
1588
|
+
...part,
|
|
1589
|
+
approval
|
|
1590
|
+
}) ?? approval
|
|
1591
|
+
};
|
|
1592
|
+
if (!part.approval || part.approval.approved !== void 0 || part.approval.resolution) return part;
|
|
1593
|
+
const { approval: _approval, ...withoutApproval } = part;
|
|
1594
|
+
return withoutApproval;
|
|
1595
|
+
}).content;
|
|
1596
|
+
}
|
|
1597
|
+
if (replay && latestStatus?.type === "complete") {
|
|
1598
|
+
const listed = pending ?? listedPendingToolCallIds({
|
|
1599
|
+
...assistant,
|
|
1600
|
+
metadata: mergedMetadata
|
|
1601
|
+
});
|
|
1602
|
+
if (calls.some((part) => (!isResolvedToolCall(part) || !replay.accountedResultIds.has(part.toolCallId)) && (!listed || listed.has(part.toolCallId)))) latestStatus = {
|
|
1603
|
+
type: "requires-action",
|
|
1604
|
+
reason: "tool-calls"
|
|
1605
|
+
};
|
|
1606
|
+
}
|
|
1607
|
+
const admission = pending && replay ? withPendingToolCallIds(mergedMetadata, pending) : mergedMetadata;
|
|
1608
|
+
const metadata = latestStatus?.type === "requires-action" && latestStatus.reason === "tool-calls" ? admission : replay && latestStatus?.type === "running" ? mergedMetadata : withoutPendingToolCallIds(admission);
|
|
1206
1609
|
return {
|
|
1207
1610
|
...assistant,
|
|
1208
1611
|
content,
|
|
@@ -1217,24 +1620,25 @@ var AgUiThreadController = class {
|
|
|
1217
1620
|
resolvedMessageId = stableId;
|
|
1218
1621
|
} else this.publishUpdate();
|
|
1219
1622
|
if (this.isPersistableStatus(latestStatus)) this.persistAssistantHistory(resolvedMessageId);
|
|
1220
|
-
this.maybeResumeAfterToolResults(resolvedMessageId);
|
|
1623
|
+
this.maybeResumeAfterToolResults(resolvedMessageId, replay?.accountedResultIds);
|
|
1221
1624
|
return resolvedMessageId;
|
|
1222
1625
|
}
|
|
1223
|
-
preserveToolResults(previous, next) {
|
|
1626
|
+
preserveToolResults(previous, next, decisionSource) {
|
|
1224
1627
|
const preserved = /* @__PURE__ */ new Map();
|
|
1225
1628
|
for (const part of iterateToolCallParts(previous)) if (isResolvedToolCall(part)) preserved.set(part.toolCallId, part);
|
|
1226
|
-
|
|
1629
|
+
const decisions = new Map(Array.from(iterateToolCallParts(decisionSource ?? [])).filter((part) => part.approval?.approved !== void 0).map((part) => [part.toolCallId, part]));
|
|
1630
|
+
if (preserved.size === 0 && decisions.size === 0) return this.preserveToolInteractions(previous, next);
|
|
1227
1631
|
const { content: merged, changed } = mapToolCallPartsDeep(next, (part) => {
|
|
1228
1632
|
const prior = preserved.get(part.toolCallId);
|
|
1229
|
-
|
|
1230
|
-
if (
|
|
1231
|
-
|
|
1232
|
-
result
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
}
|
|
1237
|
-
return
|
|
1633
|
+
const result = prior ? preserveToolResult(prior, part) : part;
|
|
1634
|
+
if (result.approval && decisions.has(part.toolCallId)) {
|
|
1635
|
+
const approval = preserveToolApprovalDecision(decisions.get(part.toolCallId), result);
|
|
1636
|
+
if (approval && approval !== result.approval) return {
|
|
1637
|
+
...result,
|
|
1638
|
+
approval
|
|
1639
|
+
};
|
|
1640
|
+
}
|
|
1641
|
+
return result;
|
|
1238
1642
|
});
|
|
1239
1643
|
return this.preserveToolInteractions(previous, changed ? merged : next);
|
|
1240
1644
|
}
|
|
@@ -1252,10 +1656,10 @@ var AgUiThreadController = class {
|
|
|
1252
1656
|
});
|
|
1253
1657
|
return changed ? content : next;
|
|
1254
1658
|
}
|
|
1255
|
-
mergeSnapshotAssistantContent(previous, next, snapshotHasReasoning, snapshotHasActivity) {
|
|
1256
|
-
const shouldKeep = (part) => part.type === "reasoning" ? this.showThinking && !snapshotHasReasoning : isActivityPart(part) && !snapshotHasActivity;
|
|
1659
|
+
mergeSnapshotAssistantContent(previous, next, snapshotHasReasoning, snapshotHasActivity, keepMissingToolCalls = /* @__PURE__ */ new Set(), keepPriorTranscripts = false) {
|
|
1660
|
+
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;
|
|
1257
1661
|
const kept = previous.filter(shouldKeep);
|
|
1258
|
-
const merged = this.reconcileSnapshotNestedContent(previous, this.preserveToolInteractions(previous, next));
|
|
1662
|
+
const merged = this.reconcileSnapshotNestedContent(previous, this.preserveToolInteractions(previous, next), keepPriorTranscripts);
|
|
1259
1663
|
if (kept.length === 0) return merged;
|
|
1260
1664
|
const mergedByType = /* @__PURE__ */ new Map();
|
|
1261
1665
|
const mergedTools = /* @__PURE__ */ new Map();
|
|
@@ -1287,7 +1691,7 @@ var AgUiThreadController = class {
|
|
|
1287
1691
|
result.push(...insertions[merged.length]);
|
|
1288
1692
|
return result;
|
|
1289
1693
|
}
|
|
1290
|
-
reconcileSnapshotNestedContent(previous, next) {
|
|
1694
|
+
reconcileSnapshotNestedContent(previous, next, keepPriorTranscripts) {
|
|
1291
1695
|
const nestedIds = /* @__PURE__ */ new Set();
|
|
1292
1696
|
for (const part of previous) {
|
|
1293
1697
|
if (part.type !== "tool-call" || !part.messages) continue;
|
|
@@ -1317,12 +1721,50 @@ var AgUiThreadController = class {
|
|
|
1317
1721
|
if (part.type === "tool-call" && flattened.has(part.toolCallId)) return [];
|
|
1318
1722
|
if (part.type !== "tool-call") return [part];
|
|
1319
1723
|
const prior = previousCalls.get(part.toolCallId);
|
|
1320
|
-
|
|
1724
|
+
if (prior?.messages === void 0) return [part];
|
|
1725
|
+
if (part.messages === void 0) return [{
|
|
1321
1726
|
...part,
|
|
1322
1727
|
messages: prior.messages
|
|
1323
1728
|
}];
|
|
1729
|
+
if (part.messages.length === 0) return [part];
|
|
1730
|
+
return [{
|
|
1731
|
+
...part,
|
|
1732
|
+
messages: this.mergeNestedTranscripts(prior.messages, part.messages, keepPriorTranscripts)
|
|
1733
|
+
}];
|
|
1324
1734
|
});
|
|
1325
1735
|
}
|
|
1736
|
+
/**
|
|
1737
|
+
* Merges one tool call's nested transcripts message by message. The incoming
|
|
1738
|
+
* copy is the base for tool calls and reasoning; resolved results and calls
|
|
1739
|
+
* only the prior copy has carry over, and the prior text wins when it extends
|
|
1740
|
+
* the incoming text. Importing a snapshot keeps prior messages it lacks, a
|
|
1741
|
+
* prior status that is running or incomplete, and the prior status when the
|
|
1742
|
+
* snapshot waits on calls the merged content has already resolved.
|
|
1743
|
+
*/
|
|
1744
|
+
mergeNestedTranscripts(prior, next, importingSnapshot) {
|
|
1745
|
+
const priorById = new Map(prior.map((message) => [message.id, message]));
|
|
1746
|
+
const merged = next.map((message) => {
|
|
1747
|
+
const previous = priorById.get(message.id);
|
|
1748
|
+
if (previous?.role !== "assistant" || message.role !== "assistant") return message;
|
|
1749
|
+
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);
|
|
1750
|
+
const priorText = textOf(previous.content);
|
|
1751
|
+
const incomingText = textOf(message.content);
|
|
1752
|
+
if (priorText.length > incomingText.length && priorText.startsWith(incomingText)) content = replaceTextParts(content, previous.content);
|
|
1753
|
+
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;
|
|
1754
|
+
return {
|
|
1755
|
+
...message,
|
|
1756
|
+
status,
|
|
1757
|
+
content,
|
|
1758
|
+
metadata: {
|
|
1759
|
+
...message.metadata,
|
|
1760
|
+
custom: mergeAgUiNamespace(previous.metadata.custom, message.metadata.custom)
|
|
1761
|
+
}
|
|
1762
|
+
};
|
|
1763
|
+
});
|
|
1764
|
+
if (!importingSnapshot) return merged;
|
|
1765
|
+
const mergedById = new Map(merged.map((message) => [message.id, message]));
|
|
1766
|
+
return [...prior.map((message) => mergedById.get(message.id) ?? message), ...merged.filter((message) => !priorById.has(message.id))];
|
|
1767
|
+
}
|
|
1326
1768
|
mergeAssistantMetadata(current, incoming) {
|
|
1327
1769
|
const annotations = incoming.unstable_annotations ? [...current.unstable_annotations, ...incoming.unstable_annotations] : current.unstable_annotations;
|
|
1328
1770
|
const data = incoming.unstable_data ? [...current.unstable_data, ...incoming.unstable_data] : current.unstable_data;
|
|
@@ -1337,7 +1779,8 @@ var AgUiThreadController = class {
|
|
|
1337
1779
|
custom: incoming.custom ? mergeAgUiNamespace(current.custom, incoming.custom) : current.custom
|
|
1338
1780
|
};
|
|
1339
1781
|
}
|
|
1340
|
-
handleEvent(aggregator, event, activeAssistantId, joinStrategy, runStartMessageIds, hasRunUserBoundary) {
|
|
1782
|
+
handleEvent(aggregator, event, activeAssistantId, joinStrategy, runStartMessageIds, hasRunUserBoundary, reconcileSnapshot) {
|
|
1783
|
+
if (event.type === "SUBAGENT_STARTED" && !this.subagentParentToolCallIds.has(event.subagentRunId)) this.subagentParentToolCallIds.set(event.subagentRunId, event.parentToolCallId);
|
|
1341
1784
|
switch (event.type) {
|
|
1342
1785
|
case "STATE_SNAPSHOT":
|
|
1343
1786
|
this.dispatch({
|
|
@@ -1360,12 +1803,26 @@ var AgUiThreadController = class {
|
|
|
1360
1803
|
}
|
|
1361
1804
|
break;
|
|
1362
1805
|
case "MESSAGES_SNAPSHOT": {
|
|
1806
|
+
aggregator.handle(event);
|
|
1363
1807
|
const previousHeadId = this.session.headId;
|
|
1364
1808
|
const activeAssistant = activeAssistantId === void 0 ? void 0 : this.session.tryGetMessage(activeAssistantId)?.message;
|
|
1365
1809
|
const hasActiveText = activeAssistant?.role === "assistant" && activeAssistant.content.some((part) => part.type === "text");
|
|
1366
|
-
this.importMessagesSnapshot(event.messages, activeAssistantId, joinStrategy, runStartMessageIds, hasRunUserBoundary);
|
|
1810
|
+
this.importMessagesSnapshot(event.messages, activeAssistantId, joinStrategy, runStartMessageIds, hasRunUserBoundary, reconcileSnapshot);
|
|
1367
1811
|
const headId = this.session.headId;
|
|
1368
|
-
|
|
1812
|
+
const head = headId === null ? void 0 : this.session.tryGetMessage(headId)?.message;
|
|
1813
|
+
const hasNewPendingToolCallAnswer = head?.role === "assistant" && !runStartMessageIds.has(head.id) && head.status?.type === "requires-action" && head.status.reason === "tool-calls";
|
|
1814
|
+
if (headId !== previousHeadId && (hasActiveText || hasNewPendingToolCallAnswer)) {
|
|
1815
|
+
const nextActiveAssistantId = head?.role === "assistant" ? headId ?? void 0 : void 0;
|
|
1816
|
+
if (activeAssistantId !== nextActiveAssistantId && activeAssistantId) {
|
|
1817
|
+
const previous = this.session.tryGetMessage(activeAssistantId)?.message;
|
|
1818
|
+
if (previous?.role === "assistant" && previous.status?.type === "running") this.updateAssistantMessage(activeAssistantId, {
|
|
1819
|
+
status: FALLBACK_USER_STATUS,
|
|
1820
|
+
content: settleNestedContent(previous.content, FALLBACK_USER_STATUS)
|
|
1821
|
+
});
|
|
1822
|
+
}
|
|
1823
|
+
activeAssistantId = nextActiveAssistantId;
|
|
1824
|
+
if (hasNewPendingToolCallAnswer && head) this.updateAssistantMessage(head.id, { status: { type: "running" } });
|
|
1825
|
+
}
|
|
1369
1826
|
this.updateActiveAssistantState(activeAssistantId);
|
|
1370
1827
|
break;
|
|
1371
1828
|
}
|
|
@@ -1427,7 +1884,8 @@ var AgUiThreadController = class {
|
|
|
1427
1884
|
text: modelText
|
|
1428
1885
|
}] } : {},
|
|
1429
1886
|
...part.isError === void 0 ? typeof event.mcpResult?.isError === "boolean" ? { isError: event.mcpResult.isError } : event.role === "tool" ? { isError: false } : {} : {},
|
|
1430
|
-
...event.messageId ? { unstable_toolMessageId: event.messageId } : {}
|
|
1887
|
+
...event.messageId ? { unstable_toolMessageId: event.messageId } : {},
|
|
1888
|
+
...event.metadata !== void 0 ? { providerMetadata: withAgUiPartMessageMetadata(part.providerMetadata, event.metadata) } : {}
|
|
1431
1889
|
};
|
|
1432
1890
|
const { modelContent: _modelContent, ...partWithoutModelContent } = part;
|
|
1433
1891
|
return {
|
|
@@ -1439,7 +1897,8 @@ var AgUiThreadController = class {
|
|
|
1439
1897
|
}] } : {},
|
|
1440
1898
|
...typeof event.mcpResult?.isError === "boolean" ? { isError: event.mcpResult.isError } : event.role === "tool" ? { isError: false } : {},
|
|
1441
1899
|
...part.mcp === void 0 && mcpAppUri !== void 0 ? { mcp: { app: { resourceUri: mcpAppUri } } } : {},
|
|
1442
|
-
...event.messageId ? { unstable_toolMessageId: event.messageId } : {}
|
|
1900
|
+
...event.messageId ? { unstable_toolMessageId: event.messageId } : {},
|
|
1901
|
+
...event.metadata !== void 0 ? { providerMetadata: withAgUiPartMessageMetadata(part.providerMetadata, event.metadata) } : {}
|
|
1443
1902
|
};
|
|
1444
1903
|
});
|
|
1445
1904
|
if (!matchedToolCall) return message;
|
|
@@ -1449,7 +1908,8 @@ var AgUiThreadController = class {
|
|
|
1449
1908
|
};
|
|
1450
1909
|
})) return;
|
|
1451
1910
|
this.publishUpdate();
|
|
1452
|
-
this.maybeCompleteAfterToolResults(messageId);
|
|
1911
|
+
if (this.maybeCompleteAfterToolResults(messageId)) return;
|
|
1912
|
+
this.persistSettledAssistant(messageId);
|
|
1453
1913
|
}
|
|
1454
1914
|
applyCrossRunActivitySnapshot(messageId, toolCallId, event) {
|
|
1455
1915
|
const resourceUri = event.content["resourceUri"];
|
|
@@ -1488,18 +1948,21 @@ var AgUiThreadController = class {
|
|
|
1488
1948
|
};
|
|
1489
1949
|
})) return;
|
|
1490
1950
|
this.publishUpdate();
|
|
1491
|
-
this.maybeCompleteAfterToolResults(messageId);
|
|
1951
|
+
if (this.maybeCompleteAfterToolResults(messageId)) return;
|
|
1952
|
+
this.persistSettledAssistant(messageId);
|
|
1492
1953
|
}
|
|
1493
|
-
importMessagesSnapshot(rawMessages, activeAssistantId, joinStrategy, runStartMessageIds, hasRunUserBoundary) {
|
|
1954
|
+
importMessagesSnapshot(rawMessages, activeAssistantId, joinStrategy, runStartMessageIds, hasRunUserBoundary, reconcileSnapshot) {
|
|
1494
1955
|
try {
|
|
1495
1956
|
const activeAssistantItem = activeAssistantId ? this.session.tryGetMessage(activeAssistantId) : void 0;
|
|
1496
1957
|
const activeMessage = activeAssistantItem?.message;
|
|
1497
1958
|
const activeAssistantParentId = activeAssistantItem?.parentId;
|
|
1498
1959
|
const activeAssistant = activeMessage?.role === "assistant" ? activeMessage : void 0;
|
|
1499
|
-
const normalized =
|
|
1960
|
+
const normalized = fromAgUiSnapshotMessages(rawMessages, { showThinking: this.showThinking }, this.subagentParentToolCallIds);
|
|
1500
1961
|
const converted = [];
|
|
1501
1962
|
for (const message of normalized) try {
|
|
1502
|
-
|
|
1963
|
+
const previous = message.id ? this.session.tryGetMessage(message.id)?.message : void 0;
|
|
1964
|
+
const fallbackStatus = previous?.role === "assistant" && previous.status?.type === "running" ? previous.status : FALLBACK_USER_STATUS;
|
|
1965
|
+
converted.push(fromThreadMessageLike(message, generateId(), fallbackStatus));
|
|
1503
1966
|
} catch (error) {
|
|
1504
1967
|
this.logger.error?.("[agui] failed to import message from snapshot", error);
|
|
1505
1968
|
}
|
|
@@ -1508,10 +1971,16 @@ var AgUiThreadController = class {
|
|
|
1508
1971
|
for (let index = 0; index < converted.length; index++) {
|
|
1509
1972
|
const convertedMessage = converted[index];
|
|
1510
1973
|
const existing = this.session.tryGetMessage(convertedMessage.id)?.message;
|
|
1511
|
-
if (convertedMessage.role === "assistant"
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1974
|
+
if (convertedMessage.role === "assistant") {
|
|
1975
|
+
const previous = existing?.role === "assistant" ? existing : void 0;
|
|
1976
|
+
const snapshot = reconcileSnapshot?.(convertedMessage, previous) ?? convertedMessage;
|
|
1977
|
+
const preservesRunningStatus = convertedMessage.id === activeAssistantId && previous?.status?.type === "running" && snapshot.status?.type === "requires-action" && snapshot.status.reason === "tool-calls";
|
|
1978
|
+
converted[index] = previous ? {
|
|
1979
|
+
...snapshot,
|
|
1980
|
+
...preservesRunningStatus ? { status: previous.status } : {},
|
|
1981
|
+
content: this.mergeSnapshotAssistantContent(previous.content, snapshot.content, snapshotHasReasoning, snapshotHasActivity, /* @__PURE__ */ new Set(), true)
|
|
1982
|
+
} : snapshot;
|
|
1983
|
+
}
|
|
1515
1984
|
}
|
|
1516
1985
|
if (joinStrategy === "concat-content") {
|
|
1517
1986
|
const activeIndex = converted.findIndex((message) => message.id === activeAssistantId);
|
|
@@ -1594,6 +2063,14 @@ var AgUiThreadController = class {
|
|
|
1594
2063
|
if (!this.history) return;
|
|
1595
2064
|
this.assistantHistoryParents.set(messageId, parentId);
|
|
1596
2065
|
}
|
|
2066
|
+
persistSettledAssistant(messageId) {
|
|
2067
|
+
const item = this.session.tryGetMessage(messageId);
|
|
2068
|
+
if (!this.history?.update || !item) return;
|
|
2069
|
+
if (!this.isTerminalStatus(item.message.status)) return;
|
|
2070
|
+
if (!this.persistedHistoryIds.has(messageId) && this.historyWrites.pending(messageId) === void 0) return;
|
|
2071
|
+
this.markPendingAssistantHistory(messageId, item.parentId);
|
|
2072
|
+
this.persistAssistantHistory(messageId);
|
|
2073
|
+
}
|
|
1597
2074
|
persistAssistantHistory(messageId) {
|
|
1598
2075
|
const history = this.history;
|
|
1599
2076
|
if (!history) return;
|
|
@@ -1604,7 +2081,7 @@ var AgUiThreadController = class {
|
|
|
1604
2081
|
if (!this.isPersistableStatus(message.status)) return;
|
|
1605
2082
|
const wasPersisted = this.persistedHistoryIds.has(messageId);
|
|
1606
2083
|
const update = history.update;
|
|
1607
|
-
if (update !== void 0 && (wasPersisted || this.snapshotHistoryIds.has(messageId))) {
|
|
2084
|
+
if (update !== void 0 && (wasPersisted || this.snapshotHistoryIds.has(messageId) || this.historyWrites.pending(messageId) !== void 0)) {
|
|
1608
2085
|
const write = this.historyWrites.chain(messageId, () => update.call(history, {
|
|
1609
2086
|
parentId,
|
|
1610
2087
|
message
|
|
@@ -1647,7 +2124,6 @@ var AgUiThreadController = class {
|
|
|
1647
2124
|
return write;
|
|
1648
2125
|
}
|
|
1649
2126
|
};
|
|
1650
|
-
//#endregion
|
|
1651
2127
|
export { AgUiThreadController };
|
|
1652
2128
|
|
|
1653
2129
|
//# sourceMappingURL=AgUiThreadController.js.map
|