@langchain/langgraph-sdk 0.1.6 → 0.1.8

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 CHANGED
@@ -1,5 +1,17 @@
1
1
  # @langchain/langgraph-sdk
2
2
 
3
+ ## 0.1.8
4
+
5
+ ### Patch Changes
6
+
7
+ - 90dcb8b: Add support for managing thread state manually outside of useStream hook via `experimental_thread` option
8
+
9
+ ## 0.1.7
10
+
11
+ ### Patch Changes
12
+
13
+ - bbc90e6: Fix thread history state being kept stale when changing `thread_id`
14
+
3
15
  ## 0.1.6
4
16
 
5
17
  ### Patch Changes
@@ -1,3 +1,3 @@
1
1
  export { useStream } from "./stream.js";
2
2
  export { FetchStreamTransport } from "./stream.custom.js";
3
- export type { MessageMetadata, UseStream, UseStreamOptions, UseStreamCustom, UseStreamCustomOptions, UseStreamTransport, } from "./types.js";
3
+ export type { MessageMetadata, UseStream, UseStreamOptions, UseStreamCustom, UseStreamCustomOptions, UseStreamTransport, UseStreamThread, } from "./types.js";
@@ -6,8 +6,8 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.FetchStreamTransport = void 0;
7
7
  exports.useStreamCustom = useStreamCustom;
8
8
  const react_1 = require("react");
9
- const manager_js_1 = require("./manager.cjs");
10
- const messages_js_1 = require("./messages.cjs");
9
+ const manager_js_1 = require("../ui/manager.cjs");
10
+ const messages_js_1 = require("../ui/messages.cjs");
11
11
  const sse_js_1 = require("../utils/sse.cjs");
12
12
  const stream_js_1 = require("../utils/stream.cjs");
13
13
  const thread_js_1 = require("./thread.cjs");
@@ -1,8 +1,8 @@
1
1
  /* __LC_ALLOW_ENTRYPOINT_SIDE_EFFECTS__ */
2
2
  "use client";
3
3
  import { useEffect, useRef, useState, useSyncExternalStore } from "react";
4
- import { StreamManager } from "./manager.js";
5
- import { MessageTupleManager } from "./messages.js";
4
+ import { StreamManager } from "../ui/manager.js";
5
+ import { MessageTupleManager } from "../ui/messages.js";
6
6
  import { BytesLineDecoder, SSEDecoder } from "../utils/sse.js";
7
7
  import { IterableReadableStream } from "../utils/stream.js";
8
8
  import { useControllableThreadId } from "./thread.js";
@@ -5,12 +5,12 @@
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.useStreamLGP = useStreamLGP;
7
7
  const react_1 = require("react");
8
- const utils_js_1 = require("./utils.cjs");
9
- const errors_js_1 = require("./errors.cjs");
10
- const branching_js_1 = require("./branching.cjs");
11
- const manager_js_1 = require("./manager.cjs");
8
+ const utils_js_1 = require("../ui/utils.cjs");
9
+ const errors_js_1 = require("../ui/errors.cjs");
10
+ const branching_js_1 = require("../ui/branching.cjs");
11
+ const manager_js_1 = require("../ui/manager.cjs");
12
12
  const client_js_1 = require("../client.cjs");
13
- const messages_js_1 = require("./messages.cjs");
13
+ const messages_js_1 = require("../ui/messages.cjs");
14
14
  const thread_js_1 = require("./thread.cjs");
15
15
  function getFetchHistoryKey(client, threadId, limit) {
16
16
  return [(0, client_js_1.getClientConfigHash)(client), threadId, limit].join(":");
@@ -29,6 +29,7 @@ function fetchHistory(client, threadId, options) {
29
29
  function useThreadHistory(client, threadId, limit, options) {
30
30
  const key = getFetchHistoryKey(client, threadId, limit);
31
31
  const [state, setState] = (0, react_1.useState)(() => ({
32
+ key: undefined,
32
33
  data: undefined,
33
34
  error: undefined,
34
35
  isLoading: threadId != null,
@@ -38,21 +39,37 @@ function useThreadHistory(client, threadId, limit, options) {
38
39
  const onErrorRef = (0, react_1.useRef)(options?.onError);
39
40
  onErrorRef.current = options?.onError;
40
41
  const fetcher = (0, react_1.useCallback)((threadId, limit) => {
42
+ // If only passthrough is enabled, don't fetch history
43
+ if (options.passthrough)
44
+ return Promise.resolve([]);
45
+ const client = clientRef.current;
46
+ const key = getFetchHistoryKey(client, threadId, limit);
41
47
  if (threadId != null) {
42
- const client = clientRef.current;
43
- setState((state) => ({ ...state, isLoading: true }));
48
+ setState((state) => {
49
+ if (state.key === key)
50
+ return { ...state, isLoading: true };
51
+ return { key, data: undefined, error: undefined, isLoading: true };
52
+ });
44
53
  return fetchHistory(client, threadId, { limit }).then((data) => {
45
- setState({ data, error: undefined, isLoading: false });
54
+ setState((state) => {
55
+ if (state.key !== key)
56
+ return state;
57
+ return { key, data, error: undefined, isLoading: false };
58
+ });
46
59
  return data;
47
60
  }, (error) => {
48
- setState(({ data }) => ({ data, error, isLoading: false }));
61
+ setState((state) => {
62
+ if (state.key !== key)
63
+ return state;
64
+ return { key, data: state.data, error, isLoading: false };
65
+ });
49
66
  onErrorRef.current?.(error);
50
67
  return Promise.reject(error);
51
68
  });
52
69
  }
53
- setState({ data: undefined, error: undefined, isLoading: false });
70
+ setState({ key, data: undefined, error: undefined, isLoading: false });
54
71
  return Promise.resolve([]);
55
- }, []);
72
+ }, [options.passthrough]);
56
73
  (0, react_1.useEffect)(() => {
57
74
  // Skip if a stream is already in progress, no need to fetch history
58
75
  if (options.submittingRef.current != null &&
@@ -152,10 +169,12 @@ function useStreamLGP(options) {
152
169
  options.fetchStateHistory != null
153
170
  ? options.fetchStateHistory.limit ?? false
154
171
  : options.fetchStateHistory ?? false;
155
- const history = useThreadHistory(client, threadId, historyLimit, {
172
+ const builtInHistory = useThreadHistory(client, threadId, historyLimit, {
173
+ passthrough: options.experimental_thread != null,
156
174
  submittingRef: threadIdStreamingRef,
157
175
  onError: options.onError,
158
176
  });
177
+ const history = options.experimental_thread ?? builtInHistory;
159
178
  const getMessages = (value) => {
160
179
  const messagesKey = options.messagesKey ?? "messages";
161
180
  return Array.isArray(value[messagesKey]) ? value[messagesKey] : [];
@@ -165,7 +184,7 @@ function useStreamLGP(options) {
165
184
  return { ...current, [messagesKey]: messages };
166
185
  };
167
186
  const [branch, setBranch] = (0, react_1.useState)("");
168
- const branchContext = (0, branching_js_1.getBranchContext)(branch, history.data);
187
+ const branchContext = (0, branching_js_1.getBranchContext)(branch, history.data ?? undefined);
169
188
  const historyValues = branchContext.threadHead?.values ??
170
189
  options.initialValues ??
171
190
  {};
@@ -330,7 +349,7 @@ function useStreamLGP(options) {
330
349
  includeImplicitBranch;
331
350
  if (shouldRefetch) {
332
351
  const newHistory = await history.mutate(usableThreadId);
333
- const lastHead = newHistory.at(0);
352
+ const lastHead = newHistory?.at(0);
334
353
  if (lastHead) {
335
354
  // We now have the latest update from /history
336
355
  // Thus we can clear the local stream state
@@ -372,7 +391,7 @@ function useStreamLGP(options) {
372
391
  async onSuccess() {
373
392
  runMetadataStorage?.removeItem(`lg:stream:${threadId}`);
374
393
  const newHistory = await history.mutate(threadId);
375
- const lastHead = newHistory.at(0);
394
+ const lastHead = newHistory?.at(0);
376
395
  if (lastHead)
377
396
  options.onFinish?.(lastHead, callbackMeta);
378
397
  },
@@ -1,12 +1,12 @@
1
1
  /* __LC_ALLOW_ENTRYPOINT_SIDE_EFFECTS__ */
2
2
  "use client";
3
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";
4
+ import { findLast, unique } from "../ui/utils.js";
5
+ import { StreamError } from "../ui/errors.js";
6
+ import { getBranchContext } from "../ui/branching.js";
7
+ import { StreamManager } from "../ui/manager.js";
8
8
  import { Client, getClientConfigHash } from "../client.js";
9
- import { MessageTupleManager } from "./messages.js";
9
+ import { MessageTupleManager } from "../ui/messages.js";
10
10
  import { useControllableThreadId } from "./thread.js";
11
11
  function getFetchHistoryKey(client, threadId, limit) {
12
12
  return [getClientConfigHash(client), threadId, limit].join(":");
@@ -25,6 +25,7 @@ function fetchHistory(client, threadId, options) {
25
25
  function useThreadHistory(client, threadId, limit, options) {
26
26
  const key = getFetchHistoryKey(client, threadId, limit);
27
27
  const [state, setState] = useState(() => ({
28
+ key: undefined,
28
29
  data: undefined,
29
30
  error: undefined,
30
31
  isLoading: threadId != null,
@@ -34,21 +35,37 @@ function useThreadHistory(client, threadId, limit, options) {
34
35
  const onErrorRef = useRef(options?.onError);
35
36
  onErrorRef.current = options?.onError;
36
37
  const fetcher = useCallback((threadId, limit) => {
38
+ // If only passthrough is enabled, don't fetch history
39
+ if (options.passthrough)
40
+ return Promise.resolve([]);
41
+ const client = clientRef.current;
42
+ const key = getFetchHistoryKey(client, threadId, limit);
37
43
  if (threadId != null) {
38
- const client = clientRef.current;
39
- setState((state) => ({ ...state, isLoading: true }));
44
+ setState((state) => {
45
+ if (state.key === key)
46
+ return { ...state, isLoading: true };
47
+ return { key, data: undefined, error: undefined, isLoading: true };
48
+ });
40
49
  return fetchHistory(client, threadId, { limit }).then((data) => {
41
- setState({ data, error: undefined, isLoading: false });
50
+ setState((state) => {
51
+ if (state.key !== key)
52
+ return state;
53
+ return { key, data, error: undefined, isLoading: false };
54
+ });
42
55
  return data;
43
56
  }, (error) => {
44
- setState(({ data }) => ({ data, error, isLoading: false }));
57
+ setState((state) => {
58
+ if (state.key !== key)
59
+ return state;
60
+ return { key, data: state.data, error, isLoading: false };
61
+ });
45
62
  onErrorRef.current?.(error);
46
63
  return Promise.reject(error);
47
64
  });
48
65
  }
49
- setState({ data: undefined, error: undefined, isLoading: false });
66
+ setState({ key, data: undefined, error: undefined, isLoading: false });
50
67
  return Promise.resolve([]);
51
- }, []);
68
+ }, [options.passthrough]);
52
69
  useEffect(() => {
53
70
  // Skip if a stream is already in progress, no need to fetch history
54
71
  if (options.submittingRef.current != null &&
@@ -148,10 +165,12 @@ export function useStreamLGP(options) {
148
165
  options.fetchStateHistory != null
149
166
  ? options.fetchStateHistory.limit ?? false
150
167
  : options.fetchStateHistory ?? false;
151
- const history = useThreadHistory(client, threadId, historyLimit, {
168
+ const builtInHistory = useThreadHistory(client, threadId, historyLimit, {
169
+ passthrough: options.experimental_thread != null,
152
170
  submittingRef: threadIdStreamingRef,
153
171
  onError: options.onError,
154
172
  });
173
+ const history = options.experimental_thread ?? builtInHistory;
155
174
  const getMessages = (value) => {
156
175
  const messagesKey = options.messagesKey ?? "messages";
157
176
  return Array.isArray(value[messagesKey]) ? value[messagesKey] : [];
@@ -161,7 +180,7 @@ export function useStreamLGP(options) {
161
180
  return { ...current, [messagesKey]: messages };
162
181
  };
163
182
  const [branch, setBranch] = useState("");
164
- const branchContext = getBranchContext(branch, history.data);
183
+ const branchContext = getBranchContext(branch, history.data ?? undefined);
165
184
  const historyValues = branchContext.threadHead?.values ??
166
185
  options.initialValues ??
167
186
  {};
@@ -326,7 +345,7 @@ export function useStreamLGP(options) {
326
345
  includeImplicitBranch;
327
346
  if (shouldRefetch) {
328
347
  const newHistory = await history.mutate(usableThreadId);
329
- const lastHead = newHistory.at(0);
348
+ const lastHead = newHistory?.at(0);
330
349
  if (lastHead) {
331
350
  // We now have the latest update from /history
332
351
  // Thus we can clear the local stream state
@@ -368,7 +387,7 @@ export function useStreamLGP(options) {
368
387
  async onSuccess() {
369
388
  runMetadataStorage?.removeItem(`lg:stream:${threadId}`);
370
389
  const newHistory = await history.mutate(threadId);
371
- const lastHead = newHistory.at(0);
390
+ const lastHead = newHistory?.at(0);
372
391
  if (lastHead)
373
392
  options.onFinish?.(lastHead, callbackMeta);
374
393
  },
@@ -3,7 +3,7 @@ import type { ThreadState, Interrupt, Config, Checkpoint, Metadata } from "../sc
3
3
  import type { Command, MultitaskStrategy, OnCompletionBehavior, DisconnectMode, Durability } from "../types.js";
4
4
  import type { Message } from "../types.messages.js";
5
5
  import type { UpdatesStreamEvent, CustomStreamEvent, MetadataStreamEvent, EventsStreamEvent, DebugStreamEvent, CheckpointsStreamEvent, TasksStreamEvent, StreamMode } from "../types.stream.js";
6
- import type { Sequence } from "./branching.js";
6
+ import type { Sequence } from "../ui/branching.js";
7
7
  export type MessageMetadata<StateType extends Record<string, unknown>> = {
8
8
  /**
9
9
  * The ID of the message used.
@@ -50,6 +50,12 @@ export interface RunCallbackMeta {
50
50
  run_id: string;
51
51
  thread_id: string;
52
52
  }
53
+ export interface UseStreamThread<StateType extends Record<string, unknown>> {
54
+ data: ThreadState<StateType>[] | null | undefined;
55
+ error: unknown;
56
+ isLoading: boolean;
57
+ mutate: (mutateId?: string) => Promise<ThreadState<StateType>[] | null | undefined>;
58
+ }
53
59
  export interface UseStreamOptions<StateType extends Record<string, unknown> = Record<string, unknown>, Bag extends BagTemplate = BagTemplate> {
54
60
  /**
55
61
  * The ID of the assistant to use.
@@ -187,6 +193,11 @@ export interface UseStreamOptions<StateType extends Record<string, unknown> = Re
187
193
  fetchStateHistory?: boolean | {
188
194
  limit: number;
189
195
  };
196
+ /**
197
+ * Manage the thread state externally.
198
+ * @experimental
199
+ */
200
+ experimental_thread?: UseStreamThread<StateType>;
190
201
  }
191
202
  interface RunMetadataStorage {
192
203
  getItem(key: `lg:stream:${string}`): string | null;
@@ -0,0 +1,370 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const vitest_1 = require("vitest");
4
+ const branching_js_1 = require("./branching.cjs");
5
+ const history = [
6
+ {
7
+ values: {
8
+ messages: [
9
+ {
10
+ content: "Fork: Hello",
11
+ additional_kwargs: {},
12
+ response_metadata: {},
13
+ id: "33357aea-9c2d-4718-92f4-20038d5a2d29",
14
+ type: "human",
15
+ },
16
+ {
17
+ content: "Hey",
18
+ additional_kwargs: {},
19
+ response_metadata: {},
20
+ tool_call_chunks: [],
21
+ id: "run-782a2f90-2e39-4c21-9ee6-0f38585a0ae6",
22
+ tool_calls: [],
23
+ invalid_tool_calls: [],
24
+ type: "ai",
25
+ },
26
+ ],
27
+ },
28
+ next: [],
29
+ tasks: [],
30
+ metadata: {
31
+ source: "loop",
32
+ step: 2,
33
+ parents: {},
34
+ thread_id: "efa80f4d-725f-4903-b19f-216309f060bc",
35
+ },
36
+ created_at: "2025-08-21T12:47:12.640Z",
37
+ checkpoint: {
38
+ thread_id: "efa80f4d-725f-4903-b19f-216309f060bc",
39
+ checkpoint_id: "1f07e8cf-56a7-6000-8002-5e272b1d5285",
40
+ checkpoint_ns: "",
41
+ checkpoint_map: null,
42
+ },
43
+ parent_checkpoint: {
44
+ thread_id: "efa80f4d-725f-4903-b19f-216309f060bc",
45
+ checkpoint_id: "1f07e8cf-53aa-6d70-8001-2096b0bbf835",
46
+ checkpoint_ns: "",
47
+ checkpoint_map: null,
48
+ },
49
+ },
50
+ {
51
+ values: {
52
+ messages: [
53
+ {
54
+ content: "Fork: Hello",
55
+ additional_kwargs: {},
56
+ response_metadata: {},
57
+ id: "33357aea-9c2d-4718-92f4-20038d5a2d29",
58
+ type: "human",
59
+ },
60
+ ],
61
+ },
62
+ next: ["agent"],
63
+ tasks: [
64
+ {
65
+ id: "680ee831-4d10-50f9-956f-e0d937321614",
66
+ name: "agent",
67
+ error: null,
68
+ interrupts: [],
69
+ path: ["__pregel_pull", "agent"],
70
+ checkpoint: null,
71
+ state: null,
72
+ result: {
73
+ messages: [
74
+ {
75
+ content: "Hey",
76
+ additional_kwargs: {},
77
+ response_metadata: {},
78
+ tool_call_chunks: [],
79
+ id: "run-782a2f90-2e39-4c21-9ee6-0f38585a0ae6",
80
+ tool_calls: [],
81
+ invalid_tool_calls: [],
82
+ type: "ai",
83
+ },
84
+ ],
85
+ },
86
+ },
87
+ ],
88
+ metadata: {
89
+ source: "loop",
90
+ step: 1,
91
+ parents: {},
92
+ thread_id: "efa80f4d-725f-4903-b19f-216309f060bc",
93
+ },
94
+ created_at: "2025-08-21T12:47:12.327Z",
95
+ checkpoint: {
96
+ thread_id: "efa80f4d-725f-4903-b19f-216309f060bc",
97
+ checkpoint_id: "1f07e8cf-53aa-6d70-8001-2096b0bbf835",
98
+ checkpoint_ns: "",
99
+ checkpoint_map: null,
100
+ },
101
+ parent_checkpoint: {
102
+ thread_id: "efa80f4d-725f-4903-b19f-216309f060bc",
103
+ checkpoint_id: "1f07e8cf-53a8-6660-8000-f157c9cf7d66",
104
+ checkpoint_ns: "",
105
+ checkpoint_map: null,
106
+ },
107
+ },
108
+ {
109
+ values: { messages: [] },
110
+ next: ["__start__"],
111
+ tasks: [
112
+ {
113
+ id: "4233f4c0-5ea2-5117-9153-95e5fe59eee3",
114
+ name: "__start__",
115
+ error: null,
116
+ interrupts: [],
117
+ path: ["__pregel_pull", "__start__"],
118
+ checkpoint: null,
119
+ state: null,
120
+ result: { messages: [{ type: "human", content: "Fork: Hello" }] },
121
+ },
122
+ ],
123
+ metadata: {
124
+ source: "input",
125
+ step: 0,
126
+ parents: {},
127
+ thread_id: "efa80f4d-725f-4903-b19f-216309f060bc",
128
+ },
129
+ created_at: "2025-08-21T12:47:12.326Z",
130
+ checkpoint: {
131
+ thread_id: "efa80f4d-725f-4903-b19f-216309f060bc",
132
+ checkpoint_id: "1f07e8cf-53a8-6660-8000-f157c9cf7d66",
133
+ checkpoint_ns: "",
134
+ checkpoint_map: null,
135
+ },
136
+ parent_checkpoint: {
137
+ thread_id: "efa80f4d-725f-4903-b19f-216309f060bc",
138
+ checkpoint_id: "1f07e8cf-4cea-6530-ffff-0995a53299c5",
139
+ checkpoint_ns: "",
140
+ checkpoint_map: null,
141
+ },
142
+ },
143
+ {
144
+ values: {
145
+ messages: [
146
+ {
147
+ content: "Hello",
148
+ additional_kwargs: {},
149
+ response_metadata: {},
150
+ id: "ec300e63-1494-4ef6-936c-60d02d7bdce1",
151
+ type: "human",
152
+ },
153
+ {
154
+ content: "Hey",
155
+ additional_kwargs: {},
156
+ response_metadata: {},
157
+ tool_call_chunks: [],
158
+ id: "run-593064bd-dd07-4ed3-99c0-a918625f4884",
159
+ tool_calls: [],
160
+ invalid_tool_calls: [],
161
+ type: "ai",
162
+ },
163
+ ],
164
+ },
165
+ next: [],
166
+ tasks: [],
167
+ metadata: {
168
+ source: "loop",
169
+ step: 1,
170
+ parents: {},
171
+ thread_id: "efa80f4d-725f-4903-b19f-216309f060bc",
172
+ },
173
+ created_at: "2025-08-21T12:47:12.285Z",
174
+ checkpoint: {
175
+ thread_id: "efa80f4d-725f-4903-b19f-216309f060bc",
176
+ checkpoint_id: "1f07e8cf-5344-64d0-8001-8a176e91d12f",
177
+ checkpoint_ns: "",
178
+ checkpoint_map: null,
179
+ },
180
+ parent_checkpoint: {
181
+ thread_id: "efa80f4d-725f-4903-b19f-216309f060bc",
182
+ checkpoint_id: "1f07e8cf-4cf8-6f90-8000-7707fd155a8a",
183
+ checkpoint_ns: "",
184
+ checkpoint_map: null,
185
+ },
186
+ },
187
+ {
188
+ values: {
189
+ messages: [
190
+ {
191
+ content: "Hello",
192
+ additional_kwargs: {},
193
+ response_metadata: {},
194
+ id: "ec300e63-1494-4ef6-936c-60d02d7bdce1",
195
+ type: "human",
196
+ },
197
+ {
198
+ content: "Hey",
199
+ additional_kwargs: {},
200
+ response_metadata: {},
201
+ tool_call_chunks: [],
202
+ id: "run-b86ef558-eaff-4838-bc03-218f20554a9f",
203
+ tool_calls: [],
204
+ invalid_tool_calls: [],
205
+ type: "ai",
206
+ },
207
+ ],
208
+ },
209
+ next: [],
210
+ tasks: [],
211
+ metadata: {
212
+ source: "loop",
213
+ step: 1,
214
+ parents: {},
215
+ thread_id: "efa80f4d-725f-4903-b19f-216309f060bc",
216
+ },
217
+ created_at: "2025-08-21T12:47:11.936Z",
218
+ checkpoint: {
219
+ thread_id: "efa80f4d-725f-4903-b19f-216309f060bc",
220
+ checkpoint_id: "1f07e8cf-4ff0-6400-8001-a54aedf52a76",
221
+ checkpoint_ns: "",
222
+ checkpoint_map: null,
223
+ },
224
+ parent_checkpoint: {
225
+ thread_id: "efa80f4d-725f-4903-b19f-216309f060bc",
226
+ checkpoint_id: "1f07e8cf-4cf8-6f90-8000-7707fd155a8a",
227
+ checkpoint_ns: "",
228
+ checkpoint_map: null,
229
+ },
230
+ },
231
+ {
232
+ values: {
233
+ messages: [
234
+ {
235
+ content: "Hello",
236
+ additional_kwargs: {},
237
+ response_metadata: {},
238
+ id: "ec300e63-1494-4ef6-936c-60d02d7bdce1",
239
+ type: "human",
240
+ },
241
+ ],
242
+ },
243
+ next: ["agent"],
244
+ tasks: [
245
+ {
246
+ id: "e3f503f1-73bc-5db1-8b4d-13f660ad1b51",
247
+ name: "agent",
248
+ error: null,
249
+ interrupts: [],
250
+ path: ["__pregel_pull", "agent"],
251
+ checkpoint: null,
252
+ state: null,
253
+ result: {
254
+ messages: [
255
+ {
256
+ content: "Hey",
257
+ additional_kwargs: {},
258
+ response_metadata: {},
259
+ tool_call_chunks: [],
260
+ id: "run-b86ef558-eaff-4838-bc03-218f20554a9f",
261
+ tool_calls: [],
262
+ invalid_tool_calls: [],
263
+ type: "ai",
264
+ },
265
+ ],
266
+ },
267
+ },
268
+ ],
269
+ metadata: {
270
+ source: "loop",
271
+ step: 0,
272
+ parents: {},
273
+ thread_id: "efa80f4d-725f-4903-b19f-216309f060bc",
274
+ },
275
+ created_at: "2025-08-21T12:47:11.625Z",
276
+ checkpoint: {
277
+ thread_id: "efa80f4d-725f-4903-b19f-216309f060bc",
278
+ checkpoint_id: "1f07e8cf-4cf8-6f90-8000-7707fd155a8a",
279
+ checkpoint_ns: "",
280
+ checkpoint_map: null,
281
+ },
282
+ parent_checkpoint: {
283
+ thread_id: "efa80f4d-725f-4903-b19f-216309f060bc",
284
+ checkpoint_id: "1f07e8cf-4cea-6530-ffff-0995a53299c5",
285
+ checkpoint_ns: "",
286
+ checkpoint_map: null,
287
+ },
288
+ },
289
+ {
290
+ values: { messages: [] },
291
+ next: ["__start__"],
292
+ tasks: [
293
+ {
294
+ id: "cddede95-d276-59f1-bc08-8051d6448734",
295
+ name: "__start__",
296
+ error: null,
297
+ interrupts: [],
298
+ path: ["__pregel_pull", "__start__"],
299
+ checkpoint: null,
300
+ state: null,
301
+ result: { messages: [{ type: "human", content: "Hello" }] },
302
+ },
303
+ ],
304
+ metadata: {
305
+ source: "input",
306
+ step: -1,
307
+ parents: {},
308
+ thread_id: "efa80f4d-725f-4903-b19f-216309f060bc",
309
+ },
310
+ created_at: "2025-08-21T12:47:11.619Z",
311
+ checkpoint: {
312
+ thread_id: "efa80f4d-725f-4903-b19f-216309f060bc",
313
+ checkpoint_id: "1f07e8cf-4cea-6530-ffff-0995a53299c5",
314
+ checkpoint_ns: "",
315
+ checkpoint_map: null,
316
+ },
317
+ parent_checkpoint: null,
318
+ },
319
+ ];
320
+ const node = (value, paths = []) => ({
321
+ type: "node",
322
+ value: typeof value === "number" ? history.at(value) : value,
323
+ path: paths.map((v) => {
324
+ if (typeof v === "number") {
325
+ return history.at(v)?.checkpoint?.checkpoint_id;
326
+ }
327
+ return v?.checkpoint?.checkpoint_id;
328
+ }),
329
+ });
330
+ const fork = (...items) => ({ type: "fork", items });
331
+ const sequence = (...items) => ({ type: "sequence", items });
332
+ (0, vitest_1.it)("full tree", async () => {
333
+ const { rootSequence, paths } = (0, branching_js_1.getBranchSequence)(history);
334
+ vitest_1.expect
335
+ .soft(paths)
336
+ .toMatchObject(vitest_1.expect.arrayContaining([[5], [5, 4], [5, 3], [2]].map((p) => p.map((i) => history.at(i)?.checkpoint?.checkpoint_id))));
337
+ vitest_1.expect
338
+ .soft(rootSequence)
339
+ .toMatchObject(sequence(node(6), fork(sequence(node(5, [5]), fork(sequence(node(4, [5, 4])), sequence(node(3, [5, 3])))), sequence(node(2, [2]), node(1, [2]), node(0, [2])))));
340
+ });
341
+ (0, vitest_1.it)("partial tree", async () => {
342
+ (0, vitest_1.expect)((0, branching_js_1.getBranchSequence)(history.slice(0, 1))).toMatchObject({
343
+ paths: [],
344
+ rootSequence: sequence(node(0)),
345
+ });
346
+ (0, vitest_1.expect)((0, branching_js_1.getBranchSequence)(history.slice(0, 2))).toMatchObject({
347
+ paths: [],
348
+ rootSequence: sequence(node(1), node(0)),
349
+ });
350
+ (0, vitest_1.expect)((0, branching_js_1.getBranchSequence)(history.slice(0, 3))).toMatchObject({
351
+ paths: [],
352
+ rootSequence: sequence(node(2), node(1), node(0)),
353
+ });
354
+ (0, vitest_1.expect)((0, branching_js_1.getBranchSequence)(history.slice(0, 4))).toMatchObject({
355
+ paths: [],
356
+ rootSequence: sequence(node(2), node(1), node(0)),
357
+ });
358
+ (0, vitest_1.expect)((0, branching_js_1.getBranchSequence)(history.slice(0, 5))).toMatchObject({
359
+ paths: [],
360
+ rootSequence: sequence(node(2), node(1), node(0)),
361
+ });
362
+ (0, vitest_1.expect)((0, branching_js_1.getBranchSequence)(history.slice(0, 6))).toMatchObject({
363
+ paths: vitest_1.expect.arrayContaining([[5], [5, 4], [5, 3], [2]].map((p) => p.map((i) => history.at(i)?.checkpoint?.checkpoint_id))),
364
+ rootSequence: sequence(fork(sequence(node(5, [5]), fork(sequence(node(4, [5, 4])), sequence(node(3, [5, 3])))), sequence(node(2, [2]), node(1, [2]), node(0, [2])))),
365
+ });
366
+ (0, vitest_1.expect)((0, branching_js_1.getBranchSequence)(history.slice(0, 7))).toMatchObject({
367
+ paths: vitest_1.expect.arrayContaining([[5], [5, 4], [5, 3], [2]].map((p) => p.map((i) => history.at(i)?.checkpoint?.checkpoint_id))),
368
+ rootSequence: sequence(node(6), fork(sequence(node(5, [5]), fork(sequence(node(4, [5, 4])), sequence(node(3, [5, 3])))), sequence(node(2, [2]), node(1, [2]), node(0, [2])))),
369
+ });
370
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,368 @@
1
+ import { it, expect } from "vitest";
2
+ import { getBranchSequence } from "./branching.js";
3
+ const history = [
4
+ {
5
+ values: {
6
+ messages: [
7
+ {
8
+ content: "Fork: Hello",
9
+ additional_kwargs: {},
10
+ response_metadata: {},
11
+ id: "33357aea-9c2d-4718-92f4-20038d5a2d29",
12
+ type: "human",
13
+ },
14
+ {
15
+ content: "Hey",
16
+ additional_kwargs: {},
17
+ response_metadata: {},
18
+ tool_call_chunks: [],
19
+ id: "run-782a2f90-2e39-4c21-9ee6-0f38585a0ae6",
20
+ tool_calls: [],
21
+ invalid_tool_calls: [],
22
+ type: "ai",
23
+ },
24
+ ],
25
+ },
26
+ next: [],
27
+ tasks: [],
28
+ metadata: {
29
+ source: "loop",
30
+ step: 2,
31
+ parents: {},
32
+ thread_id: "efa80f4d-725f-4903-b19f-216309f060bc",
33
+ },
34
+ created_at: "2025-08-21T12:47:12.640Z",
35
+ checkpoint: {
36
+ thread_id: "efa80f4d-725f-4903-b19f-216309f060bc",
37
+ checkpoint_id: "1f07e8cf-56a7-6000-8002-5e272b1d5285",
38
+ checkpoint_ns: "",
39
+ checkpoint_map: null,
40
+ },
41
+ parent_checkpoint: {
42
+ thread_id: "efa80f4d-725f-4903-b19f-216309f060bc",
43
+ checkpoint_id: "1f07e8cf-53aa-6d70-8001-2096b0bbf835",
44
+ checkpoint_ns: "",
45
+ checkpoint_map: null,
46
+ },
47
+ },
48
+ {
49
+ values: {
50
+ messages: [
51
+ {
52
+ content: "Fork: Hello",
53
+ additional_kwargs: {},
54
+ response_metadata: {},
55
+ id: "33357aea-9c2d-4718-92f4-20038d5a2d29",
56
+ type: "human",
57
+ },
58
+ ],
59
+ },
60
+ next: ["agent"],
61
+ tasks: [
62
+ {
63
+ id: "680ee831-4d10-50f9-956f-e0d937321614",
64
+ name: "agent",
65
+ error: null,
66
+ interrupts: [],
67
+ path: ["__pregel_pull", "agent"],
68
+ checkpoint: null,
69
+ state: null,
70
+ result: {
71
+ messages: [
72
+ {
73
+ content: "Hey",
74
+ additional_kwargs: {},
75
+ response_metadata: {},
76
+ tool_call_chunks: [],
77
+ id: "run-782a2f90-2e39-4c21-9ee6-0f38585a0ae6",
78
+ tool_calls: [],
79
+ invalid_tool_calls: [],
80
+ type: "ai",
81
+ },
82
+ ],
83
+ },
84
+ },
85
+ ],
86
+ metadata: {
87
+ source: "loop",
88
+ step: 1,
89
+ parents: {},
90
+ thread_id: "efa80f4d-725f-4903-b19f-216309f060bc",
91
+ },
92
+ created_at: "2025-08-21T12:47:12.327Z",
93
+ checkpoint: {
94
+ thread_id: "efa80f4d-725f-4903-b19f-216309f060bc",
95
+ checkpoint_id: "1f07e8cf-53aa-6d70-8001-2096b0bbf835",
96
+ checkpoint_ns: "",
97
+ checkpoint_map: null,
98
+ },
99
+ parent_checkpoint: {
100
+ thread_id: "efa80f4d-725f-4903-b19f-216309f060bc",
101
+ checkpoint_id: "1f07e8cf-53a8-6660-8000-f157c9cf7d66",
102
+ checkpoint_ns: "",
103
+ checkpoint_map: null,
104
+ },
105
+ },
106
+ {
107
+ values: { messages: [] },
108
+ next: ["__start__"],
109
+ tasks: [
110
+ {
111
+ id: "4233f4c0-5ea2-5117-9153-95e5fe59eee3",
112
+ name: "__start__",
113
+ error: null,
114
+ interrupts: [],
115
+ path: ["__pregel_pull", "__start__"],
116
+ checkpoint: null,
117
+ state: null,
118
+ result: { messages: [{ type: "human", content: "Fork: Hello" }] },
119
+ },
120
+ ],
121
+ metadata: {
122
+ source: "input",
123
+ step: 0,
124
+ parents: {},
125
+ thread_id: "efa80f4d-725f-4903-b19f-216309f060bc",
126
+ },
127
+ created_at: "2025-08-21T12:47:12.326Z",
128
+ checkpoint: {
129
+ thread_id: "efa80f4d-725f-4903-b19f-216309f060bc",
130
+ checkpoint_id: "1f07e8cf-53a8-6660-8000-f157c9cf7d66",
131
+ checkpoint_ns: "",
132
+ checkpoint_map: null,
133
+ },
134
+ parent_checkpoint: {
135
+ thread_id: "efa80f4d-725f-4903-b19f-216309f060bc",
136
+ checkpoint_id: "1f07e8cf-4cea-6530-ffff-0995a53299c5",
137
+ checkpoint_ns: "",
138
+ checkpoint_map: null,
139
+ },
140
+ },
141
+ {
142
+ values: {
143
+ messages: [
144
+ {
145
+ content: "Hello",
146
+ additional_kwargs: {},
147
+ response_metadata: {},
148
+ id: "ec300e63-1494-4ef6-936c-60d02d7bdce1",
149
+ type: "human",
150
+ },
151
+ {
152
+ content: "Hey",
153
+ additional_kwargs: {},
154
+ response_metadata: {},
155
+ tool_call_chunks: [],
156
+ id: "run-593064bd-dd07-4ed3-99c0-a918625f4884",
157
+ tool_calls: [],
158
+ invalid_tool_calls: [],
159
+ type: "ai",
160
+ },
161
+ ],
162
+ },
163
+ next: [],
164
+ tasks: [],
165
+ metadata: {
166
+ source: "loop",
167
+ step: 1,
168
+ parents: {},
169
+ thread_id: "efa80f4d-725f-4903-b19f-216309f060bc",
170
+ },
171
+ created_at: "2025-08-21T12:47:12.285Z",
172
+ checkpoint: {
173
+ thread_id: "efa80f4d-725f-4903-b19f-216309f060bc",
174
+ checkpoint_id: "1f07e8cf-5344-64d0-8001-8a176e91d12f",
175
+ checkpoint_ns: "",
176
+ checkpoint_map: null,
177
+ },
178
+ parent_checkpoint: {
179
+ thread_id: "efa80f4d-725f-4903-b19f-216309f060bc",
180
+ checkpoint_id: "1f07e8cf-4cf8-6f90-8000-7707fd155a8a",
181
+ checkpoint_ns: "",
182
+ checkpoint_map: null,
183
+ },
184
+ },
185
+ {
186
+ values: {
187
+ messages: [
188
+ {
189
+ content: "Hello",
190
+ additional_kwargs: {},
191
+ response_metadata: {},
192
+ id: "ec300e63-1494-4ef6-936c-60d02d7bdce1",
193
+ type: "human",
194
+ },
195
+ {
196
+ content: "Hey",
197
+ additional_kwargs: {},
198
+ response_metadata: {},
199
+ tool_call_chunks: [],
200
+ id: "run-b86ef558-eaff-4838-bc03-218f20554a9f",
201
+ tool_calls: [],
202
+ invalid_tool_calls: [],
203
+ type: "ai",
204
+ },
205
+ ],
206
+ },
207
+ next: [],
208
+ tasks: [],
209
+ metadata: {
210
+ source: "loop",
211
+ step: 1,
212
+ parents: {},
213
+ thread_id: "efa80f4d-725f-4903-b19f-216309f060bc",
214
+ },
215
+ created_at: "2025-08-21T12:47:11.936Z",
216
+ checkpoint: {
217
+ thread_id: "efa80f4d-725f-4903-b19f-216309f060bc",
218
+ checkpoint_id: "1f07e8cf-4ff0-6400-8001-a54aedf52a76",
219
+ checkpoint_ns: "",
220
+ checkpoint_map: null,
221
+ },
222
+ parent_checkpoint: {
223
+ thread_id: "efa80f4d-725f-4903-b19f-216309f060bc",
224
+ checkpoint_id: "1f07e8cf-4cf8-6f90-8000-7707fd155a8a",
225
+ checkpoint_ns: "",
226
+ checkpoint_map: null,
227
+ },
228
+ },
229
+ {
230
+ values: {
231
+ messages: [
232
+ {
233
+ content: "Hello",
234
+ additional_kwargs: {},
235
+ response_metadata: {},
236
+ id: "ec300e63-1494-4ef6-936c-60d02d7bdce1",
237
+ type: "human",
238
+ },
239
+ ],
240
+ },
241
+ next: ["agent"],
242
+ tasks: [
243
+ {
244
+ id: "e3f503f1-73bc-5db1-8b4d-13f660ad1b51",
245
+ name: "agent",
246
+ error: null,
247
+ interrupts: [],
248
+ path: ["__pregel_pull", "agent"],
249
+ checkpoint: null,
250
+ state: null,
251
+ result: {
252
+ messages: [
253
+ {
254
+ content: "Hey",
255
+ additional_kwargs: {},
256
+ response_metadata: {},
257
+ tool_call_chunks: [],
258
+ id: "run-b86ef558-eaff-4838-bc03-218f20554a9f",
259
+ tool_calls: [],
260
+ invalid_tool_calls: [],
261
+ type: "ai",
262
+ },
263
+ ],
264
+ },
265
+ },
266
+ ],
267
+ metadata: {
268
+ source: "loop",
269
+ step: 0,
270
+ parents: {},
271
+ thread_id: "efa80f4d-725f-4903-b19f-216309f060bc",
272
+ },
273
+ created_at: "2025-08-21T12:47:11.625Z",
274
+ checkpoint: {
275
+ thread_id: "efa80f4d-725f-4903-b19f-216309f060bc",
276
+ checkpoint_id: "1f07e8cf-4cf8-6f90-8000-7707fd155a8a",
277
+ checkpoint_ns: "",
278
+ checkpoint_map: null,
279
+ },
280
+ parent_checkpoint: {
281
+ thread_id: "efa80f4d-725f-4903-b19f-216309f060bc",
282
+ checkpoint_id: "1f07e8cf-4cea-6530-ffff-0995a53299c5",
283
+ checkpoint_ns: "",
284
+ checkpoint_map: null,
285
+ },
286
+ },
287
+ {
288
+ values: { messages: [] },
289
+ next: ["__start__"],
290
+ tasks: [
291
+ {
292
+ id: "cddede95-d276-59f1-bc08-8051d6448734",
293
+ name: "__start__",
294
+ error: null,
295
+ interrupts: [],
296
+ path: ["__pregel_pull", "__start__"],
297
+ checkpoint: null,
298
+ state: null,
299
+ result: { messages: [{ type: "human", content: "Hello" }] },
300
+ },
301
+ ],
302
+ metadata: {
303
+ source: "input",
304
+ step: -1,
305
+ parents: {},
306
+ thread_id: "efa80f4d-725f-4903-b19f-216309f060bc",
307
+ },
308
+ created_at: "2025-08-21T12:47:11.619Z",
309
+ checkpoint: {
310
+ thread_id: "efa80f4d-725f-4903-b19f-216309f060bc",
311
+ checkpoint_id: "1f07e8cf-4cea-6530-ffff-0995a53299c5",
312
+ checkpoint_ns: "",
313
+ checkpoint_map: null,
314
+ },
315
+ parent_checkpoint: null,
316
+ },
317
+ ];
318
+ const node = (value, paths = []) => ({
319
+ type: "node",
320
+ value: typeof value === "number" ? history.at(value) : value,
321
+ path: paths.map((v) => {
322
+ if (typeof v === "number") {
323
+ return history.at(v)?.checkpoint?.checkpoint_id;
324
+ }
325
+ return v?.checkpoint?.checkpoint_id;
326
+ }),
327
+ });
328
+ const fork = (...items) => ({ type: "fork", items });
329
+ const sequence = (...items) => ({ type: "sequence", items });
330
+ it("full tree", async () => {
331
+ const { rootSequence, paths } = getBranchSequence(history);
332
+ expect
333
+ .soft(paths)
334
+ .toMatchObject(expect.arrayContaining([[5], [5, 4], [5, 3], [2]].map((p) => p.map((i) => history.at(i)?.checkpoint?.checkpoint_id))));
335
+ expect
336
+ .soft(rootSequence)
337
+ .toMatchObject(sequence(node(6), fork(sequence(node(5, [5]), fork(sequence(node(4, [5, 4])), sequence(node(3, [5, 3])))), sequence(node(2, [2]), node(1, [2]), node(0, [2])))));
338
+ });
339
+ it("partial tree", async () => {
340
+ expect(getBranchSequence(history.slice(0, 1))).toMatchObject({
341
+ paths: [],
342
+ rootSequence: sequence(node(0)),
343
+ });
344
+ expect(getBranchSequence(history.slice(0, 2))).toMatchObject({
345
+ paths: [],
346
+ rootSequence: sequence(node(1), node(0)),
347
+ });
348
+ expect(getBranchSequence(history.slice(0, 3))).toMatchObject({
349
+ paths: [],
350
+ rootSequence: sequence(node(2), node(1), node(0)),
351
+ });
352
+ expect(getBranchSequence(history.slice(0, 4))).toMatchObject({
353
+ paths: [],
354
+ rootSequence: sequence(node(2), node(1), node(0)),
355
+ });
356
+ expect(getBranchSequence(history.slice(0, 5))).toMatchObject({
357
+ paths: [],
358
+ rootSequence: sequence(node(2), node(1), node(0)),
359
+ });
360
+ expect(getBranchSequence(history.slice(0, 6))).toMatchObject({
361
+ paths: expect.arrayContaining([[5], [5, 4], [5, 3], [2]].map((p) => p.map((i) => history.at(i)?.checkpoint?.checkpoint_id))),
362
+ rootSequence: sequence(fork(sequence(node(5, [5]), fork(sequence(node(4, [5, 4])), sequence(node(3, [5, 3])))), sequence(node(2, [2]), node(1, [2]), node(0, [2])))),
363
+ });
364
+ expect(getBranchSequence(history.slice(0, 7))).toMatchObject({
365
+ paths: expect.arrayContaining([[5], [5, 4], [5, 3], [2]].map((p) => p.map((i) => history.at(i)?.checkpoint?.checkpoint_id))),
366
+ rootSequence: sequence(node(6), fork(sequence(node(5, [5]), fork(sequence(node(4, [5, 4])), sequence(node(3, [5, 3])))), sequence(node(2, [2]), node(1, [2]), node(0, [2])))),
367
+ });
368
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@langchain/langgraph-sdk",
3
- "version": "0.1.6",
3
+ "version": "0.1.8",
4
4
  "description": "Client library for interacting with the LangGraph API",
5
5
  "type": "module",
6
6
  "scripts": {
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes