@langchain/langgraph-sdk 0.0.38 → 0.0.40

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 (40) hide show
  1. package/dist/client.cjs +10 -67
  2. package/dist/client.d.ts +18 -23
  3. package/dist/client.js +10 -67
  4. package/dist/index.d.ts +3 -0
  5. package/dist/react/debug.cjs +32 -0
  6. package/dist/react/debug.d.ts +23 -0
  7. package/dist/react/debug.js +28 -0
  8. package/dist/react/index.cjs +5 -0
  9. package/dist/react/index.d.ts +1 -0
  10. package/dist/react/index.js +1 -0
  11. package/dist/react/stream.cjs +411 -0
  12. package/dist/react/stream.d.ts +46 -0
  13. package/dist/react/stream.js +407 -0
  14. package/dist/types.d.ts +6 -16
  15. package/dist/types.messages.d.ts +88 -0
  16. package/dist/types.stream.cjs +2 -0
  17. package/dist/types.stream.d.ts +156 -0
  18. package/dist/types.stream.js +1 -0
  19. package/dist/utils/async_caller.cjs +2 -1
  20. package/dist/utils/async_caller.js +2 -1
  21. package/dist/utils/sse.cjs +157 -0
  22. package/dist/utils/sse.d.ts +11 -0
  23. package/dist/utils/sse.js +152 -0
  24. package/package.json +23 -3
  25. package/react.cjs +1 -0
  26. package/react.d.cts +1 -0
  27. package/react.d.ts +1 -0
  28. package/react.js +1 -0
  29. package/dist/utils/eventsource-parser/index.cjs +0 -7
  30. package/dist/utils/eventsource-parser/index.d.ts +0 -2
  31. package/dist/utils/eventsource-parser/index.js +0 -3
  32. package/dist/utils/eventsource-parser/parse.cjs +0 -150
  33. package/dist/utils/eventsource-parser/parse.d.ts +0 -18
  34. package/dist/utils/eventsource-parser/parse.js +0 -146
  35. package/dist/utils/eventsource-parser/stream.cjs +0 -34
  36. package/dist/utils/eventsource-parser/stream.d.ts +0 -17
  37. package/dist/utils/eventsource-parser/stream.js +0 -30
  38. package/dist/utils/eventsource-parser/types.d.ts +0 -81
  39. /package/dist/{utils/eventsource-parser/types.cjs → types.messages.cjs} +0 -0
  40. /package/dist/{utils/eventsource-parser/types.js → types.messages.js} +0 -0
@@ -0,0 +1,407 @@
1
+ /* __LC_ALLOW_ENTRYPOINT_SIDE_EFFECTS__ */
2
+ "use client";
3
+ import { Client } from "../client.js";
4
+ import { useCallback, useEffect, useMemo, useRef, useState, } from "react";
5
+ import { coerceMessageLikeToMessage, convertToChunk, } from "@langchain/core/messages";
6
+ class StreamError extends Error {
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
+ class MessageTupleManager {
16
+ constructor() {
17
+ Object.defineProperty(this, "chunks", {
18
+ enumerable: true,
19
+ configurable: true,
20
+ writable: true,
21
+ value: {}
22
+ });
23
+ this.chunks = {};
24
+ }
25
+ add(serialized) {
26
+ const chunk = convertToChunk(coerceMessageLikeToMessage(serialized));
27
+ const id = chunk.id;
28
+ if (!id)
29
+ return null;
30
+ this.chunks[id] ??= {};
31
+ this.chunks[id].chunk = this.chunks[id]?.chunk?.concat(chunk) ?? chunk;
32
+ return id;
33
+ }
34
+ clear() {
35
+ this.chunks = {};
36
+ }
37
+ get(id, defaultIndex) {
38
+ if (this.chunks[id] == null)
39
+ return null;
40
+ this.chunks[id].index ??= defaultIndex;
41
+ return this.chunks[id];
42
+ }
43
+ }
44
+ const toMessageDict = (chunk) => {
45
+ const { type, data } = chunk.toDict();
46
+ return { ...data, type };
47
+ };
48
+ function unique(array) {
49
+ return [...new Set(array)];
50
+ }
51
+ function findLastIndex(array, predicate) {
52
+ for (let i = array.length - 1; i >= 0; i--) {
53
+ if (predicate(array[i]))
54
+ return i;
55
+ }
56
+ return -1;
57
+ }
58
+ function fetchHistory(client, threadId) {
59
+ return client.threads.getHistory(threadId, { limit: 1000 });
60
+ }
61
+ function useThreadHistory(threadId, client, clearCallbackRef, submittingRef) {
62
+ const [history, setHistory] = useState([]);
63
+ const fetcher = useCallback((threadId) => {
64
+ if (threadId != null) {
65
+ return fetchHistory(client, threadId).then((history) => {
66
+ setHistory(history);
67
+ return history;
68
+ });
69
+ }
70
+ setHistory([]);
71
+ clearCallbackRef.current?.();
72
+ return Promise.resolve([]);
73
+ }, []);
74
+ useEffect(() => {
75
+ if (submittingRef.current)
76
+ return;
77
+ fetcher(threadId);
78
+ }, [fetcher, submittingRef, threadId]);
79
+ return {
80
+ data: history,
81
+ mutate: (mutateId) => fetcher(mutateId ?? threadId),
82
+ };
83
+ }
84
+ export function useStream(options) {
85
+ const { assistantId, threadId, withMessages, onError, onFinish } = options;
86
+ const client = useMemo(() => new Client({ apiUrl: options.apiUrl, apiKey: options.apiKey }), [options.apiKey, options.apiUrl]);
87
+ const [branchPath, setBranchPath] = useState([]);
88
+ const [isLoading, setIsLoading] = useState(false);
89
+ const [_, setEvents] = useState([]);
90
+ const [streamError, setStreamError] = useState(undefined);
91
+ const [streamValues, setStreamValues] = useState(null);
92
+ const messageManagerRef = useRef(new MessageTupleManager());
93
+ const submittingRef = useRef(false);
94
+ const abortRef = useRef(null);
95
+ const trackStreamModeRef = useRef(["values", "messages-tuple"]);
96
+ const trackStreamMode = useCallback((mode) => {
97
+ if (!trackStreamModeRef.current.includes(mode))
98
+ trackStreamModeRef.current.push(mode);
99
+ }, []);
100
+ const hasUpdateListener = options.onUpdateEvent != null;
101
+ const hasCustomListener = options.onCustomEvent != null;
102
+ const callbackStreamMode = useMemo(() => {
103
+ const modes = [];
104
+ if (hasUpdateListener)
105
+ modes.push("updates");
106
+ if (hasCustomListener)
107
+ modes.push("custom");
108
+ return modes;
109
+ }, [hasUpdateListener, hasCustomListener]);
110
+ const clearCallbackRef = useRef(null);
111
+ clearCallbackRef.current = () => {
112
+ setStreamError(undefined);
113
+ setStreamValues(null);
114
+ };
115
+ // TODO: this should be done on the server to avoid pagination
116
+ // TODO: should we permit adapter? SWR / React Query?
117
+ const history = useThreadHistory(threadId, client, clearCallbackRef, submittingRef);
118
+ const getMessages = useMemo(() => {
119
+ if (withMessages == null)
120
+ return undefined;
121
+ return (value) => Array.isArray(value[withMessages])
122
+ ? value[withMessages]
123
+ : [];
124
+ }, [withMessages]);
125
+ const [sequence, pathMap] = (() => {
126
+ const childrenMap = {};
127
+ // First pass - collect nodes for each checkpoint
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);
207
+ const historyValues = threadHead?.values ?? {};
208
+ const historyError = (() => {
209
+ const error = threadHead?.tasks?.at(-1)?.error;
210
+ if (error == null)
211
+ return undefined;
212
+ try {
213
+ const parsed = JSON.parse(error);
214
+ if (StreamError.isStructuredError(parsed)) {
215
+ return new StreamError(parsed);
216
+ }
217
+ return parsed;
218
+ }
219
+ catch {
220
+ // do nothing
221
+ }
222
+ return error;
223
+ })();
224
+ const messageMetadata = (() => {
225
+ if (getMessages == null)
226
+ return undefined;
227
+ const alreadyShown = new Set();
228
+ return getMessages(historyValues).map((message, idx) => {
229
+ const messageId = message.id ?? idx;
230
+ const firstSeenIdx = findLastIndex(history.data, (state) => getMessages(state.values)
231
+ .map((m, idx) => m.id ?? idx)
232
+ .includes(messageId));
233
+ const firstSeen = history.data[firstSeenIdx];
234
+ let branch = firstSeen
235
+ ? flatPaths[firstSeen.checkpoint.checkpoint_id]
236
+ : undefined;
237
+ if (!branch?.current?.length)
238
+ branch = undefined;
239
+ // serialize branches
240
+ const optionsShown = branch?.branches?.flat(2).join(",");
241
+ if (optionsShown) {
242
+ if (alreadyShown.has(optionsShown))
243
+ branch = undefined;
244
+ alreadyShown.add(optionsShown);
245
+ }
246
+ return {
247
+ messageId: messageId.toString(),
248
+ firstSeenState: firstSeen,
249
+ branch: branch?.current?.join(">"),
250
+ branchOptions: branch?.branches?.map((b) => b.join(">")),
251
+ };
252
+ });
253
+ })();
254
+ const stop = useCallback(() => {
255
+ if (abortRef.current != null)
256
+ abortRef.current.abort();
257
+ abortRef.current = null;
258
+ }, []);
259
+ const submit = async (values, submitOptions) => {
260
+ try {
261
+ setIsLoading(true);
262
+ setStreamError(undefined);
263
+ submittingRef.current = true;
264
+ abortRef.current = new AbortController();
265
+ let usableThreadId = threadId;
266
+ if (!usableThreadId) {
267
+ const thread = await client.threads.create();
268
+ options?.onThreadId?.(thread.thread_id);
269
+ usableThreadId = thread.thread_id;
270
+ }
271
+ const streamMode = unique([
272
+ ...(submitOptions?.streamMode ?? []),
273
+ ...trackStreamModeRef.current,
274
+ ...callbackStreamMode,
275
+ ]);
276
+ const checkpoint = submitOptions?.checkpoint ?? threadHead?.checkpoint ?? undefined;
277
+ // @ts-expect-error
278
+ if (checkpoint != null)
279
+ delete checkpoint.thread_id;
280
+ const run = (await client.runs.stream(usableThreadId, assistantId, {
281
+ input: values,
282
+ config: submitOptions?.config,
283
+ command: submitOptions?.command,
284
+ interruptBefore: submitOptions?.interruptBefore,
285
+ interruptAfter: submitOptions?.interruptAfter,
286
+ metadata: submitOptions?.metadata,
287
+ multitaskStrategy: submitOptions?.multitaskStrategy,
288
+ onCompletion: submitOptions?.onCompletion,
289
+ onDisconnect: submitOptions?.onDisconnect ?? "cancel",
290
+ signal: abortRef.current.signal,
291
+ checkpoint,
292
+ streamMode,
293
+ }));
294
+ // Unbranch things
295
+ const newPath = submitOptions?.checkpoint?.checkpoint_id
296
+ ? flatPaths[submitOptions?.checkpoint?.checkpoint_id]?.current
297
+ : undefined;
298
+ if (newPath != null)
299
+ setBranchPath(newPath ?? []);
300
+ // Assumption: we're setting the initial value
301
+ // Used for instant feedback
302
+ setStreamValues(() => {
303
+ const values = { ...historyValues };
304
+ if (submitOptions?.optimisticValues != null) {
305
+ return {
306
+ ...values,
307
+ ...(typeof submitOptions.optimisticValues === "function"
308
+ ? submitOptions.optimisticValues(values)
309
+ : submitOptions.optimisticValues),
310
+ };
311
+ }
312
+ return values;
313
+ });
314
+ let streamError;
315
+ for await (const { event, data } of run) {
316
+ setEvents((events) => [...events, { event, data }]);
317
+ if (event === "error") {
318
+ streamError = new StreamError(data);
319
+ break;
320
+ }
321
+ if (event === "updates") {
322
+ options.onUpdateEvent?.(data);
323
+ }
324
+ if (event === "custom") {
325
+ options.onCustomEvent?.(data);
326
+ }
327
+ if (event === "metadata") {
328
+ options.onMetadataEvent?.(data);
329
+ }
330
+ if (event === "values") {
331
+ setStreamValues(data);
332
+ }
333
+ if (event === "messages") {
334
+ if (!getMessages)
335
+ continue;
336
+ const [serialized] = data;
337
+ const messageId = messageManagerRef.current.add(serialized);
338
+ if (!messageId) {
339
+ console.warn("Failed to add message to manager, no message ID found");
340
+ continue;
341
+ }
342
+ setStreamValues((streamValues) => {
343
+ const values = { ...historyValues, ...streamValues };
344
+ // Assumption: we're concatenating the message
345
+ const messages = getMessages(values).slice();
346
+ const { chunk, index } = messageManagerRef.current.get(messageId, messages.length) ?? {};
347
+ if (!chunk || index == null)
348
+ return values;
349
+ messages[index] = toMessageDict(chunk);
350
+ return { ...values, [withMessages]: messages };
351
+ });
352
+ }
353
+ }
354
+ // TODO: stream created checkpoints to avoid an unnecessary network request
355
+ const result = await history.mutate(usableThreadId);
356
+ // TODO: write tests verifying that stream values are properly handled lifecycle-wise
357
+ setStreamValues(null);
358
+ if (streamError != null)
359
+ throw streamError;
360
+ const lastHead = result.at(0);
361
+ if (lastHead)
362
+ onFinish?.(lastHead);
363
+ }
364
+ catch (error) {
365
+ if (!(error instanceof Error &&
366
+ (error.name === "AbortError" || error.name === "TimeoutError"))) {
367
+ setStreamError(error);
368
+ onError?.(error);
369
+ }
370
+ }
371
+ finally {
372
+ setIsLoading(false);
373
+ // Assumption: messages are already handled, we can clear the manager
374
+ messageManagerRef.current.clear();
375
+ submittingRef.current = false;
376
+ abortRef.current = null;
377
+ }
378
+ };
379
+ const error = isLoading ? streamError : historyError;
380
+ const values = streamValues ?? historyValues;
381
+ const setBranch = useCallback((path) => setBranchPath(path.split(">")), [setBranchPath]);
382
+ return {
383
+ get values() {
384
+ trackStreamMode("values");
385
+ return values;
386
+ },
387
+ error,
388
+ isLoading,
389
+ stop,
390
+ submit,
391
+ setBranch,
392
+ get messages() {
393
+ 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
+ return getMessages(values);
398
+ },
399
+ getMessagesMetadata(message, index) {
400
+ 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
+ return messageMetadata?.find((m) => m.messageId === (message.id ?? index));
405
+ },
406
+ };
407
+ }
package/dist/types.d.ts CHANGED
@@ -1,20 +1,10 @@
1
1
  import { Checkpoint, Config, Metadata } from "./schema.js";
2
- /**
3
- * Stream modes
4
- * - "values": Stream only the state values.
5
- * - "messages": Stream complete messages.
6
- * - "messages-tuple": Stream (message chunk, metadata) tuples.
7
- * - "updates": Stream updates to the state.
8
- * - "events": Stream events occurring during execution.
9
- * - "debug": Stream detailed debug information.
10
- * - "custom": Stream custom events.
11
- */
12
- export type StreamMode = "values" | "messages" | "updates" | "events" | "debug" | "custom" | "messages-tuple";
2
+ import { StreamMode } from "./types.stream.js";
13
3
  export type MultitaskStrategy = "reject" | "interrupt" | "rollback" | "enqueue";
14
4
  export type OnConflictBehavior = "raise" | "do_nothing";
15
5
  export type OnCompletionBehavior = "complete" | "continue";
16
6
  export type DisconnectMode = "cancel" | "continue";
17
- export type StreamEvent = "events" | "metadata" | "debug" | "updates" | "values" | "messages/partial" | "messages/metadata" | "messages/complete" | (string & {});
7
+ export type StreamEvent = "events" | "metadata" | "debug" | "updates" | "values" | "messages/partial" | "messages/metadata" | "messages/complete" | "messages" | (string & {});
18
8
  export interface Send {
19
9
  node: string;
20
10
  input: Record<string, unknown> | null;
@@ -23,7 +13,7 @@ export interface Command {
23
13
  /**
24
14
  * An object to update the thread state with.
25
15
  */
26
- update?: Record<string, unknown> | [string, unknown][];
16
+ update?: Record<string, unknown> | [string, unknown][] | null;
27
17
  /**
28
18
  * The value to return from an `interrupt` function call.
29
19
  */
@@ -111,15 +101,15 @@ interface RunsInvokePayload {
111
101
  */
112
102
  command?: Command;
113
103
  }
114
- export interface RunsStreamPayload extends RunsInvokePayload {
104
+ export interface RunsStreamPayload<TStreamMode extends StreamMode | StreamMode[] = [], TSubgraphs extends boolean = false> extends RunsInvokePayload {
115
105
  /**
116
106
  * One of `"values"`, `"messages"`, `"messages-tuple"`, `"updates"`, `"events"`, `"debug"`, `"custom"`.
117
107
  */
118
- streamMode?: StreamMode | Array<StreamMode>;
108
+ streamMode?: TStreamMode;
119
109
  /**
120
110
  * Stream output from subgraphs. By default, streams only the top graph.
121
111
  */
122
- streamSubgraphs?: boolean;
112
+ streamSubgraphs?: TSubgraphs;
123
113
  /**
124
114
  * Pass one or more feedbackKeys if you want to request short-lived signed URLs
125
115
  * for submitting feedback to LangSmith with this key for this run.
@@ -0,0 +1,88 @@
1
+ type ImageDetail = "auto" | "low" | "high";
2
+ type MessageContentImageUrl = {
3
+ type: "image_url";
4
+ image_url: string | {
5
+ url: string;
6
+ detail?: ImageDetail | undefined;
7
+ };
8
+ };
9
+ type MessageContentText = {
10
+ type: "text";
11
+ text: string;
12
+ };
13
+ type MessageContentComplex = MessageContentText | MessageContentImageUrl;
14
+ type MessageContent = string | MessageContentComplex[];
15
+ /**
16
+ * Model-specific additional kwargs, which is passed back to the underlying LLM.
17
+ */
18
+ type MessageAdditionalKwargs = Record<string, unknown>;
19
+ export type HumanMessage = {
20
+ type: "human";
21
+ id?: string | undefined;
22
+ content: MessageContent;
23
+ };
24
+ export type AIMessage = {
25
+ type: "ai";
26
+ id?: string | undefined;
27
+ content: MessageContent;
28
+ tool_calls?: {
29
+ name: string;
30
+ args: {
31
+ [x: string]: {
32
+ [x: string]: any;
33
+ };
34
+ };
35
+ id?: string | undefined;
36
+ type?: "tool_call" | undefined;
37
+ }[] | undefined;
38
+ invalid_tool_calls?: {
39
+ name?: string | undefined;
40
+ args?: string | undefined;
41
+ id?: string | undefined;
42
+ error?: string | undefined;
43
+ type?: "invalid_tool_call" | undefined;
44
+ }[] | undefined;
45
+ usage_metadata?: {
46
+ input_tokens: number;
47
+ output_tokens: number;
48
+ total_tokens: number;
49
+ input_token_details?: {
50
+ audio?: number | undefined;
51
+ cache_read?: number | undefined;
52
+ cache_creation?: number | undefined;
53
+ } | undefined;
54
+ output_token_details?: {
55
+ audio?: number | undefined;
56
+ reasoning?: number | undefined;
57
+ } | undefined;
58
+ } | undefined;
59
+ additional_kwargs?: MessageAdditionalKwargs | undefined;
60
+ response_metadata?: Record<string, unknown> | undefined;
61
+ };
62
+ export type ToolMessage = {
63
+ type: "tool";
64
+ name?: string | undefined;
65
+ id?: string | undefined;
66
+ content: MessageContent;
67
+ status?: "error" | "success" | undefined;
68
+ tool_call_id: string;
69
+ additional_kwargs?: MessageAdditionalKwargs | undefined;
70
+ response_metadata?: Record<string, unknown> | undefined;
71
+ };
72
+ export type SystemMessage = {
73
+ type: "system";
74
+ id?: string | undefined;
75
+ content: MessageContent;
76
+ };
77
+ export type FunctionMessage = {
78
+ type: "function";
79
+ id?: string | undefined;
80
+ content: MessageContent;
81
+ };
82
+ export type RemoveMessage = {
83
+ type: "remove";
84
+ id: string;
85
+ content: MessageContent;
86
+ };
87
+ export type Message = HumanMessage | AIMessage | ToolMessage | SystemMessage | FunctionMessage | RemoveMessage;
88
+ export {};
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });