@kortyx/react 0.4.1 → 0.4.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +18 -0
- package/LICENSE +51 -43
- package/dist/index.d.ts +158 -1
- package/dist/index.js +719 -37
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -41,7 +41,11 @@ function buildAssistantMessage(args) {
|
|
|
41
41
|
role: "assistant",
|
|
42
42
|
content: plainTextContent,
|
|
43
43
|
debug: args.debug,
|
|
44
|
-
...args.trace ? args.trace : {}
|
|
44
|
+
...args.trace ? args.trace : {},
|
|
45
|
+
...args.checkpoint ? {
|
|
46
|
+
checkpointId: args.checkpoint.id,
|
|
47
|
+
checkpointTurnIndex: args.checkpoint.turnIndex
|
|
48
|
+
} : {}
|
|
45
49
|
};
|
|
46
50
|
return args.pieces.length > 0 ? { ...base, contentPieces: args.pieces } : base;
|
|
47
51
|
}
|
|
@@ -51,7 +55,9 @@ var DEFAULT_STORAGE_KEYS = {
|
|
|
51
55
|
messages: "chat.messages.v1",
|
|
52
56
|
includeHistory: "chat.includeHistory.v1",
|
|
53
57
|
sessionId: "chat.sessionId",
|
|
54
|
-
workflowId: "chat.workflowId"
|
|
58
|
+
workflowId: "chat.workflowId",
|
|
59
|
+
branches: "chat.branches.v1",
|
|
60
|
+
responseVariants: "chat.responseVariants.v1"
|
|
55
61
|
};
|
|
56
62
|
var getLocalStorage = () => {
|
|
57
63
|
const candidate = globalThis;
|
|
@@ -78,13 +84,40 @@ function createBrowserChatStorage(options) {
|
|
|
78
84
|
const workflowId = storage.getItem(keys.workflowId) ?? "";
|
|
79
85
|
const includeHistoryRaw = storage.getItem(keys.includeHistory);
|
|
80
86
|
const rawMessages = storage.getItem(keys.messages);
|
|
87
|
+
const rawBranches = storage.getItem(keys.branches);
|
|
88
|
+
const rawResponseVariants = storage.getItem(keys.responseVariants);
|
|
81
89
|
const parsedMessages = rawMessages ? JSON.parse(rawMessages) : [];
|
|
82
90
|
const messages = Array.isArray(parsedMessages) ? parsedMessages.map((item) => parseMessage(item)).filter((message) => message !== null) : [];
|
|
91
|
+
const parsedBranches = rawBranches ? JSON.parse(rawBranches) : void 0;
|
|
92
|
+
const branches = parsedBranches && typeof parsedBranches === "object" ? Object.fromEntries(
|
|
93
|
+
Object.entries(
|
|
94
|
+
parsedBranches
|
|
95
|
+
).flatMap(([sessionId2, branch]) => {
|
|
96
|
+
if (typeof branch.sessionId !== "string") return [];
|
|
97
|
+
const rawBranchMessages = Array.isArray(branch.messages) ? branch.messages : [];
|
|
98
|
+
const branchMessages = rawBranchMessages.map((item) => parseMessage(item)).filter((message) => message !== null).slice(-maxMessages);
|
|
99
|
+
return [
|
|
100
|
+
[
|
|
101
|
+
sessionId2,
|
|
102
|
+
{
|
|
103
|
+
sessionId: branch.sessionId,
|
|
104
|
+
...typeof branch.parentSessionId === "string" ? { parentSessionId: branch.parentSessionId } : {},
|
|
105
|
+
...typeof branch.forkedFrom === "string" ? { forkedFrom: branch.forkedFrom } : {},
|
|
106
|
+
messages: branchMessages,
|
|
107
|
+
...Array.isArray(branch.checkpoints) ? { checkpoints: branch.checkpoints } : {}
|
|
108
|
+
}
|
|
109
|
+
]
|
|
110
|
+
];
|
|
111
|
+
})
|
|
112
|
+
) : void 0;
|
|
113
|
+
const parsedResponseVariants = rawResponseVariants ? JSON.parse(rawResponseVariants) : void 0;
|
|
83
114
|
return {
|
|
84
115
|
sessionId,
|
|
85
116
|
workflowId,
|
|
86
117
|
includeHistory: includeHistoryRaw !== "0",
|
|
87
|
-
messages
|
|
118
|
+
messages,
|
|
119
|
+
...branches ? { branches } : {},
|
|
120
|
+
...Array.isArray(parsedResponseVariants) ? { responseVariants: parsedResponseVariants } : {}
|
|
88
121
|
};
|
|
89
122
|
} catch {
|
|
90
123
|
return {};
|
|
@@ -103,6 +136,28 @@ function createBrowserChatStorage(options) {
|
|
|
103
136
|
storage.setItem(keys.includeHistory, state.includeHistory ? "1" : "0");
|
|
104
137
|
const trimmedMessages = state.messages.slice(-maxMessages).map((message) => serializeMessage(message));
|
|
105
138
|
storage.setItem(keys.messages, JSON.stringify(trimmedMessages));
|
|
139
|
+
if (state.branches) {
|
|
140
|
+
const serializedBranches = Object.fromEntries(
|
|
141
|
+
Object.entries(state.branches).map(([sessionId, branch]) => [
|
|
142
|
+
sessionId,
|
|
143
|
+
{
|
|
144
|
+
...branch,
|
|
145
|
+
messages: branch.messages.slice(-maxMessages).map((message) => serializeMessage(message))
|
|
146
|
+
}
|
|
147
|
+
])
|
|
148
|
+
);
|
|
149
|
+
storage.setItem(keys.branches, JSON.stringify(serializedBranches));
|
|
150
|
+
} else {
|
|
151
|
+
storage.removeItem(keys.branches);
|
|
152
|
+
}
|
|
153
|
+
if (state.responseVariants) {
|
|
154
|
+
storage.setItem(
|
|
155
|
+
keys.responseVariants,
|
|
156
|
+
JSON.stringify(state.responseVariants)
|
|
157
|
+
);
|
|
158
|
+
} else {
|
|
159
|
+
storage.removeItem(keys.responseVariants);
|
|
160
|
+
}
|
|
106
161
|
} catch {
|
|
107
162
|
}
|
|
108
163
|
},
|
|
@@ -111,6 +166,8 @@ function createBrowserChatStorage(options) {
|
|
|
111
166
|
const storage = getLocalStorage();
|
|
112
167
|
if (!storage) return;
|
|
113
168
|
storage.removeItem(keys.messages);
|
|
169
|
+
storage.removeItem(keys.branches);
|
|
170
|
+
storage.removeItem(keys.responseVariants);
|
|
114
171
|
} catch {
|
|
115
172
|
}
|
|
116
173
|
}
|
|
@@ -132,7 +189,7 @@ function createChatTransport(args) {
|
|
|
132
189
|
};
|
|
133
190
|
}
|
|
134
191
|
function createRouteChatTransport(args) {
|
|
135
|
-
|
|
192
|
+
const transport = createChatTransport({
|
|
136
193
|
stream: (context) => {
|
|
137
194
|
const body = args.createBody?.(context) ?? {
|
|
138
195
|
sessionId: context.sessionId,
|
|
@@ -153,6 +210,41 @@ function createRouteChatTransport(args) {
|
|
|
153
210
|
});
|
|
154
211
|
}
|
|
155
212
|
});
|
|
213
|
+
if (!args.checkpointEndpoint) return transport;
|
|
214
|
+
const fetchJson = async (body) => {
|
|
215
|
+
const fetchImpl = args.fetchImpl ?? fetch;
|
|
216
|
+
const response = await fetchImpl(args.checkpointEndpoint, {
|
|
217
|
+
method: "POST",
|
|
218
|
+
headers: {
|
|
219
|
+
"content-type": "application/json",
|
|
220
|
+
...args.headers ?? {}
|
|
221
|
+
},
|
|
222
|
+
body: JSON.stringify(body)
|
|
223
|
+
});
|
|
224
|
+
if (!response.ok) {
|
|
225
|
+
let message = `Checkpoint request failed with status ${response.status}`;
|
|
226
|
+
try {
|
|
227
|
+
const parsed = await response.json();
|
|
228
|
+
if (typeof parsed.error === "string") message = parsed.error;
|
|
229
|
+
} catch {
|
|
230
|
+
}
|
|
231
|
+
throw new Error(message);
|
|
232
|
+
}
|
|
233
|
+
return await response.json();
|
|
234
|
+
};
|
|
235
|
+
return {
|
|
236
|
+
...transport,
|
|
237
|
+
listCheckpoints: (sessionId) => fetchJson({ action: "list", sessionId }),
|
|
238
|
+
rollbackTo: (checkpointId) => fetchJson({
|
|
239
|
+
action: "rollback",
|
|
240
|
+
checkpointId
|
|
241
|
+
}),
|
|
242
|
+
fork: (checkpointId, options) => fetchJson({
|
|
243
|
+
action: "fork",
|
|
244
|
+
checkpointId,
|
|
245
|
+
...options?.newSessionId ? { newSessionId: options.newSessionId } : {}
|
|
246
|
+
})
|
|
247
|
+
};
|
|
156
248
|
}
|
|
157
249
|
|
|
158
250
|
// src/create-live-chat-pieces.ts
|
|
@@ -437,6 +529,17 @@ function useStructuredStreams(options) {
|
|
|
437
529
|
const get = (0, import_react2.useCallback)((streamId) => {
|
|
438
530
|
return itemsRef.current.find((item) => item.streamId === streamId);
|
|
439
531
|
}, []);
|
|
532
|
+
const deleteStream = (0, import_react2.useCallback)(
|
|
533
|
+
(streamId) => {
|
|
534
|
+
const deleted = accumulatorRef.current.delete(streamId);
|
|
535
|
+
if (!deleted) return false;
|
|
536
|
+
setNextItems(
|
|
537
|
+
itemsRef.current.filter((item) => item.streamId !== streamId)
|
|
538
|
+
);
|
|
539
|
+
return true;
|
|
540
|
+
},
|
|
541
|
+
[setNextItems]
|
|
542
|
+
);
|
|
440
543
|
const byStreamId = (0, import_react2.useMemo)(
|
|
441
544
|
() => Object.fromEntries(
|
|
442
545
|
items.map((item) => [item.streamId, item.state])
|
|
@@ -448,7 +551,8 @@ function useStructuredStreams(options) {
|
|
|
448
551
|
byStreamId,
|
|
449
552
|
applyStreamChunk,
|
|
450
553
|
clear,
|
|
451
|
-
get
|
|
554
|
+
get,
|
|
555
|
+
delete: deleteStream
|
|
452
556
|
};
|
|
453
557
|
}
|
|
454
558
|
|
|
@@ -481,6 +585,62 @@ function findActiveTextInterrupt(args) {
|
|
|
481
585
|
(piece) => piece.type === "interrupt" && piece.kind === "text"
|
|
482
586
|
);
|
|
483
587
|
}
|
|
588
|
+
function trimMessagesToCheckpoint(args) {
|
|
589
|
+
const hasCheckpointMetadata = args.messages.some(
|
|
590
|
+
(message) => typeof message.checkpointTurnIndex === "number"
|
|
591
|
+
);
|
|
592
|
+
if (!hasCheckpointMetadata && args.turnIndex > 0) return args.messages;
|
|
593
|
+
const keepThroughIndex = args.messages.reduce((lastIndex, message, index) => {
|
|
594
|
+
return typeof message.checkpointTurnIndex === "number" && message.checkpointTurnIndex <= args.turnIndex ? index : lastIndex;
|
|
595
|
+
}, -1);
|
|
596
|
+
return keepThroughIndex >= 0 ? args.messages.slice(0, keepThroughIndex + 1) : [];
|
|
597
|
+
}
|
|
598
|
+
function applyForkedPendingRequests(args) {
|
|
599
|
+
if (args.pendingRequests.length === 0) return args.messages;
|
|
600
|
+
const interruptPositions = args.messages.flatMap(
|
|
601
|
+
(message, messageIndex) => (message.contentPieces ?? []).flatMap(
|
|
602
|
+
(piece, pieceIndex) => piece.type === "interrupt" ? [{ messageIndex, pieceIndex }] : []
|
|
603
|
+
)
|
|
604
|
+
);
|
|
605
|
+
const positionsToPatch = interruptPositions.slice(
|
|
606
|
+
-args.pendingRequests.length
|
|
607
|
+
);
|
|
608
|
+
if (positionsToPatch.length === 0) return args.messages;
|
|
609
|
+
return args.messages.map((message, messageIndex) => {
|
|
610
|
+
if (!message.contentPieces) return message;
|
|
611
|
+
let changed = false;
|
|
612
|
+
const contentPieces = message.contentPieces.map((piece, pieceIndex) => {
|
|
613
|
+
if (piece.type !== "interrupt") return piece;
|
|
614
|
+
const patchIndex = positionsToPatch.findIndex(
|
|
615
|
+
(position) => position.messageIndex === messageIndex && position.pieceIndex === pieceIndex
|
|
616
|
+
);
|
|
617
|
+
const pending = args.pendingRequests[patchIndex];
|
|
618
|
+
if (!pending) return piece;
|
|
619
|
+
changed = true;
|
|
620
|
+
return {
|
|
621
|
+
...piece,
|
|
622
|
+
resumeToken: pending.token,
|
|
623
|
+
requestId: pending.requestId,
|
|
624
|
+
...pending.schema?.kind ? { kind: pending.schema.kind } : {},
|
|
625
|
+
...typeof pending.schema?.multiple === "boolean" ? { multiple: pending.schema.multiple } : {},
|
|
626
|
+
...typeof pending.schema?.question === "string" ? { question: pending.schema.question } : {},
|
|
627
|
+
...typeof pending.schema?.schemaId === "string" ? { schemaId: pending.schema.schemaId } : {},
|
|
628
|
+
...typeof pending.schema?.schemaVersion === "string" ? { schemaVersion: pending.schema.schemaVersion } : {},
|
|
629
|
+
...typeof pending.schema?.id === "string" ? { interruptId: pending.schema.id } : {},
|
|
630
|
+
...pending.schema?.meta ? { meta: pending.schema.meta } : {},
|
|
631
|
+
...pending.options ? { options: pending.options } : {}
|
|
632
|
+
};
|
|
633
|
+
});
|
|
634
|
+
return changed ? { ...message, contentPieces } : message;
|
|
635
|
+
});
|
|
636
|
+
}
|
|
637
|
+
function latestAssistantMessageId(messages) {
|
|
638
|
+
for (let index = messages.length - 1; index >= 0; index--) {
|
|
639
|
+
const message = messages[index];
|
|
640
|
+
if (message?.role === "assistant") return message.id;
|
|
641
|
+
}
|
|
642
|
+
return null;
|
|
643
|
+
}
|
|
484
644
|
function useChat(options) {
|
|
485
645
|
const resolvedStorage = options.storage ?? defaultStorage;
|
|
486
646
|
const transportRef = useLatestRef(options.transport);
|
|
@@ -495,13 +655,20 @@ function useChat(options) {
|
|
|
495
655
|
const [error, setError] = (0, import_react3.useState)(null);
|
|
496
656
|
const [streamContentPieces, setStreamContentPieces] = (0, import_react3.useState)([]);
|
|
497
657
|
const [lastAssistantId, setLastAssistantId] = (0, import_react3.useState)(null);
|
|
658
|
+
const [checkpoints, setCheckpoints] = (0, import_react3.useState)([]);
|
|
498
659
|
const [includeHistory, setIncludeHistory] = (0, import_react3.useState)(true);
|
|
499
660
|
const [workflowId, setWorkflowId] = (0, import_react3.useState)("");
|
|
500
661
|
const [sessionId, setSessionId] = (0, import_react3.useState)(null);
|
|
662
|
+
const [branches, setBranches] = (0, import_react3.useState)({});
|
|
663
|
+
const [responseVariants, setResponseVariants] = (0, import_react3.useState)([]);
|
|
501
664
|
const [didHydrateStorage, setDidHydrateStorage] = (0, import_react3.useState)(false);
|
|
502
665
|
const abortControllerRef = (0, import_react3.useRef)(null);
|
|
503
666
|
const { streamDebug, clearStreamDebug, createRecorder } = useChatStreamDebug();
|
|
504
|
-
const {
|
|
667
|
+
const {
|
|
668
|
+
applyStreamChunk,
|
|
669
|
+
clear: clearStructuredStreams,
|
|
670
|
+
delete: deleteStructuredStream
|
|
671
|
+
} = useStructuredStreams({
|
|
505
672
|
createId
|
|
506
673
|
});
|
|
507
674
|
(0, import_react3.useEffect)(() => {
|
|
@@ -521,12 +688,28 @@ function useChat(options) {
|
|
|
521
688
|
if (Array.isArray(storedState.messages) && storedState.messages.length > 0) {
|
|
522
689
|
setMessages(storedState.messages);
|
|
523
690
|
}
|
|
691
|
+
if (storedState.branches && typeof storedState.branches === "object") {
|
|
692
|
+
setBranches(
|
|
693
|
+
storedState.branches
|
|
694
|
+
);
|
|
695
|
+
}
|
|
696
|
+
if (Array.isArray(storedState.responseVariants)) {
|
|
697
|
+
setResponseVariants(
|
|
698
|
+
storedState.responseVariants
|
|
699
|
+
);
|
|
700
|
+
}
|
|
524
701
|
setDidHydrateStorage(true);
|
|
525
702
|
});
|
|
526
703
|
return () => {
|
|
527
704
|
cancelled = true;
|
|
528
705
|
};
|
|
529
706
|
}, [resolvedStorage]);
|
|
707
|
+
(0, import_react3.useEffect)(() => {
|
|
708
|
+
const nextLastAssistantId = latestAssistantMessageId(messages);
|
|
709
|
+
if (lastAssistantId !== nextLastAssistantId) {
|
|
710
|
+
setLastAssistantId(nextLastAssistantId);
|
|
711
|
+
}
|
|
712
|
+
}, [lastAssistantId, messages]);
|
|
530
713
|
(0, import_react3.useEffect)(() => {
|
|
531
714
|
if (!didHydrateStorage) return;
|
|
532
715
|
const storage = storageRef.current;
|
|
@@ -534,21 +717,64 @@ function useChat(options) {
|
|
|
534
717
|
sessionId,
|
|
535
718
|
workflowId,
|
|
536
719
|
includeHistory,
|
|
537
|
-
messages
|
|
720
|
+
messages,
|
|
721
|
+
branches,
|
|
722
|
+
responseVariants
|
|
538
723
|
});
|
|
539
724
|
}, [
|
|
725
|
+
branches,
|
|
540
726
|
didHydrateStorage,
|
|
541
727
|
includeHistory,
|
|
542
728
|
messages,
|
|
729
|
+
responseVariants,
|
|
543
730
|
sessionId,
|
|
544
731
|
storageRef,
|
|
545
732
|
workflowId
|
|
546
733
|
]);
|
|
734
|
+
(0, import_react3.useEffect)(() => {
|
|
735
|
+
if (!didHydrateStorage || !sessionId) return;
|
|
736
|
+
setBranches((current) => ({
|
|
737
|
+
...current,
|
|
738
|
+
[sessionId]: {
|
|
739
|
+
...current[sessionId] ?? {},
|
|
740
|
+
sessionId,
|
|
741
|
+
messages,
|
|
742
|
+
checkpoints
|
|
743
|
+
}
|
|
744
|
+
}));
|
|
745
|
+
}, [checkpoints, didHydrateStorage, messages, sessionId]);
|
|
746
|
+
(0, import_react3.useEffect)(() => {
|
|
747
|
+
if (!didHydrateStorage || !sessionId) return;
|
|
748
|
+
setResponseVariants(
|
|
749
|
+
(current) => current.map((group) => {
|
|
750
|
+
const selectedVariant = group.variants.find(
|
|
751
|
+
(variant) => variant.id === group.selectedVariantId && variant.sessionId === sessionId
|
|
752
|
+
);
|
|
753
|
+
if (!selectedVariant) return group;
|
|
754
|
+
if (selectedVariant.messages === messages && selectedVariant.checkpoints === checkpoints) {
|
|
755
|
+
return group;
|
|
756
|
+
}
|
|
757
|
+
return {
|
|
758
|
+
...group,
|
|
759
|
+
variants: group.variants.map(
|
|
760
|
+
(variant) => variant.id === selectedVariant.id ? {
|
|
761
|
+
...variant,
|
|
762
|
+
messages,
|
|
763
|
+
checkpoints
|
|
764
|
+
} : variant
|
|
765
|
+
)
|
|
766
|
+
};
|
|
767
|
+
})
|
|
768
|
+
);
|
|
769
|
+
}, [checkpoints, didHydrateStorage, messages, sessionId]);
|
|
547
770
|
const clearActiveStream = () => {
|
|
548
771
|
abortControllerRef.current?.abort();
|
|
549
772
|
abortControllerRef.current = null;
|
|
550
773
|
setIsStreaming(false);
|
|
551
774
|
setMessages([]);
|
|
775
|
+
setCheckpoints([]);
|
|
776
|
+
setBranches({});
|
|
777
|
+
setResponseVariants([]);
|
|
552
778
|
setStreamContentPieces([]);
|
|
553
779
|
clearStreamDebug();
|
|
554
780
|
clearStructuredStreams();
|
|
@@ -560,6 +786,7 @@ function useChat(options) {
|
|
|
560
786
|
};
|
|
561
787
|
const resetSession = () => {
|
|
562
788
|
setSessionId(null);
|
|
789
|
+
setCheckpoints([]);
|
|
563
790
|
};
|
|
564
791
|
const resetChat = () => {
|
|
565
792
|
clearActiveStream();
|
|
@@ -577,7 +804,12 @@ function useChat(options) {
|
|
|
577
804
|
const persistSessionId = (sid) => {
|
|
578
805
|
setSessionId(sid);
|
|
579
806
|
};
|
|
580
|
-
const resolveSessionId = () => {
|
|
807
|
+
const resolveSessionId = (preferredSessionId) => {
|
|
808
|
+
if (preferredSessionId) {
|
|
809
|
+
if (sessionId !== preferredSessionId)
|
|
810
|
+
persistSessionId(preferredSessionId);
|
|
811
|
+
return preferredSessionId;
|
|
812
|
+
}
|
|
581
813
|
const sid = sessionId ?? createId();
|
|
582
814
|
if (!sessionId) persistSessionId(sid);
|
|
583
815
|
return sid;
|
|
@@ -587,13 +819,14 @@ function useChat(options) {
|
|
|
587
819
|
content: message.content
|
|
588
820
|
});
|
|
589
821
|
const prepareMessagesToSend = async (args) => {
|
|
822
|
+
const baseMessages = args.baseMessages ?? messages;
|
|
590
823
|
const prepared = options.prepareContextMessages ? await options.prepareContextMessages({
|
|
591
|
-
messages,
|
|
824
|
+
messages: baseMessages,
|
|
592
825
|
sessionId: args.sid,
|
|
593
826
|
workflowId,
|
|
594
827
|
reason: args.reason,
|
|
595
828
|
context: requestContext
|
|
596
|
-
}) : includeHistory ?
|
|
829
|
+
}) : includeHistory ? baseMessages.map(toOutgoingHistoryMessage) : [];
|
|
597
830
|
return [...prepared, args.outgoingMessage];
|
|
598
831
|
};
|
|
599
832
|
const streamAssistantResponse = async (args) => {
|
|
@@ -602,6 +835,7 @@ function useChat(options) {
|
|
|
602
835
|
const debug = createRecorder(`${args.debugLabel} sessionId=${args.sid}`);
|
|
603
836
|
let currentTrace;
|
|
604
837
|
let completedTrace;
|
|
838
|
+
let currentCheckpoint;
|
|
605
839
|
const pieces = createLiveChatPieces({
|
|
606
840
|
createId,
|
|
607
841
|
onChange: setStreamContentPieces,
|
|
@@ -637,6 +871,31 @@ function useChat(options) {
|
|
|
637
871
|
}
|
|
638
872
|
return;
|
|
639
873
|
}
|
|
874
|
+
if (chunk.type === "checkpoint") {
|
|
875
|
+
currentCheckpoint = {
|
|
876
|
+
id: chunk.id,
|
|
877
|
+
turnIndex: chunk.turnIndex
|
|
878
|
+
};
|
|
879
|
+
setCheckpoints((prev) => {
|
|
880
|
+
const next = {
|
|
881
|
+
id: chunk.id,
|
|
882
|
+
sessionId: chunk.sessionId,
|
|
883
|
+
turnIndex: chunk.turnIndex,
|
|
884
|
+
createdAt: Date.now(),
|
|
885
|
+
nodes: [],
|
|
886
|
+
workflow: workflowId,
|
|
887
|
+
...chunk.label ? { label: chunk.label } : {}
|
|
888
|
+
};
|
|
889
|
+
return [...prev.filter((item) => item.id !== chunk.id), next].sort(
|
|
890
|
+
(a, b) => a.turnIndex - b.turnIndex
|
|
891
|
+
);
|
|
892
|
+
});
|
|
893
|
+
return;
|
|
894
|
+
}
|
|
895
|
+
if (chunk.type === "structured-data-invalidated") {
|
|
896
|
+
deleteStructuredStream(chunk.streamId);
|
|
897
|
+
return;
|
|
898
|
+
}
|
|
640
899
|
if (chunk.type === "error") {
|
|
641
900
|
setError(new Error(chunk.message));
|
|
642
901
|
}
|
|
@@ -649,36 +908,54 @@ function useChat(options) {
|
|
|
649
908
|
});
|
|
650
909
|
}
|
|
651
910
|
});
|
|
652
|
-
if (args.signal.aborted) return;
|
|
911
|
+
if (args.signal.aborted) return void 0;
|
|
653
912
|
const assistant = buildAssistantMessage({
|
|
654
913
|
createId,
|
|
655
914
|
pieces: pieces.getPieces(),
|
|
656
915
|
debug: debug.getAll(),
|
|
657
|
-
trace: completedTrace ?? currentTrace
|
|
916
|
+
trace: completedTrace ?? currentTrace,
|
|
917
|
+
checkpoint: currentCheckpoint
|
|
658
918
|
});
|
|
659
|
-
setMessages(
|
|
919
|
+
setMessages(
|
|
920
|
+
(prev) => args.messagesBeforeAssistant ? [...args.messagesBeforeAssistant, assistant] : [...prev, assistant]
|
|
921
|
+
);
|
|
660
922
|
setLastAssistantId(assistant.id);
|
|
661
923
|
clearStructuredStreams();
|
|
662
924
|
setStreamContentPieces([]);
|
|
663
925
|
clearStreamDebug();
|
|
926
|
+
return assistant;
|
|
664
927
|
};
|
|
665
928
|
const isAbortError = (value, signal) => {
|
|
666
929
|
if (signal.aborted) return true;
|
|
667
930
|
if (!(value instanceof Error)) return false;
|
|
668
931
|
return value.name === "AbortError";
|
|
669
932
|
};
|
|
670
|
-
const
|
|
933
|
+
const respondToHumanInputFromBase = async ({
|
|
671
934
|
resumeToken,
|
|
672
935
|
requestId,
|
|
673
936
|
selected,
|
|
674
|
-
text
|
|
937
|
+
text,
|
|
938
|
+
baseMessages,
|
|
939
|
+
sid
|
|
675
940
|
}) => {
|
|
676
|
-
if (isStreaming) return;
|
|
941
|
+
if (isStreaming) return void 0;
|
|
677
942
|
setError(null);
|
|
678
943
|
const label = typeof text === "string" && text.length > 0 ? text : selected.length > 0 ? selected.length === 1 ? selected[0] : selected.join(", ") : "(selection)";
|
|
679
944
|
const userId = createId();
|
|
680
|
-
const userMsg = {
|
|
681
|
-
|
|
945
|
+
const userMsg = {
|
|
946
|
+
id: userId,
|
|
947
|
+
role: "user",
|
|
948
|
+
content: label,
|
|
949
|
+
source: {
|
|
950
|
+
type: "interrupt-response",
|
|
951
|
+
resumeToken,
|
|
952
|
+
requestId,
|
|
953
|
+
selected,
|
|
954
|
+
...text !== void 0 ? { text } : {}
|
|
955
|
+
}
|
|
956
|
+
};
|
|
957
|
+
const nextMessages = [...baseMessages ?? messages, userMsg];
|
|
958
|
+
setMessages(nextMessages);
|
|
682
959
|
setIsStreaming(true);
|
|
683
960
|
setStreamContentPieces([]);
|
|
684
961
|
clearStreamDebug();
|
|
@@ -694,18 +971,25 @@ function useChat(options) {
|
|
|
694
971
|
content: label,
|
|
695
972
|
metadata: resumePayload
|
|
696
973
|
};
|
|
697
|
-
const
|
|
974
|
+
const resolvedSessionId = resolveSessionId(sid);
|
|
698
975
|
const messagesToSend = await prepareMessagesToSend({
|
|
699
|
-
sid,
|
|
976
|
+
sid: resolvedSessionId,
|
|
700
977
|
outgoingMessage: outgoing,
|
|
701
|
-
reason: "resume"
|
|
978
|
+
reason: "resume",
|
|
979
|
+
baseMessages
|
|
702
980
|
});
|
|
703
|
-
await streamAssistantResponse({
|
|
704
|
-
sid,
|
|
981
|
+
const assistantMessage = await streamAssistantResponse({
|
|
982
|
+
sid: resolvedSessionId,
|
|
705
983
|
messagesToSend,
|
|
984
|
+
messagesBeforeAssistant: nextMessages,
|
|
706
985
|
debugLabel: "runChat (resume)",
|
|
707
986
|
signal: abortController.signal
|
|
708
987
|
});
|
|
988
|
+
return {
|
|
989
|
+
userMessage: userMsg,
|
|
990
|
+
...assistantMessage ? { assistantMessage } : {},
|
|
991
|
+
messages: assistantMessage ? [...nextMessages, assistantMessage] : nextMessages
|
|
992
|
+
};
|
|
709
993
|
} catch (err) {
|
|
710
994
|
if (isAbortError(err, abortController.signal)) return;
|
|
711
995
|
const nextError = err instanceof Error ? err : new Error(String(err));
|
|
@@ -718,36 +1002,45 @@ function useChat(options) {
|
|
|
718
1002
|
setIsStreaming(false);
|
|
719
1003
|
}
|
|
720
1004
|
};
|
|
1005
|
+
const respondToHumanInput = async (args) => {
|
|
1006
|
+
await respondToHumanInputFromBase(args);
|
|
1007
|
+
};
|
|
721
1008
|
const respondToInterrupt = async (piece, response) => {
|
|
722
1009
|
const text = response?.text;
|
|
723
1010
|
const selected = response?.selected ?? (typeof text === "string" && text.length > 0 ? [text] : []);
|
|
724
|
-
|
|
1011
|
+
await respondToHumanInput({
|
|
725
1012
|
resumeToken: piece.resumeToken,
|
|
726
1013
|
requestId: piece.requestId,
|
|
727
1014
|
selected,
|
|
728
1015
|
...text !== void 0 ? { text } : {}
|
|
729
1016
|
});
|
|
730
1017
|
};
|
|
731
|
-
const
|
|
1018
|
+
const sendFromBase = async (text, baseMessages, sid) => {
|
|
732
1019
|
const content = text.trim();
|
|
733
|
-
if (!content || isStreaming) return;
|
|
1020
|
+
if (!content || isStreaming) return void 0;
|
|
734
1021
|
setError(null);
|
|
735
1022
|
const activeTextInterrupt = findActiveTextInterrupt({
|
|
736
|
-
messages,
|
|
1023
|
+
messages: baseMessages ?? messages,
|
|
737
1024
|
streamContentPieces
|
|
738
1025
|
});
|
|
739
1026
|
if (activeTextInterrupt) {
|
|
740
|
-
|
|
1027
|
+
return respondToHumanInputFromBase({
|
|
741
1028
|
resumeToken: activeTextInterrupt.resumeToken,
|
|
742
1029
|
requestId: activeTextInterrupt.requestId,
|
|
743
1030
|
selected: [content],
|
|
744
|
-
text: content
|
|
1031
|
+
text: content,
|
|
1032
|
+
baseMessages,
|
|
1033
|
+
sid
|
|
745
1034
|
});
|
|
746
|
-
return;
|
|
747
1035
|
}
|
|
748
1036
|
const userId = createId();
|
|
749
|
-
const userMsg = {
|
|
750
|
-
|
|
1037
|
+
const userMsg = {
|
|
1038
|
+
id: userId,
|
|
1039
|
+
role: "user",
|
|
1040
|
+
content
|
|
1041
|
+
};
|
|
1042
|
+
const nextMessages = [...baseMessages ?? messages, userMsg];
|
|
1043
|
+
setMessages(nextMessages);
|
|
751
1044
|
setIsStreaming(true);
|
|
752
1045
|
setStreamContentPieces([]);
|
|
753
1046
|
clearStreamDebug();
|
|
@@ -755,19 +1048,26 @@ function useChat(options) {
|
|
|
755
1048
|
const abortController = new AbortController();
|
|
756
1049
|
abortControllerRef.current = abortController;
|
|
757
1050
|
try {
|
|
758
|
-
const
|
|
1051
|
+
const resolvedSessionId = resolveSessionId(sid);
|
|
759
1052
|
const messagesToSend = await prepareMessagesToSend({
|
|
760
|
-
sid,
|
|
1053
|
+
sid: resolvedSessionId,
|
|
761
1054
|
outgoingMessage: { role: "user", content },
|
|
762
|
-
reason: "send"
|
|
1055
|
+
reason: "send",
|
|
1056
|
+
baseMessages
|
|
763
1057
|
});
|
|
764
|
-
await streamAssistantResponse({
|
|
765
|
-
sid,
|
|
1058
|
+
const assistantMessage = await streamAssistantResponse({
|
|
1059
|
+
sid: resolvedSessionId,
|
|
766
1060
|
messagesToSend,
|
|
1061
|
+
messagesBeforeAssistant: nextMessages,
|
|
767
1062
|
debugLabel: "runChat (send)",
|
|
768
1063
|
openDebugOnInterrupt: true,
|
|
769
1064
|
signal: abortController.signal
|
|
770
1065
|
});
|
|
1066
|
+
return {
|
|
1067
|
+
userMessage: userMsg,
|
|
1068
|
+
...assistantMessage ? { assistantMessage } : {},
|
|
1069
|
+
messages: assistantMessage ? [...nextMessages, assistantMessage] : nextMessages
|
|
1070
|
+
};
|
|
771
1071
|
} catch (err) {
|
|
772
1072
|
if (isAbortError(err, abortController.signal)) return;
|
|
773
1073
|
const nextError = err instanceof Error ? err : new Error(String(err));
|
|
@@ -780,6 +1080,375 @@ function useChat(options) {
|
|
|
780
1080
|
setIsStreaming(false);
|
|
781
1081
|
}
|
|
782
1082
|
};
|
|
1083
|
+
const send = async (text) => {
|
|
1084
|
+
await sendFromBase(text);
|
|
1085
|
+
};
|
|
1086
|
+
const requireCheckpointTransport = () => {
|
|
1087
|
+
const transport = transportRef.current;
|
|
1088
|
+
if (!transport.listCheckpoints || !transport.rollbackTo || !transport.fork) {
|
|
1089
|
+
throw new Error(
|
|
1090
|
+
"Checkpoint helpers require a transport with checkpoint methods."
|
|
1091
|
+
);
|
|
1092
|
+
}
|
|
1093
|
+
return transport;
|
|
1094
|
+
};
|
|
1095
|
+
const refreshCheckpoints = async (sid) => {
|
|
1096
|
+
const transport = requireCheckpointTransport();
|
|
1097
|
+
const next = await transport.listCheckpoints(sid);
|
|
1098
|
+
setCheckpoints(next);
|
|
1099
|
+
return next;
|
|
1100
|
+
};
|
|
1101
|
+
const applyStructuredInvalidations = (streamIds) => {
|
|
1102
|
+
for (const streamId of streamIds) {
|
|
1103
|
+
deleteStructuredStream(streamId);
|
|
1104
|
+
}
|
|
1105
|
+
};
|
|
1106
|
+
const rollbackTo = async (id) => {
|
|
1107
|
+
if (isStreaming) {
|
|
1108
|
+
throw new Error("Cannot rollback while a stream is active.");
|
|
1109
|
+
}
|
|
1110
|
+
const transport = requireCheckpointTransport();
|
|
1111
|
+
const result = await transport.rollbackTo(id);
|
|
1112
|
+
applyStructuredInvalidations(result.invalidatedStructuredStreamIds);
|
|
1113
|
+
const refreshedCheckpoints = await refreshCheckpoints(result.sessionId);
|
|
1114
|
+
const target = refreshedCheckpoints.find(
|
|
1115
|
+
(checkpoint) => checkpoint.id === result.head
|
|
1116
|
+
);
|
|
1117
|
+
if (target) {
|
|
1118
|
+
setMessages((current) => {
|
|
1119
|
+
return trimMessagesToCheckpoint({
|
|
1120
|
+
messages: current,
|
|
1121
|
+
turnIndex: target.turnIndex
|
|
1122
|
+
});
|
|
1123
|
+
});
|
|
1124
|
+
}
|
|
1125
|
+
return result;
|
|
1126
|
+
};
|
|
1127
|
+
const fork = async (id, options2) => {
|
|
1128
|
+
if (isStreaming) {
|
|
1129
|
+
throw new Error("Cannot fork while a stream is active.");
|
|
1130
|
+
}
|
|
1131
|
+
const transport = requireCheckpointTransport();
|
|
1132
|
+
const sid = resolveSessionId();
|
|
1133
|
+
let availableCheckpoints = checkpoints.length > 0 ? checkpoints : await refreshCheckpoints(sid);
|
|
1134
|
+
let target = availableCheckpoints.find(
|
|
1135
|
+
(checkpoint) => checkpoint.id === id
|
|
1136
|
+
);
|
|
1137
|
+
if (!target && checkpoints.length > 0) {
|
|
1138
|
+
availableCheckpoints = await refreshCheckpoints(sid);
|
|
1139
|
+
target = availableCheckpoints.find((checkpoint) => checkpoint.id === id);
|
|
1140
|
+
}
|
|
1141
|
+
const result = await transport.fork(id, options2);
|
|
1142
|
+
setSessionId(result.sessionId);
|
|
1143
|
+
const childCheckpoints = await refreshCheckpoints(result.sessionId);
|
|
1144
|
+
const childHead = childCheckpoints.at(-1);
|
|
1145
|
+
const forkTurnIndex = target?.turnIndex ?? childHead?.turnIndex;
|
|
1146
|
+
if (typeof forkTurnIndex === "number") {
|
|
1147
|
+
setMessages(
|
|
1148
|
+
(current) => applyForkedPendingRequests({
|
|
1149
|
+
messages: trimMessagesToCheckpoint({
|
|
1150
|
+
messages: current,
|
|
1151
|
+
turnIndex: forkTurnIndex
|
|
1152
|
+
}),
|
|
1153
|
+
pendingRequests: result.checkpoint?.activePendingRequests ?? []
|
|
1154
|
+
})
|
|
1155
|
+
);
|
|
1156
|
+
}
|
|
1157
|
+
setStreamContentPieces([]);
|
|
1158
|
+
clearStructuredStreams();
|
|
1159
|
+
return result;
|
|
1160
|
+
};
|
|
1161
|
+
const checkpointForMessage = (messageId) => {
|
|
1162
|
+
return messages.find((message) => message.id === messageId)?.checkpointId ?? null;
|
|
1163
|
+
};
|
|
1164
|
+
const variantForMessage = (messageId) => {
|
|
1165
|
+
return responseVariants.find(
|
|
1166
|
+
(group) => group.sourceAssistantMessageId === messageId || group.variants.some(
|
|
1167
|
+
(variant) => variant.assistantMessageId === messageId
|
|
1168
|
+
)
|
|
1169
|
+
) ?? null;
|
|
1170
|
+
};
|
|
1171
|
+
const findResumeOverrideForFork = (baseMessages, forkedBaseMessages, userMessage) => {
|
|
1172
|
+
if (userMessage.source?.type !== "interrupt-response") return void 0;
|
|
1173
|
+
const userSource = userMessage.source;
|
|
1174
|
+
const interruptPositions = baseMessages.flatMap(
|
|
1175
|
+
(message, messageIndex) => (message.contentPieces ?? []).flatMap(
|
|
1176
|
+
(piece, pieceIndex) => piece.type === "interrupt" ? [{ messageIndex, pieceIndex, piece }] : []
|
|
1177
|
+
)
|
|
1178
|
+
);
|
|
1179
|
+
const matchedPosition = [...interruptPositions].reverse().find(
|
|
1180
|
+
({ piece }) => piece.resumeToken === userSource.resumeToken || piece.requestId === userSource.requestId
|
|
1181
|
+
);
|
|
1182
|
+
const forkedPiece = typeof matchedPosition?.messageIndex === "number" ? forkedBaseMessages[matchedPosition.messageIndex]?.contentPieces?.[matchedPosition.pieceIndex] : void 0;
|
|
1183
|
+
if (forkedPiece?.type === "interrupt") {
|
|
1184
|
+
return {
|
|
1185
|
+
resumeToken: forkedPiece.resumeToken,
|
|
1186
|
+
requestId: forkedPiece.requestId
|
|
1187
|
+
};
|
|
1188
|
+
}
|
|
1189
|
+
const latestForkedInterrupt = forkedBaseMessages.flatMap((message) => message.contentPieces ?? []).filter((piece) => piece.type === "interrupt").at(-1);
|
|
1190
|
+
return latestForkedInterrupt ? {
|
|
1191
|
+
resumeToken: latestForkedInterrupt.resumeToken,
|
|
1192
|
+
requestId: latestForkedInterrupt.requestId
|
|
1193
|
+
} : void 0;
|
|
1194
|
+
};
|
|
1195
|
+
const resolveRegenerateTarget = async (assistantMessageId) => {
|
|
1196
|
+
const assistantIndex = messages.findIndex(
|
|
1197
|
+
(message) => message.id === assistantMessageId
|
|
1198
|
+
);
|
|
1199
|
+
if (assistantIndex < 0) {
|
|
1200
|
+
throw new Error(`Assistant message "${assistantMessageId}" not found.`);
|
|
1201
|
+
}
|
|
1202
|
+
const assistant = messages[assistantIndex];
|
|
1203
|
+
if (!assistant) {
|
|
1204
|
+
throw new Error(`Assistant message "${assistantMessageId}" not found.`);
|
|
1205
|
+
}
|
|
1206
|
+
if (assistant.role !== "assistant") {
|
|
1207
|
+
throw new Error("Regenerate expects an assistant message id.");
|
|
1208
|
+
}
|
|
1209
|
+
let userIndex = -1;
|
|
1210
|
+
for (let index = assistantIndex - 1; index >= 0; index--) {
|
|
1211
|
+
if (messages[index]?.role === "user") {
|
|
1212
|
+
userIndex = index;
|
|
1213
|
+
break;
|
|
1214
|
+
}
|
|
1215
|
+
}
|
|
1216
|
+
if (userIndex < 0) {
|
|
1217
|
+
throw new Error("No preceding user message found for regenerate.");
|
|
1218
|
+
}
|
|
1219
|
+
const previousUser = messages[userIndex];
|
|
1220
|
+
const sid = resolveSessionId();
|
|
1221
|
+
let availableCheckpoints = checkpoints.length > 0 ? checkpoints : await refreshCheckpoints(sid);
|
|
1222
|
+
const assistantTurn = assistant.checkpointTurnIndex;
|
|
1223
|
+
const targetTurn = typeof assistantTurn === "number" ? assistantTurn - 1 : void 0;
|
|
1224
|
+
let target = targetTurn !== void 0 ? availableCheckpoints.find(
|
|
1225
|
+
(checkpoint) => checkpoint.turnIndex === targetTurn
|
|
1226
|
+
) : availableCheckpoints.filter(
|
|
1227
|
+
(checkpoint) => checkpoint.turnIndex < Number.MAX_SAFE_INTEGER
|
|
1228
|
+
).at(-2);
|
|
1229
|
+
if (!target && checkpoints.length > 0) {
|
|
1230
|
+
availableCheckpoints = await refreshCheckpoints(sid);
|
|
1231
|
+
target = targetTurn !== void 0 ? availableCheckpoints.find(
|
|
1232
|
+
(checkpoint) => checkpoint.turnIndex === targetTurn
|
|
1233
|
+
) : availableCheckpoints.filter(
|
|
1234
|
+
(checkpoint) => checkpoint.turnIndex < Number.MAX_SAFE_INTEGER
|
|
1235
|
+
).at(-2);
|
|
1236
|
+
}
|
|
1237
|
+
if (!target) {
|
|
1238
|
+
throw new Error("No rollback checkpoint found for regenerate.");
|
|
1239
|
+
}
|
|
1240
|
+
const baseMessages = trimMessagesToCheckpoint({
|
|
1241
|
+
messages,
|
|
1242
|
+
turnIndex: target.turnIndex
|
|
1243
|
+
});
|
|
1244
|
+
return { target, assistant, previousUser, baseMessages };
|
|
1245
|
+
};
|
|
1246
|
+
const replayUserMessage = async (userMessage, baseMessages, editedContent, options2) => {
|
|
1247
|
+
const content = editedContent ?? userMessage.content;
|
|
1248
|
+
if (userMessage.source?.type === "interrupt-response") {
|
|
1249
|
+
return respondToHumanInputFromBase({
|
|
1250
|
+
resumeToken: options2?.resumeOverride?.resumeToken ?? userMessage.source.resumeToken,
|
|
1251
|
+
requestId: options2?.resumeOverride?.requestId ?? userMessage.source.requestId,
|
|
1252
|
+
selected: editedContent !== void 0 ? [editedContent] : userMessage.source.selected,
|
|
1253
|
+
baseMessages,
|
|
1254
|
+
sid: options2?.sid,
|
|
1255
|
+
...editedContent !== void 0 ? { text: editedContent } : userMessage.source.text !== void 0 ? { text: userMessage.source.text } : {}
|
|
1256
|
+
});
|
|
1257
|
+
}
|
|
1258
|
+
return sendFromBase(content, baseMessages, options2?.sid);
|
|
1259
|
+
};
|
|
1260
|
+
const buildVariant = (args) => ({
|
|
1261
|
+
id: args.id,
|
|
1262
|
+
sessionId: args.sessionId,
|
|
1263
|
+
assistantMessageId: args.assistantMessage.id,
|
|
1264
|
+
createdAt: Date.now(),
|
|
1265
|
+
messages: args.messages,
|
|
1266
|
+
checkpoints: args.checkpoints,
|
|
1267
|
+
...args.assistantMessage.checkpointId ? { checkpointId: args.assistantMessage.checkpointId } : {},
|
|
1268
|
+
...typeof args.assistantMessage.checkpointTurnIndex === "number" ? { checkpointTurnIndex: args.assistantMessage.checkpointTurnIndex } : {},
|
|
1269
|
+
...args.label ? { label: args.label } : {}
|
|
1270
|
+
});
|
|
1271
|
+
const updateVariantSnapshot = (args) => ({
|
|
1272
|
+
...args.variant,
|
|
1273
|
+
sessionId: args.sessionId,
|
|
1274
|
+
assistantMessageId: args.assistantMessage.id,
|
|
1275
|
+
messages: args.messages,
|
|
1276
|
+
checkpoints: args.checkpoints,
|
|
1277
|
+
...args.assistantMessage.checkpointId ? { checkpointId: args.assistantMessage.checkpointId } : {},
|
|
1278
|
+
...typeof args.assistantMessage.checkpointTurnIndex === "number" ? { checkpointTurnIndex: args.assistantMessage.checkpointTurnIndex } : {}
|
|
1279
|
+
});
|
|
1280
|
+
const replaceResponseVariantGroup = (nextGroup) => {
|
|
1281
|
+
setResponseVariants((current) => [
|
|
1282
|
+
...current.filter((group) => group.id !== nextGroup.id),
|
|
1283
|
+
nextGroup
|
|
1284
|
+
]);
|
|
1285
|
+
};
|
|
1286
|
+
const regenerateVariant = async (assistantMessageId) => {
|
|
1287
|
+
if (isStreaming) {
|
|
1288
|
+
throw new Error("Cannot regenerate a variant while a stream is active.");
|
|
1289
|
+
}
|
|
1290
|
+
const { target, assistant, previousUser, baseMessages } = await resolveRegenerateTarget(assistantMessageId);
|
|
1291
|
+
const transport = requireCheckpointTransport();
|
|
1292
|
+
const parentSessionId = resolveSessionId();
|
|
1293
|
+
const existingGroup = variantForMessage(assistantMessageId);
|
|
1294
|
+
const sourceAssistantMessageId = existingGroup?.sourceAssistantMessageId ?? assistantMessageId;
|
|
1295
|
+
const existingVariants = existingGroup ? existingGroup.variants.map(
|
|
1296
|
+
(variant) => variant.id === existingGroup.selectedVariantId || variant.assistantMessageId === assistantMessageId ? updateVariantSnapshot({
|
|
1297
|
+
variant,
|
|
1298
|
+
sessionId: parentSessionId,
|
|
1299
|
+
assistantMessage: assistant,
|
|
1300
|
+
messages,
|
|
1301
|
+
checkpoints
|
|
1302
|
+
}) : variant
|
|
1303
|
+
) : void 0;
|
|
1304
|
+
const originalVariant = existingVariants?.[0] ?? buildVariant({
|
|
1305
|
+
id: `${sourceAssistantMessageId}:original`,
|
|
1306
|
+
sessionId: parentSessionId,
|
|
1307
|
+
assistantMessage: assistant,
|
|
1308
|
+
messages,
|
|
1309
|
+
checkpoints,
|
|
1310
|
+
label: "Original"
|
|
1311
|
+
});
|
|
1312
|
+
const forkResult = await transport.fork(target.id);
|
|
1313
|
+
const forkedSessionId = forkResult.sessionId;
|
|
1314
|
+
setSessionId(forkedSessionId);
|
|
1315
|
+
const childCheckpointsBeforeReplay = await refreshCheckpoints(forkedSessionId);
|
|
1316
|
+
const forkedBaseMessages = applyForkedPendingRequests({
|
|
1317
|
+
messages: baseMessages,
|
|
1318
|
+
pendingRequests: forkResult.checkpoint?.activePendingRequests ?? []
|
|
1319
|
+
});
|
|
1320
|
+
const resumeOverride = findResumeOverrideForFork(
|
|
1321
|
+
baseMessages,
|
|
1322
|
+
forkedBaseMessages,
|
|
1323
|
+
previousUser
|
|
1324
|
+
);
|
|
1325
|
+
setMessages(forkedBaseMessages);
|
|
1326
|
+
setStreamContentPieces([]);
|
|
1327
|
+
clearStructuredStreams();
|
|
1328
|
+
const replay = await replayUserMessage(
|
|
1329
|
+
previousUser,
|
|
1330
|
+
forkedBaseMessages,
|
|
1331
|
+
void 0,
|
|
1332
|
+
{
|
|
1333
|
+
sid: forkedSessionId,
|
|
1334
|
+
resumeOverride
|
|
1335
|
+
}
|
|
1336
|
+
);
|
|
1337
|
+
const childCheckpointsAfterReplay = await refreshCheckpoints(forkedSessionId);
|
|
1338
|
+
const variantMessages = replay?.messages ?? forkedBaseMessages;
|
|
1339
|
+
const variantAssistant = replay?.assistantMessage;
|
|
1340
|
+
if (!variantAssistant) {
|
|
1341
|
+
throw new Error("Regenerate variant did not produce an assistant reply.");
|
|
1342
|
+
}
|
|
1343
|
+
const nextVariant = buildVariant({
|
|
1344
|
+
id: createId(),
|
|
1345
|
+
sessionId: forkedSessionId,
|
|
1346
|
+
assistantMessage: variantAssistant,
|
|
1347
|
+
messages: variantMessages,
|
|
1348
|
+
checkpoints: childCheckpointsAfterReplay.length > 0 ? childCheckpointsAfterReplay : childCheckpointsBeforeReplay,
|
|
1349
|
+
label: `Variant ${(existingGroup?.variants.length ?? 1) + 1}`
|
|
1350
|
+
});
|
|
1351
|
+
const nextVariants = existingVariants ? [...existingVariants, nextVariant] : [originalVariant, nextVariant];
|
|
1352
|
+
const nextGroup = {
|
|
1353
|
+
id: existingGroup?.id ?? createId(),
|
|
1354
|
+
sourceAssistantMessageId,
|
|
1355
|
+
checkpointId: existingGroup?.checkpointId ?? target.id,
|
|
1356
|
+
checkpointTurnIndex: existingGroup?.checkpointTurnIndex ?? target.turnIndex,
|
|
1357
|
+
selectedVariantId: nextVariant.id,
|
|
1358
|
+
variants: nextVariants
|
|
1359
|
+
};
|
|
1360
|
+
replaceResponseVariantGroup(nextGroup);
|
|
1361
|
+
setBranches((current) => ({
|
|
1362
|
+
...current,
|
|
1363
|
+
[parentSessionId]: {
|
|
1364
|
+
...current[parentSessionId] ?? {},
|
|
1365
|
+
sessionId: parentSessionId,
|
|
1366
|
+
messages,
|
|
1367
|
+
checkpoints
|
|
1368
|
+
},
|
|
1369
|
+
[forkedSessionId]: {
|
|
1370
|
+
...current[forkedSessionId] ?? {},
|
|
1371
|
+
sessionId: forkedSessionId,
|
|
1372
|
+
parentSessionId: forkResult.parentSessionId,
|
|
1373
|
+
forkedFrom: forkResult.forkedFrom,
|
|
1374
|
+
messages: variantMessages,
|
|
1375
|
+
checkpoints: nextVariant.checkpoints
|
|
1376
|
+
}
|
|
1377
|
+
}));
|
|
1378
|
+
return nextGroup;
|
|
1379
|
+
};
|
|
1380
|
+
const selectVariant = async (assistantMessageId, variantId) => {
|
|
1381
|
+
const group = variantForMessage(assistantMessageId);
|
|
1382
|
+
if (!group) {
|
|
1383
|
+
throw new Error(
|
|
1384
|
+
`No response variants found for "${assistantMessageId}".`
|
|
1385
|
+
);
|
|
1386
|
+
}
|
|
1387
|
+
const selectedVariant = group.variants.find(
|
|
1388
|
+
(variant) => variant.id === variantId
|
|
1389
|
+
);
|
|
1390
|
+
if (!selectedVariant) {
|
|
1391
|
+
throw new Error(`Response variant "${variantId}" not found.`);
|
|
1392
|
+
}
|
|
1393
|
+
setSessionId(selectedVariant.sessionId);
|
|
1394
|
+
setMessages(selectedVariant.messages);
|
|
1395
|
+
setCheckpoints(selectedVariant.checkpoints);
|
|
1396
|
+
setStreamContentPieces([]);
|
|
1397
|
+
clearStructuredStreams();
|
|
1398
|
+
setBranches((current) => ({
|
|
1399
|
+
...current,
|
|
1400
|
+
[selectedVariant.sessionId]: {
|
|
1401
|
+
...current[selectedVariant.sessionId] ?? {},
|
|
1402
|
+
sessionId: selectedVariant.sessionId,
|
|
1403
|
+
messages: selectedVariant.messages,
|
|
1404
|
+
checkpoints: selectedVariant.checkpoints
|
|
1405
|
+
}
|
|
1406
|
+
}));
|
|
1407
|
+
const nextGroup = {
|
|
1408
|
+
...group,
|
|
1409
|
+
selectedVariantId: selectedVariant.id
|
|
1410
|
+
};
|
|
1411
|
+
replaceResponseVariantGroup(nextGroup);
|
|
1412
|
+
};
|
|
1413
|
+
const regenerate = async (assistantMessageId) => {
|
|
1414
|
+
const { target, previousUser, baseMessages } = await resolveRegenerateTarget(assistantMessageId);
|
|
1415
|
+
await rollbackTo(target.id);
|
|
1416
|
+
setMessages(baseMessages);
|
|
1417
|
+
await replayUserMessage(previousUser, baseMessages);
|
|
1418
|
+
};
|
|
1419
|
+
const regenerateFromCheckpoint = async (checkpointId) => {
|
|
1420
|
+
const sid = resolveSessionId();
|
|
1421
|
+
let availableCheckpoints = checkpoints.length > 0 ? checkpoints : await refreshCheckpoints(sid);
|
|
1422
|
+
let target = availableCheckpoints.find(
|
|
1423
|
+
(checkpoint) => checkpoint.id === checkpointId
|
|
1424
|
+
);
|
|
1425
|
+
if (!target && checkpoints.length > 0) {
|
|
1426
|
+
availableCheckpoints = await refreshCheckpoints(sid);
|
|
1427
|
+
target = availableCheckpoints.find(
|
|
1428
|
+
(checkpoint) => checkpoint.id === checkpointId
|
|
1429
|
+
);
|
|
1430
|
+
}
|
|
1431
|
+
if (!target) {
|
|
1432
|
+
throw new Error(`Checkpoint "${checkpointId}" not found.`);
|
|
1433
|
+
}
|
|
1434
|
+
const baseMessages = trimMessagesToCheckpoint({
|
|
1435
|
+
messages,
|
|
1436
|
+
turnIndex: target.turnIndex
|
|
1437
|
+
});
|
|
1438
|
+
const previousUser = messages.slice(baseMessages.length).find((message) => message.role === "user");
|
|
1439
|
+
if (!previousUser) {
|
|
1440
|
+
throw new Error("No user message found after checkpoint for regenerate.");
|
|
1441
|
+
}
|
|
1442
|
+
await rollbackTo(target.id);
|
|
1443
|
+
setMessages(baseMessages);
|
|
1444
|
+
await replayUserMessage(previousUser, baseMessages);
|
|
1445
|
+
};
|
|
1446
|
+
const retryWithEdit = async (assistantMessageId, newUserContent) => {
|
|
1447
|
+
const { target, previousUser, baseMessages } = await resolveRegenerateTarget(assistantMessageId);
|
|
1448
|
+
await rollbackTo(target.id);
|
|
1449
|
+
setMessages(baseMessages);
|
|
1450
|
+
await replayUserMessage(previousUser, baseMessages, newUserContent);
|
|
1451
|
+
};
|
|
783
1452
|
return {
|
|
784
1453
|
messages,
|
|
785
1454
|
isStreaming,
|
|
@@ -788,12 +1457,25 @@ function useChat(options) {
|
|
|
788
1457
|
streamContentPieces,
|
|
789
1458
|
streamDebug,
|
|
790
1459
|
lastAssistantId,
|
|
1460
|
+
checkpoints,
|
|
1461
|
+
activeSessionId: sessionId,
|
|
1462
|
+
branches,
|
|
1463
|
+
responseVariants,
|
|
791
1464
|
workflowId,
|
|
792
1465
|
setWorkflowId,
|
|
793
1466
|
send,
|
|
794
1467
|
respondToHumanInput,
|
|
795
1468
|
respondToInterrupt,
|
|
796
1469
|
setMessages,
|
|
1470
|
+
checkpointForMessage,
|
|
1471
|
+
rollbackTo,
|
|
1472
|
+
fork,
|
|
1473
|
+
regenerate,
|
|
1474
|
+
regenerateVariant,
|
|
1475
|
+
selectVariant,
|
|
1476
|
+
variantForMessage,
|
|
1477
|
+
regenerateFromCheckpoint,
|
|
1478
|
+
retryWithEdit,
|
|
797
1479
|
clearMessages,
|
|
798
1480
|
resetSession,
|
|
799
1481
|
resetChat,
|