@langchain/langgraph-sdk 0.0.87 → 0.0.88

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/client.cjs CHANGED
@@ -651,8 +651,8 @@ class RunsClient extends BaseClient {
651
651
  if (runMetadata)
652
652
  payload?.onRunCreated?.(runMetadata);
653
653
  const stream = (response.body || new ReadableStream({ start: (ctrl) => ctrl.close() }))
654
- .pipeThrough(new sse_js_1.BytesLineDecoder())
655
- .pipeThrough(new sse_js_1.SSEDecoder());
654
+ .pipeThrough((0, sse_js_1.BytesLineDecoder)())
655
+ .pipeThrough((0, sse_js_1.SSEDecoder)());
656
656
  yield* stream_js_1.IterableReadableStream.fromReadableStream(stream);
657
657
  }
658
658
  /**
@@ -864,8 +864,8 @@ class RunsClient extends BaseClient {
864
864
  },
865
865
  }));
866
866
  const stream = (response.body || new ReadableStream({ start: (ctrl) => ctrl.close() }))
867
- .pipeThrough(new sse_js_1.BytesLineDecoder())
868
- .pipeThrough(new sse_js_1.SSEDecoder());
867
+ .pipeThrough((0, sse_js_1.BytesLineDecoder)())
868
+ .pipeThrough((0, sse_js_1.SSEDecoder)());
869
869
  yield* stream_js_1.IterableReadableStream.fromReadableStream(stream);
870
870
  }
871
871
  /**
package/dist/client.js CHANGED
@@ -644,8 +644,8 @@ export class RunsClient extends BaseClient {
644
644
  if (runMetadata)
645
645
  payload?.onRunCreated?.(runMetadata);
646
646
  const stream = (response.body || new ReadableStream({ start: (ctrl) => ctrl.close() }))
647
- .pipeThrough(new BytesLineDecoder())
648
- .pipeThrough(new SSEDecoder());
647
+ .pipeThrough(BytesLineDecoder())
648
+ .pipeThrough(SSEDecoder());
649
649
  yield* IterableReadableStream.fromReadableStream(stream);
650
650
  }
651
651
  /**
@@ -857,8 +857,8 @@ export class RunsClient extends BaseClient {
857
857
  },
858
858
  }));
859
859
  const stream = (response.body || new ReadableStream({ start: (ctrl) => ctrl.close() }))
860
- .pipeThrough(new BytesLineDecoder())
861
- .pipeThrough(new SSEDecoder());
860
+ .pipeThrough(BytesLineDecoder())
861
+ .pipeThrough(SSEDecoder());
862
862
  yield* IterableReadableStream.fromReadableStream(stream);
863
863
  }
864
864
  /**
@@ -212,6 +212,32 @@ const useControllableThreadId = (options) => {
212
212
  }
213
213
  return [options.threadId ?? null, onThreadId];
214
214
  };
215
+ function useStreamValuesState() {
216
+ const [values, setValues] = (0, react_1.useState)(null);
217
+ const setStreamValues = (0, react_1.useCallback)((values, kind = "stream") => {
218
+ if (typeof values === "function") {
219
+ setValues((prevTuple) => {
220
+ const [prevValues, prevKind] = prevTuple ?? [null, "stream"];
221
+ const next = values(prevValues, prevKind);
222
+ if (next == null)
223
+ return null;
224
+ return [next, kind];
225
+ });
226
+ return;
227
+ }
228
+ if (values == null)
229
+ setValues(null);
230
+ setValues([values, kind]);
231
+ }, []);
232
+ const mutate = (0, react_1.useCallback)((kind, serverValues) => (update) => {
233
+ setStreamValues((clientValues) => {
234
+ const prev = { ...serverValues, ...clientValues };
235
+ const next = typeof update === "function" ? update(prev) : update;
236
+ return { ...prev, ...next };
237
+ }, kind);
238
+ }, [setStreamValues]);
239
+ return [values?.[0] ?? null, setStreamValues, mutate];
240
+ }
215
241
  function useStream(options) {
216
242
  let { assistantId, messagesKey, onCreated, onError, onFinish } = options;
217
243
  const reconnectOnMountRef = (0, react_1.useRef)(options.reconnectOnMount);
@@ -243,7 +269,7 @@ function useStream(options) {
243
269
  const [branch, setBranch] = (0, react_1.useState)("");
244
270
  const [isLoading, setIsLoading] = (0, react_1.useState)(false);
245
271
  const [streamError, setStreamError] = (0, react_1.useState)(undefined);
246
- const [streamValues, setStreamValues] = (0, react_1.useState)(null);
272
+ const [streamValues, setStreamValues, getMutateFn] = useStreamValuesState();
247
273
  const messageManagerRef = (0, react_1.useRef)(new MessageTupleManager());
248
274
  const submittingRef = (0, react_1.useRef)(false);
249
275
  const abortRef = (0, react_1.useRef)(null);
@@ -293,7 +319,7 @@ function useStream(options) {
293
319
  const { rootSequence, paths } = getBranchSequence(history.data);
294
320
  const { history: flatHistory, branchByCheckpoint } = getBranchView(rootSequence, paths, branch);
295
321
  const threadHead = flatHistory.at(-1);
296
- const historyValues = threadHead?.values ?? {};
322
+ const historyValues = threadHead?.values ?? options.initialValues ?? {};
297
323
  const historyError = (() => {
298
324
  const error = threadHead?.tasks?.at(-1)?.error;
299
325
  if (error == null)
@@ -348,6 +374,7 @@ function useStream(options) {
348
374
  client.runs.cancel(threadId, runId);
349
375
  runMetadataStorage.removeItem(`lg:stream:${threadId}`);
350
376
  }
377
+ options?.onStop?.({ mutate: getMutateFn("stop", historyValues) });
351
378
  };
352
379
  async function consumeStream(action) {
353
380
  let getCallbackMeta;
@@ -368,15 +395,7 @@ function useStream(options) {
368
395
  options.onUpdateEvent?.(data);
369
396
  if (event === "custom")
370
397
  options.onCustomEvent?.(data, {
371
- mutate: (update) => setStreamValues((prev) => {
372
- // should not happen
373
- if (prev == null)
374
- return prev;
375
- return {
376
- ...prev,
377
- ...(typeof update === "function" ? update(prev) : update),
378
- };
379
- }),
398
+ mutate: getMutateFn("stream", historyValues),
380
399
  });
381
400
  if (event === "metadata")
382
401
  options.onMetadataEvent?.(data);
@@ -412,7 +431,12 @@ function useStream(options) {
412
431
  }
413
432
  // TODO: stream created checkpoints to avoid an unnecessary network request
414
433
  const result = await run.onSuccess();
415
- setStreamValues(null);
434
+ setStreamValues((values, kind) => {
435
+ // Do not clear out the user values set on `stop`.
436
+ if (kind === "stop")
437
+ return values;
438
+ return null;
439
+ });
416
440
  if (streamError != null)
417
441
  throw streamError;
418
442
  const lastHead = result.at(0);
@@ -435,7 +459,7 @@ function useStream(options) {
435
459
  abortRef.current = null;
436
460
  }
437
461
  }
438
- const joinStream = async (runId, lastEventId) => {
462
+ const joinStream = async (runId, lastEventId, options) => {
439
463
  lastEventId ??= "-1";
440
464
  if (!threadId)
441
465
  return;
@@ -443,6 +467,7 @@ function useStream(options) {
443
467
  const stream = client.runs.joinStream(threadId, runId, {
444
468
  signal,
445
469
  lastEventId,
470
+ streamMode: options?.streamMode,
446
471
  });
447
472
  return {
448
473
  onSuccess: () => {
@@ -462,23 +487,22 @@ function useStream(options) {
462
487
  : undefined;
463
488
  if (newPath != null)
464
489
  setBranch(newPath ?? "");
465
- // Assumption: we're setting the initial value
466
- // Used for instant feedback
467
490
  setStreamValues(() => {
468
- const values = { ...historyValues };
469
491
  if (submitOptions?.optimisticValues != null) {
470
492
  return {
471
- ...values,
493
+ ...historyValues,
472
494
  ...(typeof submitOptions.optimisticValues === "function"
473
- ? submitOptions.optimisticValues(values)
495
+ ? submitOptions.optimisticValues(historyValues)
474
496
  : submitOptions.optimisticValues),
475
497
  };
476
498
  }
477
- return values;
499
+ return { ...historyValues };
478
500
  });
479
501
  let usableThreadId = threadId;
480
502
  if (!usableThreadId) {
481
- const thread = await client.threads.create();
503
+ const thread = await client.threads.create({
504
+ threadId: submitOptions?.threadId,
505
+ });
482
506
  onThreadId(thread.thread_id);
483
507
  usableThreadId = thread.thread_id;
484
508
  }
@@ -125,6 +125,28 @@ export interface UseStreamOptions<StateType extends Record<string, unknown> = Re
125
125
  * @internal This API is experimental and subject to change.
126
126
  */
127
127
  onDebugEvent?: (data: DebugStreamEvent["data"]) => void;
128
+ /**
129
+ * Callback that is called when the stream is stopped by the user.
130
+ * Provides a mutate function to update the stream state immediately
131
+ * without requiring a server roundtrip.
132
+ *
133
+ * @example
134
+ * ```typescript
135
+ * onStop: ({ mutate }) => {
136
+ * mutate((prev) => ({
137
+ * ...prev,
138
+ * ui: prev.ui?.map(component =>
139
+ * component.props.isLoading
140
+ * ? { ...component, props: { ...component.props, stopped: true, isLoading: false }}
141
+ * : component
142
+ * )
143
+ * }));
144
+ * }
145
+ * ```
146
+ */
147
+ onStop?: (options: {
148
+ mutate: (update: Partial<StateType> | ((prev: StateType) => Partial<StateType>)) => void;
149
+ }) => void;
128
150
  /**
129
151
  * The ID of the thread to fetch history and current values from.
130
152
  */
@@ -135,6 +157,16 @@ export interface UseStreamOptions<StateType extends Record<string, unknown> = Re
135
157
  onThreadId?: (threadId: string) => void;
136
158
  /** Will reconnect the stream on mount */
137
159
  reconnectOnMount?: boolean | (() => RunMetadataStorage);
160
+ /**
161
+ * Initial values to display immediately when loading a thread.
162
+ * Useful for displaying cached thread data while official history loads.
163
+ * These values will be replaced when official thread data is fetched.
164
+ *
165
+ * Note: UI components from initialValues will render immediately if they're
166
+ * predefined in LoadExternalComponent's components prop, providing instant
167
+ * cached UI display without server fetches.
168
+ */
169
+ initialValues?: StateType | null;
138
170
  }
139
171
  interface RunMetadataStorage {
140
172
  getItem(key: `lg:stream:${string}`): string | null;
@@ -208,7 +240,9 @@ export interface UseStream<StateType extends Record<string, unknown> = Record<st
208
240
  /**
209
241
  * Join an active stream.
210
242
  */
211
- joinStream: (runId: string) => Promise<void>;
243
+ joinStream: (runId: string, lastEventId?: string, options?: {
244
+ streamMode?: StreamMode | StreamMode[];
245
+ }) => Promise<void>;
212
246
  }
213
247
  type ConfigWithConfigurable<ConfigurableType extends Record<string, unknown>> = Config & {
214
248
  configurable?: ConfigurableType;
@@ -233,6 +267,13 @@ interface SubmitOptions<StateType extends Record<string, unknown> = Record<strin
233
267
  */
234
268
  streamSubgraphs?: boolean;
235
269
  streamResumable?: boolean;
270
+ /**
271
+ * The ID to use when creating a new thread. When provided, this ID will be used
272
+ * for thread creation when threadId is `null` or `undefined`.
273
+ * This enables optimistic UI updates where you know the thread ID
274
+ * before the thread is actually created.
275
+ */
276
+ threadId?: string;
236
277
  }
237
278
  export declare function useStream<StateType extends Record<string, unknown> = Record<string, unknown>, Bag extends {
238
279
  ConfigurableType?: Record<string, unknown>;
@@ -209,6 +209,32 @@ const useControllableThreadId = (options) => {
209
209
  }
210
210
  return [options.threadId ?? null, onThreadId];
211
211
  };
212
+ function useStreamValuesState() {
213
+ const [values, setValues] = useState(null);
214
+ const setStreamValues = useCallback((values, kind = "stream") => {
215
+ if (typeof values === "function") {
216
+ setValues((prevTuple) => {
217
+ const [prevValues, prevKind] = prevTuple ?? [null, "stream"];
218
+ const next = values(prevValues, prevKind);
219
+ if (next == null)
220
+ return null;
221
+ return [next, kind];
222
+ });
223
+ return;
224
+ }
225
+ if (values == null)
226
+ setValues(null);
227
+ setValues([values, kind]);
228
+ }, []);
229
+ const mutate = useCallback((kind, serverValues) => (update) => {
230
+ setStreamValues((clientValues) => {
231
+ const prev = { ...serverValues, ...clientValues };
232
+ const next = typeof update === "function" ? update(prev) : update;
233
+ return { ...prev, ...next };
234
+ }, kind);
235
+ }, [setStreamValues]);
236
+ return [values?.[0] ?? null, setStreamValues, mutate];
237
+ }
212
238
  export function useStream(options) {
213
239
  let { assistantId, messagesKey, onCreated, onError, onFinish } = options;
214
240
  const reconnectOnMountRef = useRef(options.reconnectOnMount);
@@ -240,7 +266,7 @@ export function useStream(options) {
240
266
  const [branch, setBranch] = useState("");
241
267
  const [isLoading, setIsLoading] = useState(false);
242
268
  const [streamError, setStreamError] = useState(undefined);
243
- const [streamValues, setStreamValues] = useState(null);
269
+ const [streamValues, setStreamValues, getMutateFn] = useStreamValuesState();
244
270
  const messageManagerRef = useRef(new MessageTupleManager());
245
271
  const submittingRef = useRef(false);
246
272
  const abortRef = useRef(null);
@@ -290,7 +316,7 @@ export function useStream(options) {
290
316
  const { rootSequence, paths } = getBranchSequence(history.data);
291
317
  const { history: flatHistory, branchByCheckpoint } = getBranchView(rootSequence, paths, branch);
292
318
  const threadHead = flatHistory.at(-1);
293
- const historyValues = threadHead?.values ?? {};
319
+ const historyValues = threadHead?.values ?? options.initialValues ?? {};
294
320
  const historyError = (() => {
295
321
  const error = threadHead?.tasks?.at(-1)?.error;
296
322
  if (error == null)
@@ -345,6 +371,7 @@ export function useStream(options) {
345
371
  client.runs.cancel(threadId, runId);
346
372
  runMetadataStorage.removeItem(`lg:stream:${threadId}`);
347
373
  }
374
+ options?.onStop?.({ mutate: getMutateFn("stop", historyValues) });
348
375
  };
349
376
  async function consumeStream(action) {
350
377
  let getCallbackMeta;
@@ -365,15 +392,7 @@ export function useStream(options) {
365
392
  options.onUpdateEvent?.(data);
366
393
  if (event === "custom")
367
394
  options.onCustomEvent?.(data, {
368
- mutate: (update) => setStreamValues((prev) => {
369
- // should not happen
370
- if (prev == null)
371
- return prev;
372
- return {
373
- ...prev,
374
- ...(typeof update === "function" ? update(prev) : update),
375
- };
376
- }),
395
+ mutate: getMutateFn("stream", historyValues),
377
396
  });
378
397
  if (event === "metadata")
379
398
  options.onMetadataEvent?.(data);
@@ -409,7 +428,12 @@ export function useStream(options) {
409
428
  }
410
429
  // TODO: stream created checkpoints to avoid an unnecessary network request
411
430
  const result = await run.onSuccess();
412
- setStreamValues(null);
431
+ setStreamValues((values, kind) => {
432
+ // Do not clear out the user values set on `stop`.
433
+ if (kind === "stop")
434
+ return values;
435
+ return null;
436
+ });
413
437
  if (streamError != null)
414
438
  throw streamError;
415
439
  const lastHead = result.at(0);
@@ -432,7 +456,7 @@ export function useStream(options) {
432
456
  abortRef.current = null;
433
457
  }
434
458
  }
435
- const joinStream = async (runId, lastEventId) => {
459
+ const joinStream = async (runId, lastEventId, options) => {
436
460
  lastEventId ??= "-1";
437
461
  if (!threadId)
438
462
  return;
@@ -440,6 +464,7 @@ export function useStream(options) {
440
464
  const stream = client.runs.joinStream(threadId, runId, {
441
465
  signal,
442
466
  lastEventId,
467
+ streamMode: options?.streamMode,
443
468
  });
444
469
  return {
445
470
  onSuccess: () => {
@@ -459,23 +484,22 @@ export function useStream(options) {
459
484
  : undefined;
460
485
  if (newPath != null)
461
486
  setBranch(newPath ?? "");
462
- // Assumption: we're setting the initial value
463
- // Used for instant feedback
464
487
  setStreamValues(() => {
465
- const values = { ...historyValues };
466
488
  if (submitOptions?.optimisticValues != null) {
467
489
  return {
468
- ...values,
490
+ ...historyValues,
469
491
  ...(typeof submitOptions.optimisticValues === "function"
470
- ? submitOptions.optimisticValues(values)
492
+ ? submitOptions.optimisticValues(historyValues)
471
493
  : submitOptions.optimisticValues),
472
494
  };
473
495
  }
474
- return values;
496
+ return { ...historyValues };
475
497
  });
476
498
  let usableThreadId = threadId;
477
499
  if (!usableThreadId) {
478
- const thread = await client.threads.create();
500
+ const thread = await client.threads.create({
501
+ threadId: submitOptions?.threadId,
502
+ });
479
503
  onThreadId(thread.thread_id);
480
504
  usableThreadId = thread.thread_id;
481
505
  }
@@ -7,141 +7,137 @@ const NULL = "\0".charCodeAt(0);
7
7
  const COLON = ":".charCodeAt(0);
8
8
  const SPACE = " ".charCodeAt(0);
9
9
  const TRAILING_NEWLINE = [CR, LF];
10
- class BytesLineDecoder extends TransformStream {
11
- constructor() {
12
- let buffer = [];
13
- let trailingCr = false;
14
- super({
15
- start() {
16
- buffer = [];
10
+ function BytesLineDecoder() {
11
+ let buffer = [];
12
+ let trailingCr = false;
13
+ return new TransformStream({
14
+ start() {
15
+ buffer = [];
16
+ trailingCr = false;
17
+ },
18
+ transform(chunk, controller) {
19
+ // See https://docs.python.org/3/glossary.html#term-universal-newlines
20
+ let text = chunk;
21
+ // Handle trailing CR from previous chunk
22
+ if (trailingCr) {
23
+ text = joinArrays([[CR], text]);
17
24
  trailingCr = false;
18
- },
19
- transform(chunk, controller) {
20
- // See https://docs.python.org/3/glossary.html#term-universal-newlines
21
- let text = chunk;
22
- // Handle trailing CR from previous chunk
23
- if (trailingCr) {
24
- text = joinArrays([[CR], text]);
25
- trailingCr = false;
26
- }
27
- // Check for trailing CR in current chunk
28
- if (text.length > 0 && text.at(-1) === CR) {
29
- trailingCr = true;
30
- text = text.subarray(0, -1);
31
- }
32
- if (!text.length)
33
- return;
34
- const trailingNewline = TRAILING_NEWLINE.includes(text.at(-1));
35
- const lastIdx = text.length - 1;
36
- const { lines } = text.reduce((acc, cur, idx) => {
37
- if (acc.from > idx)
38
- return acc;
39
- if (cur === CR || cur === LF) {
40
- acc.lines.push(text.subarray(acc.from, idx));
41
- if (cur === CR && text[idx + 1] === LF) {
42
- acc.from = idx + 2;
43
- }
44
- else {
45
- acc.from = idx + 1;
46
- }
25
+ }
26
+ // Check for trailing CR in current chunk
27
+ if (text.length > 0 && text.at(-1) === CR) {
28
+ trailingCr = true;
29
+ text = text.subarray(0, -1);
30
+ }
31
+ if (!text.length)
32
+ return;
33
+ const trailingNewline = TRAILING_NEWLINE.includes(text.at(-1));
34
+ const lastIdx = text.length - 1;
35
+ const { lines } = text.reduce((acc, cur, idx) => {
36
+ if (acc.from > idx)
37
+ return acc;
38
+ if (cur === CR || cur === LF) {
39
+ acc.lines.push(text.subarray(acc.from, idx));
40
+ if (cur === CR && text[idx + 1] === LF) {
41
+ acc.from = idx + 2;
47
42
  }
48
- if (idx === lastIdx && acc.from <= lastIdx) {
49
- acc.lines.push(text.subarray(acc.from));
43
+ else {
44
+ acc.from = idx + 1;
50
45
  }
51
- return acc;
52
- }, { lines: [], from: 0 });
53
- if (lines.length === 1 && !trailingNewline) {
54
- buffer.push(lines[0]);
55
- return;
56
- }
57
- if (buffer.length) {
58
- // Include existing buffer in first line
59
- buffer.push(lines[0]);
60
- lines[0] = joinArrays(buffer);
61
- buffer = [];
62
- }
63
- if (!trailingNewline) {
64
- // If the last segment is not newline terminated,
65
- // buffer it for the next chunk
66
- if (lines.length)
67
- buffer = [lines.pop()];
68
46
  }
69
- // Enqueue complete lines
70
- for (const line of lines) {
71
- controller.enqueue(line);
47
+ if (idx === lastIdx && acc.from <= lastIdx) {
48
+ acc.lines.push(text.subarray(acc.from));
72
49
  }
73
- },
74
- flush(controller) {
75
- if (buffer.length) {
76
- controller.enqueue(joinArrays(buffer));
77
- }
78
- },
79
- });
80
- }
50
+ return acc;
51
+ }, { lines: [], from: 0 });
52
+ if (lines.length === 1 && !trailingNewline) {
53
+ buffer.push(lines[0]);
54
+ return;
55
+ }
56
+ if (buffer.length) {
57
+ // Include existing buffer in first line
58
+ buffer.push(lines[0]);
59
+ lines[0] = joinArrays(buffer);
60
+ buffer = [];
61
+ }
62
+ if (!trailingNewline) {
63
+ // If the last segment is not newline terminated,
64
+ // buffer it for the next chunk
65
+ if (lines.length)
66
+ buffer = [lines.pop()];
67
+ }
68
+ // Enqueue complete lines
69
+ for (const line of lines) {
70
+ controller.enqueue(line);
71
+ }
72
+ },
73
+ flush(controller) {
74
+ if (buffer.length) {
75
+ controller.enqueue(joinArrays(buffer));
76
+ }
77
+ },
78
+ });
81
79
  }
82
80
  exports.BytesLineDecoder = BytesLineDecoder;
83
- class SSEDecoder extends TransformStream {
84
- constructor() {
85
- let event = "";
86
- let data = [];
87
- let lastEventId = "";
88
- let retry = null;
89
- const decoder = new TextDecoder();
90
- super({
91
- transform(chunk, controller) {
92
- // Handle empty line case
93
- if (!chunk.length) {
94
- if (!event && !data.length && !lastEventId && retry == null)
95
- return;
96
- const sse = {
97
- id: lastEventId || undefined,
98
- event,
99
- data: data.length ? decodeArraysToJson(decoder, data) : null,
100
- };
101
- // NOTE: as per the SSE spec, do not reset lastEventId
102
- event = "";
103
- data = [];
104
- retry = null;
105
- controller.enqueue(sse);
106
- return;
107
- }
108
- // Ignore comments
109
- if (chunk[0] === COLON)
81
+ function SSEDecoder() {
82
+ let event = "";
83
+ let data = [];
84
+ let lastEventId = "";
85
+ let retry = null;
86
+ const decoder = new TextDecoder();
87
+ return new TransformStream({
88
+ transform(chunk, controller) {
89
+ // Handle empty line case
90
+ if (!chunk.length) {
91
+ if (!event && !data.length && !lastEventId && retry == null)
110
92
  return;
111
- const sepIdx = chunk.indexOf(COLON);
112
- if (sepIdx === -1)
113
- return;
114
- const fieldName = decoder.decode(chunk.subarray(0, sepIdx));
115
- let value = chunk.subarray(sepIdx + 1);
116
- if (value[0] === SPACE)
117
- value = value.subarray(1);
118
- if (fieldName === "event") {
119
- event = decoder.decode(value);
120
- }
121
- else if (fieldName === "data") {
122
- data.push(value);
123
- }
124
- else if (fieldName === "id") {
125
- if (value.indexOf(NULL) === -1)
126
- lastEventId = decoder.decode(value);
127
- }
128
- else if (fieldName === "retry") {
129
- const retryNum = Number.parseInt(decoder.decode(value));
130
- if (!Number.isNaN(retryNum))
131
- retry = retryNum;
132
- }
133
- },
134
- flush(controller) {
135
- if (event) {
136
- controller.enqueue({
137
- id: lastEventId || undefined,
138
- event,
139
- data: data.length ? decodeArraysToJson(decoder, data) : null,
140
- });
141
- }
142
- },
143
- });
144
- }
93
+ const sse = {
94
+ id: lastEventId || undefined,
95
+ event,
96
+ data: data.length ? decodeArraysToJson(decoder, data) : null,
97
+ };
98
+ // NOTE: as per the SSE spec, do not reset lastEventId
99
+ event = "";
100
+ data = [];
101
+ retry = null;
102
+ controller.enqueue(sse);
103
+ return;
104
+ }
105
+ // Ignore comments
106
+ if (chunk[0] === COLON)
107
+ return;
108
+ const sepIdx = chunk.indexOf(COLON);
109
+ if (sepIdx === -1)
110
+ return;
111
+ const fieldName = decoder.decode(chunk.subarray(0, sepIdx));
112
+ let value = chunk.subarray(sepIdx + 1);
113
+ if (value[0] === SPACE)
114
+ value = value.subarray(1);
115
+ if (fieldName === "event") {
116
+ event = decoder.decode(value);
117
+ }
118
+ else if (fieldName === "data") {
119
+ data.push(value);
120
+ }
121
+ else if (fieldName === "id") {
122
+ if (value.indexOf(NULL) === -1)
123
+ lastEventId = decoder.decode(value);
124
+ }
125
+ else if (fieldName === "retry") {
126
+ const retryNum = Number.parseInt(decoder.decode(value));
127
+ if (!Number.isNaN(retryNum))
128
+ retry = retryNum;
129
+ }
130
+ },
131
+ flush(controller) {
132
+ if (event) {
133
+ controller.enqueue({
134
+ id: lastEventId || undefined,
135
+ event,
136
+ data: data.length ? decodeArraysToJson(decoder, data) : null,
137
+ });
138
+ }
139
+ },
140
+ });
145
141
  }
146
142
  exports.SSEDecoder = SSEDecoder;
147
143
  function joinArrays(data) {
@@ -1,12 +1,8 @@
1
- export declare class BytesLineDecoder extends TransformStream<Uint8Array, Uint8Array> {
2
- constructor();
3
- }
1
+ export declare function BytesLineDecoder(): TransformStream<Uint8Array, Uint8Array>;
4
2
  interface StreamPart {
5
3
  id: string | undefined;
6
4
  event: string;
7
5
  data: unknown;
8
6
  }
9
- export declare class SSEDecoder extends TransformStream<Uint8Array, StreamPart> {
10
- constructor();
11
- }
7
+ export declare function SSEDecoder(): TransformStream<Uint8Array, StreamPart>;
12
8
  export {};
package/dist/utils/sse.js CHANGED
@@ -4,140 +4,136 @@ const NULL = "\0".charCodeAt(0);
4
4
  const COLON = ":".charCodeAt(0);
5
5
  const SPACE = " ".charCodeAt(0);
6
6
  const TRAILING_NEWLINE = [CR, LF];
7
- export class BytesLineDecoder extends TransformStream {
8
- constructor() {
9
- let buffer = [];
10
- let trailingCr = false;
11
- super({
12
- start() {
13
- buffer = [];
7
+ export function BytesLineDecoder() {
8
+ let buffer = [];
9
+ let trailingCr = false;
10
+ return new TransformStream({
11
+ start() {
12
+ buffer = [];
13
+ trailingCr = false;
14
+ },
15
+ transform(chunk, controller) {
16
+ // See https://docs.python.org/3/glossary.html#term-universal-newlines
17
+ let text = chunk;
18
+ // Handle trailing CR from previous chunk
19
+ if (trailingCr) {
20
+ text = joinArrays([[CR], text]);
14
21
  trailingCr = false;
15
- },
16
- transform(chunk, controller) {
17
- // See https://docs.python.org/3/glossary.html#term-universal-newlines
18
- let text = chunk;
19
- // Handle trailing CR from previous chunk
20
- if (trailingCr) {
21
- text = joinArrays([[CR], text]);
22
- trailingCr = false;
23
- }
24
- // Check for trailing CR in current chunk
25
- if (text.length > 0 && text.at(-1) === CR) {
26
- trailingCr = true;
27
- text = text.subarray(0, -1);
28
- }
29
- if (!text.length)
30
- return;
31
- const trailingNewline = TRAILING_NEWLINE.includes(text.at(-1));
32
- const lastIdx = text.length - 1;
33
- const { lines } = text.reduce((acc, cur, idx) => {
34
- if (acc.from > idx)
35
- return acc;
36
- if (cur === CR || cur === LF) {
37
- acc.lines.push(text.subarray(acc.from, idx));
38
- if (cur === CR && text[idx + 1] === LF) {
39
- acc.from = idx + 2;
40
- }
41
- else {
42
- acc.from = idx + 1;
43
- }
22
+ }
23
+ // Check for trailing CR in current chunk
24
+ if (text.length > 0 && text.at(-1) === CR) {
25
+ trailingCr = true;
26
+ text = text.subarray(0, -1);
27
+ }
28
+ if (!text.length)
29
+ return;
30
+ const trailingNewline = TRAILING_NEWLINE.includes(text.at(-1));
31
+ const lastIdx = text.length - 1;
32
+ const { lines } = text.reduce((acc, cur, idx) => {
33
+ if (acc.from > idx)
34
+ return acc;
35
+ if (cur === CR || cur === LF) {
36
+ acc.lines.push(text.subarray(acc.from, idx));
37
+ if (cur === CR && text[idx + 1] === LF) {
38
+ acc.from = idx + 2;
44
39
  }
45
- if (idx === lastIdx && acc.from <= lastIdx) {
46
- acc.lines.push(text.subarray(acc.from));
40
+ else {
41
+ acc.from = idx + 1;
47
42
  }
48
- return acc;
49
- }, { lines: [], from: 0 });
50
- if (lines.length === 1 && !trailingNewline) {
51
- buffer.push(lines[0]);
52
- return;
53
- }
54
- if (buffer.length) {
55
- // Include existing buffer in first line
56
- buffer.push(lines[0]);
57
- lines[0] = joinArrays(buffer);
58
- buffer = [];
59
- }
60
- if (!trailingNewline) {
61
- // If the last segment is not newline terminated,
62
- // buffer it for the next chunk
63
- if (lines.length)
64
- buffer = [lines.pop()];
65
43
  }
66
- // Enqueue complete lines
67
- for (const line of lines) {
68
- controller.enqueue(line);
44
+ if (idx === lastIdx && acc.from <= lastIdx) {
45
+ acc.lines.push(text.subarray(acc.from));
69
46
  }
70
- },
71
- flush(controller) {
72
- if (buffer.length) {
73
- controller.enqueue(joinArrays(buffer));
74
- }
75
- },
76
- });
77
- }
47
+ return acc;
48
+ }, { lines: [], from: 0 });
49
+ if (lines.length === 1 && !trailingNewline) {
50
+ buffer.push(lines[0]);
51
+ return;
52
+ }
53
+ if (buffer.length) {
54
+ // Include existing buffer in first line
55
+ buffer.push(lines[0]);
56
+ lines[0] = joinArrays(buffer);
57
+ buffer = [];
58
+ }
59
+ if (!trailingNewline) {
60
+ // If the last segment is not newline terminated,
61
+ // buffer it for the next chunk
62
+ if (lines.length)
63
+ buffer = [lines.pop()];
64
+ }
65
+ // Enqueue complete lines
66
+ for (const line of lines) {
67
+ controller.enqueue(line);
68
+ }
69
+ },
70
+ flush(controller) {
71
+ if (buffer.length) {
72
+ controller.enqueue(joinArrays(buffer));
73
+ }
74
+ },
75
+ });
78
76
  }
79
- export class SSEDecoder extends TransformStream {
80
- constructor() {
81
- let event = "";
82
- let data = [];
83
- let lastEventId = "";
84
- let retry = null;
85
- const decoder = new TextDecoder();
86
- super({
87
- transform(chunk, controller) {
88
- // Handle empty line case
89
- if (!chunk.length) {
90
- if (!event && !data.length && !lastEventId && retry == null)
91
- return;
92
- const sse = {
93
- id: lastEventId || undefined,
94
- event,
95
- data: data.length ? decodeArraysToJson(decoder, data) : null,
96
- };
97
- // NOTE: as per the SSE spec, do not reset lastEventId
98
- event = "";
99
- data = [];
100
- retry = null;
101
- controller.enqueue(sse);
102
- return;
103
- }
104
- // Ignore comments
105
- if (chunk[0] === COLON)
77
+ export function SSEDecoder() {
78
+ let event = "";
79
+ let data = [];
80
+ let lastEventId = "";
81
+ let retry = null;
82
+ const decoder = new TextDecoder();
83
+ return new TransformStream({
84
+ transform(chunk, controller) {
85
+ // Handle empty line case
86
+ if (!chunk.length) {
87
+ if (!event && !data.length && !lastEventId && retry == null)
106
88
  return;
107
- const sepIdx = chunk.indexOf(COLON);
108
- if (sepIdx === -1)
109
- return;
110
- const fieldName = decoder.decode(chunk.subarray(0, sepIdx));
111
- let value = chunk.subarray(sepIdx + 1);
112
- if (value[0] === SPACE)
113
- value = value.subarray(1);
114
- if (fieldName === "event") {
115
- event = decoder.decode(value);
116
- }
117
- else if (fieldName === "data") {
118
- data.push(value);
119
- }
120
- else if (fieldName === "id") {
121
- if (value.indexOf(NULL) === -1)
122
- lastEventId = decoder.decode(value);
123
- }
124
- else if (fieldName === "retry") {
125
- const retryNum = Number.parseInt(decoder.decode(value));
126
- if (!Number.isNaN(retryNum))
127
- retry = retryNum;
128
- }
129
- },
130
- flush(controller) {
131
- if (event) {
132
- controller.enqueue({
133
- id: lastEventId || undefined,
134
- event,
135
- data: data.length ? decodeArraysToJson(decoder, data) : null,
136
- });
137
- }
138
- },
139
- });
140
- }
89
+ const sse = {
90
+ id: lastEventId || undefined,
91
+ event,
92
+ data: data.length ? decodeArraysToJson(decoder, data) : null,
93
+ };
94
+ // NOTE: as per the SSE spec, do not reset lastEventId
95
+ event = "";
96
+ data = [];
97
+ retry = null;
98
+ controller.enqueue(sse);
99
+ return;
100
+ }
101
+ // Ignore comments
102
+ if (chunk[0] === COLON)
103
+ return;
104
+ const sepIdx = chunk.indexOf(COLON);
105
+ if (sepIdx === -1)
106
+ return;
107
+ const fieldName = decoder.decode(chunk.subarray(0, sepIdx));
108
+ let value = chunk.subarray(sepIdx + 1);
109
+ if (value[0] === SPACE)
110
+ value = value.subarray(1);
111
+ if (fieldName === "event") {
112
+ event = decoder.decode(value);
113
+ }
114
+ else if (fieldName === "data") {
115
+ data.push(value);
116
+ }
117
+ else if (fieldName === "id") {
118
+ if (value.indexOf(NULL) === -1)
119
+ lastEventId = decoder.decode(value);
120
+ }
121
+ else if (fieldName === "retry") {
122
+ const retryNum = Number.parseInt(decoder.decode(value));
123
+ if (!Number.isNaN(retryNum))
124
+ retry = retryNum;
125
+ }
126
+ },
127
+ flush(controller) {
128
+ if (event) {
129
+ controller.enqueue({
130
+ id: lastEventId || undefined,
131
+ event,
132
+ data: data.length ? decodeArraysToJson(decoder, data) : null,
133
+ });
134
+ }
135
+ },
136
+ });
141
137
  }
142
138
  function joinArrays(data) {
143
139
  const totalLength = data.reduce((acc, curr) => acc + curr.length, 0);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@langchain/langgraph-sdk",
3
- "version": "0.0.87",
3
+ "version": "0.0.88",
4
4
  "description": "Client library for interacting with the LangGraph API",
5
5
  "type": "module",
6
6
  "packageManager": "yarn@1.22.19",
@@ -22,7 +22,9 @@
22
22
  "uuid": "^9.0.0"
23
23
  },
24
24
  "devDependencies": {
25
- "@langchain/core": "^0.3.31",
25
+ "@langchain/langgraph-api": "~0.0.41",
26
+ "@langchain/core": "^0.3.61",
27
+ "@langchain/langgraph": "^0.3.5",
26
28
  "@langchain/scripts": "^0.1.4",
27
29
  "@testing-library/dom": "^10.4.0",
28
30
  "@testing-library/jest-dom": "^6.6.3",
@@ -35,6 +37,7 @@
35
37
  "@types/uuid": "^9.0.1",
36
38
  "@vitejs/plugin-react": "^4.4.1",
37
39
  "concat-md": "^0.5.1",
40
+ "hono": "^4.8.2",
38
41
  "jsdom": "^26.1.0",
39
42
  "msw": "^2.8.2",
40
43
  "prettier": "^3.2.5",