@kortyx/react 0.4.0 → 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 +25 -0
- package/LICENSE +51 -43
- package/README.md +4 -0
- package/dist/index.d.ts +158 -1
- package/dist/index.js +726 -45
- 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
|
|
|
@@ -469,18 +573,73 @@ function useLatestRef(value) {
|
|
|
469
573
|
return ref;
|
|
470
574
|
}
|
|
471
575
|
function findActiveTextInterrupt(args) {
|
|
472
|
-
const liveInterrupt = args.streamContentPieces.find(
|
|
576
|
+
const liveInterrupt = [...args.streamContentPieces].reverse().find(
|
|
473
577
|
(piece) => piece.type === "interrupt" && piece.kind === "text"
|
|
474
578
|
);
|
|
475
579
|
if (liveInterrupt) return liveInterrupt;
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
(piece) => piece.type === "interrupt" && piece.kind === "text"
|
|
480
|
-
);
|
|
481
|
-
if (messageInterrupt) return messageInterrupt;
|
|
580
|
+
const latestMessage = args.messages.at(-1);
|
|
581
|
+
if (latestMessage?.role !== "assistant" || !latestMessage.contentPieces) {
|
|
582
|
+
return void 0;
|
|
482
583
|
}
|
|
483
|
-
return
|
|
584
|
+
return [...latestMessage.contentPieces].reverse().find(
|
|
585
|
+
(piece) => piece.type === "interrupt" && piece.kind === "text"
|
|
586
|
+
);
|
|
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;
|
|
484
643
|
}
|
|
485
644
|
function useChat(options) {
|
|
486
645
|
const resolvedStorage = options.storage ?? defaultStorage;
|
|
@@ -496,13 +655,20 @@ function useChat(options) {
|
|
|
496
655
|
const [error, setError] = (0, import_react3.useState)(null);
|
|
497
656
|
const [streamContentPieces, setStreamContentPieces] = (0, import_react3.useState)([]);
|
|
498
657
|
const [lastAssistantId, setLastAssistantId] = (0, import_react3.useState)(null);
|
|
658
|
+
const [checkpoints, setCheckpoints] = (0, import_react3.useState)([]);
|
|
499
659
|
const [includeHistory, setIncludeHistory] = (0, import_react3.useState)(true);
|
|
500
660
|
const [workflowId, setWorkflowId] = (0, import_react3.useState)("");
|
|
501
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)([]);
|
|
502
664
|
const [didHydrateStorage, setDidHydrateStorage] = (0, import_react3.useState)(false);
|
|
503
665
|
const abortControllerRef = (0, import_react3.useRef)(null);
|
|
504
666
|
const { streamDebug, clearStreamDebug, createRecorder } = useChatStreamDebug();
|
|
505
|
-
const {
|
|
667
|
+
const {
|
|
668
|
+
applyStreamChunk,
|
|
669
|
+
clear: clearStructuredStreams,
|
|
670
|
+
delete: deleteStructuredStream
|
|
671
|
+
} = useStructuredStreams({
|
|
506
672
|
createId
|
|
507
673
|
});
|
|
508
674
|
(0, import_react3.useEffect)(() => {
|
|
@@ -522,12 +688,28 @@ function useChat(options) {
|
|
|
522
688
|
if (Array.isArray(storedState.messages) && storedState.messages.length > 0) {
|
|
523
689
|
setMessages(storedState.messages);
|
|
524
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
|
+
}
|
|
525
701
|
setDidHydrateStorage(true);
|
|
526
702
|
});
|
|
527
703
|
return () => {
|
|
528
704
|
cancelled = true;
|
|
529
705
|
};
|
|
530
706
|
}, [resolvedStorage]);
|
|
707
|
+
(0, import_react3.useEffect)(() => {
|
|
708
|
+
const nextLastAssistantId = latestAssistantMessageId(messages);
|
|
709
|
+
if (lastAssistantId !== nextLastAssistantId) {
|
|
710
|
+
setLastAssistantId(nextLastAssistantId);
|
|
711
|
+
}
|
|
712
|
+
}, [lastAssistantId, messages]);
|
|
531
713
|
(0, import_react3.useEffect)(() => {
|
|
532
714
|
if (!didHydrateStorage) return;
|
|
533
715
|
const storage = storageRef.current;
|
|
@@ -535,21 +717,64 @@ function useChat(options) {
|
|
|
535
717
|
sessionId,
|
|
536
718
|
workflowId,
|
|
537
719
|
includeHistory,
|
|
538
|
-
messages
|
|
720
|
+
messages,
|
|
721
|
+
branches,
|
|
722
|
+
responseVariants
|
|
539
723
|
});
|
|
540
724
|
}, [
|
|
725
|
+
branches,
|
|
541
726
|
didHydrateStorage,
|
|
542
727
|
includeHistory,
|
|
543
728
|
messages,
|
|
729
|
+
responseVariants,
|
|
544
730
|
sessionId,
|
|
545
731
|
storageRef,
|
|
546
732
|
workflowId
|
|
547
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]);
|
|
548
770
|
const clearActiveStream = () => {
|
|
549
771
|
abortControllerRef.current?.abort();
|
|
550
772
|
abortControllerRef.current = null;
|
|
551
773
|
setIsStreaming(false);
|
|
552
774
|
setMessages([]);
|
|
775
|
+
setCheckpoints([]);
|
|
776
|
+
setBranches({});
|
|
777
|
+
setResponseVariants([]);
|
|
553
778
|
setStreamContentPieces([]);
|
|
554
779
|
clearStreamDebug();
|
|
555
780
|
clearStructuredStreams();
|
|
@@ -561,6 +786,7 @@ function useChat(options) {
|
|
|
561
786
|
};
|
|
562
787
|
const resetSession = () => {
|
|
563
788
|
setSessionId(null);
|
|
789
|
+
setCheckpoints([]);
|
|
564
790
|
};
|
|
565
791
|
const resetChat = () => {
|
|
566
792
|
clearActiveStream();
|
|
@@ -578,7 +804,12 @@ function useChat(options) {
|
|
|
578
804
|
const persistSessionId = (sid) => {
|
|
579
805
|
setSessionId(sid);
|
|
580
806
|
};
|
|
581
|
-
const resolveSessionId = () => {
|
|
807
|
+
const resolveSessionId = (preferredSessionId) => {
|
|
808
|
+
if (preferredSessionId) {
|
|
809
|
+
if (sessionId !== preferredSessionId)
|
|
810
|
+
persistSessionId(preferredSessionId);
|
|
811
|
+
return preferredSessionId;
|
|
812
|
+
}
|
|
582
813
|
const sid = sessionId ?? createId();
|
|
583
814
|
if (!sessionId) persistSessionId(sid);
|
|
584
815
|
return sid;
|
|
@@ -588,13 +819,14 @@ function useChat(options) {
|
|
|
588
819
|
content: message.content
|
|
589
820
|
});
|
|
590
821
|
const prepareMessagesToSend = async (args) => {
|
|
822
|
+
const baseMessages = args.baseMessages ?? messages;
|
|
591
823
|
const prepared = options.prepareContextMessages ? await options.prepareContextMessages({
|
|
592
|
-
messages,
|
|
824
|
+
messages: baseMessages,
|
|
593
825
|
sessionId: args.sid,
|
|
594
826
|
workflowId,
|
|
595
827
|
reason: args.reason,
|
|
596
828
|
context: requestContext
|
|
597
|
-
}) : includeHistory ?
|
|
829
|
+
}) : includeHistory ? baseMessages.map(toOutgoingHistoryMessage) : [];
|
|
598
830
|
return [...prepared, args.outgoingMessage];
|
|
599
831
|
};
|
|
600
832
|
const streamAssistantResponse = async (args) => {
|
|
@@ -603,6 +835,7 @@ function useChat(options) {
|
|
|
603
835
|
const debug = createRecorder(`${args.debugLabel} sessionId=${args.sid}`);
|
|
604
836
|
let currentTrace;
|
|
605
837
|
let completedTrace;
|
|
838
|
+
let currentCheckpoint;
|
|
606
839
|
const pieces = createLiveChatPieces({
|
|
607
840
|
createId,
|
|
608
841
|
onChange: setStreamContentPieces,
|
|
@@ -638,6 +871,31 @@ function useChat(options) {
|
|
|
638
871
|
}
|
|
639
872
|
return;
|
|
640
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
|
+
}
|
|
641
899
|
if (chunk.type === "error") {
|
|
642
900
|
setError(new Error(chunk.message));
|
|
643
901
|
}
|
|
@@ -650,36 +908,54 @@ function useChat(options) {
|
|
|
650
908
|
});
|
|
651
909
|
}
|
|
652
910
|
});
|
|
653
|
-
if (args.signal.aborted) return;
|
|
911
|
+
if (args.signal.aborted) return void 0;
|
|
654
912
|
const assistant = buildAssistantMessage({
|
|
655
913
|
createId,
|
|
656
914
|
pieces: pieces.getPieces(),
|
|
657
915
|
debug: debug.getAll(),
|
|
658
|
-
trace: completedTrace ?? currentTrace
|
|
916
|
+
trace: completedTrace ?? currentTrace,
|
|
917
|
+
checkpoint: currentCheckpoint
|
|
659
918
|
});
|
|
660
|
-
setMessages(
|
|
919
|
+
setMessages(
|
|
920
|
+
(prev) => args.messagesBeforeAssistant ? [...args.messagesBeforeAssistant, assistant] : [...prev, assistant]
|
|
921
|
+
);
|
|
661
922
|
setLastAssistantId(assistant.id);
|
|
662
923
|
clearStructuredStreams();
|
|
663
924
|
setStreamContentPieces([]);
|
|
664
925
|
clearStreamDebug();
|
|
926
|
+
return assistant;
|
|
665
927
|
};
|
|
666
928
|
const isAbortError = (value, signal) => {
|
|
667
929
|
if (signal.aborted) return true;
|
|
668
930
|
if (!(value instanceof Error)) return false;
|
|
669
931
|
return value.name === "AbortError";
|
|
670
932
|
};
|
|
671
|
-
const
|
|
933
|
+
const respondToHumanInputFromBase = async ({
|
|
672
934
|
resumeToken,
|
|
673
935
|
requestId,
|
|
674
936
|
selected,
|
|
675
|
-
text
|
|
937
|
+
text,
|
|
938
|
+
baseMessages,
|
|
939
|
+
sid
|
|
676
940
|
}) => {
|
|
677
|
-
if (isStreaming) return;
|
|
941
|
+
if (isStreaming) return void 0;
|
|
678
942
|
setError(null);
|
|
679
943
|
const label = typeof text === "string" && text.length > 0 ? text : selected.length > 0 ? selected.length === 1 ? selected[0] : selected.join(", ") : "(selection)";
|
|
680
944
|
const userId = createId();
|
|
681
|
-
const userMsg = {
|
|
682
|
-
|
|
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);
|
|
683
959
|
setIsStreaming(true);
|
|
684
960
|
setStreamContentPieces([]);
|
|
685
961
|
clearStreamDebug();
|
|
@@ -695,18 +971,25 @@ function useChat(options) {
|
|
|
695
971
|
content: label,
|
|
696
972
|
metadata: resumePayload
|
|
697
973
|
};
|
|
698
|
-
const
|
|
974
|
+
const resolvedSessionId = resolveSessionId(sid);
|
|
699
975
|
const messagesToSend = await prepareMessagesToSend({
|
|
700
|
-
sid,
|
|
976
|
+
sid: resolvedSessionId,
|
|
701
977
|
outgoingMessage: outgoing,
|
|
702
|
-
reason: "resume"
|
|
978
|
+
reason: "resume",
|
|
979
|
+
baseMessages
|
|
703
980
|
});
|
|
704
|
-
await streamAssistantResponse({
|
|
705
|
-
sid,
|
|
981
|
+
const assistantMessage = await streamAssistantResponse({
|
|
982
|
+
sid: resolvedSessionId,
|
|
706
983
|
messagesToSend,
|
|
984
|
+
messagesBeforeAssistant: nextMessages,
|
|
707
985
|
debugLabel: "runChat (resume)",
|
|
708
986
|
signal: abortController.signal
|
|
709
987
|
});
|
|
988
|
+
return {
|
|
989
|
+
userMessage: userMsg,
|
|
990
|
+
...assistantMessage ? { assistantMessage } : {},
|
|
991
|
+
messages: assistantMessage ? [...nextMessages, assistantMessage] : nextMessages
|
|
992
|
+
};
|
|
710
993
|
} catch (err) {
|
|
711
994
|
if (isAbortError(err, abortController.signal)) return;
|
|
712
995
|
const nextError = err instanceof Error ? err : new Error(String(err));
|
|
@@ -719,36 +1002,45 @@ function useChat(options) {
|
|
|
719
1002
|
setIsStreaming(false);
|
|
720
1003
|
}
|
|
721
1004
|
};
|
|
1005
|
+
const respondToHumanInput = async (args) => {
|
|
1006
|
+
await respondToHumanInputFromBase(args);
|
|
1007
|
+
};
|
|
722
1008
|
const respondToInterrupt = async (piece, response) => {
|
|
723
1009
|
const text = response?.text;
|
|
724
1010
|
const selected = response?.selected ?? (typeof text === "string" && text.length > 0 ? [text] : []);
|
|
725
|
-
|
|
1011
|
+
await respondToHumanInput({
|
|
726
1012
|
resumeToken: piece.resumeToken,
|
|
727
1013
|
requestId: piece.requestId,
|
|
728
1014
|
selected,
|
|
729
1015
|
...text !== void 0 ? { text } : {}
|
|
730
1016
|
});
|
|
731
1017
|
};
|
|
732
|
-
const
|
|
1018
|
+
const sendFromBase = async (text, baseMessages, sid) => {
|
|
733
1019
|
const content = text.trim();
|
|
734
|
-
if (!content || isStreaming) return;
|
|
1020
|
+
if (!content || isStreaming) return void 0;
|
|
735
1021
|
setError(null);
|
|
736
1022
|
const activeTextInterrupt = findActiveTextInterrupt({
|
|
737
|
-
messages,
|
|
1023
|
+
messages: baseMessages ?? messages,
|
|
738
1024
|
streamContentPieces
|
|
739
1025
|
});
|
|
740
1026
|
if (activeTextInterrupt) {
|
|
741
|
-
|
|
1027
|
+
return respondToHumanInputFromBase({
|
|
742
1028
|
resumeToken: activeTextInterrupt.resumeToken,
|
|
743
1029
|
requestId: activeTextInterrupt.requestId,
|
|
744
1030
|
selected: [content],
|
|
745
|
-
text: content
|
|
1031
|
+
text: content,
|
|
1032
|
+
baseMessages,
|
|
1033
|
+
sid
|
|
746
1034
|
});
|
|
747
|
-
return;
|
|
748
1035
|
}
|
|
749
1036
|
const userId = createId();
|
|
750
|
-
const userMsg = {
|
|
751
|
-
|
|
1037
|
+
const userMsg = {
|
|
1038
|
+
id: userId,
|
|
1039
|
+
role: "user",
|
|
1040
|
+
content
|
|
1041
|
+
};
|
|
1042
|
+
const nextMessages = [...baseMessages ?? messages, userMsg];
|
|
1043
|
+
setMessages(nextMessages);
|
|
752
1044
|
setIsStreaming(true);
|
|
753
1045
|
setStreamContentPieces([]);
|
|
754
1046
|
clearStreamDebug();
|
|
@@ -756,19 +1048,26 @@ function useChat(options) {
|
|
|
756
1048
|
const abortController = new AbortController();
|
|
757
1049
|
abortControllerRef.current = abortController;
|
|
758
1050
|
try {
|
|
759
|
-
const
|
|
1051
|
+
const resolvedSessionId = resolveSessionId(sid);
|
|
760
1052
|
const messagesToSend = await prepareMessagesToSend({
|
|
761
|
-
sid,
|
|
1053
|
+
sid: resolvedSessionId,
|
|
762
1054
|
outgoingMessage: { role: "user", content },
|
|
763
|
-
reason: "send"
|
|
1055
|
+
reason: "send",
|
|
1056
|
+
baseMessages
|
|
764
1057
|
});
|
|
765
|
-
await streamAssistantResponse({
|
|
766
|
-
sid,
|
|
1058
|
+
const assistantMessage = await streamAssistantResponse({
|
|
1059
|
+
sid: resolvedSessionId,
|
|
767
1060
|
messagesToSend,
|
|
1061
|
+
messagesBeforeAssistant: nextMessages,
|
|
768
1062
|
debugLabel: "runChat (send)",
|
|
769
1063
|
openDebugOnInterrupt: true,
|
|
770
1064
|
signal: abortController.signal
|
|
771
1065
|
});
|
|
1066
|
+
return {
|
|
1067
|
+
userMessage: userMsg,
|
|
1068
|
+
...assistantMessage ? { assistantMessage } : {},
|
|
1069
|
+
messages: assistantMessage ? [...nextMessages, assistantMessage] : nextMessages
|
|
1070
|
+
};
|
|
772
1071
|
} catch (err) {
|
|
773
1072
|
if (isAbortError(err, abortController.signal)) return;
|
|
774
1073
|
const nextError = err instanceof Error ? err : new Error(String(err));
|
|
@@ -781,6 +1080,375 @@ function useChat(options) {
|
|
|
781
1080
|
setIsStreaming(false);
|
|
782
1081
|
}
|
|
783
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
|
+
};
|
|
784
1452
|
return {
|
|
785
1453
|
messages,
|
|
786
1454
|
isStreaming,
|
|
@@ -789,12 +1457,25 @@ function useChat(options) {
|
|
|
789
1457
|
streamContentPieces,
|
|
790
1458
|
streamDebug,
|
|
791
1459
|
lastAssistantId,
|
|
1460
|
+
checkpoints,
|
|
1461
|
+
activeSessionId: sessionId,
|
|
1462
|
+
branches,
|
|
1463
|
+
responseVariants,
|
|
792
1464
|
workflowId,
|
|
793
1465
|
setWorkflowId,
|
|
794
1466
|
send,
|
|
795
1467
|
respondToHumanInput,
|
|
796
1468
|
respondToInterrupt,
|
|
797
1469
|
setMessages,
|
|
1470
|
+
checkpointForMessage,
|
|
1471
|
+
rollbackTo,
|
|
1472
|
+
fork,
|
|
1473
|
+
regenerate,
|
|
1474
|
+
regenerateVariant,
|
|
1475
|
+
selectVariant,
|
|
1476
|
+
variantForMessage,
|
|
1477
|
+
regenerateFromCheckpoint,
|
|
1478
|
+
retryWithEdit,
|
|
798
1479
|
clearMessages,
|
|
799
1480
|
resetSession,
|
|
800
1481
|
resetChat,
|