@langchain/langgraph-sdk 0.0.111 → 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 +20 -0
- package/dist/client.cjs +23 -0
- package/dist/client.d.ts +9 -1
- package/dist/client.js +23 -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 +174 -437
- package/dist/react/stream.d.ts +1 -315
- package/dist/react/stream.js +173 -436
- 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/dist/types.d.ts +10 -0
- package/dist/types.stream.d.ts +1 -0
- 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,12 +15,16 @@ 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) {
|
|
200
22
|
const [history, setHistory] = useState(undefined);
|
|
201
|
-
const [isLoading, setIsLoading] = useState(
|
|
23
|
+
const [isLoading, setIsLoading] = useState(() => {
|
|
24
|
+
if (threadId == null)
|
|
25
|
+
return false;
|
|
26
|
+
return true;
|
|
27
|
+
});
|
|
202
28
|
const [error, setError] = useState(undefined);
|
|
203
29
|
const clientHash = getClientConfigHash(client);
|
|
204
30
|
const clientRef = useRef(client);
|
|
@@ -232,7 +58,7 @@ function useThreadHistory(threadId, client, limit, clearCallbackRef, submittingR
|
|
|
232
58
|
if (submittingRef.current)
|
|
233
59
|
return;
|
|
234
60
|
void fetcher(threadId);
|
|
235
|
-
}, [fetcher, clientHash, limit,
|
|
61
|
+
}, [fetcher, submittingRef, clientHash, limit, threadId]);
|
|
236
62
|
return {
|
|
237
63
|
data: history,
|
|
238
64
|
isLoading,
|
|
@@ -253,39 +79,7 @@ const useControllableThreadId = (options) => {
|
|
|
253
79
|
}
|
|
254
80
|
return [options.threadId ?? null, onThreadId];
|
|
255
81
|
};
|
|
256
|
-
function useStreamValuesState() {
|
|
257
|
-
const [values, setValues] = useState(null);
|
|
258
|
-
const setStreamValues = useCallback((values, kind = "stream") => {
|
|
259
|
-
if (typeof values === "function") {
|
|
260
|
-
setValues((prevTuple) => {
|
|
261
|
-
const [prevValues, prevKind] = prevTuple ?? [null, "stream"];
|
|
262
|
-
const next = values(prevValues, prevKind);
|
|
263
|
-
if (next == null)
|
|
264
|
-
return null;
|
|
265
|
-
return [next, kind];
|
|
266
|
-
});
|
|
267
|
-
return;
|
|
268
|
-
}
|
|
269
|
-
if (values == null)
|
|
270
|
-
setValues(null);
|
|
271
|
-
setValues([values, kind]);
|
|
272
|
-
}, []);
|
|
273
|
-
const mutate = useCallback((kind, serverValues) => (update) => {
|
|
274
|
-
setStreamValues((clientValues) => {
|
|
275
|
-
const prev = { ...serverValues, ...clientValues };
|
|
276
|
-
const next = typeof update === "function" ? update(prev) : update;
|
|
277
|
-
return { ...prev, ...next };
|
|
278
|
-
}, kind);
|
|
279
|
-
}, [setStreamValues]);
|
|
280
|
-
return [values?.[0] ?? null, setStreamValues, mutate];
|
|
281
|
-
}
|
|
282
82
|
export function useStream(options) {
|
|
283
|
-
const matchEventType = (expected, actual, _data) => {
|
|
284
|
-
return expected === actual || actual.startsWith(`${expected}|`);
|
|
285
|
-
};
|
|
286
|
-
let { messagesKey } = options;
|
|
287
|
-
const { assistantId, fetchStateHistory } = options;
|
|
288
|
-
const { onCreated, onError, onFinish } = options;
|
|
289
83
|
const reconnectOnMountRef = useRef(options.reconnectOnMount);
|
|
290
84
|
const runMetadataStorage = useMemo(() => {
|
|
291
85
|
if (typeof window === "undefined")
|
|
@@ -297,7 +91,6 @@ export function useStream(options) {
|
|
|
297
91
|
return storage();
|
|
298
92
|
return null;
|
|
299
93
|
}, []);
|
|
300
|
-
messagesKey ??= "messages";
|
|
301
94
|
const client = useMemo(() => options.client ??
|
|
302
95
|
new Client({
|
|
303
96
|
apiUrl: options.apiUrl,
|
|
@@ -311,20 +104,16 @@ export function useStream(options) {
|
|
|
311
104
|
options.callerOptions,
|
|
312
105
|
options.defaultHeaders,
|
|
313
106
|
]);
|
|
107
|
+
const [messageManager] = useState(() => new MessageTupleManager());
|
|
108
|
+
const [stream] = useState(() => new StreamManager(messageManager));
|
|
109
|
+
useSyncExternalStore(stream.subscribe, stream.getSnapshot, stream.getSnapshot);
|
|
314
110
|
const [threadId, onThreadId] = useControllableThreadId(options);
|
|
315
|
-
const [branch, setBranch] = useState("");
|
|
316
|
-
const [isLoading, setIsLoading] = useState(false);
|
|
317
|
-
const [streamError, setStreamError] = useState(undefined);
|
|
318
|
-
const [streamValues, setStreamValues, getMutateFn] = useStreamValuesState();
|
|
319
|
-
const messageManagerRef = useRef(new MessageTupleManager());
|
|
320
|
-
const submittingRef = useRef(false);
|
|
321
|
-
const abortRef = useRef(null);
|
|
322
111
|
const trackStreamModeRef = useRef([]);
|
|
323
112
|
const trackStreamMode = useCallback((...mode) => {
|
|
113
|
+
const ref = trackStreamModeRef.current;
|
|
324
114
|
for (const m of mode) {
|
|
325
|
-
if (!
|
|
326
|
-
|
|
327
|
-
}
|
|
115
|
+
if (!ref.includes(m))
|
|
116
|
+
ref.push(m);
|
|
328
117
|
}
|
|
329
118
|
}, []);
|
|
330
119
|
const hasUpdateListener = options.onUpdateEvent != null;
|
|
@@ -357,35 +146,37 @@ export function useStream(options) {
|
|
|
357
146
|
hasTaskListener,
|
|
358
147
|
]);
|
|
359
148
|
const clearCallbackRef = useRef(null);
|
|
360
|
-
clearCallbackRef.current =
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
messageManagerRef.current.clear();
|
|
364
|
-
};
|
|
149
|
+
clearCallbackRef.current = stream.clear;
|
|
150
|
+
const submittingRef = useRef(false);
|
|
151
|
+
submittingRef.current = stream.isLoading;
|
|
365
152
|
const onErrorRef = useRef(undefined);
|
|
366
153
|
onErrorRef.current = options.onError;
|
|
367
|
-
const historyLimit = typeof fetchStateHistory === "object" &&
|
|
368
|
-
|
|
369
|
-
|
|
154
|
+
const historyLimit = typeof options.fetchStateHistory === "object" &&
|
|
155
|
+
options.fetchStateHistory != null
|
|
156
|
+
? options.fetchStateHistory.limit ?? false
|
|
157
|
+
: options.fetchStateHistory ?? false;
|
|
370
158
|
const history = useThreadHistory(threadId, client, historyLimit, clearCallbackRef, submittingRef, onErrorRef);
|
|
371
|
-
const getMessages =
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
const
|
|
380
|
-
const
|
|
381
|
-
|
|
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;
|
|
382
174
|
if (error == null)
|
|
383
175
|
return undefined;
|
|
384
176
|
try {
|
|
385
177
|
const parsed = JSON.parse(error);
|
|
386
|
-
if (StreamError.isStructuredError(parsed))
|
|
178
|
+
if (StreamError.isStructuredError(parsed))
|
|
387
179
|
return new StreamError(parsed);
|
|
388
|
-
}
|
|
389
180
|
return parsed;
|
|
390
181
|
}
|
|
391
182
|
catch {
|
|
@@ -397,16 +188,13 @@ export function useStream(options) {
|
|
|
397
188
|
const alreadyShown = new Set();
|
|
398
189
|
return getMessages(historyValues).map((message, idx) => {
|
|
399
190
|
const messageId = message.id ?? idx;
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
: undefined;
|
|
403
|
-
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)
|
|
404
193
|
.map((m, idx) => m.id ?? idx)
|
|
405
194
|
.includes(messageId));
|
|
406
|
-
const
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
? branchByCheckpoint[checkpointId]
|
|
195
|
+
const checkpointId = firstSeenState?.checkpoint?.checkpoint_id;
|
|
196
|
+
let branch = checkpointId != null
|
|
197
|
+
? branchContext.branchByCheckpoint[checkpointId]
|
|
410
198
|
: undefined;
|
|
411
199
|
if (!branch?.branch?.length)
|
|
412
200
|
branch = undefined;
|
|
@@ -419,158 +207,38 @@ export function useStream(options) {
|
|
|
419
207
|
}
|
|
420
208
|
return {
|
|
421
209
|
messageId: messageId.toString(),
|
|
422
|
-
firstSeenState
|
|
210
|
+
firstSeenState,
|
|
423
211
|
branch: branch?.branch,
|
|
424
212
|
branchOptions: branch?.branchOptions,
|
|
425
|
-
streamMetadata,
|
|
426
213
|
};
|
|
427
214
|
});
|
|
428
215
|
})();
|
|
429
|
-
const stop = () => {
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
setStreamError(undefined);
|
|
446
|
-
submittingRef.current = true;
|
|
447
|
-
abortRef.current = new AbortController();
|
|
448
|
-
const run = await action(abortRef.current.signal);
|
|
449
|
-
getCallbackMeta = run.getCallbackMeta;
|
|
450
|
-
let streamError;
|
|
451
|
-
for await (const { event, data } of run.stream) {
|
|
452
|
-
if (event === "error") {
|
|
453
|
-
streamError = new StreamError(data);
|
|
454
|
-
break;
|
|
455
|
-
}
|
|
456
|
-
const namespace = event.includes("|")
|
|
457
|
-
? event.split("|").slice(1)
|
|
458
|
-
: undefined;
|
|
459
|
-
const mutate = getMutateFn("stream", historyValues);
|
|
460
|
-
if (event === "metadata")
|
|
461
|
-
options.onMetadataEvent?.(data);
|
|
462
|
-
if (event === "events")
|
|
463
|
-
options.onLangChainEvent?.(data);
|
|
464
|
-
if (matchEventType("updates", event, data)) {
|
|
465
|
-
options.onUpdateEvent?.(data, { namespace, mutate });
|
|
466
|
-
}
|
|
467
|
-
if (matchEventType("custom", event, data)) {
|
|
468
|
-
options.onCustomEvent?.(data, { namespace, mutate });
|
|
469
|
-
}
|
|
470
|
-
if (matchEventType("checkpoints", event, data)) {
|
|
471
|
-
options.onCheckpointEvent?.(data, { namespace });
|
|
472
|
-
}
|
|
473
|
-
if (matchEventType("tasks", event, data)) {
|
|
474
|
-
options.onTaskEvent?.(data, { namespace });
|
|
475
|
-
}
|
|
476
|
-
if (matchEventType("debug", event, data)) {
|
|
477
|
-
options.onDebugEvent?.(data, { namespace });
|
|
478
|
-
}
|
|
479
|
-
if (event === "values") {
|
|
480
|
-
// don't update values on interrupt values event
|
|
481
|
-
if ("__interrupt__" in data)
|
|
482
|
-
continue;
|
|
483
|
-
setStreamValues(data);
|
|
484
|
-
}
|
|
485
|
-
// Consume subgraph messages as well
|
|
486
|
-
if (matchEventType("messages", event, data)) {
|
|
487
|
-
const [serialized, metadata] = data;
|
|
488
|
-
const messageId = messageManagerRef.current.add(serialized, metadata);
|
|
489
|
-
if (!messageId) {
|
|
490
|
-
console.warn("Failed to add message to manager, no message ID found");
|
|
491
|
-
continue;
|
|
492
|
-
}
|
|
493
|
-
setStreamValues((streamValues) => {
|
|
494
|
-
const values = { ...historyValues, ...streamValues };
|
|
495
|
-
// Assumption: we're concatenating the message
|
|
496
|
-
const messages = getMessages(values).slice();
|
|
497
|
-
const { chunk, index } = messageManagerRef.current.get(messageId, messages.length) ?? {};
|
|
498
|
-
if (!chunk || index == null)
|
|
499
|
-
return values;
|
|
500
|
-
messages[index] = toMessageDict(chunk);
|
|
501
|
-
return { ...values, [messagesKey]: messages };
|
|
502
|
-
});
|
|
503
|
-
}
|
|
504
|
-
}
|
|
505
|
-
// TODO: stream created checkpoints to avoid an unnecessary network request
|
|
506
|
-
const result = await run.onSuccess();
|
|
507
|
-
setStreamValues((values, kind) => {
|
|
508
|
-
// Do not clear out the user values set on `stop`.
|
|
509
|
-
if (kind === "stop")
|
|
510
|
-
return values;
|
|
511
|
-
return null;
|
|
512
|
-
});
|
|
513
|
-
if (streamError != null)
|
|
514
|
-
throw streamError;
|
|
515
|
-
const lastHead = result.at(0);
|
|
516
|
-
if (lastHead)
|
|
517
|
-
onFinish?.(lastHead, getCallbackMeta?.());
|
|
518
|
-
}
|
|
519
|
-
catch (error) {
|
|
520
|
-
if (!(error instanceof Error && // eslint-disable-line no-instanceof/no-instanceof
|
|
521
|
-
(error.name === "AbortError" || error.name === "TimeoutError"))) {
|
|
522
|
-
console.error(error);
|
|
523
|
-
setStreamError(error);
|
|
524
|
-
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
|
+
};
|
|
525
232
|
}
|
|
526
|
-
|
|
527
|
-
finally {
|
|
528
|
-
setIsLoading(false);
|
|
529
|
-
submittingRef.current = false;
|
|
530
|
-
abortRef.current = null;
|
|
531
|
-
}
|
|
532
|
-
}
|
|
533
|
-
const joinStream = async (runId, lastEventId, options) => {
|
|
534
|
-
// eslint-disable-next-line no-param-reassign
|
|
535
|
-
lastEventId ??= "-1";
|
|
536
|
-
if (!threadId)
|
|
537
|
-
return;
|
|
538
|
-
await consumeStream(async (signal) => {
|
|
539
|
-
const stream = client.runs.joinStream(threadId, runId, {
|
|
540
|
-
signal,
|
|
541
|
-
lastEventId,
|
|
542
|
-
streamMode: options?.streamMode,
|
|
543
|
-
});
|
|
544
|
-
return {
|
|
545
|
-
onSuccess: () => {
|
|
546
|
-
runMetadataStorage?.removeItem(`lg:stream:${threadId}`);
|
|
547
|
-
return history.mutate(threadId);
|
|
548
|
-
},
|
|
549
|
-
stream,
|
|
550
|
-
getCallbackMeta: () => ({ thread_id: threadId, run_id: runId }),
|
|
551
|
-
};
|
|
233
|
+
return { ...historyValues };
|
|
552
234
|
});
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
if (newPath != null)
|
|
561
|
-
setBranch(newPath ?? "");
|
|
562
|
-
setStreamValues(() => {
|
|
563
|
-
if (submitOptions?.optimisticValues != null) {
|
|
564
|
-
return {
|
|
565
|
-
...historyValues,
|
|
566
|
-
...(typeof submitOptions.optimisticValues === "function"
|
|
567
|
-
? submitOptions.optimisticValues(historyValues)
|
|
568
|
-
: submitOptions.optimisticValues),
|
|
569
|
-
};
|
|
570
|
-
}
|
|
571
|
-
return { ...historyValues };
|
|
572
|
-
});
|
|
573
|
-
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) => {
|
|
574
242
|
if (!usableThreadId) {
|
|
575
243
|
const thread = await client.threads.create({
|
|
576
244
|
threadId: submitOptions?.threadId,
|
|
@@ -579,26 +247,28 @@ export function useStream(options) {
|
|
|
579
247
|
onThreadId(thread.thread_id);
|
|
580
248
|
usableThreadId = thread.thread_id;
|
|
581
249
|
}
|
|
582
|
-
if (!usableThreadId)
|
|
250
|
+
if (!usableThreadId) {
|
|
583
251
|
throw new Error("Failed to obtain valid thread ID.");
|
|
252
|
+
}
|
|
584
253
|
const streamMode = unique([
|
|
585
254
|
...(submitOptions?.streamMode ?? []),
|
|
586
255
|
...trackStreamModeRef.current,
|
|
587
256
|
...callbackStreamMode,
|
|
588
257
|
]);
|
|
589
|
-
let checkpoint = submitOptions?.checkpoint ??
|
|
258
|
+
let checkpoint = submitOptions?.checkpoint ??
|
|
259
|
+
(includeImplicitBranch
|
|
260
|
+
? branchContext.threadHead?.checkpoint
|
|
261
|
+
: undefined) ??
|
|
262
|
+
undefined;
|
|
590
263
|
// Avoid specifying a checkpoint if user explicitly set it to null
|
|
591
|
-
if (submitOptions?.checkpoint === null)
|
|
264
|
+
if (submitOptions?.checkpoint === null)
|
|
592
265
|
checkpoint = undefined;
|
|
593
|
-
}
|
|
594
266
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
595
267
|
// @ts-expect-error
|
|
596
268
|
if (checkpoint != null)
|
|
597
269
|
delete checkpoint.thread_id;
|
|
598
|
-
let rejoinKey;
|
|
599
|
-
let callbackMeta;
|
|
600
270
|
const streamResumable = submitOptions?.streamResumable ?? !!runMetadataStorage;
|
|
601
|
-
|
|
271
|
+
return client.runs.stream(usableThreadId, options.assistantId, {
|
|
602
272
|
input: values,
|
|
603
273
|
config: submitOptions?.config,
|
|
604
274
|
context: submitOptions?.context,
|
|
@@ -615,31 +285,84 @@ export function useStream(options) {
|
|
|
615
285
|
streamMode,
|
|
616
286
|
streamSubgraphs: submitOptions?.streamSubgraphs,
|
|
617
287
|
streamResumable,
|
|
288
|
+
durability: submitOptions?.durability,
|
|
618
289
|
onRunCreated(params) {
|
|
619
290
|
callbackMeta = {
|
|
620
291
|
run_id: params.run_id,
|
|
621
292
|
thread_id: params.thread_id ?? usableThreadId,
|
|
622
293
|
};
|
|
623
294
|
if (runMetadataStorage) {
|
|
624
|
-
rejoinKey = `lg:stream:${
|
|
295
|
+
rejoinKey = `lg:stream:${usableThreadId}`;
|
|
625
296
|
runMetadataStorage.setItem(rejoinKey, callbackMeta.run_id);
|
|
626
297
|
}
|
|
627
|
-
onCreated?.(callbackMeta);
|
|
298
|
+
options.onCreated?.(callbackMeta);
|
|
628
299
|
},
|
|
629
300
|
});
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
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
|
+
},
|
|
639
362
|
});
|
|
640
363
|
};
|
|
641
364
|
const reconnectKey = useMemo(() => {
|
|
642
|
-
if (!runMetadataStorage || isLoading)
|
|
365
|
+
if (!runMetadataStorage || stream.isLoading)
|
|
643
366
|
return undefined;
|
|
644
367
|
if (typeof window === "undefined")
|
|
645
368
|
return undefined;
|
|
@@ -647,7 +370,7 @@ export function useStream(options) {
|
|
|
647
370
|
if (!runId)
|
|
648
371
|
return undefined;
|
|
649
372
|
return { runId, threadId };
|
|
650
|
-
}, [runMetadataStorage, isLoading, threadId]);
|
|
373
|
+
}, [runMetadataStorage, stream.isLoading, threadId]);
|
|
651
374
|
const shouldReconnect = !!runMetadataStorage;
|
|
652
375
|
const reconnectRef = useRef({ threadId, shouldReconnect });
|
|
653
376
|
const joinStreamRef = useRef(joinStream);
|
|
@@ -664,38 +387,44 @@ export function useStream(options) {
|
|
|
664
387
|
void joinStreamRef.current?.(reconnectKey.runId);
|
|
665
388
|
}
|
|
666
389
|
}, [reconnectKey]);
|
|
667
|
-
|
|
668
|
-
const
|
|
390
|
+
// --- END TRANSPORT ---
|
|
391
|
+
const error = stream.error ?? historyError ?? history.error;
|
|
392
|
+
const values = stream.values ?? historyValues;
|
|
669
393
|
return {
|
|
670
394
|
get values() {
|
|
671
395
|
trackStreamMode("values");
|
|
672
396
|
return values;
|
|
673
397
|
},
|
|
674
398
|
client,
|
|
675
|
-
assistantId,
|
|
399
|
+
assistantId: options.assistantId,
|
|
676
400
|
error,
|
|
677
|
-
isLoading,
|
|
401
|
+
isLoading: stream.isLoading,
|
|
678
402
|
stop,
|
|
679
|
-
submit,
|
|
403
|
+
submit,
|
|
680
404
|
joinStream,
|
|
681
405
|
branch,
|
|
682
406
|
setBranch,
|
|
683
|
-
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
|
+
},
|
|
684
413
|
isThreadLoading: history.isLoading && history.data == null,
|
|
685
414
|
get experimental_branchTree() {
|
|
686
415
|
if (historyLimit === false) {
|
|
687
|
-
throw new Error("`
|
|
416
|
+
throw new Error("`fetchStateHistory` must be set to `true` to use `experimental_branchTree`");
|
|
688
417
|
}
|
|
689
|
-
return
|
|
418
|
+
return branchContext.branchTree;
|
|
690
419
|
},
|
|
691
420
|
get interrupt() {
|
|
692
421
|
// Don't show the interrupt if the stream is loading
|
|
693
|
-
if (isLoading)
|
|
422
|
+
if (stream.isLoading)
|
|
694
423
|
return undefined;
|
|
695
|
-
const interrupts = threadHead?.tasks?.at(-1)?.interrupts;
|
|
424
|
+
const interrupts = branchContext.threadHead?.tasks?.at(-1)?.interrupts;
|
|
696
425
|
if (interrupts == null || interrupts.length === 0) {
|
|
697
426
|
// check if there's a next task present
|
|
698
|
-
const next = threadHead?.next ?? [];
|
|
427
|
+
const next = branchContext.threadHead?.next ?? [];
|
|
699
428
|
if (!next.length || error != null)
|
|
700
429
|
return undefined;
|
|
701
430
|
return { when: "breakpoint" };
|
|
@@ -709,7 +438,15 @@ export function useStream(options) {
|
|
|
709
438
|
},
|
|
710
439
|
getMessagesMetadata(message, index) {
|
|
711
440
|
trackStreamMode("values");
|
|
712
|
-
|
|
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;
|
|
713
450
|
},
|
|
714
451
|
};
|
|
715
452
|
}
|