@langchain/langgraph-sdk 0.0.37 → 0.0.39

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.
Files changed (46) hide show
  1. package/dist/client.cjs +10 -66
  2. package/dist/client.d.ts +19 -24
  3. package/dist/client.js +10 -66
  4. package/dist/index.cjs +3 -1
  5. package/dist/index.d.ts +5 -1
  6. package/dist/index.js +1 -0
  7. package/dist/react/debug.cjs +32 -0
  8. package/dist/react/debug.d.ts +23 -0
  9. package/dist/react/debug.js +28 -0
  10. package/dist/react/index.cjs +5 -0
  11. package/dist/react/index.d.ts +1 -0
  12. package/dist/react/index.js +1 -0
  13. package/dist/react/stream.cjs +411 -0
  14. package/dist/react/stream.d.ts +46 -0
  15. package/dist/react/stream.js +407 -0
  16. package/dist/schema.d.ts +7 -7
  17. package/dist/singletons/fetch.cjs +27 -0
  18. package/dist/singletons/fetch.d.ts +11 -0
  19. package/dist/singletons/fetch.js +22 -0
  20. package/dist/types.d.ts +6 -16
  21. package/dist/types.messages.d.ts +88 -0
  22. package/dist/types.stream.cjs +2 -0
  23. package/dist/types.stream.d.ts +156 -0
  24. package/dist/types.stream.js +1 -0
  25. package/dist/utils/async_caller.cjs +2 -1
  26. package/dist/utils/async_caller.js +2 -1
  27. package/dist/utils/sse.cjs +151 -0
  28. package/dist/utils/sse.d.ts +11 -0
  29. package/dist/utils/sse.js +146 -0
  30. package/package.json +23 -3
  31. package/react.cjs +1 -0
  32. package/react.d.cts +1 -0
  33. package/react.d.ts +1 -0
  34. package/react.js +1 -0
  35. package/dist/utils/eventsource-parser/index.cjs +0 -7
  36. package/dist/utils/eventsource-parser/index.d.ts +0 -2
  37. package/dist/utils/eventsource-parser/index.js +0 -3
  38. package/dist/utils/eventsource-parser/parse.cjs +0 -150
  39. package/dist/utils/eventsource-parser/parse.d.ts +0 -18
  40. package/dist/utils/eventsource-parser/parse.js +0 -146
  41. package/dist/utils/eventsource-parser/stream.cjs +0 -34
  42. package/dist/utils/eventsource-parser/stream.d.ts +0 -17
  43. package/dist/utils/eventsource-parser/stream.js +0 -30
  44. package/dist/utils/eventsource-parser/types.d.ts +0 -81
  45. /package/dist/{utils/eventsource-parser/types.cjs → types.messages.cjs} +0 -0
  46. /package/dist/{utils/eventsource-parser/types.js → types.messages.js} +0 -0
@@ -0,0 +1,411 @@
1
+ "use strict";
2
+ /* __LC_ALLOW_ENTRYPOINT_SIDE_EFFECTS__ */
3
+ "use client";
4
+ Object.defineProperty(exports, "__esModule", { value: true });
5
+ exports.useStream = void 0;
6
+ const client_js_1 = require("../client.cjs");
7
+ const react_1 = require("react");
8
+ const messages_1 = require("@langchain/core/messages");
9
+ class StreamError extends Error {
10
+ constructor(data) {
11
+ super(data.message);
12
+ this.name = data.name ?? data.error ?? "StreamError";
13
+ }
14
+ static isStructuredError(error) {
15
+ return typeof error === "object" && error != null && "message" in error;
16
+ }
17
+ }
18
+ class MessageTupleManager {
19
+ constructor() {
20
+ Object.defineProperty(this, "chunks", {
21
+ enumerable: true,
22
+ configurable: true,
23
+ writable: true,
24
+ value: {}
25
+ });
26
+ this.chunks = {};
27
+ }
28
+ add(serialized) {
29
+ const chunk = (0, messages_1.convertToChunk)((0, messages_1.coerceMessageLikeToMessage)(serialized));
30
+ const id = chunk.id;
31
+ if (!id)
32
+ return null;
33
+ this.chunks[id] ??= {};
34
+ this.chunks[id].chunk = this.chunks[id]?.chunk?.concat(chunk) ?? chunk;
35
+ return id;
36
+ }
37
+ clear() {
38
+ this.chunks = {};
39
+ }
40
+ get(id, defaultIndex) {
41
+ if (this.chunks[id] == null)
42
+ return null;
43
+ this.chunks[id].index ??= defaultIndex;
44
+ return this.chunks[id];
45
+ }
46
+ }
47
+ const toMessageDict = (chunk) => {
48
+ const { type, data } = chunk.toDict();
49
+ return { ...data, type };
50
+ };
51
+ function unique(array) {
52
+ return [...new Set(array)];
53
+ }
54
+ function findLastIndex(array, predicate) {
55
+ for (let i = array.length - 1; i >= 0; i--) {
56
+ if (predicate(array[i]))
57
+ return i;
58
+ }
59
+ return -1;
60
+ }
61
+ function fetchHistory(client, threadId) {
62
+ return client.threads.getHistory(threadId, { limit: 1000 });
63
+ }
64
+ function useThreadHistory(threadId, client, clearCallbackRef, submittingRef) {
65
+ const [history, setHistory] = (0, react_1.useState)([]);
66
+ const fetcher = (0, react_1.useCallback)((threadId) => {
67
+ if (threadId != null) {
68
+ return fetchHistory(client, threadId).then((history) => {
69
+ setHistory(history);
70
+ return history;
71
+ });
72
+ }
73
+ setHistory([]);
74
+ clearCallbackRef.current?.();
75
+ return Promise.resolve([]);
76
+ }, []);
77
+ (0, react_1.useEffect)(() => {
78
+ if (submittingRef.current)
79
+ return;
80
+ fetcher(threadId);
81
+ }, [fetcher, submittingRef, threadId]);
82
+ return {
83
+ data: history,
84
+ mutate: (mutateId) => fetcher(mutateId ?? threadId),
85
+ };
86
+ }
87
+ function useStream(options) {
88
+ const { assistantId, threadId, withMessages, onError, onFinish } = options;
89
+ const client = (0, react_1.useMemo)(() => new client_js_1.Client({ apiUrl: options.apiUrl, apiKey: options.apiKey }), [options.apiKey, options.apiUrl]);
90
+ const [branchPath, setBranchPath] = (0, react_1.useState)([]);
91
+ const [isLoading, setIsLoading] = (0, react_1.useState)(false);
92
+ const [_, setEvents] = (0, react_1.useState)([]);
93
+ const [streamError, setStreamError] = (0, react_1.useState)(undefined);
94
+ const [streamValues, setStreamValues] = (0, react_1.useState)(null);
95
+ const messageManagerRef = (0, react_1.useRef)(new MessageTupleManager());
96
+ const submittingRef = (0, react_1.useRef)(false);
97
+ const abortRef = (0, react_1.useRef)(null);
98
+ const trackStreamModeRef = (0, react_1.useRef)(["values", "messages-tuple"]);
99
+ const trackStreamMode = (0, react_1.useCallback)((mode) => {
100
+ if (!trackStreamModeRef.current.includes(mode))
101
+ trackStreamModeRef.current.push(mode);
102
+ }, []);
103
+ const hasUpdateListener = options.onUpdateEvent != null;
104
+ const hasCustomListener = options.onCustomEvent != null;
105
+ const callbackStreamMode = (0, react_1.useMemo)(() => {
106
+ const modes = [];
107
+ if (hasUpdateListener)
108
+ modes.push("updates");
109
+ if (hasCustomListener)
110
+ modes.push("custom");
111
+ return modes;
112
+ }, [hasUpdateListener, hasCustomListener]);
113
+ const clearCallbackRef = (0, react_1.useRef)(null);
114
+ clearCallbackRef.current = () => {
115
+ setStreamError(undefined);
116
+ setStreamValues(null);
117
+ };
118
+ // TODO: this should be done on the server to avoid pagination
119
+ // TODO: should we permit adapter? SWR / React Query?
120
+ const history = useThreadHistory(threadId, client, clearCallbackRef, submittingRef);
121
+ const getMessages = (0, react_1.useMemo)(() => {
122
+ if (withMessages == null)
123
+ return undefined;
124
+ return (value) => Array.isArray(value[withMessages])
125
+ ? value[withMessages]
126
+ : [];
127
+ }, [withMessages]);
128
+ const [sequence, pathMap] = (() => {
129
+ const childrenMap = {};
130
+ // First pass - collect nodes for each checkpoint
131
+ history.data.forEach((state) => {
132
+ const checkpointId = state.parent_checkpoint?.checkpoint_id ?? "$";
133
+ childrenMap[checkpointId] ??= [];
134
+ childrenMap[checkpointId].push(state);
135
+ });
136
+ const rootSequence = { type: "sequence", items: [] };
137
+ const queue = [{ id: "$", sequence: rootSequence, path: [] }];
138
+ const paths = [];
139
+ const visited = new Set();
140
+ while (queue.length > 0) {
141
+ const task = queue.shift();
142
+ if (visited.has(task.id))
143
+ continue;
144
+ visited.add(task.id);
145
+ const children = childrenMap[task.id];
146
+ if (children == null || children.length === 0)
147
+ continue;
148
+ // If we've encountered a fork (2+ children), push the fork
149
+ // to the sequence and add a new sequence for each child
150
+ let fork;
151
+ if (children.length > 1) {
152
+ fork = { type: "fork", items: [] };
153
+ task.sequence.items.push(fork);
154
+ }
155
+ for (const value of children) {
156
+ const id = value.checkpoint.checkpoint_id;
157
+ let sequence = task.sequence;
158
+ let path = task.path;
159
+ if (fork != null) {
160
+ sequence = { type: "sequence", items: [] };
161
+ fork.items.unshift(sequence);
162
+ path = path.slice();
163
+ path.push(id);
164
+ paths.push(path);
165
+ }
166
+ sequence.items.push({ type: "node", value, path });
167
+ queue.push({ id, sequence, path });
168
+ }
169
+ }
170
+ // Third pass, create a map for available forks
171
+ const pathMap = {};
172
+ for (const path of paths) {
173
+ const parent = path.at(-2) ?? "$";
174
+ pathMap[parent] ??= [];
175
+ pathMap[parent].unshift(path);
176
+ }
177
+ return [rootSequence, pathMap];
178
+ })();
179
+ const [flatValues, flatPaths] = (() => {
180
+ const result = [];
181
+ const flatPaths = {};
182
+ const forkStack = branchPath.slice();
183
+ const queue = [...sequence.items];
184
+ while (queue.length > 0) {
185
+ const item = queue.shift();
186
+ if (item.type === "node") {
187
+ result.push(item.value);
188
+ flatPaths[item.value.checkpoint.checkpoint_id] = {
189
+ current: item.path,
190
+ branches: item.path.length > 0 ? pathMap[item.path.at(-2) ?? "$"] ?? [] : [],
191
+ };
192
+ }
193
+ if (item.type === "fork") {
194
+ const forkId = forkStack.shift();
195
+ const index = forkId != null
196
+ ? item.items.findIndex((value) => {
197
+ const firstItem = value.items.at(0);
198
+ if (!firstItem || firstItem.type !== "node")
199
+ return false;
200
+ return firstItem.value.checkpoint.checkpoint_id === forkId;
201
+ })
202
+ : -1;
203
+ const nextItems = item.items.at(index)?.items ?? [];
204
+ queue.push(...nextItems);
205
+ }
206
+ }
207
+ return [result, flatPaths];
208
+ })();
209
+ const threadHead = flatValues.at(-1);
210
+ const historyValues = threadHead?.values ?? {};
211
+ const historyError = (() => {
212
+ const error = threadHead?.tasks?.at(-1)?.error;
213
+ if (error == null)
214
+ return undefined;
215
+ try {
216
+ const parsed = JSON.parse(error);
217
+ if (StreamError.isStructuredError(parsed)) {
218
+ return new StreamError(parsed);
219
+ }
220
+ return parsed;
221
+ }
222
+ catch {
223
+ // do nothing
224
+ }
225
+ return error;
226
+ })();
227
+ const messageMetadata = (() => {
228
+ if (getMessages == null)
229
+ return undefined;
230
+ const alreadyShown = new Set();
231
+ return getMessages(historyValues).map((message, idx) => {
232
+ const messageId = message.id ?? idx;
233
+ const firstSeenIdx = findLastIndex(history.data, (state) => getMessages(state.values)
234
+ .map((m, idx) => m.id ?? idx)
235
+ .includes(messageId));
236
+ const firstSeen = history.data[firstSeenIdx];
237
+ let branch = firstSeen
238
+ ? flatPaths[firstSeen.checkpoint.checkpoint_id]
239
+ : undefined;
240
+ if (!branch?.current?.length)
241
+ branch = undefined;
242
+ // serialize branches
243
+ const optionsShown = branch?.branches?.flat(2).join(",");
244
+ if (optionsShown) {
245
+ if (alreadyShown.has(optionsShown))
246
+ branch = undefined;
247
+ alreadyShown.add(optionsShown);
248
+ }
249
+ return {
250
+ messageId: messageId.toString(),
251
+ firstSeenState: firstSeen,
252
+ branch: branch?.current?.join(">"),
253
+ branchOptions: branch?.branches?.map((b) => b.join(">")),
254
+ };
255
+ });
256
+ })();
257
+ const stop = (0, react_1.useCallback)(() => {
258
+ if (abortRef.current != null)
259
+ abortRef.current.abort();
260
+ abortRef.current = null;
261
+ }, []);
262
+ const submit = async (values, submitOptions) => {
263
+ try {
264
+ setIsLoading(true);
265
+ setStreamError(undefined);
266
+ submittingRef.current = true;
267
+ abortRef.current = new AbortController();
268
+ let usableThreadId = threadId;
269
+ if (!usableThreadId) {
270
+ const thread = await client.threads.create();
271
+ options?.onThreadId?.(thread.thread_id);
272
+ usableThreadId = thread.thread_id;
273
+ }
274
+ const streamMode = unique([
275
+ ...(submitOptions?.streamMode ?? []),
276
+ ...trackStreamModeRef.current,
277
+ ...callbackStreamMode,
278
+ ]);
279
+ const checkpoint = submitOptions?.checkpoint ?? threadHead?.checkpoint ?? undefined;
280
+ // @ts-expect-error
281
+ if (checkpoint != null)
282
+ delete checkpoint.thread_id;
283
+ const run = (await client.runs.stream(usableThreadId, assistantId, {
284
+ input: values,
285
+ config: submitOptions?.config,
286
+ command: submitOptions?.command,
287
+ interruptBefore: submitOptions?.interruptBefore,
288
+ interruptAfter: submitOptions?.interruptAfter,
289
+ metadata: submitOptions?.metadata,
290
+ multitaskStrategy: submitOptions?.multitaskStrategy,
291
+ onCompletion: submitOptions?.onCompletion,
292
+ onDisconnect: submitOptions?.onDisconnect ?? "cancel",
293
+ signal: abortRef.current.signal,
294
+ checkpoint,
295
+ streamMode,
296
+ }));
297
+ // Unbranch things
298
+ const newPath = submitOptions?.checkpoint?.checkpoint_id
299
+ ? flatPaths[submitOptions?.checkpoint?.checkpoint_id]?.current
300
+ : undefined;
301
+ if (newPath != null)
302
+ setBranchPath(newPath ?? []);
303
+ // Assumption: we're setting the initial value
304
+ // Used for instant feedback
305
+ setStreamValues(() => {
306
+ const values = { ...historyValues };
307
+ if (submitOptions?.optimisticValues != null) {
308
+ return {
309
+ ...values,
310
+ ...(typeof submitOptions.optimisticValues === "function"
311
+ ? submitOptions.optimisticValues(values)
312
+ : submitOptions.optimisticValues),
313
+ };
314
+ }
315
+ return values;
316
+ });
317
+ let streamError;
318
+ for await (const { event, data } of run) {
319
+ setEvents((events) => [...events, { event, data }]);
320
+ if (event === "error") {
321
+ streamError = new StreamError(data);
322
+ break;
323
+ }
324
+ if (event === "updates") {
325
+ options.onUpdateEvent?.(data);
326
+ }
327
+ if (event === "custom") {
328
+ options.onCustomEvent?.(data);
329
+ }
330
+ if (event === "metadata") {
331
+ options.onMetadataEvent?.(data);
332
+ }
333
+ if (event === "values") {
334
+ setStreamValues(data);
335
+ }
336
+ if (event === "messages") {
337
+ if (!getMessages)
338
+ continue;
339
+ const [serialized] = data;
340
+ const messageId = messageManagerRef.current.add(serialized);
341
+ if (!messageId) {
342
+ console.warn("Failed to add message to manager, no message ID found");
343
+ continue;
344
+ }
345
+ setStreamValues((streamValues) => {
346
+ const values = { ...historyValues, ...streamValues };
347
+ // Assumption: we're concatenating the message
348
+ const messages = getMessages(values).slice();
349
+ const { chunk, index } = messageManagerRef.current.get(messageId, messages.length) ?? {};
350
+ if (!chunk || index == null)
351
+ return values;
352
+ messages[index] = toMessageDict(chunk);
353
+ return { ...values, [withMessages]: messages };
354
+ });
355
+ }
356
+ }
357
+ // TODO: stream created checkpoints to avoid an unnecessary network request
358
+ const result = await history.mutate(usableThreadId);
359
+ // TODO: write tests verifying that stream values are properly handled lifecycle-wise
360
+ setStreamValues(null);
361
+ if (streamError != null)
362
+ throw streamError;
363
+ const lastHead = result.at(0);
364
+ if (lastHead)
365
+ onFinish?.(lastHead);
366
+ }
367
+ catch (error) {
368
+ if (!(error instanceof Error &&
369
+ (error.name === "AbortError" || error.name === "TimeoutError"))) {
370
+ setStreamError(error);
371
+ onError?.(error);
372
+ }
373
+ }
374
+ finally {
375
+ setIsLoading(false);
376
+ // Assumption: messages are already handled, we can clear the manager
377
+ messageManagerRef.current.clear();
378
+ submittingRef.current = false;
379
+ abortRef.current = null;
380
+ }
381
+ };
382
+ const error = isLoading ? streamError : historyError;
383
+ const values = streamValues ?? historyValues;
384
+ const setBranch = (0, react_1.useCallback)((path) => setBranchPath(path.split(">")), [setBranchPath]);
385
+ return {
386
+ get values() {
387
+ trackStreamMode("values");
388
+ return values;
389
+ },
390
+ error,
391
+ isLoading,
392
+ stop,
393
+ submit,
394
+ setBranch,
395
+ get messages() {
396
+ trackStreamMode("messages-tuple");
397
+ if (getMessages == null) {
398
+ throw new Error("No messages key provided. Make sure that `useStream` contains the `messagesKey` property.");
399
+ }
400
+ return getMessages(values);
401
+ },
402
+ getMessagesMetadata(message, index) {
403
+ trackStreamMode("messages-tuple");
404
+ if (getMessages == null) {
405
+ throw new Error("No messages key provided. Make sure that `useStream` contains the `messagesKey` property.");
406
+ }
407
+ return messageMetadata?.find((m) => m.messageId === (message.id ?? index));
408
+ },
409
+ };
410
+ }
411
+ exports.useStream = useStream;
@@ -0,0 +1,46 @@
1
+ import { type ClientConfig } from "../client.js";
2
+ import type { Command, DisconnectMode, MultitaskStrategy, OnCompletionBehavior } from "../types.js";
3
+ import type { Message } from "../types.messages.js";
4
+ import type { Checkpoint, Config, Metadata, ThreadState } from "../schema.js";
5
+ import type { CustomStreamEvent, MetadataStreamEvent, StreamMode, UpdatesStreamEvent } from "../types.stream.js";
6
+ export type MessageMetadata<StateType extends Record<string, unknown>> = {
7
+ messageId: string;
8
+ firstSeenState: ThreadState<StateType> | undefined;
9
+ branch: string | undefined;
10
+ branchOptions: string[] | undefined;
11
+ };
12
+ export declare function useStream<StateType extends Record<string, unknown> = Record<string, unknown>, UpdateType extends Record<string, unknown> = Partial<StateType>, CustomType = unknown>(options: {
13
+ assistantId: string;
14
+ apiUrl: ClientConfig["apiUrl"];
15
+ apiKey?: ClientConfig["apiKey"];
16
+ withMessages?: string;
17
+ onError?: (error: unknown) => void;
18
+ onFinish?: (state: ThreadState<StateType>) => void;
19
+ onUpdateEvent?: (data: UpdatesStreamEvent<UpdateType>["data"]) => void;
20
+ onCustomEvent?: (data: CustomStreamEvent<CustomType>["data"]) => void;
21
+ onMetadataEvent?: (data: MetadataStreamEvent["data"]) => void;
22
+ threadId?: string | null;
23
+ onThreadId?: (threadId: string) => void;
24
+ }): {
25
+ readonly values: StateType;
26
+ error: unknown;
27
+ isLoading: boolean;
28
+ stop: () => void;
29
+ submit: (values: UpdateType | undefined, submitOptions?: {
30
+ config?: Config;
31
+ checkpoint?: Omit<Checkpoint, "thread_id"> | null;
32
+ command?: Command;
33
+ interruptBefore?: "*" | string[];
34
+ interruptAfter?: "*" | string[];
35
+ metadata?: Metadata;
36
+ multitaskStrategy?: MultitaskStrategy;
37
+ onCompletion?: OnCompletionBehavior;
38
+ onDisconnect?: DisconnectMode;
39
+ feedbackKeys?: string[];
40
+ streamMode?: Array<StreamMode>;
41
+ optimisticValues?: Partial<StateType> | ((prev: StateType) => Partial<StateType>);
42
+ }) => Promise<void>;
43
+ setBranch: (path: string) => void;
44
+ readonly messages: Message[];
45
+ getMessagesMetadata(message: Message, index?: number): MessageMetadata<StateType> | undefined;
46
+ };