@langchain/langgraph-sdk 0.0.112 → 0.1.1

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,7 +19,7 @@ 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) {
@@ -261,39 +83,7 @@ const useControllableThreadId = (options) => {
261
83
  }
262
84
  return [options.threadId ?? null, onThreadId];
263
85
  };
264
- function useStreamValuesState() {
265
- const [values, setValues] = (0, react_1.useState)(null);
266
- const setStreamValues = (0, react_1.useCallback)((values, kind = "stream") => {
267
- if (typeof values === "function") {
268
- setValues((prevTuple) => {
269
- const [prevValues, prevKind] = prevTuple ?? [null, "stream"];
270
- const next = values(prevValues, prevKind);
271
- if (next == null)
272
- return null;
273
- return [next, kind];
274
- });
275
- return;
276
- }
277
- if (values == null)
278
- setValues(null);
279
- setValues([values, kind]);
280
- }, []);
281
- const mutate = (0, react_1.useCallback)((kind, serverValues) => (update) => {
282
- setStreamValues((clientValues) => {
283
- const prev = { ...serverValues, ...clientValues };
284
- const next = typeof update === "function" ? update(prev) : update;
285
- return { ...prev, ...next };
286
- }, kind);
287
- }, [setStreamValues]);
288
- return [values?.[0] ?? null, setStreamValues, mutate];
289
- }
290
86
  function useStream(options) {
291
- const matchEventType = (expected, actual, _data) => {
292
- return expected === actual || actual.startsWith(`${expected}|`);
293
- };
294
- let { messagesKey } = options;
295
- const { assistantId, fetchStateHistory } = options;
296
- const { onCreated, onError, onFinish } = options;
297
87
  const reconnectOnMountRef = (0, react_1.useRef)(options.reconnectOnMount);
298
88
  const runMetadataStorage = (0, react_1.useMemo)(() => {
299
89
  if (typeof window === "undefined")
@@ -305,7 +95,6 @@ function useStream(options) {
305
95
  return storage();
306
96
  return null;
307
97
  }, []);
308
- messagesKey ??= "messages";
309
98
  const client = (0, react_1.useMemo)(() => options.client ??
310
99
  new client_js_1.Client({
311
100
  apiUrl: options.apiUrl,
@@ -319,20 +108,16 @@ function useStream(options) {
319
108
  options.callerOptions,
320
109
  options.defaultHeaders,
321
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);
322
114
  const [threadId, onThreadId] = useControllableThreadId(options);
323
- const [branch, setBranch] = (0, react_1.useState)("");
324
- const [isLoading, setIsLoading] = (0, react_1.useState)(false);
325
- const [streamError, setStreamError] = (0, react_1.useState)(undefined);
326
- const [streamValues, setStreamValues, getMutateFn] = useStreamValuesState();
327
- const messageManagerRef = (0, react_1.useRef)(new MessageTupleManager());
328
- const submittingRef = (0, react_1.useRef)(false);
329
- const abortRef = (0, react_1.useRef)(null);
330
115
  const trackStreamModeRef = (0, react_1.useRef)([]);
331
116
  const trackStreamMode = (0, react_1.useCallback)((...mode) => {
117
+ const ref = trackStreamModeRef.current;
332
118
  for (const m of mode) {
333
- if (!trackStreamModeRef.current.includes(m)) {
334
- trackStreamModeRef.current.push(m);
335
- }
119
+ if (!ref.includes(m))
120
+ ref.push(m);
336
121
  }
337
122
  }, []);
338
123
  const hasUpdateListener = options.onUpdateEvent != null;
@@ -365,35 +150,37 @@ function useStream(options) {
365
150
  hasTaskListener,
366
151
  ]);
367
152
  const clearCallbackRef = (0, react_1.useRef)(null);
368
- clearCallbackRef.current = () => {
369
- setStreamError(undefined);
370
- setStreamValues(null);
371
- messageManagerRef.current.clear();
372
- };
153
+ clearCallbackRef.current = stream.clear;
154
+ const submittingRef = (0, react_1.useRef)(false);
155
+ submittingRef.current = stream.isLoading;
373
156
  const onErrorRef = (0, react_1.useRef)(undefined);
374
157
  onErrorRef.current = options.onError;
375
- const historyLimit = typeof fetchStateHistory === "object" && fetchStateHistory != null
376
- ? fetchStateHistory.limit ?? true
377
- : fetchStateHistory ?? true;
158
+ const historyLimit = typeof options.fetchStateHistory === "object" &&
159
+ options.fetchStateHistory != null
160
+ ? options.fetchStateHistory.limit ?? false
161
+ : options.fetchStateHistory ?? false;
378
162
  const history = useThreadHistory(threadId, client, historyLimit, clearCallbackRef, submittingRef, onErrorRef);
379
- const getMessages = (0, react_1.useMemo)(() => {
380
- return (value) => Array.isArray(value[messagesKey])
381
- ? value[messagesKey]
382
- : [];
383
- }, [messagesKey]);
384
- const { rootSequence, paths } = getBranchSequence(history.data ?? []);
385
- const { history: flatHistory, branchByCheckpoint } = getBranchView(rootSequence, paths, branch);
386
- const threadHead = flatHistory.at(-1);
387
- const historyValues = threadHead?.values ?? options.initialValues ?? {};
388
- const historyValueError = (() => {
389
- 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;
390
178
  if (error == null)
391
179
  return undefined;
392
180
  try {
393
181
  const parsed = JSON.parse(error);
394
- if (StreamError.isStructuredError(parsed)) {
395
- return new StreamError(parsed);
396
- }
182
+ if (errors_js_1.StreamError.isStructuredError(parsed))
183
+ return new errors_js_1.StreamError(parsed);
397
184
  return parsed;
398
185
  }
399
186
  catch {
@@ -405,16 +192,13 @@ function useStream(options) {
405
192
  const alreadyShown = new Set();
406
193
  return getMessages(historyValues).map((message, idx) => {
407
194
  const messageId = message.id ?? idx;
408
- const streamMetadata = message.id != null
409
- ? messageManagerRef.current.get(message.id)?.metadata ?? undefined
410
- : undefined;
411
- 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)
412
197
  .map((m, idx) => m.id ?? idx)
413
198
  .includes(messageId));
414
- const firstSeen = history.data?.[firstSeenIdx];
415
- const checkpointId = firstSeen?.checkpoint?.checkpoint_id;
416
- let branch = firstSeen && checkpointId != null
417
- ? branchByCheckpoint[checkpointId]
199
+ const checkpointId = firstSeenState?.checkpoint?.checkpoint_id;
200
+ let branch = checkpointId != null
201
+ ? branchContext.branchByCheckpoint[checkpointId]
418
202
  : undefined;
419
203
  if (!branch?.branch?.length)
420
204
  branch = undefined;
@@ -427,158 +211,38 @@ function useStream(options) {
427
211
  }
428
212
  return {
429
213
  messageId: messageId.toString(),
430
- firstSeenState: firstSeen,
214
+ firstSeenState,
431
215
  branch: branch?.branch,
432
216
  branchOptions: branch?.branchOptions,
433
- streamMetadata,
434
217
  };
435
218
  });
436
219
  })();
437
- const stop = () => {
438
- if (abortRef.current != null)
439
- abortRef.current.abort();
440
- abortRef.current = null;
441
- if (runMetadataStorage && threadId) {
442
- const runId = runMetadataStorage.getItem(`lg:stream:${threadId}`);
443
- if (runId)
444
- void client.runs.cancel(threadId, runId);
445
- runMetadataStorage.removeItem(`lg:stream:${threadId}`);
446
- }
447
- options?.onStop?.({ mutate: getMutateFn("stop", historyValues) });
448
- };
449
- async function consumeStream(action) {
450
- let getCallbackMeta;
451
- try {
452
- setIsLoading(true);
453
- setStreamError(undefined);
454
- submittingRef.current = true;
455
- abortRef.current = new AbortController();
456
- const run = await action(abortRef.current.signal);
457
- getCallbackMeta = run.getCallbackMeta;
458
- let streamError;
459
- for await (const { event, data } of run.stream) {
460
- if (event === "error") {
461
- streamError = new StreamError(data);
462
- break;
463
- }
464
- const namespace = event.includes("|")
465
- ? event.split("|").slice(1)
466
- : undefined;
467
- const mutate = getMutateFn("stream", historyValues);
468
- if (event === "metadata")
469
- options.onMetadataEvent?.(data);
470
- if (event === "events")
471
- options.onLangChainEvent?.(data);
472
- if (matchEventType("updates", event, data)) {
473
- options.onUpdateEvent?.(data, { namespace, mutate });
474
- }
475
- if (matchEventType("custom", event, data)) {
476
- options.onCustomEvent?.(data, { namespace, mutate });
477
- }
478
- if (matchEventType("checkpoints", event, data)) {
479
- options.onCheckpointEvent?.(data, { namespace });
480
- }
481
- if (matchEventType("tasks", event, data)) {
482
- options.onTaskEvent?.(data, { namespace });
483
- }
484
- if (matchEventType("debug", event, data)) {
485
- options.onDebugEvent?.(data, { namespace });
486
- }
487
- if (event === "values") {
488
- // don't update values on interrupt values event
489
- if ("__interrupt__" in data)
490
- continue;
491
- setStreamValues(data);
492
- }
493
- // Consume subgraph messages as well
494
- if (matchEventType("messages", event, data)) {
495
- const [serialized, metadata] = data;
496
- const messageId = messageManagerRef.current.add(serialized, metadata);
497
- if (!messageId) {
498
- console.warn("Failed to add message to manager, no message ID found");
499
- continue;
500
- }
501
- setStreamValues((streamValues) => {
502
- const values = { ...historyValues, ...streamValues };
503
- // Assumption: we're concatenating the message
504
- const messages = getMessages(values).slice();
505
- const { chunk, index } = messageManagerRef.current.get(messageId, messages.length) ?? {};
506
- if (!chunk || index == null)
507
- return values;
508
- messages[index] = toMessageDict(chunk);
509
- return { ...values, [messagesKey]: messages };
510
- });
511
- }
512
- }
513
- // TODO: stream created checkpoints to avoid an unnecessary network request
514
- const result = await run.onSuccess();
515
- setStreamValues((values, kind) => {
516
- // Do not clear out the user values set on `stop`.
517
- if (kind === "stop")
518
- return values;
519
- return null;
520
- });
521
- if (streamError != null)
522
- throw streamError;
523
- const lastHead = result.at(0);
524
- if (lastHead)
525
- onFinish?.(lastHead, getCallbackMeta?.());
526
- }
527
- catch (error) {
528
- if (!(error instanceof Error && // eslint-disable-line no-instanceof/no-instanceof
529
- (error.name === "AbortError" || error.name === "TimeoutError"))) {
530
- console.error(error);
531
- setStreamError(error);
532
- 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
+ };
533
236
  }
534
- }
535
- finally {
536
- setIsLoading(false);
537
- submittingRef.current = false;
538
- abortRef.current = null;
539
- }
540
- }
541
- const joinStream = async (runId, lastEventId, options) => {
542
- // eslint-disable-next-line no-param-reassign
543
- lastEventId ??= "-1";
544
- if (!threadId)
545
- return;
546
- await consumeStream(async (signal) => {
547
- const stream = client.runs.joinStream(threadId, runId, {
548
- signal,
549
- lastEventId,
550
- streamMode: options?.streamMode,
551
- });
552
- return {
553
- onSuccess: () => {
554
- runMetadataStorage?.removeItem(`lg:stream:${threadId}`);
555
- return history.mutate(threadId);
556
- },
557
- stream,
558
- getCallbackMeta: () => ({ thread_id: threadId, run_id: runId }),
559
- };
237
+ return { ...historyValues };
560
238
  });
561
- };
562
- const submit = async (values, submitOptions) => {
563
- await consumeStream(async (signal) => {
564
- // Unbranch things
565
- const newPath = submitOptions?.checkpoint?.checkpoint_id
566
- ? branchByCheckpoint[submitOptions?.checkpoint?.checkpoint_id]?.branch
567
- : undefined;
568
- if (newPath != null)
569
- setBranch(newPath ?? "");
570
- setStreamValues(() => {
571
- if (submitOptions?.optimisticValues != null) {
572
- return {
573
- ...historyValues,
574
- ...(typeof submitOptions.optimisticValues === "function"
575
- ? submitOptions.optimisticValues(historyValues)
576
- : submitOptions.optimisticValues),
577
- };
578
- }
579
- return { ...historyValues };
580
- });
581
- 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) => {
582
246
  if (!usableThreadId) {
583
247
  const thread = await client.threads.create({
584
248
  threadId: submitOptions?.threadId,
@@ -587,26 +251,28 @@ function useStream(options) {
587
251
  onThreadId(thread.thread_id);
588
252
  usableThreadId = thread.thread_id;
589
253
  }
590
- if (!usableThreadId)
254
+ if (!usableThreadId) {
591
255
  throw new Error("Failed to obtain valid thread ID.");
592
- const streamMode = unique([
256
+ }
257
+ const streamMode = (0, utils_js_1.unique)([
593
258
  ...(submitOptions?.streamMode ?? []),
594
259
  ...trackStreamModeRef.current,
595
260
  ...callbackStreamMode,
596
261
  ]);
597
- let checkpoint = submitOptions?.checkpoint ?? threadHead?.checkpoint ?? undefined;
262
+ let checkpoint = submitOptions?.checkpoint ??
263
+ (includeImplicitBranch
264
+ ? branchContext.threadHead?.checkpoint
265
+ : undefined) ??
266
+ undefined;
598
267
  // Avoid specifying a checkpoint if user explicitly set it to null
599
- if (submitOptions?.checkpoint === null) {
268
+ if (submitOptions?.checkpoint === null)
600
269
  checkpoint = undefined;
601
- }
602
270
  // eslint-disable-next-line @typescript-eslint/ban-ts-comment
603
271
  // @ts-expect-error
604
272
  if (checkpoint != null)
605
273
  delete checkpoint.thread_id;
606
- let rejoinKey;
607
- let callbackMeta;
608
274
  const streamResumable = submitOptions?.streamResumable ?? !!runMetadataStorage;
609
- const stream = client.runs.stream(usableThreadId, assistantId, {
275
+ return client.runs.stream(usableThreadId, options.assistantId, {
610
276
  input: values,
611
277
  config: submitOptions?.config,
612
278
  context: submitOptions?.context,
@@ -630,25 +296,77 @@ function useStream(options) {
630
296
  thread_id: params.thread_id ?? usableThreadId,
631
297
  };
632
298
  if (runMetadataStorage) {
633
- rejoinKey = `lg:stream:${callbackMeta.thread_id}`;
299
+ rejoinKey = `lg:stream:${usableThreadId}`;
634
300
  runMetadataStorage.setItem(rejoinKey, callbackMeta.run_id);
635
301
  }
636
- onCreated?.(callbackMeta);
302
+ options.onCreated?.(callbackMeta);
637
303
  },
638
304
  });
639
- return {
640
- stream,
641
- getCallbackMeta: () => callbackMeta,
642
- onSuccess: () => {
643
- if (rejoinKey)
644
- runMetadataStorage?.removeItem(rejoinKey);
645
- return history.mutate(usableThreadId);
646
- },
647
- };
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
+ },
648
366
  });
649
367
  };
650
368
  const reconnectKey = (0, react_1.useMemo)(() => {
651
- if (!runMetadataStorage || isLoading)
369
+ if (!runMetadataStorage || stream.isLoading)
652
370
  return undefined;
653
371
  if (typeof window === "undefined")
654
372
  return undefined;
@@ -656,7 +374,7 @@ function useStream(options) {
656
374
  if (!runId)
657
375
  return undefined;
658
376
  return { runId, threadId };
659
- }, [runMetadataStorage, isLoading, threadId]);
377
+ }, [runMetadataStorage, stream.isLoading, threadId]);
660
378
  const shouldReconnect = !!runMetadataStorage;
661
379
  const reconnectRef = (0, react_1.useRef)({ threadId, shouldReconnect });
662
380
  const joinStreamRef = (0, react_1.useRef)(joinStream);
@@ -673,38 +391,55 @@ function useStream(options) {
673
391
  void joinStreamRef.current?.(reconnectKey.runId);
674
392
  }
675
393
  }, [reconnectKey]);
676
- const error = streamError ?? historyValueError ?? history.error;
677
- const values = streamValues ?? historyValues;
394
+ // --- END TRANSPORT ---
395
+ const error = stream.error ?? historyError ?? history.error;
396
+ const values = stream.values ?? historyValues;
678
397
  return {
679
398
  get values() {
680
399
  trackStreamMode("values");
681
400
  return values;
682
401
  },
683
402
  client,
684
- assistantId,
403
+ assistantId: options.assistantId,
685
404
  error,
686
- isLoading,
405
+ isLoading: stream.isLoading,
687
406
  stop,
688
- submit, // eslint-disable-line @typescript-eslint/no-misused-promises
407
+ submit,
689
408
  joinStream,
690
409
  branch,
691
410
  setBranch,
692
- 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
+ },
693
417
  isThreadLoading: history.isLoading && history.data == null,
694
418
  get experimental_branchTree() {
695
419
  if (historyLimit === false) {
696
- 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`");
697
421
  }
698
- return rootSequence;
422
+ return branchContext.branchTree;
699
423
  },
700
424
  get interrupt() {
701
- // Don't show the interrupt if the stream is loading
702
- if (isLoading)
425
+ if (values != null &&
426
+ "__interrupt__" in values &&
427
+ Array.isArray(values.__interrupt__)) {
428
+ const valueInterrupts = values.__interrupt__;
429
+ if (valueInterrupts.length === 0)
430
+ return { when: "breakpoint" };
431
+ if (valueInterrupts.length === 1)
432
+ return valueInterrupts[0];
433
+ // TODO: fix the typing of interrupts if multiple interrupts are returned
434
+ return valueInterrupts;
435
+ }
436
+ // If we're deferring to old interrupt detection logic, don't show the interrupt if the stream is loading
437
+ if (stream.isLoading)
703
438
  return undefined;
704
- const interrupts = threadHead?.tasks?.at(-1)?.interrupts;
439
+ const interrupts = branchContext.threadHead?.tasks?.at(-1)?.interrupts;
705
440
  if (interrupts == null || interrupts.length === 0) {
706
441
  // check if there's a next task present
707
- const next = threadHead?.next ?? [];
442
+ const next = branchContext.threadHead?.next ?? [];
708
443
  if (!next.length || error != null)
709
444
  return undefined;
710
445
  return { when: "breakpoint" };
@@ -718,7 +453,15 @@ function useStream(options) {
718
453
  },
719
454
  getMessagesMetadata(message, index) {
720
455
  trackStreamMode("values");
721
- return messageMetadata?.find((m) => m.messageId === (message.id ?? index));
456
+ const streamMetadata = messageManager.get(message.id)?.metadata;
457
+ const historyMetadata = messageMetadata?.find((m) => m.messageId === (message.id ?? index));
458
+ if (streamMetadata != null || historyMetadata != null) {
459
+ return {
460
+ ...historyMetadata,
461
+ streamMetadata,
462
+ };
463
+ }
464
+ return undefined;
722
465
  },
723
466
  };
724
467
  }