@langchain/langgraph-sdk 0.0.86 → 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
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.Client = exports.StoreClient = exports.RunsClient = exports.ThreadsClient = exports.AssistantsClient = exports.CronsClient = exports.getApiKey = void 0;
3
+ exports.getClientConfigHash = exports.Client = exports.StoreClient = exports.RunsClient = exports.ThreadsClient = exports.AssistantsClient = exports.CronsClient = exports.getApiKey = void 0;
4
4
  const async_caller_js_1 = require("./utils/async_caller.cjs");
5
5
  const env_js_1 = require("./utils/env.cjs");
6
6
  const signals_js_1 = require("./utils/signals.cjs");
@@ -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
  /**
@@ -1156,6 +1156,28 @@ class Client {
1156
1156
  writable: true,
1157
1157
  value: void 0
1158
1158
  });
1159
+ /**
1160
+ * @internal Used to obtain a stable key representing the client.
1161
+ */
1162
+ Object.defineProperty(this, "~configHash", {
1163
+ enumerable: true,
1164
+ configurable: true,
1165
+ writable: true,
1166
+ value: void 0
1167
+ });
1168
+ this["~configHash"] = (() => JSON.stringify({
1169
+ apiUrl: config?.apiUrl,
1170
+ apiKey: config?.apiKey,
1171
+ timeoutMs: config?.timeoutMs,
1172
+ defaultHeaders: config?.defaultHeaders,
1173
+ maxConcurrency: config?.callerOptions?.maxConcurrency,
1174
+ maxRetries: config?.callerOptions?.maxRetries,
1175
+ callbacks: {
1176
+ onFailedResponseHook: config?.callerOptions?.onFailedResponseHook != null,
1177
+ onRequest: config?.onRequest != null,
1178
+ fetch: config?.callerOptions?.fetch != null,
1179
+ },
1180
+ }))();
1159
1181
  this.assistants = new AssistantsClient(config);
1160
1182
  this.threads = new ThreadsClient(config);
1161
1183
  this.runs = new RunsClient(config);
@@ -1165,3 +1187,10 @@ class Client {
1165
1187
  }
1166
1188
  }
1167
1189
  exports.Client = Client;
1190
+ /**
1191
+ * @internal Used to obtain a stable key representing the client.
1192
+ */
1193
+ function getClientConfigHash(client) {
1194
+ return client["~configHash"];
1195
+ }
1196
+ exports.getClientConfigHash = getClientConfigHash;
package/dist/client.d.ts CHANGED
@@ -596,6 +596,14 @@ export declare class Client<TStateType = DefaultValues, TUpdateType = TStateType
596
596
  * @internal Used by LoadExternalComponent and the API might change in the future.
597
597
  */
598
598
  "~ui": UiClient;
599
+ /**
600
+ * @internal Used to obtain a stable key representing the client.
601
+ */
602
+ private "~configHash";
599
603
  constructor(config?: ClientConfig);
600
604
  }
605
+ /**
606
+ * @internal Used to obtain a stable key representing the client.
607
+ */
608
+ export declare function getClientConfigHash(client: Client): string | undefined;
601
609
  export {};
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
  /**
@@ -1147,6 +1147,28 @@ export class Client {
1147
1147
  writable: true,
1148
1148
  value: void 0
1149
1149
  });
1150
+ /**
1151
+ * @internal Used to obtain a stable key representing the client.
1152
+ */
1153
+ Object.defineProperty(this, "~configHash", {
1154
+ enumerable: true,
1155
+ configurable: true,
1156
+ writable: true,
1157
+ value: void 0
1158
+ });
1159
+ this["~configHash"] = (() => JSON.stringify({
1160
+ apiUrl: config?.apiUrl,
1161
+ apiKey: config?.apiKey,
1162
+ timeoutMs: config?.timeoutMs,
1163
+ defaultHeaders: config?.defaultHeaders,
1164
+ maxConcurrency: config?.callerOptions?.maxConcurrency,
1165
+ maxRetries: config?.callerOptions?.maxRetries,
1166
+ callbacks: {
1167
+ onFailedResponseHook: config?.callerOptions?.onFailedResponseHook != null,
1168
+ onRequest: config?.onRequest != null,
1169
+ fetch: config?.callerOptions?.fetch != null,
1170
+ },
1171
+ }))();
1150
1172
  this.assistants = new AssistantsClient(config);
1151
1173
  this.threads = new ThreadsClient(config);
1152
1174
  this.runs = new RunsClient(config);
@@ -1155,3 +1177,9 @@ export class Client {
1155
1177
  this["~ui"] = new UiClient(config);
1156
1178
  }
1157
1179
  }
1180
+ /**
1181
+ * @internal Used to obtain a stable key representing the client.
1182
+ */
1183
+ export function getClientConfigHash(client) {
1184
+ return client["~configHash"];
1185
+ }
@@ -174,8 +174,12 @@ function fetchHistory(client, threadId) {
174
174
  }
175
175
  function useThreadHistory(threadId, client, clearCallbackRef, submittingRef) {
176
176
  const [history, setHistory] = (0, react_1.useState)([]);
177
+ const clientHash = (0, client_js_1.getClientConfigHash)(client);
178
+ const clientRef = (0, react_1.useRef)(client);
179
+ clientRef.current = client;
177
180
  const fetcher = (0, react_1.useCallback)((threadId) => {
178
181
  if (threadId != null) {
182
+ const client = clientRef.current;
179
183
  return fetchHistory(client, threadId).then((history) => {
180
184
  setHistory(history);
181
185
  return history;
@@ -189,7 +193,7 @@ function useThreadHistory(threadId, client, clearCallbackRef, submittingRef) {
189
193
  if (submittingRef.current)
190
194
  return;
191
195
  fetcher(threadId);
192
- }, [fetcher, submittingRef, threadId]);
196
+ }, [fetcher, clientHash, submittingRef, threadId]);
193
197
  return {
194
198
  data: history,
195
199
  mutate: (mutateId) => fetcher(mutateId ?? threadId),
@@ -208,6 +212,32 @@ const useControllableThreadId = (options) => {
208
212
  }
209
213
  return [options.threadId ?? null, onThreadId];
210
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
+ }
211
241
  function useStream(options) {
212
242
  let { assistantId, messagesKey, onCreated, onError, onFinish } = options;
213
243
  const reconnectOnMountRef = (0, react_1.useRef)(options.reconnectOnMount);
@@ -239,7 +269,7 @@ function useStream(options) {
239
269
  const [branch, setBranch] = (0, react_1.useState)("");
240
270
  const [isLoading, setIsLoading] = (0, react_1.useState)(false);
241
271
  const [streamError, setStreamError] = (0, react_1.useState)(undefined);
242
- const [streamValues, setStreamValues] = (0, react_1.useState)(null);
272
+ const [streamValues, setStreamValues, getMutateFn] = useStreamValuesState();
243
273
  const messageManagerRef = (0, react_1.useRef)(new MessageTupleManager());
244
274
  const submittingRef = (0, react_1.useRef)(false);
245
275
  const abortRef = (0, react_1.useRef)(null);
@@ -289,7 +319,7 @@ function useStream(options) {
289
319
  const { rootSequence, paths } = getBranchSequence(history.data);
290
320
  const { history: flatHistory, branchByCheckpoint } = getBranchView(rootSequence, paths, branch);
291
321
  const threadHead = flatHistory.at(-1);
292
- const historyValues = threadHead?.values ?? {};
322
+ const historyValues = threadHead?.values ?? options.initialValues ?? {};
293
323
  const historyError = (() => {
294
324
  const error = threadHead?.tasks?.at(-1)?.error;
295
325
  if (error == null)
@@ -344,6 +374,7 @@ function useStream(options) {
344
374
  client.runs.cancel(threadId, runId);
345
375
  runMetadataStorage.removeItem(`lg:stream:${threadId}`);
346
376
  }
377
+ options?.onStop?.({ mutate: getMutateFn("stop", historyValues) });
347
378
  };
348
379
  async function consumeStream(action) {
349
380
  let getCallbackMeta;
@@ -364,15 +395,7 @@ function useStream(options) {
364
395
  options.onUpdateEvent?.(data);
365
396
  if (event === "custom")
366
397
  options.onCustomEvent?.(data, {
367
- mutate: (update) => setStreamValues((prev) => {
368
- // should not happen
369
- if (prev == null)
370
- return prev;
371
- return {
372
- ...prev,
373
- ...(typeof update === "function" ? update(prev) : update),
374
- };
375
- }),
398
+ mutate: getMutateFn("stream", historyValues),
376
399
  });
377
400
  if (event === "metadata")
378
401
  options.onMetadataEvent?.(data);
@@ -408,7 +431,12 @@ function useStream(options) {
408
431
  }
409
432
  // TODO: stream created checkpoints to avoid an unnecessary network request
410
433
  const result = await run.onSuccess();
411
- 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
+ });
412
440
  if (streamError != null)
413
441
  throw streamError;
414
442
  const lastHead = result.at(0);
@@ -431,7 +459,7 @@ function useStream(options) {
431
459
  abortRef.current = null;
432
460
  }
433
461
  }
434
- const joinStream = async (runId, lastEventId) => {
462
+ const joinStream = async (runId, lastEventId, options) => {
435
463
  lastEventId ??= "-1";
436
464
  if (!threadId)
437
465
  return;
@@ -439,6 +467,7 @@ function useStream(options) {
439
467
  const stream = client.runs.joinStream(threadId, runId, {
440
468
  signal,
441
469
  lastEventId,
470
+ streamMode: options?.streamMode,
442
471
  });
443
472
  return {
444
473
  onSuccess: () => {
@@ -458,23 +487,22 @@ function useStream(options) {
458
487
  : undefined;
459
488
  if (newPath != null)
460
489
  setBranch(newPath ?? "");
461
- // Assumption: we're setting the initial value
462
- // Used for instant feedback
463
490
  setStreamValues(() => {
464
- const values = { ...historyValues };
465
491
  if (submitOptions?.optimisticValues != null) {
466
492
  return {
467
- ...values,
493
+ ...historyValues,
468
494
  ...(typeof submitOptions.optimisticValues === "function"
469
- ? submitOptions.optimisticValues(values)
495
+ ? submitOptions.optimisticValues(historyValues)
470
496
  : submitOptions.optimisticValues),
471
497
  };
472
498
  }
473
- return values;
499
+ return { ...historyValues };
474
500
  });
475
501
  let usableThreadId = threadId;
476
502
  if (!usableThreadId) {
477
- const thread = await client.threads.create();
503
+ const thread = await client.threads.create({
504
+ threadId: submitOptions?.threadId,
505
+ });
478
506
  onThreadId(thread.thread_id);
479
507
  usableThreadId = thread.thread_id;
480
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>;
@@ -1,6 +1,6 @@
1
1
  /* __LC_ALLOW_ENTRYPOINT_SIDE_EFFECTS__ */
2
2
  "use client";
3
- import { Client } from "../client.js";
3
+ import { Client, getClientConfigHash } from "../client.js";
4
4
  import { useCallback, useEffect, useMemo, useRef, useState, } from "react";
5
5
  import { coerceMessageLikeToMessage, convertToChunk, isBaseMessageChunk, } from "@langchain/core/messages";
6
6
  class StreamError extends Error {
@@ -171,8 +171,12 @@ function fetchHistory(client, threadId) {
171
171
  }
172
172
  function useThreadHistory(threadId, client, clearCallbackRef, submittingRef) {
173
173
  const [history, setHistory] = useState([]);
174
+ const clientHash = getClientConfigHash(client);
175
+ const clientRef = useRef(client);
176
+ clientRef.current = client;
174
177
  const fetcher = useCallback((threadId) => {
175
178
  if (threadId != null) {
179
+ const client = clientRef.current;
176
180
  return fetchHistory(client, threadId).then((history) => {
177
181
  setHistory(history);
178
182
  return history;
@@ -186,7 +190,7 @@ function useThreadHistory(threadId, client, clearCallbackRef, submittingRef) {
186
190
  if (submittingRef.current)
187
191
  return;
188
192
  fetcher(threadId);
189
- }, [fetcher, submittingRef, threadId]);
193
+ }, [fetcher, clientHash, submittingRef, threadId]);
190
194
  return {
191
195
  data: history,
192
196
  mutate: (mutateId) => fetcher(mutateId ?? threadId),
@@ -205,6 +209,32 @@ const useControllableThreadId = (options) => {
205
209
  }
206
210
  return [options.threadId ?? null, onThreadId];
207
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
+ }
208
238
  export function useStream(options) {
209
239
  let { assistantId, messagesKey, onCreated, onError, onFinish } = options;
210
240
  const reconnectOnMountRef = useRef(options.reconnectOnMount);
@@ -236,7 +266,7 @@ export function useStream(options) {
236
266
  const [branch, setBranch] = useState("");
237
267
  const [isLoading, setIsLoading] = useState(false);
238
268
  const [streamError, setStreamError] = useState(undefined);
239
- const [streamValues, setStreamValues] = useState(null);
269
+ const [streamValues, setStreamValues, getMutateFn] = useStreamValuesState();
240
270
  const messageManagerRef = useRef(new MessageTupleManager());
241
271
  const submittingRef = useRef(false);
242
272
  const abortRef = useRef(null);
@@ -286,7 +316,7 @@ export function useStream(options) {
286
316
  const { rootSequence, paths } = getBranchSequence(history.data);
287
317
  const { history: flatHistory, branchByCheckpoint } = getBranchView(rootSequence, paths, branch);
288
318
  const threadHead = flatHistory.at(-1);
289
- const historyValues = threadHead?.values ?? {};
319
+ const historyValues = threadHead?.values ?? options.initialValues ?? {};
290
320
  const historyError = (() => {
291
321
  const error = threadHead?.tasks?.at(-1)?.error;
292
322
  if (error == null)
@@ -341,6 +371,7 @@ export function useStream(options) {
341
371
  client.runs.cancel(threadId, runId);
342
372
  runMetadataStorage.removeItem(`lg:stream:${threadId}`);
343
373
  }
374
+ options?.onStop?.({ mutate: getMutateFn("stop", historyValues) });
344
375
  };
345
376
  async function consumeStream(action) {
346
377
  let getCallbackMeta;
@@ -361,15 +392,7 @@ export function useStream(options) {
361
392
  options.onUpdateEvent?.(data);
362
393
  if (event === "custom")
363
394
  options.onCustomEvent?.(data, {
364
- mutate: (update) => setStreamValues((prev) => {
365
- // should not happen
366
- if (prev == null)
367
- return prev;
368
- return {
369
- ...prev,
370
- ...(typeof update === "function" ? update(prev) : update),
371
- };
372
- }),
395
+ mutate: getMutateFn("stream", historyValues),
373
396
  });
374
397
  if (event === "metadata")
375
398
  options.onMetadataEvent?.(data);
@@ -405,7 +428,12 @@ export function useStream(options) {
405
428
  }
406
429
  // TODO: stream created checkpoints to avoid an unnecessary network request
407
430
  const result = await run.onSuccess();
408
- 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
+ });
409
437
  if (streamError != null)
410
438
  throw streamError;
411
439
  const lastHead = result.at(0);
@@ -428,7 +456,7 @@ export function useStream(options) {
428
456
  abortRef.current = null;
429
457
  }
430
458
  }
431
- const joinStream = async (runId, lastEventId) => {
459
+ const joinStream = async (runId, lastEventId, options) => {
432
460
  lastEventId ??= "-1";
433
461
  if (!threadId)
434
462
  return;
@@ -436,6 +464,7 @@ export function useStream(options) {
436
464
  const stream = client.runs.joinStream(threadId, runId, {
437
465
  signal,
438
466
  lastEventId,
467
+ streamMode: options?.streamMode,
439
468
  });
440
469
  return {
441
470
  onSuccess: () => {
@@ -455,23 +484,22 @@ export function useStream(options) {
455
484
  : undefined;
456
485
  if (newPath != null)
457
486
  setBranch(newPath ?? "");
458
- // Assumption: we're setting the initial value
459
- // Used for instant feedback
460
487
  setStreamValues(() => {
461
- const values = { ...historyValues };
462
488
  if (submitOptions?.optimisticValues != null) {
463
489
  return {
464
- ...values,
490
+ ...historyValues,
465
491
  ...(typeof submitOptions.optimisticValues === "function"
466
- ? submitOptions.optimisticValues(values)
492
+ ? submitOptions.optimisticValues(historyValues)
467
493
  : submitOptions.optimisticValues),
468
494
  };
469
495
  }
470
- return values;
496
+ return { ...historyValues };
471
497
  });
472
498
  let usableThreadId = threadId;
473
499
  if (!usableThreadId) {
474
- const thread = await client.threads.create();
500
+ const thread = await client.threads.create({
501
+ threadId: submitOptions?.threadId,
502
+ });
475
503
  onThreadId(thread.thread_id);
476
504
  usableThreadId = thread.thread_id;
477
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.86",
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",