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