@langchain/langgraph-sdk 0.0.40 → 0.0.41
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/react/stream.cjs +125 -120
- package/dist/react/stream.d.ts +131 -22
- package/dist/react/stream.js +125 -120
- package/package.json +5 -4
- package/dist/client.js +0 -888
- package/dist/index.js +0 -2
- package/dist/schema.cjs +0 -2
- package/dist/singletons/fetch.cjs +0 -27
- package/dist/singletons/fetch.d.ts +0 -11
- package/dist/singletons/fetch.js +0 -22
- package/dist/types.cjs +0 -2
- package/dist/types.d.ts +0 -141
- package/dist/types.js +0 -1
- package/dist/types.messages.d.ts +0 -88
- package/dist/types.stream.d.ts +0 -156
- package/dist/types.stream.js +0 -1
- package/dist/utils/async_caller.cjs +0 -197
- package/dist/utils/async_caller.d.ts +0 -48
- package/dist/utils/env.d.ts +0 -1
- package/dist/utils/env.js +0 -12
- package/dist/utils/signals.js +0 -18
- package/dist/utils/sse.d.ts +0 -11
- package/dist/utils/sse.js +0 -152
- package/dist/utils/stream.d.ts +0 -13
- package/dist/utils/stream.js +0 -111
package/dist/react/stream.js
CHANGED
|
@@ -55,6 +55,92 @@ function findLastIndex(array, predicate) {
|
|
|
55
55
|
}
|
|
56
56
|
return -1;
|
|
57
57
|
}
|
|
58
|
+
function getBranchSequence(history) {
|
|
59
|
+
const childrenMap = {};
|
|
60
|
+
// First pass - collect nodes for each checkpoint
|
|
61
|
+
history.forEach((state) => {
|
|
62
|
+
const checkpointId = state.parent_checkpoint?.checkpoint_id ?? "$";
|
|
63
|
+
childrenMap[checkpointId] ??= [];
|
|
64
|
+
childrenMap[checkpointId].push(state);
|
|
65
|
+
});
|
|
66
|
+
const rootSequence = { type: "sequence", items: [] };
|
|
67
|
+
const queue = [{ id: "$", sequence: rootSequence, path: [] }];
|
|
68
|
+
const paths = [];
|
|
69
|
+
const visited = new Set();
|
|
70
|
+
while (queue.length > 0) {
|
|
71
|
+
const task = queue.shift();
|
|
72
|
+
if (visited.has(task.id))
|
|
73
|
+
continue;
|
|
74
|
+
visited.add(task.id);
|
|
75
|
+
const children = childrenMap[task.id];
|
|
76
|
+
if (children == null || children.length === 0)
|
|
77
|
+
continue;
|
|
78
|
+
// If we've encountered a fork (2+ children), push the fork
|
|
79
|
+
// to the sequence and add a new sequence for each child
|
|
80
|
+
let fork;
|
|
81
|
+
if (children.length > 1) {
|
|
82
|
+
fork = { type: "fork", items: [] };
|
|
83
|
+
task.sequence.items.push(fork);
|
|
84
|
+
}
|
|
85
|
+
for (const value of children) {
|
|
86
|
+
const id = value.checkpoint.checkpoint_id;
|
|
87
|
+
let sequence = task.sequence;
|
|
88
|
+
let path = task.path;
|
|
89
|
+
if (fork != null) {
|
|
90
|
+
sequence = { type: "sequence", items: [] };
|
|
91
|
+
fork.items.unshift(sequence);
|
|
92
|
+
path = path.slice();
|
|
93
|
+
path.push(id);
|
|
94
|
+
paths.push(path);
|
|
95
|
+
}
|
|
96
|
+
sequence.items.push({ type: "node", value, path });
|
|
97
|
+
queue.push({ id, sequence, path });
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
return { rootSequence, paths };
|
|
101
|
+
}
|
|
102
|
+
const PATH_SEP = ">";
|
|
103
|
+
const ROOT_ID = "$";
|
|
104
|
+
// Get flat view
|
|
105
|
+
function getBranchView(sequence, paths, branch) {
|
|
106
|
+
const path = branch.split(PATH_SEP);
|
|
107
|
+
const pathMap = {};
|
|
108
|
+
for (const path of paths) {
|
|
109
|
+
const parent = path.at(-2) ?? ROOT_ID;
|
|
110
|
+
pathMap[parent] ??= [];
|
|
111
|
+
pathMap[parent].unshift(path);
|
|
112
|
+
}
|
|
113
|
+
const history = [];
|
|
114
|
+
const branchByCheckpoint = {};
|
|
115
|
+
const forkStack = path.slice();
|
|
116
|
+
const queue = [...sequence.items];
|
|
117
|
+
while (queue.length > 0) {
|
|
118
|
+
const item = queue.shift();
|
|
119
|
+
if (item.type === "node") {
|
|
120
|
+
history.push(item.value);
|
|
121
|
+
branchByCheckpoint[item.value.checkpoint.checkpoint_id] = {
|
|
122
|
+
branch: item.path.join(PATH_SEP),
|
|
123
|
+
branchOptions: (item.path.length > 0
|
|
124
|
+
? pathMap[item.path.at(-2) ?? ROOT_ID] ?? []
|
|
125
|
+
: []).map((p) => p.join(PATH_SEP)),
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
if (item.type === "fork") {
|
|
129
|
+
const forkId = forkStack.shift();
|
|
130
|
+
const index = forkId != null
|
|
131
|
+
? item.items.findIndex((value) => {
|
|
132
|
+
const firstItem = value.items.at(0);
|
|
133
|
+
if (!firstItem || firstItem.type !== "node")
|
|
134
|
+
return false;
|
|
135
|
+
return firstItem.value.checkpoint.checkpoint_id === forkId;
|
|
136
|
+
})
|
|
137
|
+
: -1;
|
|
138
|
+
const nextItems = item.items.at(index)?.items ?? [];
|
|
139
|
+
queue.push(...nextItems);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
return { history, branchByCheckpoint };
|
|
143
|
+
}
|
|
58
144
|
function fetchHistory(client, threadId) {
|
|
59
145
|
return client.threads.getHistory(threadId, { limit: 1000 });
|
|
60
146
|
}
|
|
@@ -81,12 +167,26 @@ function useThreadHistory(threadId, client, clearCallbackRef, submittingRef) {
|
|
|
81
167
|
mutate: (mutateId) => fetcher(mutateId ?? threadId),
|
|
82
168
|
};
|
|
83
169
|
}
|
|
170
|
+
const useControllableThreadId = (options) => {
|
|
171
|
+
const [localThreadId, _setLocalThreadId] = useState(options?.threadId ?? null);
|
|
172
|
+
const onThreadIdRef = useRef(options?.onThreadId);
|
|
173
|
+
onThreadIdRef.current = options?.onThreadId;
|
|
174
|
+
const onThreadId = useCallback((threadId) => {
|
|
175
|
+
_setLocalThreadId(threadId);
|
|
176
|
+
onThreadIdRef.current?.(threadId);
|
|
177
|
+
}, []);
|
|
178
|
+
if (typeof options?.threadId === "undefined") {
|
|
179
|
+
return [localThreadId, onThreadId];
|
|
180
|
+
}
|
|
181
|
+
return [options.threadId, onThreadId];
|
|
182
|
+
};
|
|
84
183
|
export function useStream(options) {
|
|
85
|
-
|
|
184
|
+
let { assistantId, messagesKey, onError, onFinish } = options;
|
|
185
|
+
messagesKey ??= "messages";
|
|
86
186
|
const client = useMemo(() => new Client({ apiUrl: options.apiUrl, apiKey: options.apiKey }), [options.apiKey, options.apiUrl]);
|
|
87
|
-
const [
|
|
187
|
+
const [threadId, onThreadId] = useControllableThreadId(options);
|
|
188
|
+
const [branch, setBranch] = useState("");
|
|
88
189
|
const [isLoading, setIsLoading] = useState(false);
|
|
89
|
-
const [_, setEvents] = useState([]);
|
|
90
190
|
const [streamError, setStreamError] = useState(undefined);
|
|
91
191
|
const [streamValues, setStreamValues] = useState(null);
|
|
92
192
|
const messageManagerRef = useRef(new MessageTupleManager());
|
|
@@ -116,94 +216,13 @@ export function useStream(options) {
|
|
|
116
216
|
// TODO: should we permit adapter? SWR / React Query?
|
|
117
217
|
const history = useThreadHistory(threadId, client, clearCallbackRef, submittingRef);
|
|
118
218
|
const getMessages = useMemo(() => {
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
return (value) => Array.isArray(value[withMessages])
|
|
122
|
-
? value[withMessages]
|
|
219
|
+
return (value) => Array.isArray(value[messagesKey])
|
|
220
|
+
? value[messagesKey]
|
|
123
221
|
: [];
|
|
124
|
-
}, [
|
|
125
|
-
const
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
history.data.forEach((state) => {
|
|
129
|
-
const checkpointId = state.parent_checkpoint?.checkpoint_id ?? "$";
|
|
130
|
-
childrenMap[checkpointId] ??= [];
|
|
131
|
-
childrenMap[checkpointId].push(state);
|
|
132
|
-
});
|
|
133
|
-
const rootSequence = { type: "sequence", items: [] };
|
|
134
|
-
const queue = [{ id: "$", sequence: rootSequence, path: [] }];
|
|
135
|
-
const paths = [];
|
|
136
|
-
const visited = new Set();
|
|
137
|
-
while (queue.length > 0) {
|
|
138
|
-
const task = queue.shift();
|
|
139
|
-
if (visited.has(task.id))
|
|
140
|
-
continue;
|
|
141
|
-
visited.add(task.id);
|
|
142
|
-
const children = childrenMap[task.id];
|
|
143
|
-
if (children == null || children.length === 0)
|
|
144
|
-
continue;
|
|
145
|
-
// If we've encountered a fork (2+ children), push the fork
|
|
146
|
-
// to the sequence and add a new sequence for each child
|
|
147
|
-
let fork;
|
|
148
|
-
if (children.length > 1) {
|
|
149
|
-
fork = { type: "fork", items: [] };
|
|
150
|
-
task.sequence.items.push(fork);
|
|
151
|
-
}
|
|
152
|
-
for (const value of children) {
|
|
153
|
-
const id = value.checkpoint.checkpoint_id;
|
|
154
|
-
let sequence = task.sequence;
|
|
155
|
-
let path = task.path;
|
|
156
|
-
if (fork != null) {
|
|
157
|
-
sequence = { type: "sequence", items: [] };
|
|
158
|
-
fork.items.unshift(sequence);
|
|
159
|
-
path = path.slice();
|
|
160
|
-
path.push(id);
|
|
161
|
-
paths.push(path);
|
|
162
|
-
}
|
|
163
|
-
sequence.items.push({ type: "node", value, path });
|
|
164
|
-
queue.push({ id, sequence, path });
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
// Third pass, create a map for available forks
|
|
168
|
-
const pathMap = {};
|
|
169
|
-
for (const path of paths) {
|
|
170
|
-
const parent = path.at(-2) ?? "$";
|
|
171
|
-
pathMap[parent] ??= [];
|
|
172
|
-
pathMap[parent].unshift(path);
|
|
173
|
-
}
|
|
174
|
-
return [rootSequence, pathMap];
|
|
175
|
-
})();
|
|
176
|
-
const [flatValues, flatPaths] = (() => {
|
|
177
|
-
const result = [];
|
|
178
|
-
const flatPaths = {};
|
|
179
|
-
const forkStack = branchPath.slice();
|
|
180
|
-
const queue = [...sequence.items];
|
|
181
|
-
while (queue.length > 0) {
|
|
182
|
-
const item = queue.shift();
|
|
183
|
-
if (item.type === "node") {
|
|
184
|
-
result.push(item.value);
|
|
185
|
-
flatPaths[item.value.checkpoint.checkpoint_id] = {
|
|
186
|
-
current: item.path,
|
|
187
|
-
branches: item.path.length > 0 ? pathMap[item.path.at(-2) ?? "$"] ?? [] : [],
|
|
188
|
-
};
|
|
189
|
-
}
|
|
190
|
-
if (item.type === "fork") {
|
|
191
|
-
const forkId = forkStack.shift();
|
|
192
|
-
const index = forkId != null
|
|
193
|
-
? item.items.findIndex((value) => {
|
|
194
|
-
const firstItem = value.items.at(0);
|
|
195
|
-
if (!firstItem || firstItem.type !== "node")
|
|
196
|
-
return false;
|
|
197
|
-
return firstItem.value.checkpoint.checkpoint_id === forkId;
|
|
198
|
-
})
|
|
199
|
-
: -1;
|
|
200
|
-
const nextItems = item.items.at(index)?.items ?? [];
|
|
201
|
-
queue.push(...nextItems);
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
return [result, flatPaths];
|
|
205
|
-
})();
|
|
206
|
-
const threadHead = flatValues.at(-1);
|
|
222
|
+
}, [messagesKey]);
|
|
223
|
+
const { rootSequence, paths } = getBranchSequence(history.data);
|
|
224
|
+
const { history: flatHistory, branchByCheckpoint } = getBranchView(rootSequence, paths, branch);
|
|
225
|
+
const threadHead = flatHistory.at(-1);
|
|
207
226
|
const historyValues = threadHead?.values ?? {};
|
|
208
227
|
const historyError = (() => {
|
|
209
228
|
const error = threadHead?.tasks?.at(-1)?.error;
|
|
@@ -222,8 +241,6 @@ export function useStream(options) {
|
|
|
222
241
|
return error;
|
|
223
242
|
})();
|
|
224
243
|
const messageMetadata = (() => {
|
|
225
|
-
if (getMessages == null)
|
|
226
|
-
return undefined;
|
|
227
244
|
const alreadyShown = new Set();
|
|
228
245
|
return getMessages(historyValues).map((message, idx) => {
|
|
229
246
|
const messageId = message.id ?? idx;
|
|
@@ -232,12 +249,12 @@ export function useStream(options) {
|
|
|
232
249
|
.includes(messageId));
|
|
233
250
|
const firstSeen = history.data[firstSeenIdx];
|
|
234
251
|
let branch = firstSeen
|
|
235
|
-
?
|
|
252
|
+
? branchByCheckpoint[firstSeen.checkpoint.checkpoint_id]
|
|
236
253
|
: undefined;
|
|
237
|
-
if (!branch?.
|
|
254
|
+
if (!branch?.branch?.length)
|
|
238
255
|
branch = undefined;
|
|
239
256
|
// serialize branches
|
|
240
|
-
const optionsShown = branch?.
|
|
257
|
+
const optionsShown = branch?.branchOptions?.flat(2).join(",");
|
|
241
258
|
if (optionsShown) {
|
|
242
259
|
if (alreadyShown.has(optionsShown))
|
|
243
260
|
branch = undefined;
|
|
@@ -246,8 +263,8 @@ export function useStream(options) {
|
|
|
246
263
|
return {
|
|
247
264
|
messageId: messageId.toString(),
|
|
248
265
|
firstSeenState: firstSeen,
|
|
249
|
-
branch: branch?.
|
|
250
|
-
branchOptions: branch?.
|
|
266
|
+
branch: branch?.branch,
|
|
267
|
+
branchOptions: branch?.branchOptions,
|
|
251
268
|
};
|
|
252
269
|
});
|
|
253
270
|
})();
|
|
@@ -265,7 +282,7 @@ export function useStream(options) {
|
|
|
265
282
|
let usableThreadId = threadId;
|
|
266
283
|
if (!usableThreadId) {
|
|
267
284
|
const thread = await client.threads.create();
|
|
268
|
-
|
|
285
|
+
onThreadId(thread.thread_id);
|
|
269
286
|
usableThreadId = thread.thread_id;
|
|
270
287
|
}
|
|
271
288
|
const streamMode = unique([
|
|
@@ -293,10 +310,10 @@ export function useStream(options) {
|
|
|
293
310
|
}));
|
|
294
311
|
// Unbranch things
|
|
295
312
|
const newPath = submitOptions?.checkpoint?.checkpoint_id
|
|
296
|
-
?
|
|
313
|
+
? branchByCheckpoint[submitOptions?.checkpoint?.checkpoint_id]?.branch
|
|
297
314
|
: undefined;
|
|
298
315
|
if (newPath != null)
|
|
299
|
-
|
|
316
|
+
setBranch(newPath ?? "");
|
|
300
317
|
// Assumption: we're setting the initial value
|
|
301
318
|
// Used for instant feedback
|
|
302
319
|
setStreamValues(() => {
|
|
@@ -313,26 +330,19 @@ export function useStream(options) {
|
|
|
313
330
|
});
|
|
314
331
|
let streamError;
|
|
315
332
|
for await (const { event, data } of run) {
|
|
316
|
-
setEvents((events) => [...events, { event, data }]);
|
|
317
333
|
if (event === "error") {
|
|
318
334
|
streamError = new StreamError(data);
|
|
319
335
|
break;
|
|
320
336
|
}
|
|
321
|
-
if (event === "updates")
|
|
337
|
+
if (event === "updates")
|
|
322
338
|
options.onUpdateEvent?.(data);
|
|
323
|
-
|
|
324
|
-
if (event === "custom") {
|
|
339
|
+
if (event === "custom")
|
|
325
340
|
options.onCustomEvent?.(data);
|
|
326
|
-
|
|
327
|
-
if (event === "metadata") {
|
|
341
|
+
if (event === "metadata")
|
|
328
342
|
options.onMetadataEvent?.(data);
|
|
329
|
-
|
|
330
|
-
if (event === "values") {
|
|
343
|
+
if (event === "values")
|
|
331
344
|
setStreamValues(data);
|
|
332
|
-
}
|
|
333
345
|
if (event === "messages") {
|
|
334
|
-
if (!getMessages)
|
|
335
|
-
continue;
|
|
336
346
|
const [serialized] = data;
|
|
337
347
|
const messageId = messageManagerRef.current.add(serialized);
|
|
338
348
|
if (!messageId) {
|
|
@@ -347,13 +357,12 @@ export function useStream(options) {
|
|
|
347
357
|
if (!chunk || index == null)
|
|
348
358
|
return values;
|
|
349
359
|
messages[index] = toMessageDict(chunk);
|
|
350
|
-
return { ...values, [
|
|
360
|
+
return { ...values, [messagesKey]: messages };
|
|
351
361
|
});
|
|
352
362
|
}
|
|
353
363
|
}
|
|
354
364
|
// TODO: stream created checkpoints to avoid an unnecessary network request
|
|
355
365
|
const result = await history.mutate(usableThreadId);
|
|
356
|
-
// TODO: write tests verifying that stream values are properly handled lifecycle-wise
|
|
357
366
|
setStreamValues(null);
|
|
358
367
|
if (streamError != null)
|
|
359
368
|
throw streamError;
|
|
@@ -378,7 +387,6 @@ export function useStream(options) {
|
|
|
378
387
|
};
|
|
379
388
|
const error = isLoading ? streamError : historyError;
|
|
380
389
|
const values = streamValues ?? historyValues;
|
|
381
|
-
const setBranch = useCallback((path) => setBranchPath(path.split(">")), [setBranchPath]);
|
|
382
390
|
return {
|
|
383
391
|
get values() {
|
|
384
392
|
trackStreamMode("values");
|
|
@@ -388,19 +396,16 @@ export function useStream(options) {
|
|
|
388
396
|
isLoading,
|
|
389
397
|
stop,
|
|
390
398
|
submit,
|
|
399
|
+
branch,
|
|
391
400
|
setBranch,
|
|
401
|
+
history: flatHistory,
|
|
402
|
+
experimental_branchTree: rootSequence,
|
|
392
403
|
get messages() {
|
|
393
404
|
trackStreamMode("messages-tuple");
|
|
394
|
-
if (getMessages == null) {
|
|
395
|
-
throw new Error("No messages key provided. Make sure that `useStream` contains the `messagesKey` property.");
|
|
396
|
-
}
|
|
397
405
|
return getMessages(values);
|
|
398
406
|
},
|
|
399
407
|
getMessagesMetadata(message, index) {
|
|
400
408
|
trackStreamMode("messages-tuple");
|
|
401
|
-
if (getMessages == null) {
|
|
402
|
-
throw new Error("No messages key provided. Make sure that `useStream` contains the `messagesKey` property.");
|
|
403
|
-
}
|
|
404
409
|
return messageMetadata?.find((m) => m.messageId === (message.id ?? index));
|
|
405
410
|
},
|
|
406
411
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@langchain/langgraph-sdk",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.41",
|
|
4
4
|
"description": "Client library for interacting with the LangGraph API",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"packageManager": "yarn@1.22.19",
|
|
@@ -10,7 +10,8 @@
|
|
|
10
10
|
"prepublish": "yarn run build",
|
|
11
11
|
"format": "prettier --write src",
|
|
12
12
|
"lint": "prettier --check src && tsc --noEmit",
|
|
13
|
-
"test": "NODE_OPTIONS=--experimental-vm-modules jest --testPathIgnorePatterns=\\.int\\.test.ts"
|
|
13
|
+
"test": "NODE_OPTIONS=--experimental-vm-modules jest --testPathIgnorePatterns=\\.int\\.test.ts",
|
|
14
|
+
"typedoc": "typedoc && typedoc src/react/index.ts --out docs/react --options typedoc.react.json"
|
|
14
15
|
},
|
|
15
16
|
"main": "index.js",
|
|
16
17
|
"license": "MIT",
|
|
@@ -33,8 +34,8 @@
|
|
|
33
34
|
"jest": "^29.7.0",
|
|
34
35
|
"prettier": "^3.2.5",
|
|
35
36
|
"ts-jest": "^29.1.2",
|
|
36
|
-
"typedoc": "^0.
|
|
37
|
-
"typedoc-plugin-markdown": "^4.
|
|
37
|
+
"typedoc": "^0.27.7",
|
|
38
|
+
"typedoc-plugin-markdown": "^4.4.2",
|
|
38
39
|
"typescript": "^5.4.5",
|
|
39
40
|
"react": "^18.3.1"
|
|
40
41
|
},
|