@langchain/langgraph-sdk 0.0.111 → 0.1.0

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.
@@ -5,190 +5,12 @@
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.useStream = useStream;
7
7
  const react_1 = require("react");
8
- const messages_1 = require("@langchain/core/messages");
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");
9
12
  const client_js_1 = require("../client.cjs");
10
- class StreamError extends Error {
11
- constructor(data) {
12
- super(data.message);
13
- this.name = data.name ?? data.error ?? "StreamError";
14
- }
15
- static isStructuredError(error) {
16
- return typeof error === "object" && error != null && "message" in error;
17
- }
18
- }
19
- function tryConvertToChunk(message) {
20
- try {
21
- return (0, messages_1.convertToChunk)(message);
22
- }
23
- catch {
24
- return null;
25
- }
26
- }
27
- class MessageTupleManager {
28
- constructor() {
29
- Object.defineProperty(this, "chunks", {
30
- enumerable: true,
31
- configurable: true,
32
- writable: true,
33
- value: {}
34
- });
35
- this.chunks = {};
36
- }
37
- add(serialized, metadata) {
38
- // TODO: this is sometimes sent from the API
39
- // figure out how to prevent this or move this to LC.js
40
- if (serialized.type.endsWith("MessageChunk")) {
41
- // eslint-disable-next-line no-param-reassign
42
- serialized.type = serialized.type
43
- .slice(0, -"MessageChunk".length)
44
- .toLowerCase();
45
- }
46
- const message = (0, messages_1.coerceMessageLikeToMessage)(serialized);
47
- const chunk = tryConvertToChunk(message);
48
- const { id } = chunk ?? message;
49
- if (!id) {
50
- console.warn("No message ID found for chunk, ignoring in state", serialized);
51
- return null;
52
- }
53
- this.chunks[id] ??= {};
54
- this.chunks[id].metadata = metadata ?? this.chunks[id].metadata;
55
- if (chunk) {
56
- const prev = this.chunks[id].chunk;
57
- this.chunks[id].chunk =
58
- ((0, messages_1.isBaseMessageChunk)(prev) ? prev : null)?.concat(chunk) ?? chunk;
59
- }
60
- else {
61
- this.chunks[id].chunk = message;
62
- }
63
- return id;
64
- }
65
- clear() {
66
- this.chunks = {};
67
- }
68
- get(id, defaultIndex) {
69
- if (this.chunks[id] == null)
70
- return null;
71
- if (defaultIndex != null)
72
- this.chunks[id].index ??= defaultIndex;
73
- return this.chunks[id];
74
- }
75
- }
76
- const toMessageDict = (chunk) => {
77
- const { type, data } = chunk.toDict();
78
- return { ...data, type };
79
- };
80
- function unique(array) {
81
- return [...new Set(array)];
82
- }
83
- function findLastIndex(array, predicate) {
84
- for (let i = array.length - 1; i >= 0; i -= 1) {
85
- if (predicate(array[i]))
86
- return i;
87
- }
88
- return -1;
89
- }
90
- function getBranchSequence(history) {
91
- const childrenMap = {};
92
- // Short circuit if there's only a singular one state
93
- // TODO: I think we can make this more generalizable for all `fetchStateHistory` values.
94
- if (history.length <= 1) {
95
- return {
96
- rootSequence: {
97
- type: "sequence",
98
- items: history.map((value) => ({ type: "node", value, path: [] })),
99
- },
100
- paths: [],
101
- };
102
- }
103
- // First pass - collect nodes for each checkpoint
104
- history.forEach((state) => {
105
- const checkpointId = state.parent_checkpoint?.checkpoint_id ?? "$";
106
- childrenMap[checkpointId] ??= [];
107
- childrenMap[checkpointId].push(state);
108
- });
109
- const rootSequence = { type: "sequence", items: [] };
110
- const queue = [{ id: "$", sequence: rootSequence, path: [] }];
111
- const paths = [];
112
- const visited = new Set();
113
- while (queue.length > 0) {
114
- const task = queue.shift();
115
- if (visited.has(task.id))
116
- continue;
117
- visited.add(task.id);
118
- const children = childrenMap[task.id];
119
- if (children == null || children.length === 0)
120
- continue;
121
- // If we've encountered a fork (2+ children), push the fork
122
- // to the sequence and add a new sequence for each child
123
- let fork;
124
- if (children.length > 1) {
125
- fork = { type: "fork", items: [] };
126
- task.sequence.items.push(fork);
127
- }
128
- for (const value of children) {
129
- const id = value.checkpoint?.checkpoint_id;
130
- if (id == null)
131
- continue;
132
- let { sequence } = task;
133
- let { path } = task;
134
- if (fork != null) {
135
- sequence = { type: "sequence", items: [] };
136
- fork.items.unshift(sequence);
137
- path = path.slice();
138
- path.push(id);
139
- paths.push(path);
140
- }
141
- sequence.items.push({ type: "node", value, path });
142
- queue.push({ id, sequence, path });
143
- }
144
- }
145
- return { rootSequence, paths };
146
- }
147
- const PATH_SEP = ">";
148
- const ROOT_ID = "$";
149
- // Get flat view
150
- function getBranchView(sequence, paths, branch) {
151
- const path = branch.split(PATH_SEP);
152
- const pathMap = {};
153
- for (const path of paths) {
154
- const parent = path.at(-2) ?? ROOT_ID;
155
- pathMap[parent] ??= [];
156
- pathMap[parent].unshift(path);
157
- }
158
- const history = [];
159
- const branchByCheckpoint = {};
160
- const forkStack = path.slice();
161
- const queue = [...sequence.items];
162
- while (queue.length > 0) {
163
- const item = queue.shift();
164
- if (item.type === "node") {
165
- history.push(item.value);
166
- const checkpointId = item.value.checkpoint?.checkpoint_id;
167
- if (checkpointId == null)
168
- continue;
169
- branchByCheckpoint[checkpointId] = {
170
- branch: item.path.join(PATH_SEP),
171
- branchOptions: (item.path.length > 0
172
- ? pathMap[item.path.at(-2) ?? ROOT_ID] ?? []
173
- : []).map((p) => p.join(PATH_SEP)),
174
- };
175
- }
176
- if (item.type === "fork") {
177
- const forkId = forkStack.shift();
178
- const index = forkId != null
179
- ? item.items.findIndex((value) => {
180
- const firstItem = value.items.at(0);
181
- if (!firstItem || firstItem.type !== "node")
182
- return false;
183
- return firstItem.value.checkpoint?.checkpoint_id === forkId;
184
- })
185
- : -1;
186
- const nextItems = item.items.at(index)?.items ?? [];
187
- queue.push(...nextItems);
188
- }
189
- }
190
- return { history, branchByCheckpoint };
191
- }
13
+ const messages_js_1 = require("./messages.cjs");
192
14
  function fetchHistory(client, threadId, options) {
193
15
  if (options?.limit === false) {
194
16
  return client.threads.getState(threadId).then((state) => {
@@ -197,12 +19,16 @@ function fetchHistory(client, threadId, options) {
197
19
  return [state];
198
20
  });
199
21
  }
200
- const limit = typeof options?.limit === "number" ? options.limit : 1000;
22
+ const limit = typeof options?.limit === "number" ? options.limit : 10;
201
23
  return client.threads.getHistory(threadId, { limit });
202
24
  }
203
25
  function useThreadHistory(threadId, client, limit, clearCallbackRef, submittingRef, onErrorRef) {
204
26
  const [history, setHistory] = (0, react_1.useState)(undefined);
205
- const [isLoading, setIsLoading] = (0, react_1.useState)(false);
27
+ const [isLoading, setIsLoading] = (0, react_1.useState)(() => {
28
+ if (threadId == null)
29
+ return false;
30
+ return true;
31
+ });
206
32
  const [error, setError] = (0, react_1.useState)(undefined);
207
33
  const clientHash = (0, client_js_1.getClientConfigHash)(client);
208
34
  const clientRef = (0, react_1.useRef)(client);
@@ -236,7 +62,7 @@ function useThreadHistory(threadId, client, limit, clearCallbackRef, submittingR
236
62
  if (submittingRef.current)
237
63
  return;
238
64
  void fetcher(threadId);
239
- }, [fetcher, clientHash, limit, submittingRef, threadId]);
65
+ }, [fetcher, submittingRef, clientHash, limit, threadId]);
240
66
  return {
241
67
  data: history,
242
68
  isLoading,
@@ -257,39 +83,7 @@ const useControllableThreadId = (options) => {
257
83
  }
258
84
  return [options.threadId ?? null, onThreadId];
259
85
  };
260
- function useStreamValuesState() {
261
- const [values, setValues] = (0, react_1.useState)(null);
262
- const setStreamValues = (0, react_1.useCallback)((values, kind = "stream") => {
263
- if (typeof values === "function") {
264
- setValues((prevTuple) => {
265
- const [prevValues, prevKind] = prevTuple ?? [null, "stream"];
266
- const next = values(prevValues, prevKind);
267
- if (next == null)
268
- return null;
269
- return [next, kind];
270
- });
271
- return;
272
- }
273
- if (values == null)
274
- setValues(null);
275
- setValues([values, kind]);
276
- }, []);
277
- const mutate = (0, react_1.useCallback)((kind, serverValues) => (update) => {
278
- setStreamValues((clientValues) => {
279
- const prev = { ...serverValues, ...clientValues };
280
- const next = typeof update === "function" ? update(prev) : update;
281
- return { ...prev, ...next };
282
- }, kind);
283
- }, [setStreamValues]);
284
- return [values?.[0] ?? null, setStreamValues, mutate];
285
- }
286
86
  function useStream(options) {
287
- const matchEventType = (expected, actual, _data) => {
288
- return expected === actual || actual.startsWith(`${expected}|`);
289
- };
290
- let { messagesKey } = options;
291
- const { assistantId, fetchStateHistory } = options;
292
- const { onCreated, onError, onFinish } = options;
293
87
  const reconnectOnMountRef = (0, react_1.useRef)(options.reconnectOnMount);
294
88
  const runMetadataStorage = (0, react_1.useMemo)(() => {
295
89
  if (typeof window === "undefined")
@@ -301,7 +95,6 @@ function useStream(options) {
301
95
  return storage();
302
96
  return null;
303
97
  }, []);
304
- messagesKey ??= "messages";
305
98
  const client = (0, react_1.useMemo)(() => options.client ??
306
99
  new client_js_1.Client({
307
100
  apiUrl: options.apiUrl,
@@ -315,20 +108,16 @@ function useStream(options) {
315
108
  options.callerOptions,
316
109
  options.defaultHeaders,
317
110
  ]);
111
+ const [messageManager] = (0, react_1.useState)(() => new messages_js_1.MessageTupleManager());
112
+ const [stream] = (0, react_1.useState)(() => new manager_js_1.StreamManager(messageManager));
113
+ (0, react_1.useSyncExternalStore)(stream.subscribe, stream.getSnapshot, stream.getSnapshot);
318
114
  const [threadId, onThreadId] = useControllableThreadId(options);
319
- const [branch, setBranch] = (0, react_1.useState)("");
320
- const [isLoading, setIsLoading] = (0, react_1.useState)(false);
321
- const [streamError, setStreamError] = (0, react_1.useState)(undefined);
322
- const [streamValues, setStreamValues, getMutateFn] = useStreamValuesState();
323
- const messageManagerRef = (0, react_1.useRef)(new MessageTupleManager());
324
- const submittingRef = (0, react_1.useRef)(false);
325
- const abortRef = (0, react_1.useRef)(null);
326
115
  const trackStreamModeRef = (0, react_1.useRef)([]);
327
116
  const trackStreamMode = (0, react_1.useCallback)((...mode) => {
117
+ const ref = trackStreamModeRef.current;
328
118
  for (const m of mode) {
329
- if (!trackStreamModeRef.current.includes(m)) {
330
- trackStreamModeRef.current.push(m);
331
- }
119
+ if (!ref.includes(m))
120
+ ref.push(m);
332
121
  }
333
122
  }, []);
334
123
  const hasUpdateListener = options.onUpdateEvent != null;
@@ -361,35 +150,37 @@ function useStream(options) {
361
150
  hasTaskListener,
362
151
  ]);
363
152
  const clearCallbackRef = (0, react_1.useRef)(null);
364
- clearCallbackRef.current = () => {
365
- setStreamError(undefined);
366
- setStreamValues(null);
367
- messageManagerRef.current.clear();
368
- };
153
+ clearCallbackRef.current = stream.clear;
154
+ const submittingRef = (0, react_1.useRef)(false);
155
+ submittingRef.current = stream.isLoading;
369
156
  const onErrorRef = (0, react_1.useRef)(undefined);
370
157
  onErrorRef.current = options.onError;
371
- const historyLimit = typeof fetchStateHistory === "object" && fetchStateHistory != null
372
- ? fetchStateHistory.limit ?? true
373
- : fetchStateHistory ?? true;
158
+ const historyLimit = typeof options.fetchStateHistory === "object" &&
159
+ options.fetchStateHistory != null
160
+ ? options.fetchStateHistory.limit ?? false
161
+ : options.fetchStateHistory ?? false;
374
162
  const history = useThreadHistory(threadId, client, historyLimit, clearCallbackRef, submittingRef, onErrorRef);
375
- const getMessages = (0, react_1.useMemo)(() => {
376
- return (value) => Array.isArray(value[messagesKey])
377
- ? value[messagesKey]
378
- : [];
379
- }, [messagesKey]);
380
- const { rootSequence, paths } = getBranchSequence(history.data ?? []);
381
- const { history: flatHistory, branchByCheckpoint } = getBranchView(rootSequence, paths, branch);
382
- const threadHead = flatHistory.at(-1);
383
- const historyValues = threadHead?.values ?? options.initialValues ?? {};
384
- const historyValueError = (() => {
385
- const error = threadHead?.tasks?.at(-1)?.error;
163
+ const getMessages = (value) => {
164
+ const messagesKey = options.messagesKey ?? "messages";
165
+ return Array.isArray(value[messagesKey]) ? value[messagesKey] : [];
166
+ };
167
+ const setMessages = (current, messages) => {
168
+ const messagesKey = options.messagesKey ?? "messages";
169
+ return { ...current, [messagesKey]: messages };
170
+ };
171
+ const [branch, setBranch] = (0, react_1.useState)("");
172
+ const branchContext = (0, branching_js_1.getBranchContext)(branch, history.data);
173
+ const historyValues = branchContext.threadHead?.values ??
174
+ options.initialValues ??
175
+ {};
176
+ const historyError = (() => {
177
+ const error = branchContext.threadHead?.tasks?.at(-1)?.error;
386
178
  if (error == null)
387
179
  return undefined;
388
180
  try {
389
181
  const parsed = JSON.parse(error);
390
- if (StreamError.isStructuredError(parsed)) {
391
- return new StreamError(parsed);
392
- }
182
+ if (errors_js_1.StreamError.isStructuredError(parsed))
183
+ return new errors_js_1.StreamError(parsed);
393
184
  return parsed;
394
185
  }
395
186
  catch {
@@ -401,16 +192,13 @@ function useStream(options) {
401
192
  const alreadyShown = new Set();
402
193
  return getMessages(historyValues).map((message, idx) => {
403
194
  const messageId = message.id ?? idx;
404
- const streamMetadata = message.id != null
405
- ? messageManagerRef.current.get(message.id)?.metadata ?? undefined
406
- : undefined;
407
- const firstSeenIdx = findLastIndex(history.data ?? [], (state) => getMessages(state.values)
195
+ // Find the first checkpoint where the message was seen
196
+ const firstSeenState = (0, utils_js_1.findLast)(history.data ?? [], (state) => getMessages(state.values)
408
197
  .map((m, idx) => m.id ?? idx)
409
198
  .includes(messageId));
410
- const firstSeen = history.data?.[firstSeenIdx];
411
- const checkpointId = firstSeen?.checkpoint?.checkpoint_id;
412
- let branch = firstSeen && checkpointId != null
413
- ? branchByCheckpoint[checkpointId]
199
+ const checkpointId = firstSeenState?.checkpoint?.checkpoint_id;
200
+ let branch = checkpointId != null
201
+ ? branchContext.branchByCheckpoint[checkpointId]
414
202
  : undefined;
415
203
  if (!branch?.branch?.length)
416
204
  branch = undefined;
@@ -423,158 +211,38 @@ function useStream(options) {
423
211
  }
424
212
  return {
425
213
  messageId: messageId.toString(),
426
- firstSeenState: firstSeen,
214
+ firstSeenState,
427
215
  branch: branch?.branch,
428
216
  branchOptions: branch?.branchOptions,
429
- streamMetadata,
430
217
  };
431
218
  });
432
219
  })();
433
- const stop = () => {
434
- if (abortRef.current != null)
435
- abortRef.current.abort();
436
- abortRef.current = null;
437
- if (runMetadataStorage && threadId) {
438
- const runId = runMetadataStorage.getItem(`lg:stream:${threadId}`);
439
- if (runId)
440
- void client.runs.cancel(threadId, runId);
441
- runMetadataStorage.removeItem(`lg:stream:${threadId}`);
442
- }
443
- options?.onStop?.({ mutate: getMutateFn("stop", historyValues) });
444
- };
445
- async function consumeStream(action) {
446
- let getCallbackMeta;
447
- try {
448
- setIsLoading(true);
449
- setStreamError(undefined);
450
- submittingRef.current = true;
451
- abortRef.current = new AbortController();
452
- const run = await action(abortRef.current.signal);
453
- getCallbackMeta = run.getCallbackMeta;
454
- let streamError;
455
- for await (const { event, data } of run.stream) {
456
- if (event === "error") {
457
- streamError = new StreamError(data);
458
- break;
459
- }
460
- const namespace = event.includes("|")
461
- ? event.split("|").slice(1)
462
- : undefined;
463
- const mutate = getMutateFn("stream", historyValues);
464
- if (event === "metadata")
465
- options.onMetadataEvent?.(data);
466
- if (event === "events")
467
- options.onLangChainEvent?.(data);
468
- if (matchEventType("updates", event, data)) {
469
- options.onUpdateEvent?.(data, { namespace, mutate });
470
- }
471
- if (matchEventType("custom", event, data)) {
472
- options.onCustomEvent?.(data, { namespace, mutate });
473
- }
474
- if (matchEventType("checkpoints", event, data)) {
475
- options.onCheckpointEvent?.(data, { namespace });
476
- }
477
- if (matchEventType("tasks", event, data)) {
478
- options.onTaskEvent?.(data, { namespace });
479
- }
480
- if (matchEventType("debug", event, data)) {
481
- options.onDebugEvent?.(data, { namespace });
482
- }
483
- if (event === "values") {
484
- // don't update values on interrupt values event
485
- if ("__interrupt__" in data)
486
- continue;
487
- setStreamValues(data);
488
- }
489
- // Consume subgraph messages as well
490
- if (matchEventType("messages", event, data)) {
491
- const [serialized, metadata] = data;
492
- const messageId = messageManagerRef.current.add(serialized, metadata);
493
- if (!messageId) {
494
- console.warn("Failed to add message to manager, no message ID found");
495
- continue;
496
- }
497
- setStreamValues((streamValues) => {
498
- const values = { ...historyValues, ...streamValues };
499
- // Assumption: we're concatenating the message
500
- const messages = getMessages(values).slice();
501
- const { chunk, index } = messageManagerRef.current.get(messageId, messages.length) ?? {};
502
- if (!chunk || index == null)
503
- return values;
504
- messages[index] = toMessageDict(chunk);
505
- return { ...values, [messagesKey]: messages };
506
- });
507
- }
508
- }
509
- // TODO: stream created checkpoints to avoid an unnecessary network request
510
- const result = await run.onSuccess();
511
- setStreamValues((values, kind) => {
512
- // Do not clear out the user values set on `stop`.
513
- if (kind === "stop")
514
- return values;
515
- return null;
516
- });
517
- if (streamError != null)
518
- throw streamError;
519
- const lastHead = result.at(0);
520
- if (lastHead)
521
- onFinish?.(lastHead, getCallbackMeta?.());
522
- }
523
- catch (error) {
524
- if (!(error instanceof Error && // eslint-disable-line no-instanceof/no-instanceof
525
- (error.name === "AbortError" || error.name === "TimeoutError"))) {
526
- console.error(error);
527
- setStreamError(error);
528
- onError?.(error, getCallbackMeta?.());
220
+ const stop = () => stream.stop(historyValues, { onStop: options.onStop });
221
+ // --- TRANSPORT ---
222
+ const submit = async (values, submitOptions) => {
223
+ // Unbranch things
224
+ const checkpointId = submitOptions?.checkpoint?.checkpoint_id;
225
+ setBranch(checkpointId != null
226
+ ? branchContext.branchByCheckpoint[checkpointId]?.branch ?? ""
227
+ : "");
228
+ stream.setStreamValues(() => {
229
+ if (submitOptions?.optimisticValues != null) {
230
+ return {
231
+ ...historyValues,
232
+ ...(typeof submitOptions.optimisticValues === "function"
233
+ ? submitOptions.optimisticValues(historyValues)
234
+ : submitOptions.optimisticValues),
235
+ };
529
236
  }
530
- }
531
- finally {
532
- setIsLoading(false);
533
- submittingRef.current = false;
534
- abortRef.current = null;
535
- }
536
- }
537
- const joinStream = async (runId, lastEventId, options) => {
538
- // eslint-disable-next-line no-param-reassign
539
- lastEventId ??= "-1";
540
- if (!threadId)
541
- return;
542
- await consumeStream(async (signal) => {
543
- const stream = client.runs.joinStream(threadId, runId, {
544
- signal,
545
- lastEventId,
546
- streamMode: options?.streamMode,
547
- });
548
- return {
549
- onSuccess: () => {
550
- runMetadataStorage?.removeItem(`lg:stream:${threadId}`);
551
- return history.mutate(threadId);
552
- },
553
- stream,
554
- getCallbackMeta: () => ({ thread_id: threadId, run_id: runId }),
555
- };
237
+ return { ...historyValues };
556
238
  });
557
- };
558
- const submit = async (values, submitOptions) => {
559
- await consumeStream(async (signal) => {
560
- // Unbranch things
561
- const newPath = submitOptions?.checkpoint?.checkpoint_id
562
- ? branchByCheckpoint[submitOptions?.checkpoint?.checkpoint_id]?.branch
563
- : undefined;
564
- if (newPath != null)
565
- setBranch(newPath ?? "");
566
- setStreamValues(() => {
567
- if (submitOptions?.optimisticValues != null) {
568
- return {
569
- ...historyValues,
570
- ...(typeof submitOptions.optimisticValues === "function"
571
- ? submitOptions.optimisticValues(historyValues)
572
- : submitOptions.optimisticValues),
573
- };
574
- }
575
- return { ...historyValues };
576
- });
577
- let usableThreadId = threadId;
239
+ // When `fetchStateHistory` is requested, thus we assume that branching
240
+ // is enabled. We then need to include the implicit branch.
241
+ const includeImplicitBranch = historyLimit === true || typeof historyLimit === "number";
242
+ let callbackMeta;
243
+ let rejoinKey;
244
+ let usableThreadId = threadId;
245
+ await stream.start(async (signal) => {
578
246
  if (!usableThreadId) {
579
247
  const thread = await client.threads.create({
580
248
  threadId: submitOptions?.threadId,
@@ -583,26 +251,28 @@ function useStream(options) {
583
251
  onThreadId(thread.thread_id);
584
252
  usableThreadId = thread.thread_id;
585
253
  }
586
- if (!usableThreadId)
254
+ if (!usableThreadId) {
587
255
  throw new Error("Failed to obtain valid thread ID.");
588
- const streamMode = unique([
256
+ }
257
+ const streamMode = (0, utils_js_1.unique)([
589
258
  ...(submitOptions?.streamMode ?? []),
590
259
  ...trackStreamModeRef.current,
591
260
  ...callbackStreamMode,
592
261
  ]);
593
- let checkpoint = submitOptions?.checkpoint ?? threadHead?.checkpoint ?? undefined;
262
+ let checkpoint = submitOptions?.checkpoint ??
263
+ (includeImplicitBranch
264
+ ? branchContext.threadHead?.checkpoint
265
+ : undefined) ??
266
+ undefined;
594
267
  // Avoid specifying a checkpoint if user explicitly set it to null
595
- if (submitOptions?.checkpoint === null) {
268
+ if (submitOptions?.checkpoint === null)
596
269
  checkpoint = undefined;
597
- }
598
270
  // eslint-disable-next-line @typescript-eslint/ban-ts-comment
599
271
  // @ts-expect-error
600
272
  if (checkpoint != null)
601
273
  delete checkpoint.thread_id;
602
- let rejoinKey;
603
- let callbackMeta;
604
274
  const streamResumable = submitOptions?.streamResumable ?? !!runMetadataStorage;
605
- const stream = client.runs.stream(usableThreadId, assistantId, {
275
+ return client.runs.stream(usableThreadId, options.assistantId, {
606
276
  input: values,
607
277
  config: submitOptions?.config,
608
278
  context: submitOptions?.context,
@@ -619,31 +289,84 @@ function useStream(options) {
619
289
  streamMode,
620
290
  streamSubgraphs: submitOptions?.streamSubgraphs,
621
291
  streamResumable,
292
+ durability: submitOptions?.durability,
622
293
  onRunCreated(params) {
623
294
  callbackMeta = {
624
295
  run_id: params.run_id,
625
296
  thread_id: params.thread_id ?? usableThreadId,
626
297
  };
627
298
  if (runMetadataStorage) {
628
- rejoinKey = `lg:stream:${callbackMeta.thread_id}`;
299
+ rejoinKey = `lg:stream:${usableThreadId}`;
629
300
  runMetadataStorage.setItem(rejoinKey, callbackMeta.run_id);
630
301
  }
631
- onCreated?.(callbackMeta);
302
+ options.onCreated?.(callbackMeta);
632
303
  },
633
304
  });
634
- return {
635
- stream,
636
- getCallbackMeta: () => callbackMeta,
637
- onSuccess: () => {
638
- if (rejoinKey)
639
- runMetadataStorage?.removeItem(rejoinKey);
640
- return history.mutate(usableThreadId);
641
- },
642
- };
305
+ }, {
306
+ getMessages,
307
+ setMessages,
308
+ initialValues: historyValues,
309
+ callbacks: options,
310
+ async onSuccess() {
311
+ if (rejoinKey)
312
+ runMetadataStorage?.removeItem(rejoinKey);
313
+ const shouldRefetch =
314
+ // We're expecting the whole thread state in onFinish
315
+ options.onFinish != null ||
316
+ // We're fetching history, thus we need the latest checkpoint
317
+ // to ensure we're not accidentally submitting to a wrong branch
318
+ includeImplicitBranch;
319
+ if (shouldRefetch) {
320
+ const newHistory = await history.mutate(usableThreadId);
321
+ const lastHead = newHistory.at(0);
322
+ if (lastHead) {
323
+ // We now have the latest update from /history
324
+ // Thus we can clear the local stream state
325
+ options.onFinish?.(lastHead, callbackMeta);
326
+ return null;
327
+ }
328
+ }
329
+ return undefined;
330
+ },
331
+ onError(error) {
332
+ options.onError?.(error, callbackMeta);
333
+ },
334
+ });
335
+ };
336
+ const joinStream = async (runId, lastEventId, joinOptions) => {
337
+ // eslint-disable-next-line no-param-reassign
338
+ lastEventId ??= "-1";
339
+ if (!threadId)
340
+ return;
341
+ const callbackMeta = {
342
+ thread_id: threadId,
343
+ run_id: runId,
344
+ };
345
+ await stream.start(async (signal) => {
346
+ return client.runs.joinStream(threadId, runId, {
347
+ signal,
348
+ lastEventId,
349
+ streamMode: joinOptions?.streamMode,
350
+ });
351
+ }, {
352
+ getMessages,
353
+ setMessages,
354
+ initialValues: historyValues,
355
+ callbacks: options,
356
+ async onSuccess() {
357
+ runMetadataStorage?.removeItem(`lg:stream:${threadId}`);
358
+ const newHistory = await history.mutate(threadId);
359
+ const lastHead = newHistory.at(0);
360
+ if (lastHead)
361
+ options.onFinish?.(lastHead, callbackMeta);
362
+ },
363
+ onError(error) {
364
+ options.onError?.(error, callbackMeta);
365
+ },
643
366
  });
644
367
  };
645
368
  const reconnectKey = (0, react_1.useMemo)(() => {
646
- if (!runMetadataStorage || isLoading)
369
+ if (!runMetadataStorage || stream.isLoading)
647
370
  return undefined;
648
371
  if (typeof window === "undefined")
649
372
  return undefined;
@@ -651,7 +374,7 @@ function useStream(options) {
651
374
  if (!runId)
652
375
  return undefined;
653
376
  return { runId, threadId };
654
- }, [runMetadataStorage, isLoading, threadId]);
377
+ }, [runMetadataStorage, stream.isLoading, threadId]);
655
378
  const shouldReconnect = !!runMetadataStorage;
656
379
  const reconnectRef = (0, react_1.useRef)({ threadId, shouldReconnect });
657
380
  const joinStreamRef = (0, react_1.useRef)(joinStream);
@@ -668,38 +391,44 @@ function useStream(options) {
668
391
  void joinStreamRef.current?.(reconnectKey.runId);
669
392
  }
670
393
  }, [reconnectKey]);
671
- const error = streamError ?? historyValueError ?? history.error;
672
- const values = streamValues ?? historyValues;
394
+ // --- END TRANSPORT ---
395
+ const error = stream.error ?? historyError ?? history.error;
396
+ const values = stream.values ?? historyValues;
673
397
  return {
674
398
  get values() {
675
399
  trackStreamMode("values");
676
400
  return values;
677
401
  },
678
402
  client,
679
- assistantId,
403
+ assistantId: options.assistantId,
680
404
  error,
681
- isLoading,
405
+ isLoading: stream.isLoading,
682
406
  stop,
683
- submit, // eslint-disable-line @typescript-eslint/no-misused-promises
407
+ submit,
684
408
  joinStream,
685
409
  branch,
686
410
  setBranch,
687
- history: flatHistory,
411
+ get history() {
412
+ if (historyLimit === false) {
413
+ throw new Error("`fetchStateHistory` must be set to `true` to use `history`");
414
+ }
415
+ return branchContext.flatHistory;
416
+ },
688
417
  isThreadLoading: history.isLoading && history.data == null,
689
418
  get experimental_branchTree() {
690
419
  if (historyLimit === false) {
691
- throw new Error("`experimental_branchTree` is not available when `fetchStateHistory` is set to `false`");
420
+ throw new Error("`fetchStateHistory` must be set to `true` to use `experimental_branchTree`");
692
421
  }
693
- return rootSequence;
422
+ return branchContext.branchTree;
694
423
  },
695
424
  get interrupt() {
696
425
  // Don't show the interrupt if the stream is loading
697
- if (isLoading)
426
+ if (stream.isLoading)
698
427
  return undefined;
699
- const interrupts = threadHead?.tasks?.at(-1)?.interrupts;
428
+ const interrupts = branchContext.threadHead?.tasks?.at(-1)?.interrupts;
700
429
  if (interrupts == null || interrupts.length === 0) {
701
430
  // check if there's a next task present
702
- const next = threadHead?.next ?? [];
431
+ const next = branchContext.threadHead?.next ?? [];
703
432
  if (!next.length || error != null)
704
433
  return undefined;
705
434
  return { when: "breakpoint" };
@@ -713,7 +442,15 @@ function useStream(options) {
713
442
  },
714
443
  getMessagesMetadata(message, index) {
715
444
  trackStreamMode("values");
716
- return messageMetadata?.find((m) => m.messageId === (message.id ?? index));
445
+ const streamMetadata = messageManager.get(message.id)?.metadata;
446
+ const historyMetadata = messageMetadata?.find((m) => m.messageId === (message.id ?? index));
447
+ if (streamMetadata != null || historyMetadata != null) {
448
+ return {
449
+ ...historyMetadata,
450
+ streamMetadata,
451
+ };
452
+ }
453
+ return undefined;
717
454
  },
718
455
  };
719
456
  }