@langchain/langgraph-sdk 0.1.5 → 0.1.7

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.
Files changed (36) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/dist/react/index.cjs +3 -1
  3. package/dist/react/index.d.ts +2 -1
  4. package/dist/react/index.js +1 -0
  5. package/dist/react/stream.cjs +12 -460
  6. package/dist/react/stream.custom.cjs +148 -0
  7. package/dist/react/stream.custom.d.ts +41 -0
  8. package/dist/react/stream.custom.js +142 -0
  9. package/dist/react/stream.d.ts +61 -1
  10. package/dist/react/stream.js +13 -460
  11. package/dist/react/stream.lgp.cjs +499 -0
  12. package/dist/react/stream.lgp.d.ts +7 -0
  13. package/dist/react/stream.lgp.js +495 -0
  14. package/dist/react/thread.cjs +19 -0
  15. package/dist/react/thread.d.ts +4 -0
  16. package/dist/react/thread.js +15 -0
  17. package/dist/react/types.d.ts +25 -1
  18. package/dist/ui/branching.test.cjs +370 -0
  19. package/dist/ui/branching.test.d.ts +1 -0
  20. package/dist/ui/branching.test.js +368 -0
  21. package/dist/{react → ui}/manager.cjs +8 -4
  22. package/dist/{react → ui}/manager.d.ts +1 -0
  23. package/dist/{react → ui}/manager.js +8 -4
  24. package/package.json +1 -1
  25. /package/dist/{react → ui}/branching.cjs +0 -0
  26. /package/dist/{react → ui}/branching.d.ts +0 -0
  27. /package/dist/{react → ui}/branching.js +0 -0
  28. /package/dist/{react → ui}/errors.cjs +0 -0
  29. /package/dist/{react → ui}/errors.d.ts +0 -0
  30. /package/dist/{react → ui}/errors.js +0 -0
  31. /package/dist/{react → ui}/messages.cjs +0 -0
  32. /package/dist/{react → ui}/messages.d.ts +0 -0
  33. /package/dist/{react → ui}/messages.js +0 -0
  34. /package/dist/{react → ui}/utils.cjs +0 -0
  35. /package/dist/{react → ui}/utils.d.ts +0 -0
  36. /package/dist/{react → ui}/utils.js +0 -0
package/CHANGELOG.md CHANGED
@@ -1,5 +1,19 @@
1
1
  # @langchain/langgraph-sdk
2
2
 
3
+ ## 0.1.7
4
+
5
+ ### Patch Changes
6
+
7
+ - bbc90e6: Fix thread history state being kept stale when changing `thread_id`
8
+
9
+ ## 0.1.6
10
+
11
+ ### Patch Changes
12
+
13
+ - 5603276: Fix `useStream()` keeping stale thread history when switching threads mid-stream (#1632)
14
+ - b65c80b: Add `transport` option to useStream, allowing custom endpoints, that emit compatible Server-Sent Events to be used with `useStream`.
15
+ - 5603276: Fix `stop()` behavior when cancelling a resumable stream via `useStream()` (#1610)
16
+
3
17
  ## 0.1.5
4
18
 
5
19
  ### Patch Changes
@@ -1,5 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.useStream = void 0;
3
+ exports.FetchStreamTransport = exports.useStream = void 0;
4
4
  var stream_js_1 = require("./stream.cjs");
5
5
  Object.defineProperty(exports, "useStream", { enumerable: true, get: function () { return stream_js_1.useStream; } });
6
+ var stream_custom_js_1 = require("./stream.custom.cjs");
7
+ Object.defineProperty(exports, "FetchStreamTransport", { enumerable: true, get: function () { return stream_custom_js_1.FetchStreamTransport; } });
@@ -1,2 +1,3 @@
1
1
  export { useStream } from "./stream.js";
2
- export type { MessageMetadata, UseStream, UseStreamOptions } from "./types.js";
2
+ export { FetchStreamTransport } from "./stream.custom.js";
3
+ export type { MessageMetadata, UseStream, UseStreamOptions, UseStreamCustom, UseStreamCustomOptions, UseStreamTransport, } from "./types.js";
@@ -1 +1,2 @@
1
1
  export { useStream } from "./stream.js";
2
+ export { FetchStreamTransport } from "./stream.custom.js";
@@ -1,467 +1,19 @@
1
1
  "use strict";
2
- /* __LC_ALLOW_ENTRYPOINT_SIDE_EFFECTS__ */
3
- "use client";
4
- /* __LC_ALLOW_ENTRYPOINT_SIDE_EFFECTS__ */
5
2
  Object.defineProperty(exports, "__esModule", { value: true });
6
3
  exports.useStream = useStream;
7
4
  const react_1 = require("react");
8
- const utils_js_1 = require("./utils.cjs");
9
- const errors_js_1 = require("./errors.cjs");
10
- const branching_js_1 = require("./branching.cjs");
11
- const manager_js_1 = require("./manager.cjs");
12
- const client_js_1 = require("../client.cjs");
13
- const messages_js_1 = require("./messages.cjs");
14
- function fetchHistory(client, threadId, options) {
15
- if (options?.limit === false) {
16
- return client.threads.getState(threadId).then((state) => {
17
- if (state.checkpoint == null)
18
- return [];
19
- return [state];
20
- });
21
- }
22
- const limit = typeof options?.limit === "number" ? options.limit : 10;
23
- return client.threads.getHistory(threadId, { limit });
24
- }
25
- function useThreadHistory(threadId, client, limit, clearCallbackRef, submittingRef, onErrorRef) {
26
- const [history, setHistory] = (0, react_1.useState)(undefined);
27
- const [isLoading, setIsLoading] = (0, react_1.useState)(() => {
28
- if (threadId == null)
29
- return false;
30
- return true;
31
- });
32
- const [error, setError] = (0, react_1.useState)(undefined);
33
- const clientHash = (0, client_js_1.getClientConfigHash)(client);
34
- const clientRef = (0, react_1.useRef)(client);
35
- clientRef.current = client;
36
- const fetcher = (0, react_1.useCallback)((threadId) => {
37
- if (threadId != null) {
38
- const client = clientRef.current;
39
- setIsLoading(true);
40
- return fetchHistory(client, threadId, {
41
- limit,
42
- })
43
- .then((history) => {
44
- setHistory(history);
45
- return history;
46
- }, (error) => {
47
- setError(error);
48
- onErrorRef.current?.(error);
49
- return Promise.reject(error);
50
- })
51
- .finally(() => {
52
- setIsLoading(false);
53
- });
54
- }
55
- setHistory(undefined);
56
- setError(undefined);
57
- setIsLoading(false);
58
- clearCallbackRef.current?.();
59
- return Promise.resolve([]);
60
- }, [clearCallbackRef, onErrorRef, limit]);
61
- (0, react_1.useEffect)(() => {
62
- if (submittingRef.current)
63
- return;
64
- void fetcher(threadId);
65
- }, [fetcher, submittingRef, clientHash, limit, threadId]);
66
- return {
67
- data: history,
68
- isLoading,
69
- error,
70
- mutate: (mutateId) => fetcher(mutateId ?? threadId),
71
- };
5
+ const stream_lgp_js_1 = require("./stream.lgp.cjs");
6
+ const stream_custom_js_1 = require("./stream.custom.cjs");
7
+ function isCustomOptions(options) {
8
+ return "transport" in options;
72
9
  }
73
- const useControllableThreadId = (options) => {
74
- const [localThreadId, _setLocalThreadId] = (0, react_1.useState)(options?.threadId ?? null);
75
- const onThreadIdRef = (0, react_1.useRef)(options?.onThreadId);
76
- onThreadIdRef.current = options?.onThreadId;
77
- const onThreadId = (0, react_1.useCallback)((threadId) => {
78
- _setLocalThreadId(threadId);
79
- onThreadIdRef.current?.(threadId);
80
- }, []);
81
- if (!options || !("threadId" in options)) {
82
- return [localThreadId, onThreadId];
83
- }
84
- return [options.threadId ?? null, onThreadId];
85
- };
86
10
  function useStream(options) {
87
- const reconnectOnMountRef = (0, react_1.useRef)(options.reconnectOnMount);
88
- const runMetadataStorage = (0, react_1.useMemo)(() => {
89
- if (typeof window === "undefined")
90
- return null;
91
- const storage = reconnectOnMountRef.current;
92
- if (storage === true)
93
- return window.sessionStorage;
94
- if (typeof storage === "function")
95
- return storage();
96
- return null;
97
- }, []);
98
- const client = (0, react_1.useMemo)(() => options.client ??
99
- new client_js_1.Client({
100
- apiUrl: options.apiUrl,
101
- apiKey: options.apiKey,
102
- callerOptions: options.callerOptions,
103
- defaultHeaders: options.defaultHeaders,
104
- }), [
105
- options.client,
106
- options.apiKey,
107
- options.apiUrl,
108
- options.callerOptions,
109
- options.defaultHeaders,
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);
114
- const [threadId, onThreadId] = useControllableThreadId(options);
115
- const trackStreamModeRef = (0, react_1.useRef)([]);
116
- const trackStreamMode = (0, react_1.useCallback)((...mode) => {
117
- const ref = trackStreamModeRef.current;
118
- for (const m of mode) {
119
- if (!ref.includes(m))
120
- ref.push(m);
121
- }
122
- }, []);
123
- const hasUpdateListener = options.onUpdateEvent != null;
124
- const hasCustomListener = options.onCustomEvent != null;
125
- const hasLangChainListener = options.onLangChainEvent != null;
126
- const hasDebugListener = options.onDebugEvent != null;
127
- const hasCheckpointListener = options.onCheckpointEvent != null;
128
- const hasTaskListener = options.onTaskEvent != null;
129
- const callbackStreamMode = (0, react_1.useMemo)(() => {
130
- const modes = [];
131
- if (hasUpdateListener)
132
- modes.push("updates");
133
- if (hasCustomListener)
134
- modes.push("custom");
135
- if (hasLangChainListener)
136
- modes.push("events");
137
- if (hasDebugListener)
138
- modes.push("debug");
139
- if (hasCheckpointListener)
140
- modes.push("checkpoints");
141
- if (hasTaskListener)
142
- modes.push("tasks");
143
- return modes;
144
- }, [
145
- hasUpdateListener,
146
- hasCustomListener,
147
- hasLangChainListener,
148
- hasDebugListener,
149
- hasCheckpointListener,
150
- hasTaskListener,
151
- ]);
152
- const clearCallbackRef = (0, react_1.useRef)(null);
153
- clearCallbackRef.current = stream.clear;
154
- const submittingRef = (0, react_1.useRef)(false);
155
- submittingRef.current = stream.isLoading;
156
- const onErrorRef = (0, react_1.useRef)(undefined);
157
- onErrorRef.current = options.onError;
158
- const historyLimit = typeof options.fetchStateHistory === "object" &&
159
- options.fetchStateHistory != null
160
- ? options.fetchStateHistory.limit ?? false
161
- : options.fetchStateHistory ?? false;
162
- const history = useThreadHistory(threadId, client, historyLimit, clearCallbackRef, submittingRef, onErrorRef);
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;
178
- if (error == null)
179
- return undefined;
180
- try {
181
- const parsed = JSON.parse(error);
182
- if (errors_js_1.StreamError.isStructuredError(parsed))
183
- return new errors_js_1.StreamError(parsed);
184
- return parsed;
185
- }
186
- catch {
187
- // do nothing
188
- }
189
- return error;
190
- })();
191
- const messageMetadata = (() => {
192
- const alreadyShown = new Set();
193
- return getMessages(historyValues).map((message, idx) => {
194
- const messageId = message.id ?? idx;
195
- // Find the first checkpoint where the message was seen
196
- const firstSeenState = (0, utils_js_1.findLast)(history.data ?? [], (state) => getMessages(state.values)
197
- .map((m, idx) => m.id ?? idx)
198
- .includes(messageId));
199
- const checkpointId = firstSeenState?.checkpoint?.checkpoint_id;
200
- let branch = checkpointId != null
201
- ? branchContext.branchByCheckpoint[checkpointId]
202
- : undefined;
203
- if (!branch?.branch?.length)
204
- branch = undefined;
205
- // serialize branches
206
- const optionsShown = branch?.branchOptions?.flat(2).join(",");
207
- if (optionsShown) {
208
- if (alreadyShown.has(optionsShown))
209
- branch = undefined;
210
- alreadyShown.add(optionsShown);
211
- }
212
- return {
213
- messageId: messageId.toString(),
214
- firstSeenState,
215
- branch: branch?.branch,
216
- branchOptions: branch?.branchOptions,
217
- };
218
- });
219
- })();
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
- };
236
- }
237
- return { ...historyValues };
238
- });
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) => {
246
- if (!usableThreadId) {
247
- const thread = await client.threads.create({
248
- threadId: submitOptions?.threadId,
249
- metadata: submitOptions?.metadata,
250
- });
251
- onThreadId(thread.thread_id);
252
- usableThreadId = thread.thread_id;
253
- }
254
- if (!usableThreadId) {
255
- throw new Error("Failed to obtain valid thread ID.");
256
- }
257
- const streamMode = (0, utils_js_1.unique)([
258
- ...(submitOptions?.streamMode ?? []),
259
- ...trackStreamModeRef.current,
260
- ...callbackStreamMode,
261
- ]);
262
- let checkpoint = submitOptions?.checkpoint ??
263
- (includeImplicitBranch
264
- ? branchContext.threadHead?.checkpoint
265
- : undefined) ??
266
- undefined;
267
- // Avoid specifying a checkpoint if user explicitly set it to null
268
- if (submitOptions?.checkpoint === null)
269
- checkpoint = undefined;
270
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
271
- // @ts-expect-error
272
- if (checkpoint != null)
273
- delete checkpoint.thread_id;
274
- const streamResumable = submitOptions?.streamResumable ?? !!runMetadataStorage;
275
- return client.runs.stream(usableThreadId, options.assistantId, {
276
- input: values,
277
- config: submitOptions?.config,
278
- context: submitOptions?.context,
279
- command: submitOptions?.command,
280
- interruptBefore: submitOptions?.interruptBefore,
281
- interruptAfter: submitOptions?.interruptAfter,
282
- metadata: submitOptions?.metadata,
283
- multitaskStrategy: submitOptions?.multitaskStrategy,
284
- onCompletion: submitOptions?.onCompletion,
285
- onDisconnect: submitOptions?.onDisconnect ??
286
- (streamResumable ? "continue" : "cancel"),
287
- signal,
288
- checkpoint,
289
- streamMode,
290
- streamSubgraphs: submitOptions?.streamSubgraphs,
291
- streamResumable,
292
- durability: submitOptions?.durability,
293
- onRunCreated(params) {
294
- callbackMeta = {
295
- run_id: params.run_id,
296
- thread_id: params.thread_id ?? usableThreadId,
297
- };
298
- if (runMetadataStorage) {
299
- rejoinKey = `lg:stream:${usableThreadId}`;
300
- runMetadataStorage.setItem(rejoinKey, callbackMeta.run_id);
301
- }
302
- options.onCreated?.(callbackMeta);
303
- },
304
- });
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
- },
366
- });
367
- };
368
- const reconnectKey = (0, react_1.useMemo)(() => {
369
- if (!runMetadataStorage || stream.isLoading)
370
- return undefined;
371
- if (typeof window === "undefined")
372
- return undefined;
373
- const runId = runMetadataStorage?.getItem(`lg:stream:${threadId}`);
374
- if (!runId)
375
- return undefined;
376
- return { runId, threadId };
377
- }, [runMetadataStorage, stream.isLoading, threadId]);
378
- const shouldReconnect = !!runMetadataStorage;
379
- const reconnectRef = (0, react_1.useRef)({ threadId, shouldReconnect });
380
- const joinStreamRef = (0, react_1.useRef)(joinStream);
381
- joinStreamRef.current = joinStream;
382
- (0, react_1.useEffect)(() => {
383
- // reset shouldReconnect when switching threads
384
- if (reconnectRef.current.threadId !== threadId) {
385
- reconnectRef.current = { threadId, shouldReconnect };
386
- }
387
- }, [threadId, shouldReconnect]);
388
- (0, react_1.useEffect)(() => {
389
- if (reconnectKey && reconnectRef.current.shouldReconnect) {
390
- reconnectRef.current.shouldReconnect = false;
391
- void joinStreamRef.current?.(reconnectKey.runId);
392
- }
393
- }, [reconnectKey]);
394
- // --- END TRANSPORT ---
395
- const error = stream.error ?? historyError ?? history.error;
396
- const values = stream.values ?? historyValues;
397
- return {
398
- get values() {
399
- trackStreamMode("values");
400
- return values;
401
- },
402
- client,
403
- assistantId: options.assistantId,
404
- error,
405
- isLoading: stream.isLoading,
406
- stop,
407
- submit,
408
- joinStream,
409
- branch,
410
- setBranch,
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
- },
417
- isThreadLoading: history.isLoading && history.data == null,
418
- get experimental_branchTree() {
419
- if (historyLimit === false) {
420
- throw new Error("`fetchStateHistory` must be set to `true` to use `experimental_branchTree`");
421
- }
422
- return branchContext.branchTree;
423
- },
424
- get interrupt() {
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)
438
- return undefined;
439
- const interrupts = branchContext.threadHead?.tasks?.at(-1)?.interrupts;
440
- if (interrupts == null || interrupts.length === 0) {
441
- // check if there's a next task present
442
- const next = branchContext.threadHead?.next ?? [];
443
- if (!next.length || error != null)
444
- return undefined;
445
- return { when: "breakpoint" };
446
- }
447
- // Return only the current interrupt
448
- return interrupts.at(-1);
449
- },
450
- get messages() {
451
- trackStreamMode("messages-tuple", "values");
452
- return getMessages(values);
453
- },
454
- getMessagesMetadata(message, index) {
455
- trackStreamMode("values");
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;
465
- },
466
- };
11
+ // Store this in useState to make sure we're not changing the implementation in re-renders
12
+ const [isCustom] = (0, react_1.useState)(isCustomOptions(options));
13
+ if (isCustom) {
14
+ // eslint-disable-next-line react-hooks/rules-of-hooks
15
+ return (0, stream_custom_js_1.useStreamCustom)(options);
16
+ }
17
+ // eslint-disable-next-line react-hooks/rules-of-hooks
18
+ return (0, stream_lgp_js_1.useStreamLGP)(options);
467
19
  }
@@ -0,0 +1,148 @@
1
+ "use strict";
2
+ /* __LC_ALLOW_ENTRYPOINT_SIDE_EFFECTS__ */
3
+ "use client";
4
+ /* __LC_ALLOW_ENTRYPOINT_SIDE_EFFECTS__ */
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.FetchStreamTransport = void 0;
7
+ exports.useStreamCustom = useStreamCustom;
8
+ const react_1 = require("react");
9
+ const manager_js_1 = require("../ui/manager.cjs");
10
+ const messages_js_1 = require("../ui/messages.cjs");
11
+ const sse_js_1 = require("../utils/sse.cjs");
12
+ const stream_js_1 = require("../utils/stream.cjs");
13
+ const thread_js_1 = require("./thread.cjs");
14
+ class FetchStreamTransport {
15
+ constructor(options) {
16
+ Object.defineProperty(this, "options", {
17
+ enumerable: true,
18
+ configurable: true,
19
+ writable: true,
20
+ value: options
21
+ });
22
+ }
23
+ async stream(payload) {
24
+ const { signal, ...body } = payload;
25
+ let requestInit = {
26
+ method: "POST",
27
+ headers: {
28
+ "Content-Type": "application/json",
29
+ ...this.options.defaultHeaders,
30
+ },
31
+ body: JSON.stringify(body),
32
+ signal,
33
+ };
34
+ if (this.options.onRequest) {
35
+ requestInit = await this.options.onRequest(this.options.apiUrl, requestInit);
36
+ }
37
+ const fetchFn = this.options.fetch ?? fetch;
38
+ const response = await fetchFn(this.options.apiUrl, requestInit);
39
+ if (!response.ok) {
40
+ throw new Error(`Failed to stream: ${response.statusText}`);
41
+ }
42
+ const stream = (response.body || new ReadableStream({ start: (ctrl) => ctrl.close() }))
43
+ .pipeThrough((0, sse_js_1.BytesLineDecoder)())
44
+ .pipeThrough((0, sse_js_1.SSEDecoder)());
45
+ return stream_js_1.IterableReadableStream.fromReadableStream(stream);
46
+ }
47
+ }
48
+ exports.FetchStreamTransport = FetchStreamTransport;
49
+ function useStreamCustom(options) {
50
+ const [messageManager] = (0, react_1.useState)(() => new messages_js_1.MessageTupleManager());
51
+ const [stream] = (0, react_1.useState)(() => new manager_js_1.StreamManager(messageManager));
52
+ (0, react_1.useSyncExternalStore)(stream.subscribe, stream.getSnapshot, stream.getSnapshot);
53
+ const [threadId, onThreadId] = (0, thread_js_1.useControllableThreadId)(options);
54
+ const threadIdRef = (0, react_1.useRef)(threadId);
55
+ // Cancel the stream if thread ID has changed
56
+ (0, react_1.useEffect)(() => {
57
+ if (threadIdRef.current !== threadId) {
58
+ threadIdRef.current = threadId;
59
+ stream.clear();
60
+ }
61
+ }, [threadId, stream]);
62
+ const getMessages = (value) => {
63
+ const messagesKey = options.messagesKey ?? "messages";
64
+ return Array.isArray(value[messagesKey]) ? value[messagesKey] : [];
65
+ };
66
+ const setMessages = (current, messages) => {
67
+ const messagesKey = options.messagesKey ?? "messages";
68
+ return { ...current, [messagesKey]: messages };
69
+ };
70
+ const historyValues = options.initialValues ?? {};
71
+ const stop = () => stream.stop(historyValues, { onStop: options.onStop });
72
+ const submit = async (values, submitOptions) => {
73
+ let callbackMeta;
74
+ let usableThreadId = threadId;
75
+ stream.setStreamValues(() => {
76
+ if (submitOptions?.optimisticValues != null) {
77
+ return {
78
+ ...historyValues,
79
+ ...(typeof submitOptions.optimisticValues === "function"
80
+ ? submitOptions.optimisticValues(historyValues)
81
+ : submitOptions.optimisticValues),
82
+ };
83
+ }
84
+ return { ...historyValues };
85
+ });
86
+ await stream.start(async (signal) => {
87
+ if (!usableThreadId) {
88
+ // generate random thread id
89
+ usableThreadId = crypto.randomUUID();
90
+ threadIdRef.current = usableThreadId;
91
+ onThreadId(usableThreadId);
92
+ }
93
+ if (!usableThreadId) {
94
+ throw new Error("Failed to obtain valid thread ID.");
95
+ }
96
+ return options.transport.stream({
97
+ input: values,
98
+ context: submitOptions?.context,
99
+ command: submitOptions?.command,
100
+ signal,
101
+ config: {
102
+ ...submitOptions?.config,
103
+ configurable: {
104
+ thread_id: usableThreadId,
105
+ ...submitOptions?.config?.configurable,
106
+ },
107
+ },
108
+ });
109
+ }, {
110
+ getMessages,
111
+ setMessages,
112
+ initialValues: {},
113
+ callbacks: options,
114
+ onSuccess: () => undefined,
115
+ onError(error) {
116
+ options.onError?.(error, callbackMeta);
117
+ },
118
+ });
119
+ };
120
+ return {
121
+ get values() {
122
+ return stream.values ?? {};
123
+ },
124
+ error: stream.error,
125
+ isLoading: stream.isLoading,
126
+ stop,
127
+ submit,
128
+ get interrupt() {
129
+ if (stream.values != null &&
130
+ "__interrupt__" in stream.values &&
131
+ Array.isArray(stream.values.__interrupt__)) {
132
+ const valueInterrupts = stream.values.__interrupt__;
133
+ if (valueInterrupts.length === 0)
134
+ return { when: "breakpoint" };
135
+ if (valueInterrupts.length === 1)
136
+ return valueInterrupts[0];
137
+ // TODO: fix the typing of interrupts if multiple interrupts are returned
138
+ return valueInterrupts;
139
+ }
140
+ return undefined;
141
+ },
142
+ get messages() {
143
+ if (!stream.values)
144
+ return [];
145
+ return getMessages(stream.values);
146
+ },
147
+ };
148
+ }