@langchain/langgraph-sdk 0.0.112 → 0.1.0
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 +12 -0
- package/dist/react/branching.cjs +149 -0
- package/dist/react/branching.d.ts +35 -0
- package/dist/react/branching.js +144 -0
- package/dist/react/errors.cjs +13 -0
- package/dist/react/errors.d.ts +12 -0
- package/dist/react/errors.js +9 -0
- package/dist/react/index.d.ts +2 -1
- package/dist/react/index.js +1 -1
- package/dist/react/manager.cjs +219 -0
- package/dist/react/manager.d.ts +86 -0
- package/dist/react/manager.js +215 -0
- package/dist/react/messages.cjs +69 -0
- package/dist/react/messages.d.ts +18 -0
- package/dist/react/messages.js +64 -0
- package/dist/react/stream.cjs +167 -435
- package/dist/react/stream.d.ts +1 -323
- package/dist/react/stream.js +166 -434
- package/dist/react/types.cjs +2 -0
- package/dist/react/types.d.ts +316 -0
- package/dist/react/types.js +1 -0
- package/dist/react/utils.cjs +14 -0
- package/dist/react/utils.d.ts +2 -0
- package/dist/react/utils.js +10 -0
- package/dist/react-ui/client.d.ts +1 -1
- package/package.json +1 -1
- package/dist/react/debug.cjs +0 -31
- package/dist/react/debug.d.ts +0 -23
- package/dist/react/debug.js +0 -28
package/dist/react/stream.js
CHANGED
|
@@ -1,190 +1,12 @@
|
|
|
1
1
|
/* __LC_ALLOW_ENTRYPOINT_SIDE_EFFECTS__ */
|
|
2
2
|
"use client";
|
|
3
|
-
import { useCallback, useEffect, useMemo, useRef, useState, } from "react";
|
|
4
|
-
import {
|
|
3
|
+
import { useCallback, useEffect, useMemo, useRef, useState, useSyncExternalStore, } from "react";
|
|
4
|
+
import { findLast, unique } from "./utils.js";
|
|
5
|
+
import { StreamError } from "./errors.js";
|
|
6
|
+
import { getBranchContext } from "./branching.js";
|
|
7
|
+
import { StreamManager } from "./manager.js";
|
|
5
8
|
import { Client, getClientConfigHash } from "../client.js";
|
|
6
|
-
|
|
7
|
-
constructor(data) {
|
|
8
|
-
super(data.message);
|
|
9
|
-
this.name = data.name ?? data.error ?? "StreamError";
|
|
10
|
-
}
|
|
11
|
-
static isStructuredError(error) {
|
|
12
|
-
return typeof error === "object" && error != null && "message" in error;
|
|
13
|
-
}
|
|
14
|
-
}
|
|
15
|
-
function tryConvertToChunk(message) {
|
|
16
|
-
try {
|
|
17
|
-
return convertToChunk(message);
|
|
18
|
-
}
|
|
19
|
-
catch {
|
|
20
|
-
return null;
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
class MessageTupleManager {
|
|
24
|
-
constructor() {
|
|
25
|
-
Object.defineProperty(this, "chunks", {
|
|
26
|
-
enumerable: true,
|
|
27
|
-
configurable: true,
|
|
28
|
-
writable: true,
|
|
29
|
-
value: {}
|
|
30
|
-
});
|
|
31
|
-
this.chunks = {};
|
|
32
|
-
}
|
|
33
|
-
add(serialized, metadata) {
|
|
34
|
-
// TODO: this is sometimes sent from the API
|
|
35
|
-
// figure out how to prevent this or move this to LC.js
|
|
36
|
-
if (serialized.type.endsWith("MessageChunk")) {
|
|
37
|
-
// eslint-disable-next-line no-param-reassign
|
|
38
|
-
serialized.type = serialized.type
|
|
39
|
-
.slice(0, -"MessageChunk".length)
|
|
40
|
-
.toLowerCase();
|
|
41
|
-
}
|
|
42
|
-
const message = coerceMessageLikeToMessage(serialized);
|
|
43
|
-
const chunk = tryConvertToChunk(message);
|
|
44
|
-
const { id } = chunk ?? message;
|
|
45
|
-
if (!id) {
|
|
46
|
-
console.warn("No message ID found for chunk, ignoring in state", serialized);
|
|
47
|
-
return null;
|
|
48
|
-
}
|
|
49
|
-
this.chunks[id] ??= {};
|
|
50
|
-
this.chunks[id].metadata = metadata ?? this.chunks[id].metadata;
|
|
51
|
-
if (chunk) {
|
|
52
|
-
const prev = this.chunks[id].chunk;
|
|
53
|
-
this.chunks[id].chunk =
|
|
54
|
-
(isBaseMessageChunk(prev) ? prev : null)?.concat(chunk) ?? chunk;
|
|
55
|
-
}
|
|
56
|
-
else {
|
|
57
|
-
this.chunks[id].chunk = message;
|
|
58
|
-
}
|
|
59
|
-
return id;
|
|
60
|
-
}
|
|
61
|
-
clear() {
|
|
62
|
-
this.chunks = {};
|
|
63
|
-
}
|
|
64
|
-
get(id, defaultIndex) {
|
|
65
|
-
if (this.chunks[id] == null)
|
|
66
|
-
return null;
|
|
67
|
-
if (defaultIndex != null)
|
|
68
|
-
this.chunks[id].index ??= defaultIndex;
|
|
69
|
-
return this.chunks[id];
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
const toMessageDict = (chunk) => {
|
|
73
|
-
const { type, data } = chunk.toDict();
|
|
74
|
-
return { ...data, type };
|
|
75
|
-
};
|
|
76
|
-
function unique(array) {
|
|
77
|
-
return [...new Set(array)];
|
|
78
|
-
}
|
|
79
|
-
function findLastIndex(array, predicate) {
|
|
80
|
-
for (let i = array.length - 1; i >= 0; i -= 1) {
|
|
81
|
-
if (predicate(array[i]))
|
|
82
|
-
return i;
|
|
83
|
-
}
|
|
84
|
-
return -1;
|
|
85
|
-
}
|
|
86
|
-
function getBranchSequence(history) {
|
|
87
|
-
const childrenMap = {};
|
|
88
|
-
// Short circuit if there's only a singular one state
|
|
89
|
-
// TODO: I think we can make this more generalizable for all `fetchStateHistory` values.
|
|
90
|
-
if (history.length <= 1) {
|
|
91
|
-
return {
|
|
92
|
-
rootSequence: {
|
|
93
|
-
type: "sequence",
|
|
94
|
-
items: history.map((value) => ({ type: "node", value, path: [] })),
|
|
95
|
-
},
|
|
96
|
-
paths: [],
|
|
97
|
-
};
|
|
98
|
-
}
|
|
99
|
-
// First pass - collect nodes for each checkpoint
|
|
100
|
-
history.forEach((state) => {
|
|
101
|
-
const checkpointId = state.parent_checkpoint?.checkpoint_id ?? "$";
|
|
102
|
-
childrenMap[checkpointId] ??= [];
|
|
103
|
-
childrenMap[checkpointId].push(state);
|
|
104
|
-
});
|
|
105
|
-
const rootSequence = { type: "sequence", items: [] };
|
|
106
|
-
const queue = [{ id: "$", sequence: rootSequence, path: [] }];
|
|
107
|
-
const paths = [];
|
|
108
|
-
const visited = new Set();
|
|
109
|
-
while (queue.length > 0) {
|
|
110
|
-
const task = queue.shift();
|
|
111
|
-
if (visited.has(task.id))
|
|
112
|
-
continue;
|
|
113
|
-
visited.add(task.id);
|
|
114
|
-
const children = childrenMap[task.id];
|
|
115
|
-
if (children == null || children.length === 0)
|
|
116
|
-
continue;
|
|
117
|
-
// If we've encountered a fork (2+ children), push the fork
|
|
118
|
-
// to the sequence and add a new sequence for each child
|
|
119
|
-
let fork;
|
|
120
|
-
if (children.length > 1) {
|
|
121
|
-
fork = { type: "fork", items: [] };
|
|
122
|
-
task.sequence.items.push(fork);
|
|
123
|
-
}
|
|
124
|
-
for (const value of children) {
|
|
125
|
-
const id = value.checkpoint?.checkpoint_id;
|
|
126
|
-
if (id == null)
|
|
127
|
-
continue;
|
|
128
|
-
let { sequence } = task;
|
|
129
|
-
let { path } = task;
|
|
130
|
-
if (fork != null) {
|
|
131
|
-
sequence = { type: "sequence", items: [] };
|
|
132
|
-
fork.items.unshift(sequence);
|
|
133
|
-
path = path.slice();
|
|
134
|
-
path.push(id);
|
|
135
|
-
paths.push(path);
|
|
136
|
-
}
|
|
137
|
-
sequence.items.push({ type: "node", value, path });
|
|
138
|
-
queue.push({ id, sequence, path });
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
return { rootSequence, paths };
|
|
142
|
-
}
|
|
143
|
-
const PATH_SEP = ">";
|
|
144
|
-
const ROOT_ID = "$";
|
|
145
|
-
// Get flat view
|
|
146
|
-
function getBranchView(sequence, paths, branch) {
|
|
147
|
-
const path = branch.split(PATH_SEP);
|
|
148
|
-
const pathMap = {};
|
|
149
|
-
for (const path of paths) {
|
|
150
|
-
const parent = path.at(-2) ?? ROOT_ID;
|
|
151
|
-
pathMap[parent] ??= [];
|
|
152
|
-
pathMap[parent].unshift(path);
|
|
153
|
-
}
|
|
154
|
-
const history = [];
|
|
155
|
-
const branchByCheckpoint = {};
|
|
156
|
-
const forkStack = path.slice();
|
|
157
|
-
const queue = [...sequence.items];
|
|
158
|
-
while (queue.length > 0) {
|
|
159
|
-
const item = queue.shift();
|
|
160
|
-
if (item.type === "node") {
|
|
161
|
-
history.push(item.value);
|
|
162
|
-
const checkpointId = item.value.checkpoint?.checkpoint_id;
|
|
163
|
-
if (checkpointId == null)
|
|
164
|
-
continue;
|
|
165
|
-
branchByCheckpoint[checkpointId] = {
|
|
166
|
-
branch: item.path.join(PATH_SEP),
|
|
167
|
-
branchOptions: (item.path.length > 0
|
|
168
|
-
? pathMap[item.path.at(-2) ?? ROOT_ID] ?? []
|
|
169
|
-
: []).map((p) => p.join(PATH_SEP)),
|
|
170
|
-
};
|
|
171
|
-
}
|
|
172
|
-
if (item.type === "fork") {
|
|
173
|
-
const forkId = forkStack.shift();
|
|
174
|
-
const index = forkId != null
|
|
175
|
-
? item.items.findIndex((value) => {
|
|
176
|
-
const firstItem = value.items.at(0);
|
|
177
|
-
if (!firstItem || firstItem.type !== "node")
|
|
178
|
-
return false;
|
|
179
|
-
return firstItem.value.checkpoint?.checkpoint_id === forkId;
|
|
180
|
-
})
|
|
181
|
-
: -1;
|
|
182
|
-
const nextItems = item.items.at(index)?.items ?? [];
|
|
183
|
-
queue.push(...nextItems);
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
return { history, branchByCheckpoint };
|
|
187
|
-
}
|
|
9
|
+
import { MessageTupleManager } from "./messages.js";
|
|
188
10
|
function fetchHistory(client, threadId, options) {
|
|
189
11
|
if (options?.limit === false) {
|
|
190
12
|
return client.threads.getState(threadId).then((state) => {
|
|
@@ -193,7 +15,7 @@ function fetchHistory(client, threadId, options) {
|
|
|
193
15
|
return [state];
|
|
194
16
|
});
|
|
195
17
|
}
|
|
196
|
-
const limit = typeof options?.limit === "number" ? options.limit :
|
|
18
|
+
const limit = typeof options?.limit === "number" ? options.limit : 10;
|
|
197
19
|
return client.threads.getHistory(threadId, { limit });
|
|
198
20
|
}
|
|
199
21
|
function useThreadHistory(threadId, client, limit, clearCallbackRef, submittingRef, onErrorRef) {
|
|
@@ -257,39 +79,7 @@ const useControllableThreadId = (options) => {
|
|
|
257
79
|
}
|
|
258
80
|
return [options.threadId ?? null, onThreadId];
|
|
259
81
|
};
|
|
260
|
-
function useStreamValuesState() {
|
|
261
|
-
const [values, setValues] = useState(null);
|
|
262
|
-
const setStreamValues = useCallback((values, kind = "stream") => {
|
|
263
|
-
if (typeof values === "function") {
|
|
264
|
-
setValues((prevTuple) => {
|
|
265
|
-
const [prevValues, prevKind] = prevTuple ?? [null, "stream"];
|
|
266
|
-
const next = values(prevValues, prevKind);
|
|
267
|
-
if (next == null)
|
|
268
|
-
return null;
|
|
269
|
-
return [next, kind];
|
|
270
|
-
});
|
|
271
|
-
return;
|
|
272
|
-
}
|
|
273
|
-
if (values == null)
|
|
274
|
-
setValues(null);
|
|
275
|
-
setValues([values, kind]);
|
|
276
|
-
}, []);
|
|
277
|
-
const mutate = useCallback((kind, serverValues) => (update) => {
|
|
278
|
-
setStreamValues((clientValues) => {
|
|
279
|
-
const prev = { ...serverValues, ...clientValues };
|
|
280
|
-
const next = typeof update === "function" ? update(prev) : update;
|
|
281
|
-
return { ...prev, ...next };
|
|
282
|
-
}, kind);
|
|
283
|
-
}, [setStreamValues]);
|
|
284
|
-
return [values?.[0] ?? null, setStreamValues, mutate];
|
|
285
|
-
}
|
|
286
82
|
export function useStream(options) {
|
|
287
|
-
const matchEventType = (expected, actual, _data) => {
|
|
288
|
-
return expected === actual || actual.startsWith(`${expected}|`);
|
|
289
|
-
};
|
|
290
|
-
let { messagesKey } = options;
|
|
291
|
-
const { assistantId, fetchStateHistory } = options;
|
|
292
|
-
const { onCreated, onError, onFinish } = options;
|
|
293
83
|
const reconnectOnMountRef = useRef(options.reconnectOnMount);
|
|
294
84
|
const runMetadataStorage = useMemo(() => {
|
|
295
85
|
if (typeof window === "undefined")
|
|
@@ -301,7 +91,6 @@ export function useStream(options) {
|
|
|
301
91
|
return storage();
|
|
302
92
|
return null;
|
|
303
93
|
}, []);
|
|
304
|
-
messagesKey ??= "messages";
|
|
305
94
|
const client = useMemo(() => options.client ??
|
|
306
95
|
new Client({
|
|
307
96
|
apiUrl: options.apiUrl,
|
|
@@ -315,20 +104,16 @@ export function useStream(options) {
|
|
|
315
104
|
options.callerOptions,
|
|
316
105
|
options.defaultHeaders,
|
|
317
106
|
]);
|
|
107
|
+
const [messageManager] = useState(() => new MessageTupleManager());
|
|
108
|
+
const [stream] = useState(() => new StreamManager(messageManager));
|
|
109
|
+
useSyncExternalStore(stream.subscribe, stream.getSnapshot, stream.getSnapshot);
|
|
318
110
|
const [threadId, onThreadId] = useControllableThreadId(options);
|
|
319
|
-
const [branch, setBranch] = useState("");
|
|
320
|
-
const [isLoading, setIsLoading] = useState(false);
|
|
321
|
-
const [streamError, setStreamError] = useState(undefined);
|
|
322
|
-
const [streamValues, setStreamValues, getMutateFn] = useStreamValuesState();
|
|
323
|
-
const messageManagerRef = useRef(new MessageTupleManager());
|
|
324
|
-
const submittingRef = useRef(false);
|
|
325
|
-
const abortRef = useRef(null);
|
|
326
111
|
const trackStreamModeRef = useRef([]);
|
|
327
112
|
const trackStreamMode = useCallback((...mode) => {
|
|
113
|
+
const ref = trackStreamModeRef.current;
|
|
328
114
|
for (const m of mode) {
|
|
329
|
-
if (!
|
|
330
|
-
|
|
331
|
-
}
|
|
115
|
+
if (!ref.includes(m))
|
|
116
|
+
ref.push(m);
|
|
332
117
|
}
|
|
333
118
|
}, []);
|
|
334
119
|
const hasUpdateListener = options.onUpdateEvent != null;
|
|
@@ -361,35 +146,37 @@ export function useStream(options) {
|
|
|
361
146
|
hasTaskListener,
|
|
362
147
|
]);
|
|
363
148
|
const clearCallbackRef = useRef(null);
|
|
364
|
-
clearCallbackRef.current =
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
messageManagerRef.current.clear();
|
|
368
|
-
};
|
|
149
|
+
clearCallbackRef.current = stream.clear;
|
|
150
|
+
const submittingRef = useRef(false);
|
|
151
|
+
submittingRef.current = stream.isLoading;
|
|
369
152
|
const onErrorRef = useRef(undefined);
|
|
370
153
|
onErrorRef.current = options.onError;
|
|
371
|
-
const historyLimit = typeof fetchStateHistory === "object" &&
|
|
372
|
-
|
|
373
|
-
|
|
154
|
+
const historyLimit = typeof options.fetchStateHistory === "object" &&
|
|
155
|
+
options.fetchStateHistory != null
|
|
156
|
+
? options.fetchStateHistory.limit ?? false
|
|
157
|
+
: options.fetchStateHistory ?? false;
|
|
374
158
|
const history = useThreadHistory(threadId, client, historyLimit, clearCallbackRef, submittingRef, onErrorRef);
|
|
375
|
-
const getMessages =
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
const
|
|
384
|
-
const
|
|
385
|
-
|
|
159
|
+
const getMessages = (value) => {
|
|
160
|
+
const messagesKey = options.messagesKey ?? "messages";
|
|
161
|
+
return Array.isArray(value[messagesKey]) ? value[messagesKey] : [];
|
|
162
|
+
};
|
|
163
|
+
const setMessages = (current, messages) => {
|
|
164
|
+
const messagesKey = options.messagesKey ?? "messages";
|
|
165
|
+
return { ...current, [messagesKey]: messages };
|
|
166
|
+
};
|
|
167
|
+
const [branch, setBranch] = useState("");
|
|
168
|
+
const branchContext = getBranchContext(branch, history.data);
|
|
169
|
+
const historyValues = branchContext.threadHead?.values ??
|
|
170
|
+
options.initialValues ??
|
|
171
|
+
{};
|
|
172
|
+
const historyError = (() => {
|
|
173
|
+
const error = branchContext.threadHead?.tasks?.at(-1)?.error;
|
|
386
174
|
if (error == null)
|
|
387
175
|
return undefined;
|
|
388
176
|
try {
|
|
389
177
|
const parsed = JSON.parse(error);
|
|
390
|
-
if (StreamError.isStructuredError(parsed))
|
|
178
|
+
if (StreamError.isStructuredError(parsed))
|
|
391
179
|
return new StreamError(parsed);
|
|
392
|
-
}
|
|
393
180
|
return parsed;
|
|
394
181
|
}
|
|
395
182
|
catch {
|
|
@@ -401,16 +188,13 @@ export function useStream(options) {
|
|
|
401
188
|
const alreadyShown = new Set();
|
|
402
189
|
return getMessages(historyValues).map((message, idx) => {
|
|
403
190
|
const messageId = message.id ?? idx;
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
: undefined;
|
|
407
|
-
const firstSeenIdx = findLastIndex(history.data ?? [], (state) => getMessages(state.values)
|
|
191
|
+
// Find the first checkpoint where the message was seen
|
|
192
|
+
const firstSeenState = findLast(history.data ?? [], (state) => getMessages(state.values)
|
|
408
193
|
.map((m, idx) => m.id ?? idx)
|
|
409
194
|
.includes(messageId));
|
|
410
|
-
const
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
? branchByCheckpoint[checkpointId]
|
|
195
|
+
const checkpointId = firstSeenState?.checkpoint?.checkpoint_id;
|
|
196
|
+
let branch = checkpointId != null
|
|
197
|
+
? branchContext.branchByCheckpoint[checkpointId]
|
|
414
198
|
: undefined;
|
|
415
199
|
if (!branch?.branch?.length)
|
|
416
200
|
branch = undefined;
|
|
@@ -423,158 +207,38 @@ export function useStream(options) {
|
|
|
423
207
|
}
|
|
424
208
|
return {
|
|
425
209
|
messageId: messageId.toString(),
|
|
426
|
-
firstSeenState
|
|
210
|
+
firstSeenState,
|
|
427
211
|
branch: branch?.branch,
|
|
428
212
|
branchOptions: branch?.branchOptions,
|
|
429
|
-
streamMetadata,
|
|
430
213
|
};
|
|
431
214
|
});
|
|
432
215
|
})();
|
|
433
|
-
const stop = () => {
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
setStreamError(undefined);
|
|
450
|
-
submittingRef.current = true;
|
|
451
|
-
abortRef.current = new AbortController();
|
|
452
|
-
const run = await action(abortRef.current.signal);
|
|
453
|
-
getCallbackMeta = run.getCallbackMeta;
|
|
454
|
-
let streamError;
|
|
455
|
-
for await (const { event, data } of run.stream) {
|
|
456
|
-
if (event === "error") {
|
|
457
|
-
streamError = new StreamError(data);
|
|
458
|
-
break;
|
|
459
|
-
}
|
|
460
|
-
const namespace = event.includes("|")
|
|
461
|
-
? event.split("|").slice(1)
|
|
462
|
-
: undefined;
|
|
463
|
-
const mutate = getMutateFn("stream", historyValues);
|
|
464
|
-
if (event === "metadata")
|
|
465
|
-
options.onMetadataEvent?.(data);
|
|
466
|
-
if (event === "events")
|
|
467
|
-
options.onLangChainEvent?.(data);
|
|
468
|
-
if (matchEventType("updates", event, data)) {
|
|
469
|
-
options.onUpdateEvent?.(data, { namespace, mutate });
|
|
470
|
-
}
|
|
471
|
-
if (matchEventType("custom", event, data)) {
|
|
472
|
-
options.onCustomEvent?.(data, { namespace, mutate });
|
|
473
|
-
}
|
|
474
|
-
if (matchEventType("checkpoints", event, data)) {
|
|
475
|
-
options.onCheckpointEvent?.(data, { namespace });
|
|
476
|
-
}
|
|
477
|
-
if (matchEventType("tasks", event, data)) {
|
|
478
|
-
options.onTaskEvent?.(data, { namespace });
|
|
479
|
-
}
|
|
480
|
-
if (matchEventType("debug", event, data)) {
|
|
481
|
-
options.onDebugEvent?.(data, { namespace });
|
|
482
|
-
}
|
|
483
|
-
if (event === "values") {
|
|
484
|
-
// don't update values on interrupt values event
|
|
485
|
-
if ("__interrupt__" in data)
|
|
486
|
-
continue;
|
|
487
|
-
setStreamValues(data);
|
|
488
|
-
}
|
|
489
|
-
// Consume subgraph messages as well
|
|
490
|
-
if (matchEventType("messages", event, data)) {
|
|
491
|
-
const [serialized, metadata] = data;
|
|
492
|
-
const messageId = messageManagerRef.current.add(serialized, metadata);
|
|
493
|
-
if (!messageId) {
|
|
494
|
-
console.warn("Failed to add message to manager, no message ID found");
|
|
495
|
-
continue;
|
|
496
|
-
}
|
|
497
|
-
setStreamValues((streamValues) => {
|
|
498
|
-
const values = { ...historyValues, ...streamValues };
|
|
499
|
-
// Assumption: we're concatenating the message
|
|
500
|
-
const messages = getMessages(values).slice();
|
|
501
|
-
const { chunk, index } = messageManagerRef.current.get(messageId, messages.length) ?? {};
|
|
502
|
-
if (!chunk || index == null)
|
|
503
|
-
return values;
|
|
504
|
-
messages[index] = toMessageDict(chunk);
|
|
505
|
-
return { ...values, [messagesKey]: messages };
|
|
506
|
-
});
|
|
507
|
-
}
|
|
508
|
-
}
|
|
509
|
-
// TODO: stream created checkpoints to avoid an unnecessary network request
|
|
510
|
-
const result = await run.onSuccess();
|
|
511
|
-
setStreamValues((values, kind) => {
|
|
512
|
-
// Do not clear out the user values set on `stop`.
|
|
513
|
-
if (kind === "stop")
|
|
514
|
-
return values;
|
|
515
|
-
return null;
|
|
516
|
-
});
|
|
517
|
-
if (streamError != null)
|
|
518
|
-
throw streamError;
|
|
519
|
-
const lastHead = result.at(0);
|
|
520
|
-
if (lastHead)
|
|
521
|
-
onFinish?.(lastHead, getCallbackMeta?.());
|
|
522
|
-
}
|
|
523
|
-
catch (error) {
|
|
524
|
-
if (!(error instanceof Error && // eslint-disable-line no-instanceof/no-instanceof
|
|
525
|
-
(error.name === "AbortError" || error.name === "TimeoutError"))) {
|
|
526
|
-
console.error(error);
|
|
527
|
-
setStreamError(error);
|
|
528
|
-
onError?.(error, getCallbackMeta?.());
|
|
216
|
+
const stop = () => stream.stop(historyValues, { onStop: options.onStop });
|
|
217
|
+
// --- TRANSPORT ---
|
|
218
|
+
const submit = async (values, submitOptions) => {
|
|
219
|
+
// Unbranch things
|
|
220
|
+
const checkpointId = submitOptions?.checkpoint?.checkpoint_id;
|
|
221
|
+
setBranch(checkpointId != null
|
|
222
|
+
? branchContext.branchByCheckpoint[checkpointId]?.branch ?? ""
|
|
223
|
+
: "");
|
|
224
|
+
stream.setStreamValues(() => {
|
|
225
|
+
if (submitOptions?.optimisticValues != null) {
|
|
226
|
+
return {
|
|
227
|
+
...historyValues,
|
|
228
|
+
...(typeof submitOptions.optimisticValues === "function"
|
|
229
|
+
? submitOptions.optimisticValues(historyValues)
|
|
230
|
+
: submitOptions.optimisticValues),
|
|
231
|
+
};
|
|
529
232
|
}
|
|
530
|
-
|
|
531
|
-
finally {
|
|
532
|
-
setIsLoading(false);
|
|
533
|
-
submittingRef.current = false;
|
|
534
|
-
abortRef.current = null;
|
|
535
|
-
}
|
|
536
|
-
}
|
|
537
|
-
const joinStream = async (runId, lastEventId, options) => {
|
|
538
|
-
// eslint-disable-next-line no-param-reassign
|
|
539
|
-
lastEventId ??= "-1";
|
|
540
|
-
if (!threadId)
|
|
541
|
-
return;
|
|
542
|
-
await consumeStream(async (signal) => {
|
|
543
|
-
const stream = client.runs.joinStream(threadId, runId, {
|
|
544
|
-
signal,
|
|
545
|
-
lastEventId,
|
|
546
|
-
streamMode: options?.streamMode,
|
|
547
|
-
});
|
|
548
|
-
return {
|
|
549
|
-
onSuccess: () => {
|
|
550
|
-
runMetadataStorage?.removeItem(`lg:stream:${threadId}`);
|
|
551
|
-
return history.mutate(threadId);
|
|
552
|
-
},
|
|
553
|
-
stream,
|
|
554
|
-
getCallbackMeta: () => ({ thread_id: threadId, run_id: runId }),
|
|
555
|
-
};
|
|
233
|
+
return { ...historyValues };
|
|
556
234
|
});
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
if (newPath != null)
|
|
565
|
-
setBranch(newPath ?? "");
|
|
566
|
-
setStreamValues(() => {
|
|
567
|
-
if (submitOptions?.optimisticValues != null) {
|
|
568
|
-
return {
|
|
569
|
-
...historyValues,
|
|
570
|
-
...(typeof submitOptions.optimisticValues === "function"
|
|
571
|
-
? submitOptions.optimisticValues(historyValues)
|
|
572
|
-
: submitOptions.optimisticValues),
|
|
573
|
-
};
|
|
574
|
-
}
|
|
575
|
-
return { ...historyValues };
|
|
576
|
-
});
|
|
577
|
-
let usableThreadId = threadId;
|
|
235
|
+
// When `fetchStateHistory` is requested, thus we assume that branching
|
|
236
|
+
// is enabled. We then need to include the implicit branch.
|
|
237
|
+
const includeImplicitBranch = historyLimit === true || typeof historyLimit === "number";
|
|
238
|
+
let callbackMeta;
|
|
239
|
+
let rejoinKey;
|
|
240
|
+
let usableThreadId = threadId;
|
|
241
|
+
await stream.start(async (signal) => {
|
|
578
242
|
if (!usableThreadId) {
|
|
579
243
|
const thread = await client.threads.create({
|
|
580
244
|
threadId: submitOptions?.threadId,
|
|
@@ -583,26 +247,28 @@ export function useStream(options) {
|
|
|
583
247
|
onThreadId(thread.thread_id);
|
|
584
248
|
usableThreadId = thread.thread_id;
|
|
585
249
|
}
|
|
586
|
-
if (!usableThreadId)
|
|
250
|
+
if (!usableThreadId) {
|
|
587
251
|
throw new Error("Failed to obtain valid thread ID.");
|
|
252
|
+
}
|
|
588
253
|
const streamMode = unique([
|
|
589
254
|
...(submitOptions?.streamMode ?? []),
|
|
590
255
|
...trackStreamModeRef.current,
|
|
591
256
|
...callbackStreamMode,
|
|
592
257
|
]);
|
|
593
|
-
let checkpoint = submitOptions?.checkpoint ??
|
|
258
|
+
let checkpoint = submitOptions?.checkpoint ??
|
|
259
|
+
(includeImplicitBranch
|
|
260
|
+
? branchContext.threadHead?.checkpoint
|
|
261
|
+
: undefined) ??
|
|
262
|
+
undefined;
|
|
594
263
|
// Avoid specifying a checkpoint if user explicitly set it to null
|
|
595
|
-
if (submitOptions?.checkpoint === null)
|
|
264
|
+
if (submitOptions?.checkpoint === null)
|
|
596
265
|
checkpoint = undefined;
|
|
597
|
-
}
|
|
598
266
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
599
267
|
// @ts-expect-error
|
|
600
268
|
if (checkpoint != null)
|
|
601
269
|
delete checkpoint.thread_id;
|
|
602
|
-
let rejoinKey;
|
|
603
|
-
let callbackMeta;
|
|
604
270
|
const streamResumable = submitOptions?.streamResumable ?? !!runMetadataStorage;
|
|
605
|
-
|
|
271
|
+
return client.runs.stream(usableThreadId, options.assistantId, {
|
|
606
272
|
input: values,
|
|
607
273
|
config: submitOptions?.config,
|
|
608
274
|
context: submitOptions?.context,
|
|
@@ -626,25 +292,77 @@ export function useStream(options) {
|
|
|
626
292
|
thread_id: params.thread_id ?? usableThreadId,
|
|
627
293
|
};
|
|
628
294
|
if (runMetadataStorage) {
|
|
629
|
-
rejoinKey = `lg:stream:${
|
|
295
|
+
rejoinKey = `lg:stream:${usableThreadId}`;
|
|
630
296
|
runMetadataStorage.setItem(rejoinKey, callbackMeta.run_id);
|
|
631
297
|
}
|
|
632
|
-
onCreated?.(callbackMeta);
|
|
298
|
+
options.onCreated?.(callbackMeta);
|
|
633
299
|
},
|
|
634
300
|
});
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
301
|
+
}, {
|
|
302
|
+
getMessages,
|
|
303
|
+
setMessages,
|
|
304
|
+
initialValues: historyValues,
|
|
305
|
+
callbacks: options,
|
|
306
|
+
async onSuccess() {
|
|
307
|
+
if (rejoinKey)
|
|
308
|
+
runMetadataStorage?.removeItem(rejoinKey);
|
|
309
|
+
const shouldRefetch =
|
|
310
|
+
// We're expecting the whole thread state in onFinish
|
|
311
|
+
options.onFinish != null ||
|
|
312
|
+
// We're fetching history, thus we need the latest checkpoint
|
|
313
|
+
// to ensure we're not accidentally submitting to a wrong branch
|
|
314
|
+
includeImplicitBranch;
|
|
315
|
+
if (shouldRefetch) {
|
|
316
|
+
const newHistory = await history.mutate(usableThreadId);
|
|
317
|
+
const lastHead = newHistory.at(0);
|
|
318
|
+
if (lastHead) {
|
|
319
|
+
// We now have the latest update from /history
|
|
320
|
+
// Thus we can clear the local stream state
|
|
321
|
+
options.onFinish?.(lastHead, callbackMeta);
|
|
322
|
+
return null;
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
return undefined;
|
|
326
|
+
},
|
|
327
|
+
onError(error) {
|
|
328
|
+
options.onError?.(error, callbackMeta);
|
|
329
|
+
},
|
|
330
|
+
});
|
|
331
|
+
};
|
|
332
|
+
const joinStream = async (runId, lastEventId, joinOptions) => {
|
|
333
|
+
// eslint-disable-next-line no-param-reassign
|
|
334
|
+
lastEventId ??= "-1";
|
|
335
|
+
if (!threadId)
|
|
336
|
+
return;
|
|
337
|
+
const callbackMeta = {
|
|
338
|
+
thread_id: threadId,
|
|
339
|
+
run_id: runId,
|
|
340
|
+
};
|
|
341
|
+
await stream.start(async (signal) => {
|
|
342
|
+
return client.runs.joinStream(threadId, runId, {
|
|
343
|
+
signal,
|
|
344
|
+
lastEventId,
|
|
345
|
+
streamMode: joinOptions?.streamMode,
|
|
346
|
+
});
|
|
347
|
+
}, {
|
|
348
|
+
getMessages,
|
|
349
|
+
setMessages,
|
|
350
|
+
initialValues: historyValues,
|
|
351
|
+
callbacks: options,
|
|
352
|
+
async onSuccess() {
|
|
353
|
+
runMetadataStorage?.removeItem(`lg:stream:${threadId}`);
|
|
354
|
+
const newHistory = await history.mutate(threadId);
|
|
355
|
+
const lastHead = newHistory.at(0);
|
|
356
|
+
if (lastHead)
|
|
357
|
+
options.onFinish?.(lastHead, callbackMeta);
|
|
358
|
+
},
|
|
359
|
+
onError(error) {
|
|
360
|
+
options.onError?.(error, callbackMeta);
|
|
361
|
+
},
|
|
644
362
|
});
|
|
645
363
|
};
|
|
646
364
|
const reconnectKey = useMemo(() => {
|
|
647
|
-
if (!runMetadataStorage || isLoading)
|
|
365
|
+
if (!runMetadataStorage || stream.isLoading)
|
|
648
366
|
return undefined;
|
|
649
367
|
if (typeof window === "undefined")
|
|
650
368
|
return undefined;
|
|
@@ -652,7 +370,7 @@ export function useStream(options) {
|
|
|
652
370
|
if (!runId)
|
|
653
371
|
return undefined;
|
|
654
372
|
return { runId, threadId };
|
|
655
|
-
}, [runMetadataStorage, isLoading, threadId]);
|
|
373
|
+
}, [runMetadataStorage, stream.isLoading, threadId]);
|
|
656
374
|
const shouldReconnect = !!runMetadataStorage;
|
|
657
375
|
const reconnectRef = useRef({ threadId, shouldReconnect });
|
|
658
376
|
const joinStreamRef = useRef(joinStream);
|
|
@@ -669,38 +387,44 @@ export function useStream(options) {
|
|
|
669
387
|
void joinStreamRef.current?.(reconnectKey.runId);
|
|
670
388
|
}
|
|
671
389
|
}, [reconnectKey]);
|
|
672
|
-
|
|
673
|
-
const
|
|
390
|
+
// --- END TRANSPORT ---
|
|
391
|
+
const error = stream.error ?? historyError ?? history.error;
|
|
392
|
+
const values = stream.values ?? historyValues;
|
|
674
393
|
return {
|
|
675
394
|
get values() {
|
|
676
395
|
trackStreamMode("values");
|
|
677
396
|
return values;
|
|
678
397
|
},
|
|
679
398
|
client,
|
|
680
|
-
assistantId,
|
|
399
|
+
assistantId: options.assistantId,
|
|
681
400
|
error,
|
|
682
|
-
isLoading,
|
|
401
|
+
isLoading: stream.isLoading,
|
|
683
402
|
stop,
|
|
684
|
-
submit,
|
|
403
|
+
submit,
|
|
685
404
|
joinStream,
|
|
686
405
|
branch,
|
|
687
406
|
setBranch,
|
|
688
|
-
history
|
|
407
|
+
get history() {
|
|
408
|
+
if (historyLimit === false) {
|
|
409
|
+
throw new Error("`fetchStateHistory` must be set to `true` to use `history`");
|
|
410
|
+
}
|
|
411
|
+
return branchContext.flatHistory;
|
|
412
|
+
},
|
|
689
413
|
isThreadLoading: history.isLoading && history.data == null,
|
|
690
414
|
get experimental_branchTree() {
|
|
691
415
|
if (historyLimit === false) {
|
|
692
|
-
throw new Error("`
|
|
416
|
+
throw new Error("`fetchStateHistory` must be set to `true` to use `experimental_branchTree`");
|
|
693
417
|
}
|
|
694
|
-
return
|
|
418
|
+
return branchContext.branchTree;
|
|
695
419
|
},
|
|
696
420
|
get interrupt() {
|
|
697
421
|
// Don't show the interrupt if the stream is loading
|
|
698
|
-
if (isLoading)
|
|
422
|
+
if (stream.isLoading)
|
|
699
423
|
return undefined;
|
|
700
|
-
const interrupts = threadHead?.tasks?.at(-1)?.interrupts;
|
|
424
|
+
const interrupts = branchContext.threadHead?.tasks?.at(-1)?.interrupts;
|
|
701
425
|
if (interrupts == null || interrupts.length === 0) {
|
|
702
426
|
// check if there's a next task present
|
|
703
|
-
const next = threadHead?.next ?? [];
|
|
427
|
+
const next = branchContext.threadHead?.next ?? [];
|
|
704
428
|
if (!next.length || error != null)
|
|
705
429
|
return undefined;
|
|
706
430
|
return { when: "breakpoint" };
|
|
@@ -714,7 +438,15 @@ export function useStream(options) {
|
|
|
714
438
|
},
|
|
715
439
|
getMessagesMetadata(message, index) {
|
|
716
440
|
trackStreamMode("values");
|
|
717
|
-
|
|
441
|
+
const streamMetadata = messageManager.get(message.id)?.metadata;
|
|
442
|
+
const historyMetadata = messageMetadata?.find((m) => m.messageId === (message.id ?? index));
|
|
443
|
+
if (streamMetadata != null || historyMetadata != null) {
|
|
444
|
+
return {
|
|
445
|
+
...historyMetadata,
|
|
446
|
+
streamMetadata,
|
|
447
|
+
};
|
|
448
|
+
}
|
|
449
|
+
return undefined;
|
|
718
450
|
},
|
|
719
451
|
};
|
|
720
452
|
}
|