@assistant-ui/react-ag-ui 0.1.0 → 0.1.1
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/dist/agUiExtras.js +1 -2
- package/dist/hooks.js +1 -2
- package/dist/runtime/AgUiThreadController.d.ts.map +1 -1
- package/dist/runtime/AgUiThreadController.js +354 -68
- package/dist/runtime/AgUiThreadController.js.map +1 -1
- package/dist/runtime/adapter/conversions.d.ts.map +1 -1
- package/dist/runtime/adapter/conversions.js +10 -3
- 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.js +1 -2
- package/dist/runtime/adapter/subscriber.js +1 -2
- 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.js +1 -2
- 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.js +1 -2
- package/dist/useAgUiRuntime.js +1 -2
- package/package.json +5 -5
- package/src/runtime/AgUiThreadController.deferred-results.test.ts +548 -0
- package/src/runtime/AgUiThreadController.replay.test.ts +3932 -0
- package/src/runtime/AgUiThreadController.test.ts +79 -0
- package/src/runtime/AgUiThreadController.ts +725 -109
- package/src/runtime/adapter/conversions.content.test.ts +113 -1
- package/src/runtime/adapter/conversions.ts +18 -8
- package/src/runtime/adapter/replay-content.test.ts +235 -0
- package/src/runtime/adapter/replay-content.ts +306 -0
- package/src/runtime/adapter/tool-approval.ts +41 -3
- package/src/runtime/agUiThreadState.test.ts +21 -2
- package/src/runtime/agUiThreadState.ts +12 -3
|
@@ -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
6
|
import { RunAggregator, isPlainObject, tryParseJSON } from "./adapter/run-aggregator.js";
|
|
7
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,6 +37,21 @@ 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",
|
|
@@ -299,7 +320,7 @@ var AgUiThreadController = class {
|
|
|
299
320
|
const historyImport = this.waitForHistoryImport();
|
|
300
321
|
if (historyImport && !await historyImport) return;
|
|
301
322
|
this.assertNoPendingInterrupts();
|
|
302
|
-
await this.startRun(config.parentId, config.runConfig ?? this.state.lastRunConfig, void 0, config.stream);
|
|
323
|
+
await this.startRun(config.parentId, config.runConfig ?? this.state.lastRunConfig, void 0, config.stream, true);
|
|
303
324
|
}
|
|
304
325
|
async resumeInFlightRun(messages) {
|
|
305
326
|
const historyImport = this.waitForHistoryImport();
|
|
@@ -321,9 +342,7 @@ var AgUiThreadController = class {
|
|
|
321
342
|
throw new Error("[agui] cannot start a new run while interrupts are pending; resolve them with submitInterruptResponses()");
|
|
322
343
|
}
|
|
323
344
|
findRequiresActionAssistant(reason) {
|
|
324
|
-
|
|
325
|
-
if (!assistant || assistant.status?.type !== "requires-action" || assistant.status.reason !== reason) return null;
|
|
326
|
-
return assistant;
|
|
345
|
+
return this.getTrailingAssistantTurn().findLast((message) => message.status?.type === "requires-action" && message.status.reason === reason) ?? null;
|
|
327
346
|
}
|
|
328
347
|
getTrailingAssistantTurn() {
|
|
329
348
|
const messages = this.getMessages();
|
|
@@ -383,8 +402,9 @@ var AgUiThreadController = class {
|
|
|
383
402
|
* unretryable.
|
|
384
403
|
*/
|
|
385
404
|
async resumeWithResponses(messageId, resume) {
|
|
405
|
+
const parentId = this.session.headId ?? messageId;
|
|
386
406
|
this.clearPendingInterrupts(messageId, resume);
|
|
387
|
-
await this.startRun(
|
|
407
|
+
await this.startRun(parentId, this.state.lastRunConfig, {
|
|
388
408
|
entries: resume,
|
|
389
409
|
interruptedMessageId: messageId
|
|
390
410
|
});
|
|
@@ -657,23 +677,27 @@ var AgUiThreadController = class {
|
|
|
657
677
|
}
|
|
658
678
|
this.startResumeRun(parentId);
|
|
659
679
|
}
|
|
660
|
-
maybeResumeAfterToolResults(messageId) {
|
|
680
|
+
maybeResumeAfterToolResults(messageId, accountedResultIds) {
|
|
661
681
|
if (!this.maybeCompleteAfterToolResults(messageId)) return;
|
|
682
|
+
if (accountedResultIds) {
|
|
683
|
+
const message = this.session.tryGetMessage(messageId)?.message;
|
|
684
|
+
if (message?.role !== "assistant" || !Array.from(iterateToolCallParts(message.content)).some((part) => isResolvedToolCall(part) && !accountedResultIds.has(part.toolCallId))) return;
|
|
685
|
+
}
|
|
662
686
|
if (this.getPendingInterrupts()) return;
|
|
663
687
|
const turn = this.getTrailingAssistantTurn();
|
|
664
688
|
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
689
|
const owner = this.abortController;
|
|
669
690
|
if (owner) {
|
|
670
691
|
this.dispatch({
|
|
671
692
|
type: "resumeDeferred",
|
|
672
693
|
owner,
|
|
673
|
-
messageId
|
|
694
|
+
messageId
|
|
674
695
|
});
|
|
675
696
|
return;
|
|
676
697
|
}
|
|
698
|
+
if (this.getPendingToolCalls()) return;
|
|
699
|
+
const headId = turn.at(-1)?.id;
|
|
700
|
+
if (headId === void 0) return;
|
|
677
701
|
this.startResumeRun(headId);
|
|
678
702
|
}
|
|
679
703
|
maybeCompleteAfterToolResults(messageId) {
|
|
@@ -729,6 +753,10 @@ var AgUiThreadController = class {
|
|
|
729
753
|
let lastAppliedId = null;
|
|
730
754
|
let hardReplace = false;
|
|
731
755
|
const seen = /* @__PURE__ */ new Set();
|
|
756
|
+
const retainToolResults = (existing, message) => existing?.role === "assistant" && message.role === "assistant" ? {
|
|
757
|
+
...message,
|
|
758
|
+
content: this.preserveToolResults(existing.content, message.content)
|
|
759
|
+
} : message;
|
|
732
760
|
for (const message of messages) {
|
|
733
761
|
if (seen.has(message.id)) continue;
|
|
734
762
|
seen.add(message.id);
|
|
@@ -737,11 +765,12 @@ var AgUiThreadController = class {
|
|
|
737
765
|
hardReplace = true;
|
|
738
766
|
break;
|
|
739
767
|
}
|
|
740
|
-
this.session.addOrUpdateMessage(expectedParentId, message);
|
|
768
|
+
this.session.addOrUpdateMessage(expectedParentId, retainToolResults(existing?.message, message));
|
|
741
769
|
expectedParentId = message.id;
|
|
742
770
|
lastAppliedId = message.id;
|
|
743
771
|
}
|
|
744
772
|
if (hardReplace) {
|
|
773
|
+
const previous = new Map(this.getMessageRepository().messages.map(({ message }) => [message.id, message]));
|
|
745
774
|
this.session.clear();
|
|
746
775
|
expectedParentId = null;
|
|
747
776
|
lastAppliedId = null;
|
|
@@ -749,17 +778,29 @@ var AgUiThreadController = class {
|
|
|
749
778
|
for (const message of messages) {
|
|
750
779
|
if (seen.has(message.id)) continue;
|
|
751
780
|
seen.add(message.id);
|
|
752
|
-
this.session.addOrUpdateMessage(expectedParentId, message);
|
|
781
|
+
this.session.addOrUpdateMessage(expectedParentId, retainToolResults(previous.get(message.id), message));
|
|
753
782
|
expectedParentId = message.id;
|
|
754
783
|
lastAppliedId = message.id;
|
|
755
784
|
}
|
|
756
785
|
}
|
|
757
786
|
this.session.resetHead(lastAppliedId);
|
|
787
|
+
for (const messageId of seen) this.maybeCompleteAfterToolResults(messageId);
|
|
758
788
|
}
|
|
759
789
|
this.snapshotHistoryIds.clear();
|
|
760
790
|
for (const { message } of this.getMessageRepository().messages) this.snapshotHistoryIds.add(message.id);
|
|
761
|
-
const
|
|
762
|
-
if (
|
|
791
|
+
const pendingResume = this.state.pendingResume;
|
|
792
|
+
if (pendingResume) {
|
|
793
|
+
const selectedResultOwnerIds = new Set(this.session.getMessages().filter(hasResolvedToolCalls).map((message) => message.id));
|
|
794
|
+
const retained = pendingResume.messageIds.filter((id) => selectedResultOwnerIds.has(id));
|
|
795
|
+
if (retained.length !== pendingResume.messageIds.length) {
|
|
796
|
+
this.dispatch({ type: "resumeCleared" });
|
|
797
|
+
for (const messageId of retained) this.dispatch({
|
|
798
|
+
type: "resumeDeferred",
|
|
799
|
+
owner: pendingResume.owner,
|
|
800
|
+
messageId
|
|
801
|
+
});
|
|
802
|
+
}
|
|
803
|
+
}
|
|
763
804
|
this.publishUpdate();
|
|
764
805
|
}
|
|
765
806
|
loadExternalState(state) {
|
|
@@ -799,9 +840,10 @@ var AgUiThreadController = class {
|
|
|
799
840
|
}
|
|
800
841
|
} else this.publishUpdate();
|
|
801
842
|
}
|
|
802
|
-
async startRun(parentId, runConfig, resume, resumeStream) {
|
|
843
|
+
async startRun(parentId, runConfig, resume, resumeStream, replayExistingMessages = false) {
|
|
803
844
|
if (this.isThreadSwitching?.()) return;
|
|
804
845
|
if (!this.supersedeActiveRun()) return;
|
|
846
|
+
const generation = this.state.historyMutationGeneration;
|
|
805
847
|
const normalizedRunConfig = runConfig ?? {};
|
|
806
848
|
this.dispatch({
|
|
807
849
|
type: "runConfigRecorded",
|
|
@@ -811,13 +853,43 @@ var AgUiThreadController = class {
|
|
|
811
853
|
const shouldEagerlyInsertAssistant = parentId !== null && parent !== void 0 && parentId !== this.session.headId;
|
|
812
854
|
const historicalMessages = [...shouldEagerlyInsertAssistant ? this.session.tryGetMessages(parentId) ?? this.session.getMessages() : this.session.getMessages()];
|
|
813
855
|
const runStartMessageIds = new Set(this.getMessageRepository().messages.map(({ message }) => message.id));
|
|
856
|
+
const selectedMessages = this.session.getMessages();
|
|
857
|
+
const parentIndex = selectedMessages.findIndex((message) => message.id === parentId);
|
|
858
|
+
let replayStart = parentIndex + 1;
|
|
859
|
+
if (selectedMessages[parentIndex]?.role === "assistant") {
|
|
860
|
+
replayStart = parentIndex;
|
|
861
|
+
while (replayStart > 0 && selectedMessages[replayStart - 1]?.role === "assistant") replayStart--;
|
|
862
|
+
}
|
|
863
|
+
const replayMessageIds = [];
|
|
864
|
+
if (replayExistingMessages && !resumeStream && parentIndex >= 0) for (const message of selectedMessages.slice(replayStart)) {
|
|
865
|
+
if (message.role !== "assistant") break;
|
|
866
|
+
replayMessageIds.push(message.id);
|
|
867
|
+
}
|
|
868
|
+
const accountedResultIds = new Set(historicalMessages.flatMap((message) => message.role === "assistant" ? Array.from(iterateToolCallParts(message.content)).filter(isResolvedToolCall).map((part) => part.toolCallId) : []));
|
|
869
|
+
const candidateContexts = /* @__PURE__ */ new Map();
|
|
870
|
+
for (const message of selectedMessages) {
|
|
871
|
+
if (message.role !== "assistant" || !replayMessageIds.includes(message.id)) continue;
|
|
872
|
+
candidateContexts.set(message.id, {
|
|
873
|
+
originalContent: message.content,
|
|
874
|
+
accountedResultIds,
|
|
875
|
+
admittedToolCallIds: actionableToolCallIds(message),
|
|
876
|
+
merge: createReplayContentMerger()
|
|
877
|
+
});
|
|
878
|
+
}
|
|
879
|
+
let replayContexts = /* @__PURE__ */ new Map();
|
|
880
|
+
let lastReplayedIndex = -1;
|
|
881
|
+
let replayEnded = replayMessageIds.length === 0;
|
|
882
|
+
const replayTail = replayMessageIds.at(-1);
|
|
814
883
|
const joinStrategy = this.joinStrategy;
|
|
815
884
|
const hasRunUserBoundary = parent?.message.role === "user" && !resume;
|
|
816
885
|
let pendingError = null;
|
|
817
886
|
const assistantParentId = parent ? parentId : this.session.headId;
|
|
818
887
|
let assistantMessageId;
|
|
888
|
+
let snapshotAssistantId;
|
|
889
|
+
let replayInterruptMessageId;
|
|
819
890
|
let assistantCollided = false;
|
|
820
891
|
const heldMessages = /* @__PURE__ */ new Map();
|
|
892
|
+
let frameOwnerIds = /* @__PURE__ */ new Set();
|
|
821
893
|
let restrictedPendingToolCallIds;
|
|
822
894
|
let settlingRun = false;
|
|
823
895
|
const ensureAssistant = (allowRecreate = false) => {
|
|
@@ -825,22 +897,33 @@ var AgUiThreadController = class {
|
|
|
825
897
|
if (cached !== void 0 && this.session.tryGetMessage(cached)) return cached;
|
|
826
898
|
if (cached !== void 0 && (assistantCollided || !allowRecreate)) return cached;
|
|
827
899
|
const repositoryHeadId = this.session.headId;
|
|
828
|
-
const parentId = (cached === void 0 || shouldEagerlyInsertAssistant && (repositoryHeadId === null || runStartMessageIds.has(repositoryHeadId))) && assistantParentId && this.session.hasMessage(assistantParentId) ? assistantParentId : repositoryHeadId;
|
|
900
|
+
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
901
|
const created = this.insertAssistantPlaceholder(parentId, cached);
|
|
830
902
|
assistantMessageId = created;
|
|
831
903
|
this.markPendingAssistantHistory(created, parentId);
|
|
832
904
|
return created;
|
|
833
905
|
};
|
|
834
|
-
if (shouldEagerlyInsertAssistant) ensureAssistant();
|
|
906
|
+
if (shouldEagerlyInsertAssistant && replayEnded) ensureAssistant();
|
|
835
907
|
const settleHeldMessages = (terminalStatus) => {
|
|
836
908
|
const pendingMessages = [];
|
|
909
|
+
const producerPending = restrictedPendingToolCallIds;
|
|
910
|
+
const canWaitForTools = terminalStatus.type === "complete" || terminalStatus.type === "requires-action";
|
|
911
|
+
if (producerPending && canWaitForTools) for (const message of this.getTrailingAssistantTurn()) {
|
|
912
|
+
const replay = replayContexts.get(message.id);
|
|
913
|
+
if (message.id === assistantMessageId || heldMessages.has(message.id) || !replay || message.status?.type === "requires-action" && message.status.reason === "interrupt") continue;
|
|
914
|
+
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());
|
|
915
|
+
}
|
|
837
916
|
for (const [messageId, openToolCallIds] of heldMessages) {
|
|
917
|
+
const existing = this.session.tryGetMessage(messageId);
|
|
918
|
+
if (existing) this.markPendingAssistantHistory(messageId, existing.parentId);
|
|
838
919
|
let requiresToolResult = false;
|
|
839
920
|
if (this.session.updateMessage(messageId, (message) => {
|
|
840
921
|
if (message.role !== "assistant") return message;
|
|
841
922
|
const assistant = message;
|
|
842
|
-
const pendingToolCallIds = Array.from(
|
|
843
|
-
const
|
|
923
|
+
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);
|
|
924
|
+
const replay = replayContexts.get(messageId);
|
|
925
|
+
if (replay) replay.admittedToolCallIds = new Set(pendingToolCallIds);
|
|
926
|
+
const status = canWaitForTools ? pendingToolCallIds.length > 0 ? {
|
|
844
927
|
type: "requires-action",
|
|
845
928
|
reason: "tool-calls"
|
|
846
929
|
} : {
|
|
@@ -852,16 +935,7 @@ var AgUiThreadController = class {
|
|
|
852
935
|
return {
|
|
853
936
|
...assistant,
|
|
854
937
|
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
|
|
938
|
+
metadata: status.type === "requires-action" && (restrictedPendingToolCallIds || replayContexts.has(messageId)) ? withPendingToolCallIds(metadata, pendingToolCallIds) : metadata
|
|
865
939
|
};
|
|
866
940
|
})) {
|
|
867
941
|
this.publishUpdate();
|
|
@@ -872,29 +946,139 @@ var AgUiThreadController = class {
|
|
|
872
946
|
heldMessages.clear();
|
|
873
947
|
return pendingMessages;
|
|
874
948
|
};
|
|
949
|
+
const accountProducerResults = (content) => {
|
|
950
|
+
for (const part of iterateToolCallParts(content)) if (isResolvedToolCall(part)) accountedResultIds.add(part.toolCallId);
|
|
951
|
+
};
|
|
952
|
+
const reconcileReplaySnapshot = (snapshot, previous) => {
|
|
953
|
+
accountProducerResults(snapshot.content);
|
|
954
|
+
const replay = candidateContexts.get(snapshot.id);
|
|
955
|
+
if (!replay) return snapshot;
|
|
956
|
+
replay.admittedToolCallIds = actionableToolCallIds(snapshot);
|
|
957
|
+
if (heldMessages.has(snapshot.id)) heldMessages.set(snapshot.id, new Set(replay.admittedToolCallIds));
|
|
958
|
+
if (!replayContexts.has(snapshot.id)) return snapshot;
|
|
959
|
+
return previous ? {
|
|
960
|
+
...snapshot,
|
|
961
|
+
content: this.preserveToolResults(previous.content, snapshot.content, replay.originalContent)
|
|
962
|
+
} : snapshot;
|
|
963
|
+
};
|
|
875
964
|
const applyUpdate = (update) => {
|
|
965
|
+
if (!assistantCollided && replayContexts.size && update.content !== void 0) accountProducerResults(update.content);
|
|
966
|
+
let ownerParts;
|
|
967
|
+
let contentOwnerIds;
|
|
968
|
+
const hasStreamContent = Array.isArray(update.content) && update.content.some((part) => part.type !== "data");
|
|
969
|
+
if (!replayEnded && assistantMessageId === void 0) {
|
|
970
|
+
if (!update.status || update.status.type === "running") return;
|
|
971
|
+
replayEnded = true;
|
|
972
|
+
if (replayContexts.size === 0 && snapshotAssistantId !== void 0 && this.session.headId === snapshotAssistantId && !hasStreamContent) {
|
|
973
|
+
if (update.status.type !== "complete") this.updateAssistantMessage(snapshotAssistantId, {
|
|
974
|
+
status: update.status,
|
|
975
|
+
...update.metadata ? { metadata: update.metadata } : {}
|
|
976
|
+
});
|
|
977
|
+
return;
|
|
978
|
+
}
|
|
979
|
+
}
|
|
980
|
+
const streamMessageId = assistantMessageId;
|
|
981
|
+
let primaryMessageId = streamMessageId;
|
|
876
982
|
const terminalStatus = update.status;
|
|
983
|
+
const replayInterrupt = replayContexts.size > 0 && terminalStatus?.type === "requires-action" && terminalStatus.reason === "interrupt";
|
|
984
|
+
if (streamMessageId !== void 0 && !assistantCollided && replayContexts.size > 0 && (update.content !== void 0 || replayInterrupt)) {
|
|
985
|
+
const owners = this.getTrailingAssistantTurn().filter((message) => replayContexts.has(message.id));
|
|
986
|
+
const grouped = update.content !== void 0 ? groupReplayToolContent(owners, update.content, streamMessageId) : /* @__PURE__ */ new Map([[streamMessageId, [...update.content ?? []]]]);
|
|
987
|
+
if (update.content !== void 0) {
|
|
988
|
+
const currentOwnerIds = new Set(grouped.keys());
|
|
989
|
+
for (const ownerId of frameOwnerIds) grouped.set(ownerId, grouped.get(ownerId) ?? []);
|
|
990
|
+
contentOwnerIds = new Set(grouped.keys());
|
|
991
|
+
frameOwnerIds = currentOwnerIds;
|
|
992
|
+
}
|
|
993
|
+
ownerParts = grouped;
|
|
994
|
+
if (replayInterrupt) {
|
|
995
|
+
const interrupts = (update.metadata?.custom?.[AG_UI_METADATA_NAMESPACE])?.interrupts;
|
|
996
|
+
const approvals = projectAgUiToolApprovals(interrupts);
|
|
997
|
+
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) ?? []);
|
|
998
|
+
const anchor = owners.find((message) => projectAgUiToolApprovals(interrupts, new Set(Array.from(iterateToolCallParts([...message.content, ...grouped.get(message.id) ?? []])).map((part) => part.toolCallId))).size > 0);
|
|
999
|
+
if (anchor) {
|
|
1000
|
+
primaryMessageId = anchor.id;
|
|
1001
|
+
replayInterruptMessageId = anchor.id;
|
|
1002
|
+
grouped.set(anchor.id, grouped.get(anchor.id) ?? []);
|
|
1003
|
+
}
|
|
1004
|
+
}
|
|
1005
|
+
}
|
|
877
1006
|
const pendingMessages = settlingRun && terminalStatus && terminalStatus.type !== "running" ? settleHeldMessages(terminalStatus) : [];
|
|
878
|
-
const
|
|
879
|
-
const
|
|
880
|
-
|
|
881
|
-
|
|
1007
|
+
const frameMessageId = ensureAssistant(hasStreamContent);
|
|
1008
|
+
const messageId = primaryMessageId ?? frameMessageId;
|
|
1009
|
+
let primaryUpdate = update;
|
|
1010
|
+
if (ownerParts) {
|
|
1011
|
+
const interrupt = terminalStatus?.type === "requires-action" && terminalStatus.reason === "interrupt";
|
|
1012
|
+
const ownerStatus = terminalStatus?.type === "requires-action" ? {
|
|
1013
|
+
type: "complete",
|
|
1014
|
+
reason: "unknown"
|
|
1015
|
+
} : terminalStatus;
|
|
1016
|
+
for (const [ownerId, content] of ownerParts) {
|
|
1017
|
+
const existing = this.session.tryGetMessage(ownerId);
|
|
1018
|
+
if (existing) this.markPendingAssistantHistory(ownerId, existing.parentId);
|
|
1019
|
+
if (ownerId === messageId) continue;
|
|
1020
|
+
this.updateAssistantMessage(ownerId, {
|
|
1021
|
+
...contentOwnerIds?.has(ownerId) ? { content } : {},
|
|
1022
|
+
...ownerStatus ? { status: ownerStatus } : {}
|
|
1023
|
+
}, replayContexts.get(ownerId), interrupt ? /* @__PURE__ */ new Set() : restrictedPendingToolCallIds);
|
|
1024
|
+
}
|
|
1025
|
+
const { content: _content, ...controlUpdate } = update;
|
|
1026
|
+
const content = ownerParts.get(messageId) ?? [];
|
|
1027
|
+
primaryUpdate = {
|
|
1028
|
+
...controlUpdate,
|
|
1029
|
+
...contentOwnerIds?.has(messageId) ? { content } : {},
|
|
1030
|
+
...ownerStatus && !interrupt && replayContexts.has(messageId) ? { status: ownerStatus } : {}
|
|
1031
|
+
};
|
|
1032
|
+
}
|
|
1033
|
+
const resolved = this.updateAssistantMessage(messageId, primaryUpdate, replayContexts.get(messageId), restrictedPendingToolCallIds);
|
|
1034
|
+
if (messageId === frameMessageId && resolved !== assistantMessageId) assistantMessageId = resolved;
|
|
1035
|
+
for (const messageId of pendingMessages) this.maybeResumeAfterToolResults(messageId, replayMessageIds.includes(messageId) ? accountedResultIds : void 0);
|
|
882
1036
|
};
|
|
883
1037
|
const adoptServerMessageId = (serverId, startNewMessage, openToolCallIds = []) => {
|
|
1038
|
+
const existing = this.session.tryGetMessage(serverId);
|
|
1039
|
+
const replayIndex = replayMessageIds.indexOf(serverId);
|
|
1040
|
+
const adoptsReplay = !replayEnded && !assistantCollided && replayIndex > lastReplayedIndex && existing?.message.role === "assistant" && this.session.getMessages().some((message) => message.id === serverId);
|
|
1041
|
+
if (adoptsReplay && replayContexts.size === 0) {
|
|
1042
|
+
replayContexts = candidateContexts;
|
|
1043
|
+
const selected = this.session.getMessages();
|
|
1044
|
+
let tailId = serverId;
|
|
1045
|
+
for (const message of selected.slice(selected.findIndex((message) => message.id === serverId) + 1)) {
|
|
1046
|
+
if (message.role !== "assistant") break;
|
|
1047
|
+
tailId = message.id;
|
|
1048
|
+
}
|
|
1049
|
+
this.session.selectHead(tailId);
|
|
1050
|
+
}
|
|
884
1051
|
if (startNewMessage && assistantMessageId !== void 0) {
|
|
885
|
-
|
|
1052
|
+
let hasReplayCallsToHold = false;
|
|
1053
|
+
const replay = replayContexts.get(assistantMessageId);
|
|
1054
|
+
if (replay) {
|
|
1055
|
+
const current = this.session.tryGetMessage(assistantMessageId)?.message;
|
|
1056
|
+
if (current?.role === "assistant") {
|
|
1057
|
+
const retainedPending = Array.from(iterateToolCallParts(current.content)).filter((part) => !isResolvedToolCall(part) || replay.admittedToolCallIds.has(part.toolCallId) && !accountedResultIds.has(part.toolCallId));
|
|
1058
|
+
hasReplayCallsToHold = retainedPending.length > 0;
|
|
1059
|
+
openToolCallIds = [.../* @__PURE__ */ new Set([...openToolCallIds, ...retainedPending.filter((part) => replay.admittedToolCallIds.has(part.toolCallId)).map((part) => part.toolCallId)])];
|
|
1060
|
+
}
|
|
1061
|
+
}
|
|
1062
|
+
if (openToolCallIds.length > 0 || hasReplayCallsToHold) heldMessages.set(assistantMessageId, new Set(openToolCallIds));
|
|
886
1063
|
else applyUpdate({ status: {
|
|
887
1064
|
type: "complete",
|
|
888
1065
|
reason: "unknown"
|
|
889
1066
|
} });
|
|
890
1067
|
const previousId = assistantMessageId;
|
|
891
1068
|
assistantMessageId = void 0;
|
|
892
|
-
if (this.session.tryGetMessage(previousId)) {
|
|
1069
|
+
if (!adoptsReplay && this.session.tryGetMessage(previousId)) {
|
|
893
1070
|
const created = this.insertAssistantPlaceholder(previousId);
|
|
894
1071
|
assistantMessageId = created;
|
|
895
1072
|
this.markPendingAssistantHistory(created, previousId);
|
|
896
1073
|
}
|
|
897
1074
|
}
|
|
1075
|
+
if (adoptsReplay) {
|
|
1076
|
+
assistantMessageId = serverId;
|
|
1077
|
+
lastReplayedIndex = replayIndex;
|
|
1078
|
+
this.markPendingAssistantHistory(serverId, existing.parentId);
|
|
1079
|
+
return;
|
|
1080
|
+
}
|
|
1081
|
+
replayEnded = true;
|
|
898
1082
|
const placeholder = ensureAssistant(true);
|
|
899
1083
|
if (placeholder === serverId) return;
|
|
900
1084
|
const reassigned = this.reassignAssistantId(placeholder, serverId);
|
|
@@ -917,21 +1101,57 @@ var AgUiThreadController = class {
|
|
|
917
1101
|
onServerMessageId: (serverId) => {
|
|
918
1102
|
adoptServerMessageId(serverId, false);
|
|
919
1103
|
},
|
|
920
|
-
onTextMessageStart: (serverId, openToolCallIds) =>
|
|
1104
|
+
onTextMessageStart: (serverId, openToolCallIds) => {
|
|
1105
|
+
const owners = this.getTrailingAssistantTurn().filter((message) => replayContexts.has(message.id));
|
|
1106
|
+
const toolOwners = indexReplayToolOwners(owners);
|
|
1107
|
+
const frameOpenIds = openToolCallIds.filter((id) => {
|
|
1108
|
+
const ownerId = toolOwners.get(id);
|
|
1109
|
+
if (!ownerId || ownerId === assistantMessageId || assistantCollided) return true;
|
|
1110
|
+
const held = heldMessages.get(ownerId) ?? /* @__PURE__ */ new Set();
|
|
1111
|
+
held.add(id);
|
|
1112
|
+
heldMessages.set(ownerId, held);
|
|
1113
|
+
return false;
|
|
1114
|
+
});
|
|
1115
|
+
for (const ownerId of frameOwnerIds) {
|
|
1116
|
+
const replay = replayContexts.get(ownerId);
|
|
1117
|
+
if (replay) replay.merge = createReplayContentMerger();
|
|
1118
|
+
}
|
|
1119
|
+
frameOwnerIds.clear();
|
|
1120
|
+
adoptServerMessageId(serverId, true, frameOpenIds);
|
|
1121
|
+
}
|
|
921
1122
|
});
|
|
922
1123
|
let runFinished = false;
|
|
923
1124
|
const dispatch = (event) => {
|
|
924
1125
|
if (this.abortController !== abortController) return;
|
|
925
1126
|
if (event.type === "RUN_FINISHED") {
|
|
926
1127
|
runFinished = true;
|
|
1128
|
+
if (event.outcome?.type === "interrupt") replayInterruptMessageId = assistantMessageId ?? replayTail;
|
|
927
1129
|
const ids = event.outcome?.type === "success" ? event.outcome.pendingToolCallIds : void 0;
|
|
928
|
-
restrictedPendingToolCallIds = ids
|
|
1130
|
+
restrictedPendingToolCallIds = ids === void 0 ? void 0 : new Set(ids);
|
|
1131
|
+
}
|
|
1132
|
+
if (event.type === "TOOL_CALL_RESULT") {
|
|
1133
|
+
if (!assistantCollided && candidateContexts.size) accountedResultIds.add(event.toolCallId);
|
|
1134
|
+
for (const openToolCallIds of heldMessages.values()) openToolCallIds.delete(event.toolCallId);
|
|
929
1135
|
}
|
|
930
|
-
if (event.type === "TOOL_CALL_RESULT") for (const openToolCallIds of heldMessages.values()) openToolCallIds.delete(event.toolCallId);
|
|
931
1136
|
settlingRun = event.type === "RUN_FINISHED" || event.type === "RUN_ERROR" || event.type === "RUN_CANCELLED";
|
|
1137
|
+
const beforeSnapshot = event.type === "MESSAGES_SNAPSHOT" ? this.session.getMessages() : [];
|
|
932
1138
|
try {
|
|
933
|
-
const nextAssistantMessageId = this.handleEvent(aggregator, event, assistantMessageId, joinStrategy, runStartMessageIds, hasRunUserBoundary);
|
|
934
|
-
if (event.type === "MESSAGES_SNAPSHOT")
|
|
1139
|
+
const nextAssistantMessageId = this.handleEvent(aggregator, event, assistantMessageId, joinStrategy, runStartMessageIds, hasRunUserBoundary, candidateContexts.size ? reconcileReplaySnapshot : void 0);
|
|
1140
|
+
if (event.type === "MESSAGES_SNAPSHOT") {
|
|
1141
|
+
for (const message of beforeSnapshot) {
|
|
1142
|
+
const context = replayContexts.get(message.id);
|
|
1143
|
+
const current = this.session.tryGetMessage(message.id)?.message;
|
|
1144
|
+
if (context && current?.content !== message.content) {
|
|
1145
|
+
context.merge = createReplayContentMerger();
|
|
1146
|
+
frameOwnerIds.delete(message.id);
|
|
1147
|
+
}
|
|
1148
|
+
}
|
|
1149
|
+
assistantMessageId = nextAssistantMessageId;
|
|
1150
|
+
if (!replayEnded && assistantMessageId === void 0) {
|
|
1151
|
+
const tail = this.session.getMessages().at(-1);
|
|
1152
|
+
snapshotAssistantId = tail?.role === "assistant" ? tail.id : void 0;
|
|
1153
|
+
}
|
|
1154
|
+
}
|
|
935
1155
|
} finally {
|
|
936
1156
|
settlingRun = false;
|
|
937
1157
|
}
|
|
@@ -1008,10 +1228,40 @@ var AgUiThreadController = class {
|
|
|
1008
1228
|
this.clearDeferredContinuations(abortController);
|
|
1009
1229
|
throw err;
|
|
1010
1230
|
}
|
|
1231
|
+
const pending = replayInterruptMessageId ? this.getPendingInterrupts() : null;
|
|
1232
|
+
if (pending && pending.messageId === replayInterruptMessageId && replayContexts.has(pending.messageId) && !abortSignal.aborted && this.abortController === null && generation === this.state.historyMutationGeneration && !this.isThreadSwitching?.()) {
|
|
1233
|
+
const message = this.session.tryGetMessage(pending.messageId)?.message;
|
|
1234
|
+
const responses = message?.role === "assistant" ? buildToolApprovalResume(message.content, pending.interrupts) : null;
|
|
1235
|
+
if (responses) {
|
|
1236
|
+
this.clearDeferredContinuations(abortController);
|
|
1237
|
+
try {
|
|
1238
|
+
await this.submitInterruptResponses(responses);
|
|
1239
|
+
} catch (error) {
|
|
1240
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
1241
|
+
if (!this.reportedErrors.has(err)) {
|
|
1242
|
+
this.reportError(err);
|
|
1243
|
+
this.reportedErrors.add(err);
|
|
1244
|
+
}
|
|
1245
|
+
throw err;
|
|
1246
|
+
}
|
|
1247
|
+
return;
|
|
1248
|
+
}
|
|
1249
|
+
}
|
|
1011
1250
|
if (this.state.pendingResume?.owner === abortController) {
|
|
1012
|
-
const {
|
|
1251
|
+
const { messageIds } = this.state.pendingResume;
|
|
1013
1252
|
this.dispatch({ type: "resumeCleared" });
|
|
1014
|
-
if (!abortSignal.aborted) this.
|
|
1253
|
+
if (!abortSignal.aborted && !this.getPendingInterrupts() && !this.getPendingToolCalls()) {
|
|
1254
|
+
const turn = this.getTrailingAssistantTurn();
|
|
1255
|
+
if (turn.some((message) => {
|
|
1256
|
+
if (!messageIds.includes(message.id)) return false;
|
|
1257
|
+
const replay = replayContexts.get(message.id);
|
|
1258
|
+
if (!replay) return hasResolvedToolCalls(message);
|
|
1259
|
+
return Array.from(iterateToolCallParts(message.content)).some((part) => isResolvedToolCall(part) && replay.admittedToolCallIds.has(part.toolCallId) && !accountedResultIds.has(part.toolCallId));
|
|
1260
|
+
})) {
|
|
1261
|
+
const headId = turn.at(-1)?.id;
|
|
1262
|
+
if (headId !== void 0) this.startResumeRun(headId);
|
|
1263
|
+
}
|
|
1264
|
+
}
|
|
1015
1265
|
}
|
|
1016
1266
|
if (this.state.pendingA2uiResumeOwner === abortController) {
|
|
1017
1267
|
this.dispatch({ type: "a2uiResumeCleared" });
|
|
@@ -1194,15 +1444,47 @@ var AgUiThreadController = class {
|
|
|
1194
1444
|
this.publishUpdate();
|
|
1195
1445
|
return !collidesWithExisting;
|
|
1196
1446
|
}
|
|
1197
|
-
updateAssistantMessage(messageId, update) {
|
|
1447
|
+
updateAssistantMessage(messageId, update, replay, pendingToolCallIds) {
|
|
1198
1448
|
let latestStatus;
|
|
1199
1449
|
if (!this.session.updateMessage(messageId, (message) => {
|
|
1200
1450
|
if (message.role !== "assistant") return message;
|
|
1201
1451
|
const assistant = message;
|
|
1202
1452
|
const mergedMetadata = update.metadata ? this.mergeAssistantMetadata(assistant.metadata, update.metadata) : assistant.metadata;
|
|
1203
1453
|
latestStatus = update.status ?? assistant.status;
|
|
1204
|
-
|
|
1205
|
-
const
|
|
1454
|
+
let content = update.content !== void 0 ? replay ? replay.merge(assistant.content, update.content) : this.preserveToolResults(assistant.content, update.content) : assistant.content;
|
|
1455
|
+
const calls = replay ? Array.from(iterateToolCallParts(content)) : [];
|
|
1456
|
+
const pending = pendingToolCallIds ?? (replay ? /* @__PURE__ */ new Set([...replay.admittedToolCallIds, ...Array.from(iterateToolCallParts(update.content ?? [])).filter((part) => !isResolvedToolCall(part)).map((part) => part.toolCallId)]) : void 0);
|
|
1457
|
+
if (replay && latestStatus?.type !== "running" && pending) replay.admittedToolCallIds = pending;
|
|
1458
|
+
if (replay) {
|
|
1459
|
+
const interrupts = latestStatus?.type === "requires-action" && latestStatus.reason === "interrupt" ? mergedMetadata.custom["agui"]?.interrupts : void 0;
|
|
1460
|
+
const approvals = projectAgUiToolApprovals(interrupts, new Set(calls.map((part) => part.toolCallId)));
|
|
1461
|
+
const originalCalls = new Map(Array.from(iterateToolCallParts(replay.originalContent), (part) => [part.toolCallId, part]));
|
|
1462
|
+
content = mapToolCallPartsDeep(content, (part) => {
|
|
1463
|
+
const approval = approvals.get(part.toolCallId);
|
|
1464
|
+
if (approval) return {
|
|
1465
|
+
...part,
|
|
1466
|
+
approval: preserveToolApprovalDecision(originalCalls.get(part.toolCallId), {
|
|
1467
|
+
...part,
|
|
1468
|
+
approval
|
|
1469
|
+
}) ?? approval
|
|
1470
|
+
};
|
|
1471
|
+
if (!part.approval || part.approval.approved !== void 0 || part.approval.resolution) return part;
|
|
1472
|
+
const { approval: _approval, ...withoutApproval } = part;
|
|
1473
|
+
return withoutApproval;
|
|
1474
|
+
}).content;
|
|
1475
|
+
}
|
|
1476
|
+
if (replay && latestStatus?.type === "complete") {
|
|
1477
|
+
const listed = pending ?? listedPendingToolCallIds({
|
|
1478
|
+
...assistant,
|
|
1479
|
+
metadata: mergedMetadata
|
|
1480
|
+
});
|
|
1481
|
+
if (calls.some((part) => (!isResolvedToolCall(part) || !replay.accountedResultIds.has(part.toolCallId)) && (!listed || listed.has(part.toolCallId)))) latestStatus = {
|
|
1482
|
+
type: "requires-action",
|
|
1483
|
+
reason: "tool-calls"
|
|
1484
|
+
};
|
|
1485
|
+
}
|
|
1486
|
+
const admission = pending && replay ? withPendingToolCallIds(mergedMetadata, pending) : mergedMetadata;
|
|
1487
|
+
const metadata = latestStatus?.type === "requires-action" && latestStatus.reason === "tool-calls" ? admission : replay && latestStatus?.type === "running" ? mergedMetadata : withoutPendingToolCallIds(admission);
|
|
1206
1488
|
return {
|
|
1207
1489
|
...assistant,
|
|
1208
1490
|
content,
|
|
@@ -1217,24 +1499,25 @@ var AgUiThreadController = class {
|
|
|
1217
1499
|
resolvedMessageId = stableId;
|
|
1218
1500
|
} else this.publishUpdate();
|
|
1219
1501
|
if (this.isPersistableStatus(latestStatus)) this.persistAssistantHistory(resolvedMessageId);
|
|
1220
|
-
this.maybeResumeAfterToolResults(resolvedMessageId);
|
|
1502
|
+
this.maybeResumeAfterToolResults(resolvedMessageId, replay?.accountedResultIds);
|
|
1221
1503
|
return resolvedMessageId;
|
|
1222
1504
|
}
|
|
1223
|
-
preserveToolResults(previous, next) {
|
|
1505
|
+
preserveToolResults(previous, next, decisionSource) {
|
|
1224
1506
|
const preserved = /* @__PURE__ */ new Map();
|
|
1225
1507
|
for (const part of iterateToolCallParts(previous)) if (isResolvedToolCall(part)) preserved.set(part.toolCallId, part);
|
|
1226
|
-
|
|
1508
|
+
const decisions = new Map(Array.from(iterateToolCallParts(decisionSource ?? [])).filter((part) => part.approval?.approved !== void 0).map((part) => [part.toolCallId, part]));
|
|
1509
|
+
if (preserved.size === 0 && decisions.size === 0) return this.preserveToolInteractions(previous, next);
|
|
1227
1510
|
const { content: merged, changed } = mapToolCallPartsDeep(next, (part) => {
|
|
1228
1511
|
const prior = preserved.get(part.toolCallId);
|
|
1229
|
-
|
|
1230
|
-
if (
|
|
1231
|
-
|
|
1232
|
-
result
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
}
|
|
1237
|
-
return
|
|
1512
|
+
const result = prior ? preserveToolResult(prior, part) : part;
|
|
1513
|
+
if (result.approval && decisions.has(part.toolCallId)) {
|
|
1514
|
+
const approval = preserveToolApprovalDecision(decisions.get(part.toolCallId), result);
|
|
1515
|
+
if (approval && approval !== result.approval) return {
|
|
1516
|
+
...result,
|
|
1517
|
+
approval
|
|
1518
|
+
};
|
|
1519
|
+
}
|
|
1520
|
+
return result;
|
|
1238
1521
|
});
|
|
1239
1522
|
return this.preserveToolInteractions(previous, changed ? merged : next);
|
|
1240
1523
|
}
|
|
@@ -1337,7 +1620,7 @@ var AgUiThreadController = class {
|
|
|
1337
1620
|
custom: incoming.custom ? mergeAgUiNamespace(current.custom, incoming.custom) : current.custom
|
|
1338
1621
|
};
|
|
1339
1622
|
}
|
|
1340
|
-
handleEvent(aggregator, event, activeAssistantId, joinStrategy, runStartMessageIds, hasRunUserBoundary) {
|
|
1623
|
+
handleEvent(aggregator, event, activeAssistantId, joinStrategy, runStartMessageIds, hasRunUserBoundary, reconcileSnapshot) {
|
|
1341
1624
|
switch (event.type) {
|
|
1342
1625
|
case "STATE_SNAPSHOT":
|
|
1343
1626
|
this.dispatch({
|
|
@@ -1363,7 +1646,7 @@ var AgUiThreadController = class {
|
|
|
1363
1646
|
const previousHeadId = this.session.headId;
|
|
1364
1647
|
const activeAssistant = activeAssistantId === void 0 ? void 0 : this.session.tryGetMessage(activeAssistantId)?.message;
|
|
1365
1648
|
const hasActiveText = activeAssistant?.role === "assistant" && activeAssistant.content.some((part) => part.type === "text");
|
|
1366
|
-
this.importMessagesSnapshot(event.messages, activeAssistantId, joinStrategy, runStartMessageIds, hasRunUserBoundary);
|
|
1649
|
+
this.importMessagesSnapshot(event.messages, activeAssistantId, joinStrategy, runStartMessageIds, hasRunUserBoundary, reconcileSnapshot);
|
|
1367
1650
|
const headId = this.session.headId;
|
|
1368
1651
|
if (hasActiveText && headId !== previousHeadId) activeAssistantId = (headId === null ? void 0 : this.session.tryGetMessage(headId)?.message)?.role === "assistant" ? headId ?? void 0 : void 0;
|
|
1369
1652
|
this.updateActiveAssistantState(activeAssistantId);
|
|
@@ -1490,7 +1773,7 @@ var AgUiThreadController = class {
|
|
|
1490
1773
|
this.publishUpdate();
|
|
1491
1774
|
this.maybeCompleteAfterToolResults(messageId);
|
|
1492
1775
|
}
|
|
1493
|
-
importMessagesSnapshot(rawMessages, activeAssistantId, joinStrategy, runStartMessageIds, hasRunUserBoundary) {
|
|
1776
|
+
importMessagesSnapshot(rawMessages, activeAssistantId, joinStrategy, runStartMessageIds, hasRunUserBoundary, reconcileSnapshot) {
|
|
1494
1777
|
try {
|
|
1495
1778
|
const activeAssistantItem = activeAssistantId ? this.session.tryGetMessage(activeAssistantId) : void 0;
|
|
1496
1779
|
const activeMessage = activeAssistantItem?.message;
|
|
@@ -1508,10 +1791,14 @@ var AgUiThreadController = class {
|
|
|
1508
1791
|
for (let index = 0; index < converted.length; index++) {
|
|
1509
1792
|
const convertedMessage = converted[index];
|
|
1510
1793
|
const existing = this.session.tryGetMessage(convertedMessage.id)?.message;
|
|
1511
|
-
if (convertedMessage.role === "assistant"
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1794
|
+
if (convertedMessage.role === "assistant") {
|
|
1795
|
+
const previous = existing?.role === "assistant" ? existing : void 0;
|
|
1796
|
+
const snapshot = reconcileSnapshot?.(convertedMessage, previous) ?? convertedMessage;
|
|
1797
|
+
converted[index] = previous ? {
|
|
1798
|
+
...snapshot,
|
|
1799
|
+
content: this.mergeSnapshotAssistantContent(previous.content, snapshot.content, snapshotHasReasoning, snapshotHasActivity)
|
|
1800
|
+
} : snapshot;
|
|
1801
|
+
}
|
|
1515
1802
|
}
|
|
1516
1803
|
if (joinStrategy === "concat-content") {
|
|
1517
1804
|
const activeIndex = converted.findIndex((message) => message.id === activeAssistantId);
|
|
@@ -1647,7 +1934,6 @@ var AgUiThreadController = class {
|
|
|
1647
1934
|
return write;
|
|
1648
1935
|
}
|
|
1649
1936
|
};
|
|
1650
|
-
//#endregion
|
|
1651
1937
|
export { AgUiThreadController };
|
|
1652
1938
|
|
|
1653
1939
|
//# sourceMappingURL=AgUiThreadController.js.map
|