@langchain/langgraph-sdk 1.3.0 → 1.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +6 -0
- package/dist/client.cjs +49 -23
- package/dist/client.cjs.map +1 -1
- package/dist/client.d.cts +23 -0
- package/dist/client.d.cts.map +1 -1
- package/dist/client.d.ts +23 -0
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +50 -24
- package/dist/client.js.map +1 -1
- package/dist/schema.d.cts.map +1 -1
- package/dist/schema.d.ts.map +1 -1
- package/dist/types.stream.d.ts.map +1 -1
- package/dist/utils/error.cjs +17 -0
- package/dist/utils/error.cjs.map +1 -0
- package/dist/utils/error.js +16 -0
- package/dist/utils/error.js.map +1 -0
- package/dist/utils/stream.cjs +85 -0
- package/dist/utils/stream.cjs.map +1 -1
- package/dist/utils/stream.js +86 -1
- package/dist/utils/stream.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
|
|
2
|
+
//#region src/utils/error.ts
|
|
3
|
+
const isError = (error) => {
|
|
4
|
+
if ("isError" in Error && typeof Error.isError === "function") return Error.isError(error);
|
|
5
|
+
const stringTag = Object.prototype.toString.call(error);
|
|
6
|
+
return stringTag === "[object Error]" || stringTag === "[object DOMException]" || stringTag === "[object DOMError]" || stringTag === "[object Exception]";
|
|
7
|
+
};
|
|
8
|
+
const isNetworkError = (error) => {
|
|
9
|
+
if (!isError(error)) return false;
|
|
10
|
+
if (error.name !== "TypeError" || typeof error.message !== "string") return false;
|
|
11
|
+
const msg = error.message.toLowerCase();
|
|
12
|
+
return msg.includes("fetch") || msg.includes("network") || msg.includes("connection") || msg.includes("error sending request") || msg.includes("load failed");
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
//#endregion
|
|
16
|
+
exports.isNetworkError = isNetworkError;
|
|
17
|
+
//# sourceMappingURL=error.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"error.cjs","names":[],"sources":["../../src/utils/error.ts"],"sourcesContent":["export const isError = (error: unknown): error is Error => {\n // check for presence of `Error.isError` for newer browsers\n if (\"isError\" in Error && typeof Error.isError === \"function\") {\n return Error.isError(error);\n }\n\n // Resort to checking string tag\n const stringTag = Object.prototype.toString.call(error);\n return (\n stringTag === \"[object Error]\" ||\n stringTag === \"[object DOMException]\" ||\n stringTag === \"[object DOMError]\" ||\n stringTag === \"[object Exception]\"\n );\n};\n\nexport const isNetworkError = (error: unknown): error is Error => {\n if (!isError(error)) return false;\n if (error.name !== \"TypeError\" || typeof error.message !== \"string\") {\n return false;\n }\n const msg = error.message.toLowerCase();\n return (\n msg.includes(\"fetch\") ||\n msg.includes(\"network\") ||\n msg.includes(\"connection\") ||\n msg.includes(\"error sending request\") ||\n msg.includes(\"load failed\")\n );\n};\n"],"mappings":";;AAAA,MAAa,WAAW,UAAmC;AAEzD,KAAI,aAAa,SAAS,OAAO,MAAM,YAAY,WACjD,QAAO,MAAM,QAAQ;CAIvB,MAAM,YAAY,OAAO,UAAU,SAAS,KAAK;AACjD,QACE,cAAc,oBACd,cAAc,2BACd,cAAc,uBACd,cAAc;;AAIlB,MAAa,kBAAkB,UAAmC;AAChE,KAAI,CAAC,QAAQ,OAAQ,QAAO;AAC5B,KAAI,MAAM,SAAS,eAAe,OAAO,MAAM,YAAY,SACzD,QAAO;CAET,MAAM,MAAM,MAAM,QAAQ;AAC1B,QACE,IAAI,SAAS,YACb,IAAI,SAAS,cACb,IAAI,SAAS,iBACb,IAAI,SAAS,4BACb,IAAI,SAAS"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
//#region src/utils/error.ts
|
|
2
|
+
const isError = (error) => {
|
|
3
|
+
if ("isError" in Error && typeof Error.isError === "function") return Error.isError(error);
|
|
4
|
+
const stringTag = Object.prototype.toString.call(error);
|
|
5
|
+
return stringTag === "[object Error]" || stringTag === "[object DOMException]" || stringTag === "[object DOMError]" || stringTag === "[object Exception]";
|
|
6
|
+
};
|
|
7
|
+
const isNetworkError = (error) => {
|
|
8
|
+
if (!isError(error)) return false;
|
|
9
|
+
if (error.name !== "TypeError" || typeof error.message !== "string") return false;
|
|
10
|
+
const msg = error.message.toLowerCase();
|
|
11
|
+
return msg.includes("fetch") || msg.includes("network") || msg.includes("connection") || msg.includes("error sending request") || msg.includes("load failed");
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
//#endregion
|
|
15
|
+
export { isNetworkError };
|
|
16
|
+
//# sourceMappingURL=error.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"error.js","names":[],"sources":["../../src/utils/error.ts"],"sourcesContent":["export const isError = (error: unknown): error is Error => {\n // check for presence of `Error.isError` for newer browsers\n if (\"isError\" in Error && typeof Error.isError === \"function\") {\n return Error.isError(error);\n }\n\n // Resort to checking string tag\n const stringTag = Object.prototype.toString.call(error);\n return (\n stringTag === \"[object Error]\" ||\n stringTag === \"[object DOMException]\" ||\n stringTag === \"[object DOMError]\" ||\n stringTag === \"[object Exception]\"\n );\n};\n\nexport const isNetworkError = (error: unknown): error is Error => {\n if (!isError(error)) return false;\n if (error.name !== \"TypeError\" || typeof error.message !== \"string\") {\n return false;\n }\n const msg = error.message.toLowerCase();\n return (\n msg.includes(\"fetch\") ||\n msg.includes(\"network\") ||\n msg.includes(\"connection\") ||\n msg.includes(\"error sending request\") ||\n msg.includes(\"load failed\")\n );\n};\n"],"mappings":";AAAA,MAAa,WAAW,UAAmC;AAEzD,KAAI,aAAa,SAAS,OAAO,MAAM,YAAY,WACjD,QAAO,MAAM,QAAQ;CAIvB,MAAM,YAAY,OAAO,UAAU,SAAS,KAAK;AACjD,QACE,cAAc,oBACd,cAAc,2BACd,cAAc,uBACd,cAAc;;AAIlB,MAAa,kBAAkB,UAAmC;AAChE,KAAI,CAAC,QAAQ,OAAQ,QAAO;AAC5B,KAAI,MAAM,SAAS,eAAe,OAAO,MAAM,YAAY,SACzD,QAAO;CAET,MAAM,MAAM,MAAM,QAAQ;AAC1B,QACE,IAAI,SAAS,YACb,IAAI,SAAS,cACb,IAAI,SAAS,iBACb,IAAI,SAAS,4BACb,IAAI,SAAS"}
|
package/dist/utils/stream.cjs
CHANGED
|
@@ -1,5 +1,89 @@
|
|
|
1
|
+
const require_error = require('./error.cjs');
|
|
1
2
|
|
|
2
3
|
//#region src/utils/stream.ts
|
|
4
|
+
/**
|
|
5
|
+
* Error thrown when maximum reconnection attempts are exceeded.
|
|
6
|
+
*/
|
|
7
|
+
var MaxReconnectAttemptsError = class extends Error {
|
|
8
|
+
constructor(maxAttempts, cause) {
|
|
9
|
+
super(`Exceeded maximum SSE reconnection attempts (${maxAttempts})`);
|
|
10
|
+
this.name = "MaxReconnectAttemptsError";
|
|
11
|
+
this.cause = cause;
|
|
12
|
+
}
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* Stream with automatic retry logic for SSE connections.
|
|
16
|
+
* Implements reconnection behavior similar to the Python SDK.
|
|
17
|
+
*
|
|
18
|
+
* @param makeRequest Function to make requests. When `params` is undefined/empty, it's the initial request.
|
|
19
|
+
* When `params.lastEventId` is provided, it's a reconnection request.
|
|
20
|
+
* @param options Configuration options
|
|
21
|
+
* @returns AsyncGenerator yielding stream events
|
|
22
|
+
*/
|
|
23
|
+
async function* streamWithRetry(makeRequest, options = {}) {
|
|
24
|
+
const maxRetries = options.maxRetries ?? 5;
|
|
25
|
+
let attempt = 0;
|
|
26
|
+
let lastEventId;
|
|
27
|
+
let reconnectPath;
|
|
28
|
+
while (true) {
|
|
29
|
+
let shouldRetry = false;
|
|
30
|
+
let lastError;
|
|
31
|
+
let reader;
|
|
32
|
+
try {
|
|
33
|
+
if (options.signal?.aborted) return;
|
|
34
|
+
const { response, stream } = await makeRequest(lastEventId ? {
|
|
35
|
+
lastEventId,
|
|
36
|
+
reconnectPath
|
|
37
|
+
} : void 0);
|
|
38
|
+
const locationHeader = response.headers.get("location");
|
|
39
|
+
if (locationHeader) reconnectPath = locationHeader;
|
|
40
|
+
const contentType = response.headers.get("content-type")?.split(";")[0];
|
|
41
|
+
if (contentType && !contentType.includes("text/event-stream")) throw new Error(`Expected response header Content-Type to contain 'text/event-stream', got '${contentType}'`);
|
|
42
|
+
reader = stream.getReader();
|
|
43
|
+
try {
|
|
44
|
+
while (true) {
|
|
45
|
+
if (options.signal?.aborted) {
|
|
46
|
+
await reader.cancel();
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
const { done, value } = await reader.read();
|
|
50
|
+
if (done) break;
|
|
51
|
+
if (value.id) lastEventId = value.id;
|
|
52
|
+
yield value;
|
|
53
|
+
}
|
|
54
|
+
break;
|
|
55
|
+
} catch (error) {
|
|
56
|
+
if (lastEventId && reconnectPath && !options.signal?.aborted) shouldRetry = true;
|
|
57
|
+
else throw error;
|
|
58
|
+
} finally {
|
|
59
|
+
if (reader) try {
|
|
60
|
+
reader.releaseLock();
|
|
61
|
+
} catch {}
|
|
62
|
+
}
|
|
63
|
+
} catch (error) {
|
|
64
|
+
lastError = error;
|
|
65
|
+
if (require_error.isNetworkError(error) && lastEventId && reconnectPath && !options.signal?.aborted) shouldRetry = true;
|
|
66
|
+
else throw error;
|
|
67
|
+
}
|
|
68
|
+
if (shouldRetry) {
|
|
69
|
+
attempt += 1;
|
|
70
|
+
if (attempt > maxRetries) throw new MaxReconnectAttemptsError(maxRetries, lastError);
|
|
71
|
+
options.onReconnect?.({
|
|
72
|
+
attempt,
|
|
73
|
+
lastEventId,
|
|
74
|
+
cause: lastError
|
|
75
|
+
});
|
|
76
|
+
const baseDelay = Math.min(1e3 * 2 ** (attempt - 1), 5e3);
|
|
77
|
+
const jitter = Math.random() * 1e3;
|
|
78
|
+
const delay = baseDelay + jitter;
|
|
79
|
+
await new Promise((resolve) => {
|
|
80
|
+
setTimeout(resolve, delay);
|
|
81
|
+
});
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
break;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
3
87
|
var IterableReadableStream = class IterableReadableStream extends ReadableStream {
|
|
4
88
|
reader;
|
|
5
89
|
ensureReader() {
|
|
@@ -88,4 +172,5 @@ var IterableReadableStream = class IterableReadableStream extends ReadableStream
|
|
|
88
172
|
|
|
89
173
|
//#endregion
|
|
90
174
|
exports.IterableReadableStream = IterableReadableStream;
|
|
175
|
+
exports.streamWithRetry = streamWithRetry;
|
|
91
176
|
//# sourceMappingURL=stream.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"stream.cjs","names":[],"sources":["../../src/utils/stream.ts"],"sourcesContent":["// in this case don't quite match.\ntype IterableReadableStreamInterface<T> = ReadableStream<T> & AsyncIterable<T>;\n\n/*\n * Support async iterator syntax for ReadableStreams in all environments.\n * Source: https://github.com/MattiasBuelens/web-streams-polyfill/pull/122#issuecomment-1627354490\n */\nexport class IterableReadableStream<T>\n extends ReadableStream<T>\n implements IterableReadableStreamInterface<T>\n{\n public reader: ReadableStreamDefaultReader<T>;\n\n ensureReader() {\n if (!this.reader) {\n this.reader = this.getReader();\n }\n }\n\n async next(): Promise<IteratorResult<T>> {\n this.ensureReader();\n try {\n const result = await this.reader.read();\n if (result.done) {\n this.reader.releaseLock(); // release lock when stream becomes closed\n return {\n done: true,\n value: undefined,\n };\n } else {\n return {\n done: false,\n value: result.value,\n };\n }\n } catch (e) {\n this.reader.releaseLock(); // release lock when stream becomes errored\n throw e;\n }\n }\n\n async return(): Promise<IteratorResult<T>> {\n this.ensureReader();\n // If wrapped in a Node stream, cancel is already called.\n if (this.locked) {\n const cancelPromise = this.reader.cancel(); // cancel first, but don't await yet\n this.reader.releaseLock(); // release lock first\n await cancelPromise; // now await it\n }\n return { done: true, value: undefined };\n }\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n async throw(e: any): Promise<IteratorResult<T>> {\n this.ensureReader();\n if (this.locked) {\n const cancelPromise = this.reader.cancel(); // cancel first, but don't await yet\n this.reader.releaseLock(); // release lock first\n await cancelPromise; // now await it\n }\n throw e;\n }\n\n // eslint-disable-next-line @typescript-eslint/ban-ts-comment\n // @ts-ignore Not present in Node 18 types, required in latest Node 22\n async [Symbol.asyncDispose]() {\n await this.return();\n }\n\n [Symbol.asyncIterator]() {\n return this;\n }\n\n static fromReadableStream<T>(stream: ReadableStream<T>) {\n // From https://developer.mozilla.org/en-US/docs/Web/API/Streams_API/Using_readable_streams#reading_the_stream\n const reader = stream.getReader();\n return new IterableReadableStream<T>({\n start(controller) {\n return pump();\n function pump(): Promise<T | undefined> {\n return reader.read().then(({ done, value }) => {\n // When no more data needs to be consumed, close the stream\n if (done) {\n controller.close();\n return;\n }\n // Enqueue the next data chunk into our target stream\n controller.enqueue(value);\n return pump();\n });\n }\n },\n cancel() {\n reader.releaseLock();\n },\n });\n }\n\n static fromAsyncGenerator<T>(generator: AsyncGenerator<T>) {\n return new IterableReadableStream<T>({\n async pull(controller) {\n const { value, done } = await generator.next();\n // When no more data needs to be consumed, close the stream\n if (done) {\n controller.close();\n }\n // Fix: `else if (value)` will hang the streaming when nullish value (e.g. empty string) is pulled\n controller.enqueue(value);\n },\n async cancel(reason) {\n await generator.return(reason);\n },\n });\n }\n}\n"],"mappings":";;AAOA,IAAa,yBAAb,MAAa,+BACH,eAEV;CACE,AAAO;CAEP,eAAe;AACb,MAAI,CAAC,KAAK,OACR,MAAK,SAAS,KAAK;;CAIvB,MAAM,OAAmC;AACvC,OAAK;AACL,MAAI;GACF,MAAM,SAAS,MAAM,KAAK,OAAO;AACjC,OAAI,OAAO,MAAM;AACf,SAAK,OAAO;AACZ,WAAO;KACL,MAAM;KACN,OAAO;;SAGT,QAAO;IACL,MAAM;IACN,OAAO,OAAO;;WAGX,GAAG;AACV,QAAK,OAAO;AACZ,SAAM;;;CAIV,MAAM,SAAqC;AACzC,OAAK;AAEL,MAAI,KAAK,QAAQ;GACf,MAAM,gBAAgB,KAAK,OAAO;AAClC,QAAK,OAAO;AACZ,SAAM;;AAER,SAAO;GAAE,MAAM;GAAM,OAAO;;;CAI9B,MAAM,MAAM,GAAoC;AAC9C,OAAK;AACL,MAAI,KAAK,QAAQ;GACf,MAAM,gBAAgB,KAAK,OAAO;AAClC,QAAK,OAAO;AACZ,SAAM;;AAER,QAAM;;CAKR,OAAO,OAAO,gBAAgB;AAC5B,QAAM,KAAK;;CAGb,CAAC,OAAO,iBAAiB;AACvB,SAAO;;CAGT,OAAO,mBAAsB,QAA2B;EAEtD,MAAM,SAAS,OAAO;AACtB,SAAO,IAAI,uBAA0B;GACnC,MAAM,YAAY;AAChB,WAAO;IACP,SAAS,OAA+B;AACtC,YAAO,OAAO,OAAO,MAAM,EAAE,MAAM,YAAY;AAE7C,UAAI,MAAM;AACR,kBAAW;AACX;;AAGF,iBAAW,QAAQ;AACnB,aAAO;;;;GAIb,SAAS;AACP,WAAO;;;;CAKb,OAAO,mBAAsB,WAA8B;AACzD,SAAO,IAAI,uBAA0B;GACnC,MAAM,KAAK,YAAY;IACrB,MAAM,EAAE,OAAO,SAAS,MAAM,UAAU;AAExC,QAAI,KACF,YAAW;AAGb,eAAW,QAAQ;;GAErB,MAAM,OAAO,QAAQ;AACnB,UAAM,UAAU,OAAO"}
|
|
1
|
+
{"version":3,"file":"stream.cjs","names":["lastEventId: string | undefined","reconnectPath: string | undefined","lastError: unknown","reader: ReadableStreamDefaultReader<T> | undefined","isNetworkError"],"sources":["../../src/utils/stream.ts"],"sourcesContent":["import { isNetworkError } from \"./error.js\";\n\n// in this case don't quite match.\ntype IterableReadableStreamInterface<T> = ReadableStream<T> & AsyncIterable<T>;\n\n/**\n * Options for streaming with automatic retry logic.\n */\nexport interface StreamWithRetryOptions {\n /**\n * Maximum number of reconnection attempts. Default is 5.\n */\n maxRetries?: number;\n\n /**\n * AbortSignal to cancel the stream.\n */\n signal?: AbortSignal;\n\n /**\n * Callback invoked when a reconnection attempt is made.\n */\n onReconnect?: (options: {\n attempt: number;\n lastEventId?: string;\n cause: unknown;\n }) => void;\n}\n\n/**\n * Parameters for making a stream request\n */\nexport interface StreamRequestParams {\n /**\n * If provided, this is a reconnection request with the last event ID\n */\n lastEventId?: string;\n\n /**\n * Optional reconnection path from the Location header\n */\n reconnectPath?: string;\n}\n\n/**\n * Error thrown when maximum reconnection attempts are exceeded.\n */\nexport class MaxReconnectAttemptsError extends Error {\n constructor(maxAttempts: number, cause: unknown) {\n super(`Exceeded maximum SSE reconnection attempts (${maxAttempts})`);\n this.name = \"MaxReconnectAttemptsError\";\n this.cause = cause;\n }\n}\n\n/**\n * Stream with automatic retry logic for SSE connections.\n * Implements reconnection behavior similar to the Python SDK.\n *\n * @param makeRequest Function to make requests. When `params` is undefined/empty, it's the initial request.\n * When `params.lastEventId` is provided, it's a reconnection request.\n * @param options Configuration options\n * @returns AsyncGenerator yielding stream events\n */\nexport async function* streamWithRetry<T extends { id?: string }>(\n makeRequest: (params?: StreamRequestParams) => Promise<{\n response: Response;\n stream: ReadableStream<T>;\n }>,\n options: StreamWithRetryOptions = {}\n): AsyncGenerator<T> {\n const maxRetries = options.maxRetries ?? 5;\n let attempt = 0;\n let lastEventId: string | undefined;\n let reconnectPath: string | undefined;\n\n while (true) {\n let shouldRetry = false;\n let lastError: unknown;\n let reader: ReadableStreamDefaultReader<T> | undefined;\n\n try {\n // Check if aborted before making request\n if (options.signal?.aborted) return;\n\n // Make request - initial if no lastEventId, reconnect otherwise\n const { response, stream } = await makeRequest(\n lastEventId ? { lastEventId, reconnectPath } : undefined\n );\n\n // Check for Location header (server-provided reconnection path)\n const locationHeader = response.headers.get(\"location\");\n if (locationHeader) {\n reconnectPath = locationHeader;\n }\n\n // Verify content type\n const contentType = response.headers.get(\"content-type\")?.split(\";\")[0];\n if (contentType && !contentType.includes(\"text/event-stream\")) {\n throw new Error(\n `Expected response header Content-Type to contain 'text/event-stream', got '${contentType}'`\n );\n }\n\n reader = stream.getReader();\n\n try {\n while (true) {\n // Check abort signal before each read\n if (options.signal?.aborted) {\n await reader.cancel();\n return;\n }\n\n const { done, value } = await reader.read();\n\n if (done) {\n // Stream completed successfully\n break;\n }\n\n // Track last event ID for reconnection\n if (value.id) {\n lastEventId = value.id;\n }\n\n yield value;\n }\n\n // Stream completed successfully, exit retry loop\n break;\n } catch (error) {\n // Error during streaming - attempt reconnect if we have lastEventId and a location header\n if (lastEventId && reconnectPath && !options.signal?.aborted) {\n shouldRetry = true;\n } else {\n throw error;\n }\n } finally {\n if (reader) {\n try {\n reader.releaseLock();\n } catch {\n // Ignore errors when releasing lock\n }\n }\n }\n } catch (error) {\n lastError = error;\n\n // Only retry if we have reconnection capability and it's a network error\n if (\n isNetworkError(error) &&\n lastEventId &&\n reconnectPath &&\n !options.signal?.aborted\n ) {\n shouldRetry = true;\n } else {\n throw error;\n }\n }\n\n if (shouldRetry) {\n attempt += 1;\n if (attempt > maxRetries) {\n throw new MaxReconnectAttemptsError(maxRetries, lastError);\n }\n\n // Notify about reconnection attempt\n options.onReconnect?.({ attempt, lastEventId, cause: lastError });\n\n // Exponential backoff with jitter: min(1000 * 2^attempt, 5000) + random jitter\n const baseDelay = Math.min(1000 * 2 ** (attempt - 1), 5000);\n const jitter = Math.random() * 1000;\n const delay = baseDelay + jitter;\n\n await new Promise((resolve) => {\n setTimeout(resolve, delay);\n });\n\n continue;\n }\n\n // Successfully completed\n break;\n }\n}\n\n/*\n * Support async iterator syntax for ReadableStreams in all environments.\n * Source: https://github.com/MattiasBuelens/web-streams-polyfill/pull/122#issuecomment-1627354490\n */\nexport class IterableReadableStream<T>\n extends ReadableStream<T>\n implements IterableReadableStreamInterface<T>\n{\n public reader: ReadableStreamDefaultReader<T>;\n\n ensureReader() {\n if (!this.reader) {\n this.reader = this.getReader();\n }\n }\n\n async next(): Promise<IteratorResult<T>> {\n this.ensureReader();\n try {\n const result = await this.reader.read();\n if (result.done) {\n this.reader.releaseLock(); // release lock when stream becomes closed\n return {\n done: true,\n value: undefined,\n };\n } else {\n return {\n done: false,\n value: result.value,\n };\n }\n } catch (e) {\n this.reader.releaseLock(); // release lock when stream becomes errored\n throw e;\n }\n }\n\n async return(): Promise<IteratorResult<T>> {\n this.ensureReader();\n // If wrapped in a Node stream, cancel is already called.\n if (this.locked) {\n const cancelPromise = this.reader.cancel(); // cancel first, but don't await yet\n this.reader.releaseLock(); // release lock first\n await cancelPromise; // now await it\n }\n return { done: true, value: undefined };\n }\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n async throw(e: any): Promise<IteratorResult<T>> {\n this.ensureReader();\n if (this.locked) {\n const cancelPromise = this.reader.cancel(); // cancel first, but don't await yet\n this.reader.releaseLock(); // release lock first\n await cancelPromise; // now await it\n }\n throw e;\n }\n\n // eslint-disable-next-line @typescript-eslint/ban-ts-comment\n // @ts-ignore Not present in Node 18 types, required in latest Node 22\n async [Symbol.asyncDispose]() {\n await this.return();\n }\n\n [Symbol.asyncIterator]() {\n return this;\n }\n\n static fromReadableStream<T>(stream: ReadableStream<T>) {\n // From https://developer.mozilla.org/en-US/docs/Web/API/Streams_API/Using_readable_streams#reading_the_stream\n const reader = stream.getReader();\n return new IterableReadableStream<T>({\n start(controller) {\n return pump();\n function pump(): Promise<T | undefined> {\n return reader.read().then(({ done, value }) => {\n // When no more data needs to be consumed, close the stream\n if (done) {\n controller.close();\n return;\n }\n // Enqueue the next data chunk into our target stream\n controller.enqueue(value);\n return pump();\n });\n }\n },\n cancel() {\n reader.releaseLock();\n },\n });\n }\n\n static fromAsyncGenerator<T>(generator: AsyncGenerator<T>) {\n return new IterableReadableStream<T>({\n async pull(controller) {\n const { value, done } = await generator.next();\n // When no more data needs to be consumed, close the stream\n if (done) {\n controller.close();\n }\n // Fix: `else if (value)` will hang the streaming when nullish value (e.g. empty string) is pulled\n controller.enqueue(value);\n },\n async cancel(reason) {\n await generator.return(reason);\n },\n });\n }\n}\n"],"mappings":"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"}
|
package/dist/utils/stream.js
CHANGED
|
@@ -1,4 +1,89 @@
|
|
|
1
|
+
import { isNetworkError } from "./error.js";
|
|
2
|
+
|
|
1
3
|
//#region src/utils/stream.ts
|
|
4
|
+
/**
|
|
5
|
+
* Error thrown when maximum reconnection attempts are exceeded.
|
|
6
|
+
*/
|
|
7
|
+
var MaxReconnectAttemptsError = class extends Error {
|
|
8
|
+
constructor(maxAttempts, cause) {
|
|
9
|
+
super(`Exceeded maximum SSE reconnection attempts (${maxAttempts})`);
|
|
10
|
+
this.name = "MaxReconnectAttemptsError";
|
|
11
|
+
this.cause = cause;
|
|
12
|
+
}
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* Stream with automatic retry logic for SSE connections.
|
|
16
|
+
* Implements reconnection behavior similar to the Python SDK.
|
|
17
|
+
*
|
|
18
|
+
* @param makeRequest Function to make requests. When `params` is undefined/empty, it's the initial request.
|
|
19
|
+
* When `params.lastEventId` is provided, it's a reconnection request.
|
|
20
|
+
* @param options Configuration options
|
|
21
|
+
* @returns AsyncGenerator yielding stream events
|
|
22
|
+
*/
|
|
23
|
+
async function* streamWithRetry(makeRequest, options = {}) {
|
|
24
|
+
const maxRetries = options.maxRetries ?? 5;
|
|
25
|
+
let attempt = 0;
|
|
26
|
+
let lastEventId;
|
|
27
|
+
let reconnectPath;
|
|
28
|
+
while (true) {
|
|
29
|
+
let shouldRetry = false;
|
|
30
|
+
let lastError;
|
|
31
|
+
let reader;
|
|
32
|
+
try {
|
|
33
|
+
if (options.signal?.aborted) return;
|
|
34
|
+
const { response, stream } = await makeRequest(lastEventId ? {
|
|
35
|
+
lastEventId,
|
|
36
|
+
reconnectPath
|
|
37
|
+
} : void 0);
|
|
38
|
+
const locationHeader = response.headers.get("location");
|
|
39
|
+
if (locationHeader) reconnectPath = locationHeader;
|
|
40
|
+
const contentType = response.headers.get("content-type")?.split(";")[0];
|
|
41
|
+
if (contentType && !contentType.includes("text/event-stream")) throw new Error(`Expected response header Content-Type to contain 'text/event-stream', got '${contentType}'`);
|
|
42
|
+
reader = stream.getReader();
|
|
43
|
+
try {
|
|
44
|
+
while (true) {
|
|
45
|
+
if (options.signal?.aborted) {
|
|
46
|
+
await reader.cancel();
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
const { done, value } = await reader.read();
|
|
50
|
+
if (done) break;
|
|
51
|
+
if (value.id) lastEventId = value.id;
|
|
52
|
+
yield value;
|
|
53
|
+
}
|
|
54
|
+
break;
|
|
55
|
+
} catch (error) {
|
|
56
|
+
if (lastEventId && reconnectPath && !options.signal?.aborted) shouldRetry = true;
|
|
57
|
+
else throw error;
|
|
58
|
+
} finally {
|
|
59
|
+
if (reader) try {
|
|
60
|
+
reader.releaseLock();
|
|
61
|
+
} catch {}
|
|
62
|
+
}
|
|
63
|
+
} catch (error) {
|
|
64
|
+
lastError = error;
|
|
65
|
+
if (isNetworkError(error) && lastEventId && reconnectPath && !options.signal?.aborted) shouldRetry = true;
|
|
66
|
+
else throw error;
|
|
67
|
+
}
|
|
68
|
+
if (shouldRetry) {
|
|
69
|
+
attempt += 1;
|
|
70
|
+
if (attempt > maxRetries) throw new MaxReconnectAttemptsError(maxRetries, lastError);
|
|
71
|
+
options.onReconnect?.({
|
|
72
|
+
attempt,
|
|
73
|
+
lastEventId,
|
|
74
|
+
cause: lastError
|
|
75
|
+
});
|
|
76
|
+
const baseDelay = Math.min(1e3 * 2 ** (attempt - 1), 5e3);
|
|
77
|
+
const jitter = Math.random() * 1e3;
|
|
78
|
+
const delay = baseDelay + jitter;
|
|
79
|
+
await new Promise((resolve) => {
|
|
80
|
+
setTimeout(resolve, delay);
|
|
81
|
+
});
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
break;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
2
87
|
var IterableReadableStream = class IterableReadableStream extends ReadableStream {
|
|
3
88
|
reader;
|
|
4
89
|
ensureReader() {
|
|
@@ -86,5 +171,5 @@ var IterableReadableStream = class IterableReadableStream extends ReadableStream
|
|
|
86
171
|
};
|
|
87
172
|
|
|
88
173
|
//#endregion
|
|
89
|
-
export { IterableReadableStream };
|
|
174
|
+
export { IterableReadableStream, streamWithRetry };
|
|
90
175
|
//# sourceMappingURL=stream.js.map
|
package/dist/utils/stream.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"stream.js","names":[],"sources":["../../src/utils/stream.ts"],"sourcesContent":["// in this case don't quite match.\ntype IterableReadableStreamInterface<T> = ReadableStream<T> & AsyncIterable<T>;\n\n/*\n * Support async iterator syntax for ReadableStreams in all environments.\n * Source: https://github.com/MattiasBuelens/web-streams-polyfill/pull/122#issuecomment-1627354490\n */\nexport class IterableReadableStream<T>\n extends ReadableStream<T>\n implements IterableReadableStreamInterface<T>\n{\n public reader: ReadableStreamDefaultReader<T>;\n\n ensureReader() {\n if (!this.reader) {\n this.reader = this.getReader();\n }\n }\n\n async next(): Promise<IteratorResult<T>> {\n this.ensureReader();\n try {\n const result = await this.reader.read();\n if (result.done) {\n this.reader.releaseLock(); // release lock when stream becomes closed\n return {\n done: true,\n value: undefined,\n };\n } else {\n return {\n done: false,\n value: result.value,\n };\n }\n } catch (e) {\n this.reader.releaseLock(); // release lock when stream becomes errored\n throw e;\n }\n }\n\n async return(): Promise<IteratorResult<T>> {\n this.ensureReader();\n // If wrapped in a Node stream, cancel is already called.\n if (this.locked) {\n const cancelPromise = this.reader.cancel(); // cancel first, but don't await yet\n this.reader.releaseLock(); // release lock first\n await cancelPromise; // now await it\n }\n return { done: true, value: undefined };\n }\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n async throw(e: any): Promise<IteratorResult<T>> {\n this.ensureReader();\n if (this.locked) {\n const cancelPromise = this.reader.cancel(); // cancel first, but don't await yet\n this.reader.releaseLock(); // release lock first\n await cancelPromise; // now await it\n }\n throw e;\n }\n\n // eslint-disable-next-line @typescript-eslint/ban-ts-comment\n // @ts-ignore Not present in Node 18 types, required in latest Node 22\n async [Symbol.asyncDispose]() {\n await this.return();\n }\n\n [Symbol.asyncIterator]() {\n return this;\n }\n\n static fromReadableStream<T>(stream: ReadableStream<T>) {\n // From https://developer.mozilla.org/en-US/docs/Web/API/Streams_API/Using_readable_streams#reading_the_stream\n const reader = stream.getReader();\n return new IterableReadableStream<T>({\n start(controller) {\n return pump();\n function pump(): Promise<T | undefined> {\n return reader.read().then(({ done, value }) => {\n // When no more data needs to be consumed, close the stream\n if (done) {\n controller.close();\n return;\n }\n // Enqueue the next data chunk into our target stream\n controller.enqueue(value);\n return pump();\n });\n }\n },\n cancel() {\n reader.releaseLock();\n },\n });\n }\n\n static fromAsyncGenerator<T>(generator: AsyncGenerator<T>) {\n return new IterableReadableStream<T>({\n async pull(controller) {\n const { value, done } = await generator.next();\n // When no more data needs to be consumed, close the stream\n if (done) {\n controller.close();\n }\n // Fix: `else if (value)` will hang the streaming when nullish value (e.g. empty string) is pulled\n controller.enqueue(value);\n },\n async cancel(reason) {\n await generator.return(reason);\n },\n });\n }\n}\n"],"mappings":";AAOA,IAAa,yBAAb,MAAa,+BACH,eAEV;CACE,AAAO;CAEP,eAAe;AACb,MAAI,CAAC,KAAK,OACR,MAAK,SAAS,KAAK;;CAIvB,MAAM,OAAmC;AACvC,OAAK;AACL,MAAI;GACF,MAAM,SAAS,MAAM,KAAK,OAAO;AACjC,OAAI,OAAO,MAAM;AACf,SAAK,OAAO;AACZ,WAAO;KACL,MAAM;KACN,OAAO;;SAGT,QAAO;IACL,MAAM;IACN,OAAO,OAAO;;WAGX,GAAG;AACV,QAAK,OAAO;AACZ,SAAM;;;CAIV,MAAM,SAAqC;AACzC,OAAK;AAEL,MAAI,KAAK,QAAQ;GACf,MAAM,gBAAgB,KAAK,OAAO;AAClC,QAAK,OAAO;AACZ,SAAM;;AAER,SAAO;GAAE,MAAM;GAAM,OAAO;;;CAI9B,MAAM,MAAM,GAAoC;AAC9C,OAAK;AACL,MAAI,KAAK,QAAQ;GACf,MAAM,gBAAgB,KAAK,OAAO;AAClC,QAAK,OAAO;AACZ,SAAM;;AAER,QAAM;;CAKR,OAAO,OAAO,gBAAgB;AAC5B,QAAM,KAAK;;CAGb,CAAC,OAAO,iBAAiB;AACvB,SAAO;;CAGT,OAAO,mBAAsB,QAA2B;EAEtD,MAAM,SAAS,OAAO;AACtB,SAAO,IAAI,uBAA0B;GACnC,MAAM,YAAY;AAChB,WAAO;IACP,SAAS,OAA+B;AACtC,YAAO,OAAO,OAAO,MAAM,EAAE,MAAM,YAAY;AAE7C,UAAI,MAAM;AACR,kBAAW;AACX;;AAGF,iBAAW,QAAQ;AACnB,aAAO;;;;GAIb,SAAS;AACP,WAAO;;;;CAKb,OAAO,mBAAsB,WAA8B;AACzD,SAAO,IAAI,uBAA0B;GACnC,MAAM,KAAK,YAAY;IACrB,MAAM,EAAE,OAAO,SAAS,MAAM,UAAU;AAExC,QAAI,KACF,YAAW;AAGb,eAAW,QAAQ;;GAErB,MAAM,OAAO,QAAQ;AACnB,UAAM,UAAU,OAAO"}
|
|
1
|
+
{"version":3,"file":"stream.js","names":["lastEventId: string | undefined","reconnectPath: string | undefined","lastError: unknown","reader: ReadableStreamDefaultReader<T> | undefined"],"sources":["../../src/utils/stream.ts"],"sourcesContent":["import { isNetworkError } from \"./error.js\";\n\n// in this case don't quite match.\ntype IterableReadableStreamInterface<T> = ReadableStream<T> & AsyncIterable<T>;\n\n/**\n * Options for streaming with automatic retry logic.\n */\nexport interface StreamWithRetryOptions {\n /**\n * Maximum number of reconnection attempts. Default is 5.\n */\n maxRetries?: number;\n\n /**\n * AbortSignal to cancel the stream.\n */\n signal?: AbortSignal;\n\n /**\n * Callback invoked when a reconnection attempt is made.\n */\n onReconnect?: (options: {\n attempt: number;\n lastEventId?: string;\n cause: unknown;\n }) => void;\n}\n\n/**\n * Parameters for making a stream request\n */\nexport interface StreamRequestParams {\n /**\n * If provided, this is a reconnection request with the last event ID\n */\n lastEventId?: string;\n\n /**\n * Optional reconnection path from the Location header\n */\n reconnectPath?: string;\n}\n\n/**\n * Error thrown when maximum reconnection attempts are exceeded.\n */\nexport class MaxReconnectAttemptsError extends Error {\n constructor(maxAttempts: number, cause: unknown) {\n super(`Exceeded maximum SSE reconnection attempts (${maxAttempts})`);\n this.name = \"MaxReconnectAttemptsError\";\n this.cause = cause;\n }\n}\n\n/**\n * Stream with automatic retry logic for SSE connections.\n * Implements reconnection behavior similar to the Python SDK.\n *\n * @param makeRequest Function to make requests. When `params` is undefined/empty, it's the initial request.\n * When `params.lastEventId` is provided, it's a reconnection request.\n * @param options Configuration options\n * @returns AsyncGenerator yielding stream events\n */\nexport async function* streamWithRetry<T extends { id?: string }>(\n makeRequest: (params?: StreamRequestParams) => Promise<{\n response: Response;\n stream: ReadableStream<T>;\n }>,\n options: StreamWithRetryOptions = {}\n): AsyncGenerator<T> {\n const maxRetries = options.maxRetries ?? 5;\n let attempt = 0;\n let lastEventId: string | undefined;\n let reconnectPath: string | undefined;\n\n while (true) {\n let shouldRetry = false;\n let lastError: unknown;\n let reader: ReadableStreamDefaultReader<T> | undefined;\n\n try {\n // Check if aborted before making request\n if (options.signal?.aborted) return;\n\n // Make request - initial if no lastEventId, reconnect otherwise\n const { response, stream } = await makeRequest(\n lastEventId ? { lastEventId, reconnectPath } : undefined\n );\n\n // Check for Location header (server-provided reconnection path)\n const locationHeader = response.headers.get(\"location\");\n if (locationHeader) {\n reconnectPath = locationHeader;\n }\n\n // Verify content type\n const contentType = response.headers.get(\"content-type\")?.split(\";\")[0];\n if (contentType && !contentType.includes(\"text/event-stream\")) {\n throw new Error(\n `Expected response header Content-Type to contain 'text/event-stream', got '${contentType}'`\n );\n }\n\n reader = stream.getReader();\n\n try {\n while (true) {\n // Check abort signal before each read\n if (options.signal?.aborted) {\n await reader.cancel();\n return;\n }\n\n const { done, value } = await reader.read();\n\n if (done) {\n // Stream completed successfully\n break;\n }\n\n // Track last event ID for reconnection\n if (value.id) {\n lastEventId = value.id;\n }\n\n yield value;\n }\n\n // Stream completed successfully, exit retry loop\n break;\n } catch (error) {\n // Error during streaming - attempt reconnect if we have lastEventId and a location header\n if (lastEventId && reconnectPath && !options.signal?.aborted) {\n shouldRetry = true;\n } else {\n throw error;\n }\n } finally {\n if (reader) {\n try {\n reader.releaseLock();\n } catch {\n // Ignore errors when releasing lock\n }\n }\n }\n } catch (error) {\n lastError = error;\n\n // Only retry if we have reconnection capability and it's a network error\n if (\n isNetworkError(error) &&\n lastEventId &&\n reconnectPath &&\n !options.signal?.aborted\n ) {\n shouldRetry = true;\n } else {\n throw error;\n }\n }\n\n if (shouldRetry) {\n attempt += 1;\n if (attempt > maxRetries) {\n throw new MaxReconnectAttemptsError(maxRetries, lastError);\n }\n\n // Notify about reconnection attempt\n options.onReconnect?.({ attempt, lastEventId, cause: lastError });\n\n // Exponential backoff with jitter: min(1000 * 2^attempt, 5000) + random jitter\n const baseDelay = Math.min(1000 * 2 ** (attempt - 1), 5000);\n const jitter = Math.random() * 1000;\n const delay = baseDelay + jitter;\n\n await new Promise((resolve) => {\n setTimeout(resolve, delay);\n });\n\n continue;\n }\n\n // Successfully completed\n break;\n }\n}\n\n/*\n * Support async iterator syntax for ReadableStreams in all environments.\n * Source: https://github.com/MattiasBuelens/web-streams-polyfill/pull/122#issuecomment-1627354490\n */\nexport class IterableReadableStream<T>\n extends ReadableStream<T>\n implements IterableReadableStreamInterface<T>\n{\n public reader: ReadableStreamDefaultReader<T>;\n\n ensureReader() {\n if (!this.reader) {\n this.reader = this.getReader();\n }\n }\n\n async next(): Promise<IteratorResult<T>> {\n this.ensureReader();\n try {\n const result = await this.reader.read();\n if (result.done) {\n this.reader.releaseLock(); // release lock when stream becomes closed\n return {\n done: true,\n value: undefined,\n };\n } else {\n return {\n done: false,\n value: result.value,\n };\n }\n } catch (e) {\n this.reader.releaseLock(); // release lock when stream becomes errored\n throw e;\n }\n }\n\n async return(): Promise<IteratorResult<T>> {\n this.ensureReader();\n // If wrapped in a Node stream, cancel is already called.\n if (this.locked) {\n const cancelPromise = this.reader.cancel(); // cancel first, but don't await yet\n this.reader.releaseLock(); // release lock first\n await cancelPromise; // now await it\n }\n return { done: true, value: undefined };\n }\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n async throw(e: any): Promise<IteratorResult<T>> {\n this.ensureReader();\n if (this.locked) {\n const cancelPromise = this.reader.cancel(); // cancel first, but don't await yet\n this.reader.releaseLock(); // release lock first\n await cancelPromise; // now await it\n }\n throw e;\n }\n\n // eslint-disable-next-line @typescript-eslint/ban-ts-comment\n // @ts-ignore Not present in Node 18 types, required in latest Node 22\n async [Symbol.asyncDispose]() {\n await this.return();\n }\n\n [Symbol.asyncIterator]() {\n return this;\n }\n\n static fromReadableStream<T>(stream: ReadableStream<T>) {\n // From https://developer.mozilla.org/en-US/docs/Web/API/Streams_API/Using_readable_streams#reading_the_stream\n const reader = stream.getReader();\n return new IterableReadableStream<T>({\n start(controller) {\n return pump();\n function pump(): Promise<T | undefined> {\n return reader.read().then(({ done, value }) => {\n // When no more data needs to be consumed, close the stream\n if (done) {\n controller.close();\n return;\n }\n // Enqueue the next data chunk into our target stream\n controller.enqueue(value);\n return pump();\n });\n }\n },\n cancel() {\n reader.releaseLock();\n },\n });\n }\n\n static fromAsyncGenerator<T>(generator: AsyncGenerator<T>) {\n return new IterableReadableStream<T>({\n async pull(controller) {\n const { value, done } = await generator.next();\n // When no more data needs to be consumed, close the stream\n if (done) {\n controller.close();\n }\n // Fix: `else if (value)` will hang the streaming when nullish value (e.g. empty string) is pulled\n controller.enqueue(value);\n },\n async cancel(reason) {\n await generator.return(reason);\n },\n });\n }\n}\n"],"mappings":"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"}
|