@langchain/langgraph-sdk 0.0.87 → 0.0.88
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/client.cjs +4 -4
- package/dist/client.js +4 -4
- package/dist/react/stream.cjs +44 -20
- package/dist/react/stream.d.ts +42 -1
- package/dist/react/stream.js +44 -20
- package/dist/utils/sse.cjs +123 -127
- package/dist/utils/sse.d.ts +2 -6
- package/dist/utils/sse.js +123 -127
- package/package.json +5 -2
package/dist/client.cjs
CHANGED
|
@@ -651,8 +651,8 @@ class RunsClient extends BaseClient {
|
|
|
651
651
|
if (runMetadata)
|
|
652
652
|
payload?.onRunCreated?.(runMetadata);
|
|
653
653
|
const stream = (response.body || new ReadableStream({ start: (ctrl) => ctrl.close() }))
|
|
654
|
-
.pipeThrough(
|
|
655
|
-
.pipeThrough(
|
|
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(
|
|
868
|
-
.pipeThrough(
|
|
867
|
+
.pipeThrough((0, sse_js_1.BytesLineDecoder)())
|
|
868
|
+
.pipeThrough((0, sse_js_1.SSEDecoder)());
|
|
869
869
|
yield* stream_js_1.IterableReadableStream.fromReadableStream(stream);
|
|
870
870
|
}
|
|
871
871
|
/**
|
package/dist/client.js
CHANGED
|
@@ -644,8 +644,8 @@ export class RunsClient extends BaseClient {
|
|
|
644
644
|
if (runMetadata)
|
|
645
645
|
payload?.onRunCreated?.(runMetadata);
|
|
646
646
|
const stream = (response.body || new ReadableStream({ start: (ctrl) => ctrl.close() }))
|
|
647
|
-
.pipeThrough(
|
|
648
|
-
.pipeThrough(
|
|
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(
|
|
861
|
-
.pipeThrough(
|
|
860
|
+
.pipeThrough(BytesLineDecoder())
|
|
861
|
+
.pipeThrough(SSEDecoder());
|
|
862
862
|
yield* IterableReadableStream.fromReadableStream(stream);
|
|
863
863
|
}
|
|
864
864
|
/**
|
package/dist/react/stream.cjs
CHANGED
|
@@ -212,6 +212,32 @@ const useControllableThreadId = (options) => {
|
|
|
212
212
|
}
|
|
213
213
|
return [options.threadId ?? null, onThreadId];
|
|
214
214
|
};
|
|
215
|
+
function useStreamValuesState() {
|
|
216
|
+
const [values, setValues] = (0, react_1.useState)(null);
|
|
217
|
+
const setStreamValues = (0, react_1.useCallback)((values, kind = "stream") => {
|
|
218
|
+
if (typeof values === "function") {
|
|
219
|
+
setValues((prevTuple) => {
|
|
220
|
+
const [prevValues, prevKind] = prevTuple ?? [null, "stream"];
|
|
221
|
+
const next = values(prevValues, prevKind);
|
|
222
|
+
if (next == null)
|
|
223
|
+
return null;
|
|
224
|
+
return [next, kind];
|
|
225
|
+
});
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
if (values == null)
|
|
229
|
+
setValues(null);
|
|
230
|
+
setValues([values, kind]);
|
|
231
|
+
}, []);
|
|
232
|
+
const mutate = (0, react_1.useCallback)((kind, serverValues) => (update) => {
|
|
233
|
+
setStreamValues((clientValues) => {
|
|
234
|
+
const prev = { ...serverValues, ...clientValues };
|
|
235
|
+
const next = typeof update === "function" ? update(prev) : update;
|
|
236
|
+
return { ...prev, ...next };
|
|
237
|
+
}, kind);
|
|
238
|
+
}, [setStreamValues]);
|
|
239
|
+
return [values?.[0] ?? null, setStreamValues, mutate];
|
|
240
|
+
}
|
|
215
241
|
function useStream(options) {
|
|
216
242
|
let { assistantId, messagesKey, onCreated, onError, onFinish } = options;
|
|
217
243
|
const reconnectOnMountRef = (0, react_1.useRef)(options.reconnectOnMount);
|
|
@@ -243,7 +269,7 @@ function useStream(options) {
|
|
|
243
269
|
const [branch, setBranch] = (0, react_1.useState)("");
|
|
244
270
|
const [isLoading, setIsLoading] = (0, react_1.useState)(false);
|
|
245
271
|
const [streamError, setStreamError] = (0, react_1.useState)(undefined);
|
|
246
|
-
const [streamValues, setStreamValues] = (
|
|
272
|
+
const [streamValues, setStreamValues, getMutateFn] = useStreamValuesState();
|
|
247
273
|
const messageManagerRef = (0, react_1.useRef)(new MessageTupleManager());
|
|
248
274
|
const submittingRef = (0, react_1.useRef)(false);
|
|
249
275
|
const abortRef = (0, react_1.useRef)(null);
|
|
@@ -293,7 +319,7 @@ function useStream(options) {
|
|
|
293
319
|
const { rootSequence, paths } = getBranchSequence(history.data);
|
|
294
320
|
const { history: flatHistory, branchByCheckpoint } = getBranchView(rootSequence, paths, branch);
|
|
295
321
|
const threadHead = flatHistory.at(-1);
|
|
296
|
-
const historyValues = threadHead?.values ?? {};
|
|
322
|
+
const historyValues = threadHead?.values ?? options.initialValues ?? {};
|
|
297
323
|
const historyError = (() => {
|
|
298
324
|
const error = threadHead?.tasks?.at(-1)?.error;
|
|
299
325
|
if (error == null)
|
|
@@ -348,6 +374,7 @@ function useStream(options) {
|
|
|
348
374
|
client.runs.cancel(threadId, runId);
|
|
349
375
|
runMetadataStorage.removeItem(`lg:stream:${threadId}`);
|
|
350
376
|
}
|
|
377
|
+
options?.onStop?.({ mutate: getMutateFn("stop", historyValues) });
|
|
351
378
|
};
|
|
352
379
|
async function consumeStream(action) {
|
|
353
380
|
let getCallbackMeta;
|
|
@@ -368,15 +395,7 @@ function useStream(options) {
|
|
|
368
395
|
options.onUpdateEvent?.(data);
|
|
369
396
|
if (event === "custom")
|
|
370
397
|
options.onCustomEvent?.(data, {
|
|
371
|
-
mutate: (
|
|
372
|
-
// should not happen
|
|
373
|
-
if (prev == null)
|
|
374
|
-
return prev;
|
|
375
|
-
return {
|
|
376
|
-
...prev,
|
|
377
|
-
...(typeof update === "function" ? update(prev) : update),
|
|
378
|
-
};
|
|
379
|
-
}),
|
|
398
|
+
mutate: getMutateFn("stream", historyValues),
|
|
380
399
|
});
|
|
381
400
|
if (event === "metadata")
|
|
382
401
|
options.onMetadataEvent?.(data);
|
|
@@ -412,7 +431,12 @@ function useStream(options) {
|
|
|
412
431
|
}
|
|
413
432
|
// TODO: stream created checkpoints to avoid an unnecessary network request
|
|
414
433
|
const result = await run.onSuccess();
|
|
415
|
-
setStreamValues(
|
|
434
|
+
setStreamValues((values, kind) => {
|
|
435
|
+
// Do not clear out the user values set on `stop`.
|
|
436
|
+
if (kind === "stop")
|
|
437
|
+
return values;
|
|
438
|
+
return null;
|
|
439
|
+
});
|
|
416
440
|
if (streamError != null)
|
|
417
441
|
throw streamError;
|
|
418
442
|
const lastHead = result.at(0);
|
|
@@ -435,7 +459,7 @@ function useStream(options) {
|
|
|
435
459
|
abortRef.current = null;
|
|
436
460
|
}
|
|
437
461
|
}
|
|
438
|
-
const joinStream = async (runId, lastEventId) => {
|
|
462
|
+
const joinStream = async (runId, lastEventId, options) => {
|
|
439
463
|
lastEventId ??= "-1";
|
|
440
464
|
if (!threadId)
|
|
441
465
|
return;
|
|
@@ -443,6 +467,7 @@ function useStream(options) {
|
|
|
443
467
|
const stream = client.runs.joinStream(threadId, runId, {
|
|
444
468
|
signal,
|
|
445
469
|
lastEventId,
|
|
470
|
+
streamMode: options?.streamMode,
|
|
446
471
|
});
|
|
447
472
|
return {
|
|
448
473
|
onSuccess: () => {
|
|
@@ -462,23 +487,22 @@ function useStream(options) {
|
|
|
462
487
|
: undefined;
|
|
463
488
|
if (newPath != null)
|
|
464
489
|
setBranch(newPath ?? "");
|
|
465
|
-
// Assumption: we're setting the initial value
|
|
466
|
-
// Used for instant feedback
|
|
467
490
|
setStreamValues(() => {
|
|
468
|
-
const values = { ...historyValues };
|
|
469
491
|
if (submitOptions?.optimisticValues != null) {
|
|
470
492
|
return {
|
|
471
|
-
...
|
|
493
|
+
...historyValues,
|
|
472
494
|
...(typeof submitOptions.optimisticValues === "function"
|
|
473
|
-
? submitOptions.optimisticValues(
|
|
495
|
+
? submitOptions.optimisticValues(historyValues)
|
|
474
496
|
: submitOptions.optimisticValues),
|
|
475
497
|
};
|
|
476
498
|
}
|
|
477
|
-
return
|
|
499
|
+
return { ...historyValues };
|
|
478
500
|
});
|
|
479
501
|
let usableThreadId = threadId;
|
|
480
502
|
if (!usableThreadId) {
|
|
481
|
-
const thread = await client.threads.create(
|
|
503
|
+
const thread = await client.threads.create({
|
|
504
|
+
threadId: submitOptions?.threadId,
|
|
505
|
+
});
|
|
482
506
|
onThreadId(thread.thread_id);
|
|
483
507
|
usableThreadId = thread.thread_id;
|
|
484
508
|
}
|
package/dist/react/stream.d.ts
CHANGED
|
@@ -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
|
|
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>;
|
package/dist/react/stream.js
CHANGED
|
@@ -209,6 +209,32 @@ const useControllableThreadId = (options) => {
|
|
|
209
209
|
}
|
|
210
210
|
return [options.threadId ?? null, onThreadId];
|
|
211
211
|
};
|
|
212
|
+
function useStreamValuesState() {
|
|
213
|
+
const [values, setValues] = useState(null);
|
|
214
|
+
const setStreamValues = useCallback((values, kind = "stream") => {
|
|
215
|
+
if (typeof values === "function") {
|
|
216
|
+
setValues((prevTuple) => {
|
|
217
|
+
const [prevValues, prevKind] = prevTuple ?? [null, "stream"];
|
|
218
|
+
const next = values(prevValues, prevKind);
|
|
219
|
+
if (next == null)
|
|
220
|
+
return null;
|
|
221
|
+
return [next, kind];
|
|
222
|
+
});
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
if (values == null)
|
|
226
|
+
setValues(null);
|
|
227
|
+
setValues([values, kind]);
|
|
228
|
+
}, []);
|
|
229
|
+
const mutate = useCallback((kind, serverValues) => (update) => {
|
|
230
|
+
setStreamValues((clientValues) => {
|
|
231
|
+
const prev = { ...serverValues, ...clientValues };
|
|
232
|
+
const next = typeof update === "function" ? update(prev) : update;
|
|
233
|
+
return { ...prev, ...next };
|
|
234
|
+
}, kind);
|
|
235
|
+
}, [setStreamValues]);
|
|
236
|
+
return [values?.[0] ?? null, setStreamValues, mutate];
|
|
237
|
+
}
|
|
212
238
|
export function useStream(options) {
|
|
213
239
|
let { assistantId, messagesKey, onCreated, onError, onFinish } = options;
|
|
214
240
|
const reconnectOnMountRef = useRef(options.reconnectOnMount);
|
|
@@ -240,7 +266,7 @@ export function useStream(options) {
|
|
|
240
266
|
const [branch, setBranch] = useState("");
|
|
241
267
|
const [isLoading, setIsLoading] = useState(false);
|
|
242
268
|
const [streamError, setStreamError] = useState(undefined);
|
|
243
|
-
const [streamValues, setStreamValues] =
|
|
269
|
+
const [streamValues, setStreamValues, getMutateFn] = useStreamValuesState();
|
|
244
270
|
const messageManagerRef = useRef(new MessageTupleManager());
|
|
245
271
|
const submittingRef = useRef(false);
|
|
246
272
|
const abortRef = useRef(null);
|
|
@@ -290,7 +316,7 @@ export function useStream(options) {
|
|
|
290
316
|
const { rootSequence, paths } = getBranchSequence(history.data);
|
|
291
317
|
const { history: flatHistory, branchByCheckpoint } = getBranchView(rootSequence, paths, branch);
|
|
292
318
|
const threadHead = flatHistory.at(-1);
|
|
293
|
-
const historyValues = threadHead?.values ?? {};
|
|
319
|
+
const historyValues = threadHead?.values ?? options.initialValues ?? {};
|
|
294
320
|
const historyError = (() => {
|
|
295
321
|
const error = threadHead?.tasks?.at(-1)?.error;
|
|
296
322
|
if (error == null)
|
|
@@ -345,6 +371,7 @@ export function useStream(options) {
|
|
|
345
371
|
client.runs.cancel(threadId, runId);
|
|
346
372
|
runMetadataStorage.removeItem(`lg:stream:${threadId}`);
|
|
347
373
|
}
|
|
374
|
+
options?.onStop?.({ mutate: getMutateFn("stop", historyValues) });
|
|
348
375
|
};
|
|
349
376
|
async function consumeStream(action) {
|
|
350
377
|
let getCallbackMeta;
|
|
@@ -365,15 +392,7 @@ export function useStream(options) {
|
|
|
365
392
|
options.onUpdateEvent?.(data);
|
|
366
393
|
if (event === "custom")
|
|
367
394
|
options.onCustomEvent?.(data, {
|
|
368
|
-
mutate: (
|
|
369
|
-
// should not happen
|
|
370
|
-
if (prev == null)
|
|
371
|
-
return prev;
|
|
372
|
-
return {
|
|
373
|
-
...prev,
|
|
374
|
-
...(typeof update === "function" ? update(prev) : update),
|
|
375
|
-
};
|
|
376
|
-
}),
|
|
395
|
+
mutate: getMutateFn("stream", historyValues),
|
|
377
396
|
});
|
|
378
397
|
if (event === "metadata")
|
|
379
398
|
options.onMetadataEvent?.(data);
|
|
@@ -409,7 +428,12 @@ export function useStream(options) {
|
|
|
409
428
|
}
|
|
410
429
|
// TODO: stream created checkpoints to avoid an unnecessary network request
|
|
411
430
|
const result = await run.onSuccess();
|
|
412
|
-
setStreamValues(
|
|
431
|
+
setStreamValues((values, kind) => {
|
|
432
|
+
// Do not clear out the user values set on `stop`.
|
|
433
|
+
if (kind === "stop")
|
|
434
|
+
return values;
|
|
435
|
+
return null;
|
|
436
|
+
});
|
|
413
437
|
if (streamError != null)
|
|
414
438
|
throw streamError;
|
|
415
439
|
const lastHead = result.at(0);
|
|
@@ -432,7 +456,7 @@ export function useStream(options) {
|
|
|
432
456
|
abortRef.current = null;
|
|
433
457
|
}
|
|
434
458
|
}
|
|
435
|
-
const joinStream = async (runId, lastEventId) => {
|
|
459
|
+
const joinStream = async (runId, lastEventId, options) => {
|
|
436
460
|
lastEventId ??= "-1";
|
|
437
461
|
if (!threadId)
|
|
438
462
|
return;
|
|
@@ -440,6 +464,7 @@ export function useStream(options) {
|
|
|
440
464
|
const stream = client.runs.joinStream(threadId, runId, {
|
|
441
465
|
signal,
|
|
442
466
|
lastEventId,
|
|
467
|
+
streamMode: options?.streamMode,
|
|
443
468
|
});
|
|
444
469
|
return {
|
|
445
470
|
onSuccess: () => {
|
|
@@ -459,23 +484,22 @@ export function useStream(options) {
|
|
|
459
484
|
: undefined;
|
|
460
485
|
if (newPath != null)
|
|
461
486
|
setBranch(newPath ?? "");
|
|
462
|
-
// Assumption: we're setting the initial value
|
|
463
|
-
// Used for instant feedback
|
|
464
487
|
setStreamValues(() => {
|
|
465
|
-
const values = { ...historyValues };
|
|
466
488
|
if (submitOptions?.optimisticValues != null) {
|
|
467
489
|
return {
|
|
468
|
-
...
|
|
490
|
+
...historyValues,
|
|
469
491
|
...(typeof submitOptions.optimisticValues === "function"
|
|
470
|
-
? submitOptions.optimisticValues(
|
|
492
|
+
? submitOptions.optimisticValues(historyValues)
|
|
471
493
|
: submitOptions.optimisticValues),
|
|
472
494
|
};
|
|
473
495
|
}
|
|
474
|
-
return
|
|
496
|
+
return { ...historyValues };
|
|
475
497
|
});
|
|
476
498
|
let usableThreadId = threadId;
|
|
477
499
|
if (!usableThreadId) {
|
|
478
|
-
const thread = await client.threads.create(
|
|
500
|
+
const thread = await client.threads.create({
|
|
501
|
+
threadId: submitOptions?.threadId,
|
|
502
|
+
});
|
|
479
503
|
onThreadId(thread.thread_id);
|
|
480
504
|
usableThreadId = thread.thread_id;
|
|
481
505
|
}
|
package/dist/utils/sse.cjs
CHANGED
|
@@ -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
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
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
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
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
|
-
|
|
49
|
-
acc.
|
|
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
|
-
|
|
70
|
-
|
|
71
|
-
controller.enqueue(line);
|
|
47
|
+
if (idx === lastIdx && acc.from <= lastIdx) {
|
|
48
|
+
acc.lines.push(text.subarray(acc.from));
|
|
72
49
|
}
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
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
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
if (!
|
|
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
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
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) {
|
package/dist/utils/sse.d.ts
CHANGED
|
@@ -1,12 +1,8 @@
|
|
|
1
|
-
export declare
|
|
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
|
|
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
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
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
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
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
|
-
|
|
46
|
-
acc.
|
|
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
|
-
|
|
67
|
-
|
|
68
|
-
controller.enqueue(line);
|
|
44
|
+
if (idx === lastIdx && acc.from <= lastIdx) {
|
|
45
|
+
acc.lines.push(text.subarray(acc.from));
|
|
69
46
|
}
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
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
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
if (!
|
|
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
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
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.
|
|
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/
|
|
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",
|