@langchain/langgraph-sdk 1.9.19 → 1.9.21
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/index.cjs +2 -1
- package/dist/client/index.cjs.map +1 -1
- package/dist/client/index.d.cts +1 -0
- package/dist/client/index.d.cts.map +1 -1
- package/dist/client/index.d.ts +1 -0
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +2 -1
- package/dist/client/index.js.map +1 -1
- package/dist/client/stream/error.cjs +21 -0
- package/dist/client/stream/error.cjs.map +1 -1
- package/dist/client/stream/error.js +21 -1
- package/dist/client/stream/error.js.map +1 -1
- package/dist/client/stream/index.cjs +24 -1
- package/dist/client/stream/index.cjs.map +1 -1
- package/dist/client/stream/index.d.cts.map +1 -1
- package/dist/client/stream/index.d.ts.map +1 -1
- package/dist/client/stream/index.js +24 -1
- package/dist/client/stream/index.js.map +1 -1
- package/dist/client/stream/resolve-client-api-url.cjs +20 -0
- package/dist/client/stream/resolve-client-api-url.cjs.map +1 -0
- package/dist/client/stream/resolve-client-api-url.d.cts +17 -0
- package/dist/client/stream/resolve-client-api-url.d.cts.map +1 -0
- package/dist/client/stream/resolve-client-api-url.d.ts +17 -0
- package/dist/client/stream/resolve-client-api-url.d.ts.map +1 -0
- package/dist/client/stream/resolve-client-api-url.js +20 -0
- package/dist/client/stream/resolve-client-api-url.js.map +1 -0
- package/dist/client/stream/transport/agent-server.cjs +13 -1
- package/dist/client/stream/transport/agent-server.cjs.map +1 -1
- package/dist/client/stream/transport/agent-server.d.cts +13 -0
- package/dist/client/stream/transport/agent-server.d.cts.map +1 -1
- package/dist/client/stream/transport/agent-server.d.ts +13 -0
- package/dist/client/stream/transport/agent-server.d.ts.map +1 -1
- package/dist/client/stream/transport/agent-server.js +13 -1
- package/dist/client/stream/transport/agent-server.js.map +1 -1
- package/dist/client/stream/transport/http.cjs +74 -10
- package/dist/client/stream/transport/http.cjs.map +1 -1
- package/dist/client/stream/transport/http.d.cts +25 -1
- package/dist/client/stream/transport/http.d.cts.map +1 -1
- package/dist/client/stream/transport/http.d.ts +25 -1
- package/dist/client/stream/transport/http.d.ts.map +1 -1
- package/dist/client/stream/transport/http.js +74 -10
- package/dist/client/stream/transport/http.js.map +1 -1
- package/dist/client/stream/transport/index.cjs +2 -1
- package/dist/client/stream/transport/index.js +2 -1
- package/dist/client/stream/transport/types.d.cts +46 -0
- package/dist/client/stream/transport/types.d.cts.map +1 -1
- package/dist/client/stream/transport/types.d.ts +46 -0
- package/dist/client/stream/transport/types.d.ts.map +1 -1
- package/dist/client/stream/transport/websocket.cjs +105 -16
- package/dist/client/stream/transport/websocket.cjs.map +1 -1
- package/dist/client/stream/transport/websocket.d.cts +19 -0
- package/dist/client/stream/transport/websocket.d.cts.map +1 -1
- package/dist/client/stream/transport/websocket.d.ts +19 -0
- package/dist/client/stream/transport/websocket.d.ts.map +1 -1
- package/dist/client/stream/transport/websocket.js +105 -17
- package/dist/client/stream/transport/websocket.js.map +1 -1
- package/dist/client/stream/transport.d.cts +10 -4
- package/dist/client/stream/transport.d.cts.map +1 -1
- package/dist/client/stream/transport.d.ts +10 -4
- package/dist/client/stream/transport.d.ts.map +1 -1
- package/dist/client/stream/types.d.cts +17 -0
- package/dist/client/stream/types.d.cts.map +1 -1
- package/dist/client/stream/types.d.ts +17 -0
- package/dist/client/stream/types.d.ts.map +1 -1
- package/dist/client/threads/index.cjs +30 -14
- package/dist/client/threads/index.cjs.map +1 -1
- package/dist/client/threads/index.js +30 -14
- package/dist/client/threads/index.js.map +1 -1
- package/dist/client.cjs +3 -1
- package/dist/client.d.cts +2 -1
- package/dist/client.d.ts +2 -1
- package/dist/client.js +3 -2
- package/dist/index.cjs +1 -1
- package/dist/index.js +1 -1
- package/dist/stream/controller.cjs +31 -2
- package/dist/stream/controller.cjs.map +1 -1
- package/dist/stream/controller.d.cts.map +1 -1
- package/dist/stream/controller.d.ts.map +1 -1
- package/dist/stream/controller.js +31 -2
- package/dist/stream/controller.js.map +1 -1
- package/dist/stream/index.cjs +2 -0
- package/dist/stream/index.d.cts +2 -1
- package/dist/stream/index.d.ts +2 -1
- package/dist/stream/index.js +2 -1
- package/dist/stream/projections/channel-effect.cjs +52 -0
- package/dist/stream/projections/channel-effect.cjs.map +1 -0
- package/dist/stream/projections/channel-effect.d.cts +35 -0
- package/dist/stream/projections/channel-effect.d.cts.map +1 -0
- package/dist/stream/projections/channel-effect.d.ts +35 -0
- package/dist/stream/projections/channel-effect.d.ts.map +1 -0
- package/dist/stream/projections/channel-effect.js +52 -0
- package/dist/stream/projections/channel-effect.js.map +1 -0
- package/dist/stream/projections/index.cjs +1 -0
- package/dist/stream/projections/index.d.ts +1 -0
- package/dist/stream/projections/index.js +1 -0
- package/dist/stream/root-message-projection.cjs +55 -0
- package/dist/stream/root-message-projection.cjs.map +1 -1
- package/dist/stream/root-message-projection.js +55 -0
- package/dist/stream/root-message-projection.js.map +1 -1
- package/dist/ui/branching.d.cts +1 -1
- package/dist/ui/branching.d.ts +1 -1
- package/dist/ui/orchestrator.d.cts +1 -1
- package/dist/ui/orchestrator.d.cts.map +1 -1
- package/dist/ui/orchestrator.d.ts +1 -1
- package/dist/ui/orchestrator.d.ts.map +1 -1
- package/dist/utils/stream.d.cts +1 -1
- package/dist/utils/stream.d.cts.map +1 -1
- package/dist/utils/stream.d.ts +1 -1
- package/dist/utils/stream.d.ts.map +1 -1
- package/package.json +5 -2
|
@@ -2,6 +2,7 @@ import { BytesLineDecoder, SSEDecoder } from "../../../utils/sse.js";
|
|
|
2
2
|
import { IterableReadableStream } from "../../../utils/stream.js";
|
|
3
3
|
import { AsyncQueue } from "./queue.js";
|
|
4
4
|
import { isProtocolResponse, isRecord, mergeHeaders, toAbsoluteUrl, toError } from "./utils.js";
|
|
5
|
+
import { webSocketReconnectDelayMs } from "./websocket.js";
|
|
5
6
|
//#region src/client/stream/transport/http.ts
|
|
6
7
|
/**
|
|
7
8
|
* Transport adapter that speaks the thread-centric protocol over HTTP
|
|
@@ -11,14 +12,19 @@ import { isProtocolResponse, isRecord, mergeHeaders, toAbsoluteUrl, toError } fr
|
|
|
11
12
|
*/
|
|
12
13
|
var ProtocolSseTransportAdapter = class {
|
|
13
14
|
threadId;
|
|
15
|
+
apiUrl;
|
|
14
16
|
queue = new AsyncQueue();
|
|
15
17
|
fetchImpl;
|
|
16
|
-
apiUrl;
|
|
17
18
|
defaultHeaders;
|
|
18
19
|
onRequest;
|
|
19
20
|
fetchFactory;
|
|
21
|
+
asyncCaller;
|
|
22
|
+
maxReconnectAttempts;
|
|
23
|
+
onReconnect;
|
|
24
|
+
reconnectDelayMs;
|
|
20
25
|
commandsUrl;
|
|
21
26
|
streamUrl;
|
|
27
|
+
stateUrl;
|
|
22
28
|
sessionAbortController = new AbortController();
|
|
23
29
|
eventStreams = /* @__PURE__ */ new Set();
|
|
24
30
|
closed = false;
|
|
@@ -28,9 +34,37 @@ var ProtocolSseTransportAdapter = class {
|
|
|
28
34
|
this.defaultHeaders = options.defaultHeaders ?? {};
|
|
29
35
|
this.onRequest = options.onRequest;
|
|
30
36
|
this.fetchFactory = options.fetchFactory;
|
|
37
|
+
this.asyncCaller = options.asyncCaller;
|
|
38
|
+
this.maxReconnectAttempts = options.fetch != null ? 0 : options.maxReconnectAttempts ?? 5;
|
|
39
|
+
this.onReconnect = options.onReconnect;
|
|
40
|
+
this.reconnectDelayMs = options.reconnectDelayMs ?? webSocketReconnectDelayMs;
|
|
31
41
|
this.threadId = options.threadId;
|
|
32
42
|
this.commandsUrl = options.paths?.commands ?? `/threads/${this.threadId}/commands`;
|
|
33
43
|
this.streamUrl = options.paths?.stream ?? `/threads/${this.threadId}/stream/events`;
|
|
44
|
+
this.stateUrl = options.paths?.state ?? `/threads/${this.threadId}/state`;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Fetch checkpointed thread state for hydration.
|
|
48
|
+
*
|
|
49
|
+
* Uses `GET`, matching `client.threads.getState()` and both LangGraph
|
|
50
|
+
* Platform and Agent Protocol custom backends (`POST` is reserved for
|
|
51
|
+
* `updateState`).
|
|
52
|
+
*/
|
|
53
|
+
async getState() {
|
|
54
|
+
const url = toAbsoluteUrl(this.apiUrl, this.stateUrl);
|
|
55
|
+
let requestInit = {
|
|
56
|
+
method: "GET",
|
|
57
|
+
headers: mergeHeaders(this.defaultHeaders, {})
|
|
58
|
+
};
|
|
59
|
+
if (this.onRequest) requestInit = await this.onRequest(url, requestInit);
|
|
60
|
+
const response = await (await this.resolveFetch())(url.toString(), requestInit);
|
|
61
|
+
if (response.status === 404) return null;
|
|
62
|
+
if (!response.ok) {
|
|
63
|
+
const error = toError(/* @__PURE__ */ new Error(`Thread state request failed: ${response.status} ${response.statusText}`));
|
|
64
|
+
error.status = response.status;
|
|
65
|
+
throw error;
|
|
66
|
+
}
|
|
67
|
+
return await response.json();
|
|
34
68
|
}
|
|
35
69
|
async resolveFetch() {
|
|
36
70
|
if (this.fetchFactory) return await this.fetchFactory();
|
|
@@ -83,9 +117,11 @@ var ProtocolSseTransportAdapter = class {
|
|
|
83
117
|
resolveReady = resolve;
|
|
84
118
|
rejectReady = reject;
|
|
85
119
|
});
|
|
86
|
-
|
|
120
|
+
let resumeAfterSeq = typeof params.since === "number" ? params.since : void 0;
|
|
121
|
+
let readySettled = false;
|
|
87
122
|
const startStream = async () => {
|
|
88
|
-
|
|
123
|
+
let attempt = 0;
|
|
124
|
+
while (!ac.signal.aborted && !this.closed) try {
|
|
89
125
|
const response = await this.request(streamUrl, {
|
|
90
126
|
method: "POST",
|
|
91
127
|
headers: {
|
|
@@ -96,11 +132,14 @@ var ProtocolSseTransportAdapter = class {
|
|
|
96
132
|
channels: params.channels,
|
|
97
133
|
...params.namespaces ? { namespaces: params.namespaces } : {},
|
|
98
134
|
...params.depth != null ? { depth: params.depth } : {},
|
|
99
|
-
...
|
|
135
|
+
...resumeAfterSeq != null ? { since: resumeAfterSeq } : {}
|
|
100
136
|
}),
|
|
101
137
|
signal: ac.signal
|
|
102
|
-
});
|
|
103
|
-
|
|
138
|
+
}, { stream: true });
|
|
139
|
+
if (!readySettled) {
|
|
140
|
+
readySettled = true;
|
|
141
|
+
resolveReady();
|
|
142
|
+
}
|
|
104
143
|
const stream = (response.body ?? new ReadableStream({ start(controller) {
|
|
105
144
|
controller.close();
|
|
106
145
|
} })).pipeThrough(BytesLineDecoder()).pipeThrough(SSEDecoder());
|
|
@@ -109,17 +148,37 @@ var ProtocolSseTransportAdapter = class {
|
|
|
109
148
|
if (ac.signal.aborted || this.closed) break;
|
|
110
149
|
if (isRecord(event.data)) {
|
|
111
150
|
const msg = event.data;
|
|
151
|
+
if (typeof msg.seq === "number") resumeAfterSeq = msg.seq;
|
|
112
152
|
streamQueue.push(msg);
|
|
113
153
|
}
|
|
114
154
|
}
|
|
115
155
|
streamQueue.close();
|
|
156
|
+
return;
|
|
116
157
|
} catch (error) {
|
|
117
|
-
rejectReady(error);
|
|
118
158
|
if (ac.signal.aborted || this.closed) {
|
|
159
|
+
if (!readySettled) rejectReady(error);
|
|
119
160
|
streamQueue.close();
|
|
120
161
|
return;
|
|
121
162
|
}
|
|
122
|
-
|
|
163
|
+
if (this.maxReconnectAttempts <= 0) {
|
|
164
|
+
if (!readySettled) rejectReady(error);
|
|
165
|
+
streamQueue.close(toError(error));
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
attempt += 1;
|
|
169
|
+
if (attempt > this.maxReconnectAttempts) {
|
|
170
|
+
if (!readySettled) rejectReady(error);
|
|
171
|
+
streamQueue.close(toError(error));
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
this.onReconnect?.({
|
|
175
|
+
attempt,
|
|
176
|
+
cause: error
|
|
177
|
+
});
|
|
178
|
+
const delay = this.reconnectDelayMs(attempt);
|
|
179
|
+
if (delay > 0) await new Promise((resolve) => {
|
|
180
|
+
setTimeout(resolve, delay);
|
|
181
|
+
});
|
|
123
182
|
}
|
|
124
183
|
};
|
|
125
184
|
startStream();
|
|
@@ -151,16 +210,18 @@ var ProtocolSseTransportAdapter = class {
|
|
|
151
210
|
this.eventStreams.clear();
|
|
152
211
|
this.queue.close();
|
|
153
212
|
}
|
|
154
|
-
async request(path, init) {
|
|
213
|
+
async request(path, init, options) {
|
|
155
214
|
const url = toAbsoluteUrl(this.apiUrl, path);
|
|
156
215
|
let requestInit = {
|
|
157
216
|
...init,
|
|
158
217
|
headers: mergeHeaders(this.defaultHeaders, init.headers)
|
|
159
218
|
};
|
|
160
219
|
if (this.onRequest) requestInit = await this.onRequest(url, requestInit);
|
|
161
|
-
|
|
220
|
+
const useAsyncCaller = this.asyncCaller != null && !options?.stream;
|
|
221
|
+
const execute = async () => {
|
|
162
222
|
const response = await (await this.resolveFetch())(url.toString(), requestInit);
|
|
163
223
|
if (!response.ok) {
|
|
224
|
+
if (useAsyncCaller) throw response;
|
|
164
225
|
let detail = "";
|
|
165
226
|
try {
|
|
166
227
|
const body = await response.text();
|
|
@@ -172,6 +233,9 @@ var ProtocolSseTransportAdapter = class {
|
|
|
172
233
|
throw new Error(message);
|
|
173
234
|
}
|
|
174
235
|
return response;
|
|
236
|
+
};
|
|
237
|
+
try {
|
|
238
|
+
return useAsyncCaller ? await this.asyncCaller.call(execute) : await execute();
|
|
175
239
|
} catch (error) {
|
|
176
240
|
throw toError(error);
|
|
177
241
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"http.js","names":[],"sources":["../../../../src/client/stream/transport/http.ts"],"sourcesContent":["import { AsyncQueue } from \"./queue.js\";\nimport type {\n Message,\n SubscribeParams,\n Command,\n CommandResponse,\n ErrorResponse,\n} from \"@langchain/protocol\";\n\nimport type {\n HeaderValue,\n ProtocolRequestHook,\n ProtocolSseTransportOptions,\n} from \"./types.js\";\nimport type { TransportAdapter, EventStreamHandle } from \"../transport.js\";\nimport {\n toAbsoluteUrl,\n isRecord,\n mergeHeaders,\n toError,\n isProtocolResponse,\n} from \"./utils.js\";\nimport { BytesLineDecoder, SSEDecoder } from \"../../../utils/sse.js\";\nimport { IterableReadableStream } from \"../../../utils/stream.js\";\n\n/**\n * Transport adapter that speaks the thread-centric protocol over HTTP\n * commands plus SSE event streams. Bound to a specific `threadId`\n * at construction. Each {@link openEventStream} call opens an independent\n * filtered SSE connection via `POST /threads/:thread_id/stream/events`.\n */\nexport class ProtocolSseTransportAdapter implements TransportAdapter {\n readonly threadId: string;\n\n private readonly queue = new AsyncQueue<Message>();\n\n private readonly fetchImpl: typeof fetch;\n\n private readonly apiUrl: string;\n\n private readonly defaultHeaders: Record<string, HeaderValue>;\n\n private readonly onRequest?: ProtocolRequestHook;\n\n private readonly fetchFactory?: () => typeof fetch | Promise<typeof fetch>;\n\n private readonly commandsUrl: string;\n\n private readonly streamUrl: string;\n\n private readonly sessionAbortController = new AbortController();\n\n private readonly eventStreams = new Set<AbortController>();\n\n private closed = false;\n\n constructor(options: ProtocolSseTransportOptions) {\n this.fetchImpl = options.fetch ?? fetch;\n this.apiUrl = options.apiUrl;\n this.defaultHeaders = options.defaultHeaders ?? {};\n this.onRequest = options.onRequest;\n this.fetchFactory = options.fetchFactory;\n this.threadId = options.threadId;\n this.commandsUrl =\n options.paths?.commands ?? `/threads/${this.threadId}/commands`;\n this.streamUrl =\n options.paths?.stream ?? `/threads/${this.threadId}/stream/events`;\n }\n\n private async resolveFetch(): Promise<typeof fetch> {\n if (this.fetchFactory) {\n return await this.fetchFactory();\n }\n return this.fetchImpl;\n }\n\n /**\n * HTTP/SSE transports have no handshake — connections are made\n * per-command and per-subscription.\n */\n async open(): Promise<void> {\n // no-op\n }\n\n async send(\n command: Command\n ): Promise<CommandResponse | ErrorResponse | void> {\n const response = await this.request(this.commandsUrl, {\n method: \"POST\",\n headers: { \"content-type\": \"application/json\" },\n body: JSON.stringify(command),\n signal: this.sessionAbortController.signal,\n });\n\n if (response.status === 202 || response.status === 204) {\n return undefined;\n }\n\n const payload = (await response.json()) as unknown;\n if (!isProtocolResponse(payload)) {\n throw new Error(\"Protocol command did not return a valid response.\");\n }\n return payload;\n }\n\n /**\n * WebSocket-style single event stream.\n * For the SSE transport this returns a dummy iterable; real event\n * delivery happens via {@link openEventStream}.\n */\n events(): AsyncIterable<Message> {\n const queue = this.queue;\n return {\n [Symbol.asyncIterator]: () => ({\n next: async () => await queue.shift(),\n return: async () => {\n queue.close();\n return { done: true, value: undefined };\n },\n }),\n };\n }\n\n openEventStream(params: SubscribeParams): EventStreamHandle {\n if (this.closed) {\n throw new Error(\"Protocol transport is closed.\");\n }\n\n const ac = new AbortController();\n this.eventStreams.add(ac);\n const streamQueue = new AsyncQueue<Message>();\n const streamUrl = this.streamUrl;\n\n let resolveReady!: () => void;\n let rejectReady!: (err: unknown) => void;\n const ready = new Promise<void>((resolve, reject) => {\n resolveReady = resolve;\n rejectReady = reject;\n });\n\n const since = (params as SubscribeParams & { since?: unknown }).since;\n\n const startStream = async () => {\n try {\n const response = await this.request(streamUrl, {\n method: \"POST\",\n headers: {\n \"content-type\": \"application/json\",\n accept: \"text/event-stream\",\n },\n body: JSON.stringify({\n channels: params.channels,\n ...(params.namespaces ? { namespaces: params.namespaces } : {}),\n ...(params.depth != null ? { depth: params.depth } : {}),\n ...(typeof since === \"number\" ? { since } : {}),\n }),\n signal: ac.signal,\n });\n\n resolveReady();\n\n const readable =\n response.body ??\n new ReadableStream<Uint8Array>({\n start(controller) {\n controller.close();\n },\n });\n\n const stream = readable\n .pipeThrough(BytesLineDecoder())\n .pipeThrough(SSEDecoder());\n const iterable = IterableReadableStream.fromReadableStream(stream);\n\n for await (const event of iterable) {\n if (ac.signal.aborted || this.closed) {\n break;\n }\n if (isRecord(event.data)) {\n const msg = event.data as Message & {\n seq?: number;\n method?: string;\n };\n streamQueue.push(msg);\n }\n }\n streamQueue.close();\n } catch (error) {\n rejectReady(error);\n if (ac.signal.aborted || this.closed) {\n streamQueue.close();\n return;\n }\n streamQueue.close(error);\n }\n };\n\n void startStream();\n\n const cleanup = () => {\n this.eventStreams.delete(ac);\n ac.abort();\n streamQueue.close();\n };\n\n return {\n events: {\n [Symbol.asyncIterator]: () => ({\n next: async () => await streamQueue.shift(),\n return: async () => {\n cleanup();\n return { done: true, value: undefined };\n },\n }),\n },\n ready,\n close: cleanup,\n };\n }\n\n async close(): Promise<void> {\n if (this.closed) {\n return;\n }\n this.closed = true;\n this.sessionAbortController.abort();\n for (const ac of this.eventStreams) ac.abort();\n this.eventStreams.clear();\n this.queue.close();\n }\n\n private async request(path: string, init: RequestInit): Promise<Response> {\n const url = toAbsoluteUrl(this.apiUrl, path);\n let requestInit: RequestInit = {\n ...init,\n headers: mergeHeaders(this.defaultHeaders, init.headers),\n };\n\n if (this.onRequest) {\n requestInit = await this.onRequest(url, requestInit);\n }\n\n try {\n const fetchImpl = await this.resolveFetch();\n const response = await fetchImpl(url.toString(), requestInit);\n if (!response.ok) {\n let detail = \"\";\n try {\n const body = await response.text();\n const parsed = JSON.parse(body);\n if (typeof parsed === \"object\" && parsed != null) {\n detail =\n ((parsed as Record<string, unknown>).message as string) ??\n ((parsed as Record<string, unknown>).error as string) ??\n \"\";\n }\n if (!detail) detail = body;\n } catch {\n // body unreadable or not JSON — fall through\n }\n const message = detail\n ? `Protocol request failed: ${response.status} ${response.statusText} — ${detail}`\n : `Protocol request failed: ${response.status} ${response.statusText}`;\n throw new Error(message);\n }\n return response;\n } catch (error) {\n throw toError(error);\n }\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"http.js","names":[],"sources":["../../../../src/client/stream/transport/http.ts"],"sourcesContent":["import { AsyncQueue } from \"./queue.js\";\nimport type {\n Message,\n SubscribeParams,\n Command,\n CommandResponse,\n ErrorResponse,\n} from \"@langchain/protocol\";\n\nimport type { AsyncCaller } from \"../../../utils/async_caller.js\";\nimport type {\n HeaderValue,\n ProtocolRequestHook,\n ProtocolSseTransportOptions,\n} from \"./types.js\";\nimport type { TransportAdapter, EventStreamHandle } from \"../transport.js\";\nimport {\n toAbsoluteUrl,\n isRecord,\n mergeHeaders,\n toError,\n isProtocolResponse,\n} from \"./utils.js\";\nimport { BytesLineDecoder, SSEDecoder } from \"../../../utils/sse.js\";\nimport { IterableReadableStream } from \"../../../utils/stream.js\";\nimport { webSocketReconnectDelayMs } from \"./websocket.js\";\n\n/**\n * Transport adapter that speaks the thread-centric protocol over HTTP\n * commands plus SSE event streams. Bound to a specific `threadId`\n * at construction. Each {@link openEventStream} call opens an independent\n * filtered SSE connection via `POST /threads/:thread_id/stream/events`.\n */\nexport class ProtocolSseTransportAdapter implements TransportAdapter {\n readonly threadId: string;\n\n readonly apiUrl: string;\n\n private readonly queue = new AsyncQueue<Message>();\n\n private readonly fetchImpl: typeof fetch;\n\n private readonly defaultHeaders: Record<string, HeaderValue>;\n\n private readonly onRequest?: ProtocolRequestHook;\n\n private readonly fetchFactory?: () => typeof fetch | Promise<typeof fetch>;\n\n private readonly asyncCaller?: AsyncCaller;\n\n private readonly maxReconnectAttempts: number;\n\n private readonly onReconnect?: ProtocolSseTransportOptions[\"onReconnect\"];\n\n private readonly reconnectDelayMs: (attempt: number) => number;\n\n private readonly commandsUrl: string;\n\n private readonly streamUrl: string;\n\n private readonly stateUrl: string;\n\n private readonly sessionAbortController = new AbortController();\n\n private readonly eventStreams = new Set<AbortController>();\n\n private closed = false;\n\n constructor(options: ProtocolSseTransportOptions) {\n this.fetchImpl = options.fetch ?? fetch;\n this.apiUrl = options.apiUrl;\n this.defaultHeaders = options.defaultHeaders ?? {};\n this.onRequest = options.onRequest;\n this.fetchFactory = options.fetchFactory;\n this.asyncCaller = options.asyncCaller;\n // Custom fetch (tests/mocks) must not auto-reconnect — same policy as skipping AsyncCaller.\n this.maxReconnectAttempts =\n options.fetch != null ? 0 : (options.maxReconnectAttempts ?? 5);\n this.onReconnect = options.onReconnect;\n this.reconnectDelayMs =\n options.reconnectDelayMs ?? webSocketReconnectDelayMs;\n this.threadId = options.threadId;\n this.commandsUrl =\n options.paths?.commands ?? `/threads/${this.threadId}/commands`;\n this.streamUrl =\n options.paths?.stream ?? `/threads/${this.threadId}/stream/events`;\n this.stateUrl = options.paths?.state ?? `/threads/${this.threadId}/state`;\n }\n\n /**\n * Fetch checkpointed thread state for hydration.\n *\n * Uses `GET`, matching `client.threads.getState()` and both LangGraph\n * Platform and Agent Protocol custom backends (`POST` is reserved for\n * `updateState`).\n */\n async getState<StateType = unknown>(): Promise<{\n values: StateType;\n next?: unknown;\n tasks?: unknown;\n metadata?: unknown;\n checkpoint?: { checkpoint_id?: string } | null;\n parent_checkpoint?: { checkpoint_id?: string } | null;\n } | null> {\n const url = toAbsoluteUrl(this.apiUrl, this.stateUrl);\n let requestInit: RequestInit = {\n method: \"GET\",\n headers: mergeHeaders(this.defaultHeaders, {}),\n };\n\n if (this.onRequest) {\n requestInit = await this.onRequest(url, requestInit);\n }\n\n const fetchImpl = await this.resolveFetch();\n const response = await fetchImpl(url.toString(), requestInit);\n if (response.status === 404) return null;\n if (!response.ok) {\n const error = toError(\n new Error(\n `Thread state request failed: ${response.status} ${response.statusText}`\n )\n ) as Error & { status?: number };\n error.status = response.status;\n throw error;\n }\n\n return (await response.json()) as {\n values: StateType;\n next?: unknown;\n tasks?: unknown;\n metadata?: unknown;\n checkpoint?: { checkpoint_id?: string } | null;\n parent_checkpoint?: { checkpoint_id?: string } | null;\n };\n }\n\n private async resolveFetch(): Promise<typeof fetch> {\n if (this.fetchFactory) {\n return await this.fetchFactory();\n }\n return this.fetchImpl;\n }\n\n /**\n * HTTP/SSE transports have no handshake — connections are made\n * per-command and per-subscription.\n */\n async open(): Promise<void> {\n // no-op\n }\n\n async send(\n command: Command\n ): Promise<CommandResponse | ErrorResponse | void> {\n const response = await this.request(this.commandsUrl, {\n method: \"POST\",\n headers: { \"content-type\": \"application/json\" },\n body: JSON.stringify(command),\n signal: this.sessionAbortController.signal,\n });\n\n if (response.status === 202 || response.status === 204) {\n return undefined;\n }\n\n const payload = (await response.json()) as unknown;\n if (!isProtocolResponse(payload)) {\n throw new Error(\"Protocol command did not return a valid response.\");\n }\n return payload;\n }\n\n /**\n * WebSocket-style single event stream.\n * For the SSE transport this returns a dummy iterable; real event\n * delivery happens via {@link openEventStream}.\n */\n events(): AsyncIterable<Message> {\n const queue = this.queue;\n return {\n [Symbol.asyncIterator]: () => ({\n next: async () => await queue.shift(),\n return: async () => {\n queue.close();\n return { done: true, value: undefined };\n },\n }),\n };\n }\n\n openEventStream(params: SubscribeParams): EventStreamHandle {\n if (this.closed) {\n throw new Error(\"Protocol transport is closed.\");\n }\n\n const ac = new AbortController();\n this.eventStreams.add(ac);\n const streamQueue = new AsyncQueue<Message>();\n const streamUrl = this.streamUrl;\n\n let resolveReady!: () => void;\n let rejectReady!: (err: unknown) => void;\n const ready = new Promise<void>((resolve, reject) => {\n resolveReady = resolve;\n rejectReady = reject;\n });\n\n let resumeAfterSeq =\n typeof (params as SubscribeParams & { since?: unknown }).since ===\n \"number\"\n ? (params as SubscribeParams & { since: number }).since\n : undefined;\n\n let readySettled = false;\n\n const startStream = async () => {\n let attempt = 0;\n\n while (!ac.signal.aborted && !this.closed) {\n try {\n const response = await this.request(\n streamUrl,\n {\n method: \"POST\",\n headers: {\n \"content-type\": \"application/json\",\n accept: \"text/event-stream\",\n },\n body: JSON.stringify({\n channels: params.channels,\n ...(params.namespaces ? { namespaces: params.namespaces } : {}),\n ...(params.depth != null ? { depth: params.depth } : {}),\n ...(resumeAfterSeq != null ? { since: resumeAfterSeq } : {}),\n }),\n signal: ac.signal,\n },\n { stream: true }\n );\n\n if (!readySettled) {\n readySettled = true;\n resolveReady();\n }\n\n const readable =\n response.body ??\n new ReadableStream<Uint8Array>({\n start(controller) {\n controller.close();\n },\n });\n\n const stream = readable\n .pipeThrough(BytesLineDecoder())\n .pipeThrough(SSEDecoder());\n const iterable = IterableReadableStream.fromReadableStream(stream);\n\n for await (const event of iterable) {\n if (ac.signal.aborted || this.closed) {\n break;\n }\n if (isRecord(event.data)) {\n const msg = event.data as Message & {\n seq?: number;\n method?: string;\n };\n if (typeof msg.seq === \"number\") {\n resumeAfterSeq = msg.seq;\n }\n streamQueue.push(msg);\n }\n }\n streamQueue.close();\n return;\n } catch (error) {\n if (ac.signal.aborted || this.closed) {\n if (!readySettled) {\n rejectReady(error);\n }\n streamQueue.close();\n return;\n }\n if (this.maxReconnectAttempts <= 0) {\n if (!readySettled) {\n rejectReady(error);\n }\n streamQueue.close(toError(error));\n return;\n }\n attempt += 1;\n if (attempt > this.maxReconnectAttempts) {\n if (!readySettled) {\n rejectReady(error);\n }\n streamQueue.close(toError(error));\n return;\n }\n this.onReconnect?.({ attempt, cause: error });\n const delay = this.reconnectDelayMs(attempt);\n if (delay > 0) {\n await new Promise<void>((resolve) => {\n setTimeout(resolve, delay);\n });\n }\n }\n }\n };\n\n void startStream();\n\n const cleanup = () => {\n this.eventStreams.delete(ac);\n ac.abort();\n streamQueue.close();\n };\n\n return {\n events: {\n [Symbol.asyncIterator]: () => ({\n next: async () => await streamQueue.shift(),\n return: async () => {\n cleanup();\n return { done: true, value: undefined };\n },\n }),\n },\n ready,\n close: cleanup,\n };\n }\n\n async close(): Promise<void> {\n if (this.closed) {\n return;\n }\n this.closed = true;\n this.sessionAbortController.abort();\n for (const ac of this.eventStreams) ac.abort();\n this.eventStreams.clear();\n this.queue.close();\n }\n\n private async request(\n path: string,\n init: RequestInit,\n options?: { stream?: boolean }\n ): Promise<Response> {\n const url = toAbsoluteUrl(this.apiUrl, path);\n let requestInit: RequestInit = {\n ...init,\n headers: mergeHeaders(this.defaultHeaders, init.headers),\n };\n\n if (this.onRequest) {\n requestInit = await this.onRequest(url, requestInit);\n }\n\n // Long-lived SSE event streams must not run through AsyncCaller: its\n // p-queue/p-retry semantics are designed for discrete request/response\n // calls, and wrapping a streaming response stalls the call (and can\n // leak retries). Stream resilience is handled separately by the\n // reconnect loop in `openEventStream`.\n const useAsyncCaller = this.asyncCaller != null && !options?.stream;\n\n const execute = async (): Promise<Response> => {\n const fetchImpl = await this.resolveFetch();\n const response = await fetchImpl(url.toString(), requestInit);\n if (!response.ok) {\n // Reject with the Response so AsyncCaller maps it to HTTPError and\n // applies STATUS_NO_RETRY / retry policy consistently with REST.\n if (useAsyncCaller) {\n throw response;\n }\n let detail = \"\";\n try {\n const body = await response.text();\n const parsed = JSON.parse(body);\n if (typeof parsed === \"object\" && parsed != null) {\n detail =\n ((parsed as Record<string, unknown>).message as string) ??\n ((parsed as Record<string, unknown>).error as string) ??\n \"\";\n }\n if (!detail) detail = body;\n } catch {\n // body unreadable or not JSON — fall through\n }\n const message = detail\n ? `Protocol request failed: ${response.status} ${response.statusText} — ${detail}`\n : `Protocol request failed: ${response.status} ${response.statusText}`;\n throw new Error(message);\n }\n return response;\n };\n\n try {\n return useAsyncCaller\n ? await this.asyncCaller!.call(execute)\n : await execute();\n } catch (error) {\n throw toError(error);\n }\n }\n}\n"],"mappings":"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"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { AsyncCaller } from "../../../utils/async_caller.cjs";
|
|
1
2
|
import { CommandResponse, ErrorResponse } from "@langchain/protocol";
|
|
2
3
|
|
|
3
4
|
//#region src/client/stream/transport/types.d.ts
|
|
@@ -5,6 +6,8 @@ type ProtocolRequestHook = (url: URL, init: RequestInit) => Promise<RequestInit>
|
|
|
5
6
|
interface ProtocolTransportPaths {
|
|
6
7
|
commands?: string;
|
|
7
8
|
stream?: string;
|
|
9
|
+
/** `GET` path for thread-state hydration. Defaults to `/threads/:threadId/state`. */
|
|
10
|
+
state?: string;
|
|
8
11
|
}
|
|
9
12
|
interface ProtocolSseTransportOptions {
|
|
10
13
|
apiUrl: string;
|
|
@@ -13,7 +16,28 @@ interface ProtocolSseTransportOptions {
|
|
|
13
16
|
onRequest?: ProtocolRequestHook;
|
|
14
17
|
fetch?: typeof fetch;
|
|
15
18
|
fetchFactory?: () => typeof fetch | Promise<typeof fetch>;
|
|
19
|
+
/**
|
|
20
|
+
* When set, command and SSE subscription HTTP requests are executed
|
|
21
|
+
* through {@link AsyncCaller} (retries, concurrency). Typically wired
|
|
22
|
+
* from {@link BaseClient} via `client.threads.stream()`.
|
|
23
|
+
*/
|
|
24
|
+
asyncCaller?: AsyncCaller;
|
|
16
25
|
paths?: ProtocolTransportPaths;
|
|
26
|
+
/**
|
|
27
|
+
* Maximum reconnect attempts after an unexpected SSE disconnect.
|
|
28
|
+
* Defaults to 5. Set to 0 to disable automatic reconnection.
|
|
29
|
+
*/
|
|
30
|
+
maxReconnectAttempts?: number;
|
|
31
|
+
/** Called before each SSE reconnect attempt (after backoff delay). */
|
|
32
|
+
onReconnect?: (options: {
|
|
33
|
+
attempt: number;
|
|
34
|
+
cause: unknown;
|
|
35
|
+
}) => void;
|
|
36
|
+
/**
|
|
37
|
+
* Backoff before each SSE reconnect attempt. Defaults to
|
|
38
|
+
* {@link webSocketReconnectDelayMs} from `./websocket.js`.
|
|
39
|
+
*/
|
|
40
|
+
reconnectDelayMs?: (attempt: number) => number;
|
|
17
41
|
}
|
|
18
42
|
interface ProtocolWebSocketTransportOptions {
|
|
19
43
|
apiUrl: string;
|
|
@@ -22,6 +46,28 @@ interface ProtocolWebSocketTransportOptions {
|
|
|
22
46
|
onRequest?: ProtocolRequestHook;
|
|
23
47
|
webSocketFactory?: (url: string) => WebSocket;
|
|
24
48
|
paths?: Pick<ProtocolTransportPaths, "stream">;
|
|
49
|
+
/**
|
|
50
|
+
* Maximum reconnect attempts after an unexpected socket close.
|
|
51
|
+
* Defaults to 5. Set to 0 to disable automatic reconnection.
|
|
52
|
+
*/
|
|
53
|
+
maxReconnectAttempts?: number;
|
|
54
|
+
/**
|
|
55
|
+
* Called before each reconnect attempt (after backoff delay).
|
|
56
|
+
*/
|
|
57
|
+
onReconnect?: (options: {
|
|
58
|
+
attempt: number;
|
|
59
|
+
cause: unknown;
|
|
60
|
+
}) => void;
|
|
61
|
+
/**
|
|
62
|
+
* Invoked after the socket has been re-established. Use to restore
|
|
63
|
+
* server-side subscription state (see `ThreadStream`).
|
|
64
|
+
*/
|
|
65
|
+
onReconnected?: () => void | Promise<void>;
|
|
66
|
+
/**
|
|
67
|
+
* Backoff before each reconnect attempt. Defaults to
|
|
68
|
+
* {@link webSocketReconnectDelayMs}.
|
|
69
|
+
*/
|
|
70
|
+
reconnectDelayMs?: (attempt: number) => number;
|
|
25
71
|
}
|
|
26
72
|
type HeaderValue = string | undefined | null;
|
|
27
73
|
//#endregion
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.cts","names":[],"sources":["../../../../src/client/stream/transport/types.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"types.d.cts","names":[],"sources":["../../../../src/client/stream/transport/types.ts"],"mappings":";;;;KAGY,mBAAA,IACV,GAAA,EAAK,GAAA,EACL,IAAA,EAAM,WAAA,KACH,OAAA,CAAQ,WAAA,IAAe,WAAA;AAAA,UAEX,sBAAA;EACf,QAAA;EACA,MAAA;;EAEA,KAAA;AAAA;AAAA,UAGe,2BAAA;EACf,MAAA;EACA,QAAA;EACA,cAAA,GAAiB,MAAA,SAAe,WAAA;EAChC,SAAA,GAAY,mBAAA;EACZ,KAAA,UAAe,KAAA;EACf,YAAA,gBAA4B,KAAA,GAAQ,OAAA,QAAe,KAAA;EAhB7C;;;;;EAsBN,WAAA,GAAc,WAAA;EACd,KAAA,GAAQ,sBAAA;EApBO;;;;EAyBf,oBAAA;EAvBA;EAyBA,WAAA,IAAe,OAAA;IAAW,OAAA;IAAiB,KAAA;EAAA;EApBD;;;;EAyB1C,gBAAA,IAAoB,OAAA;AAAA;AAAA,UAGL,iCAAA;EACf,MAAA;EACA,QAAA;EACA,cAAA,GAAiB,MAAA,SAAe,WAAA;EAChC,SAAA,GAAY,mBAAA;EACZ,gBAAA,IAAoB,GAAA,aAAgB,SAAA;EACpC,KAAA,GAAQ,IAAA,CAAK,sBAAA;EAjCb;;;;EAsCA,oBAAA;EAnCA;;;EAuCA,WAAA,IAAe,OAAA;IAAW,OAAA;IAAiB,KAAA;EAAA;EArCQ;;;;EA0CnD,aAAA,gBAA6B,OAAA;EA9B7B;;;;EAmCA,gBAAA,IAAoB,OAAA;AAAA;AAAA,KAGV,WAAA"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { AsyncCaller } from "../../../utils/async_caller.js";
|
|
1
2
|
import { CommandResponse, ErrorResponse } from "@langchain/protocol";
|
|
2
3
|
|
|
3
4
|
//#region src/client/stream/transport/types.d.ts
|
|
@@ -5,6 +6,8 @@ type ProtocolRequestHook = (url: URL, init: RequestInit) => Promise<RequestInit>
|
|
|
5
6
|
interface ProtocolTransportPaths {
|
|
6
7
|
commands?: string;
|
|
7
8
|
stream?: string;
|
|
9
|
+
/** `GET` path for thread-state hydration. Defaults to `/threads/:threadId/state`. */
|
|
10
|
+
state?: string;
|
|
8
11
|
}
|
|
9
12
|
interface ProtocolSseTransportOptions {
|
|
10
13
|
apiUrl: string;
|
|
@@ -13,7 +16,28 @@ interface ProtocolSseTransportOptions {
|
|
|
13
16
|
onRequest?: ProtocolRequestHook;
|
|
14
17
|
fetch?: typeof fetch;
|
|
15
18
|
fetchFactory?: () => typeof fetch | Promise<typeof fetch>;
|
|
19
|
+
/**
|
|
20
|
+
* When set, command and SSE subscription HTTP requests are executed
|
|
21
|
+
* through {@link AsyncCaller} (retries, concurrency). Typically wired
|
|
22
|
+
* from {@link BaseClient} via `client.threads.stream()`.
|
|
23
|
+
*/
|
|
24
|
+
asyncCaller?: AsyncCaller;
|
|
16
25
|
paths?: ProtocolTransportPaths;
|
|
26
|
+
/**
|
|
27
|
+
* Maximum reconnect attempts after an unexpected SSE disconnect.
|
|
28
|
+
* Defaults to 5. Set to 0 to disable automatic reconnection.
|
|
29
|
+
*/
|
|
30
|
+
maxReconnectAttempts?: number;
|
|
31
|
+
/** Called before each SSE reconnect attempt (after backoff delay). */
|
|
32
|
+
onReconnect?: (options: {
|
|
33
|
+
attempt: number;
|
|
34
|
+
cause: unknown;
|
|
35
|
+
}) => void;
|
|
36
|
+
/**
|
|
37
|
+
* Backoff before each SSE reconnect attempt. Defaults to
|
|
38
|
+
* {@link webSocketReconnectDelayMs} from `./websocket.js`.
|
|
39
|
+
*/
|
|
40
|
+
reconnectDelayMs?: (attempt: number) => number;
|
|
17
41
|
}
|
|
18
42
|
interface ProtocolWebSocketTransportOptions {
|
|
19
43
|
apiUrl: string;
|
|
@@ -22,6 +46,28 @@ interface ProtocolWebSocketTransportOptions {
|
|
|
22
46
|
onRequest?: ProtocolRequestHook;
|
|
23
47
|
webSocketFactory?: (url: string) => WebSocket;
|
|
24
48
|
paths?: Pick<ProtocolTransportPaths, "stream">;
|
|
49
|
+
/**
|
|
50
|
+
* Maximum reconnect attempts after an unexpected socket close.
|
|
51
|
+
* Defaults to 5. Set to 0 to disable automatic reconnection.
|
|
52
|
+
*/
|
|
53
|
+
maxReconnectAttempts?: number;
|
|
54
|
+
/**
|
|
55
|
+
* Called before each reconnect attempt (after backoff delay).
|
|
56
|
+
*/
|
|
57
|
+
onReconnect?: (options: {
|
|
58
|
+
attempt: number;
|
|
59
|
+
cause: unknown;
|
|
60
|
+
}) => void;
|
|
61
|
+
/**
|
|
62
|
+
* Invoked after the socket has been re-established. Use to restore
|
|
63
|
+
* server-side subscription state (see `ThreadStream`).
|
|
64
|
+
*/
|
|
65
|
+
onReconnected?: () => void | Promise<void>;
|
|
66
|
+
/**
|
|
67
|
+
* Backoff before each reconnect attempt. Defaults to
|
|
68
|
+
* {@link webSocketReconnectDelayMs}.
|
|
69
|
+
*/
|
|
70
|
+
reconnectDelayMs?: (attempt: number) => number;
|
|
25
71
|
}
|
|
26
72
|
type HeaderValue = string | undefined | null;
|
|
27
73
|
//#endregion
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","names":[],"sources":["../../../../src/client/stream/transport/types.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"types.d.ts","names":[],"sources":["../../../../src/client/stream/transport/types.ts"],"mappings":";;;;KAGY,mBAAA,IACV,GAAA,EAAK,GAAA,EACL,IAAA,EAAM,WAAA,KACH,OAAA,CAAQ,WAAA,IAAe,WAAA;AAAA,UAEX,sBAAA;EACf,QAAA;EACA,MAAA;;EAEA,KAAA;AAAA;AAAA,UAGe,2BAAA;EACf,MAAA;EACA,QAAA;EACA,cAAA,GAAiB,MAAA,SAAe,WAAA;EAChC,SAAA,GAAY,mBAAA;EACZ,KAAA,UAAe,KAAA;EACf,YAAA,gBAA4B,KAAA,GAAQ,OAAA,QAAe,KAAA;EAhB7C;;;;;EAsBN,WAAA,GAAc,WAAA;EACd,KAAA,GAAQ,sBAAA;EApBO;;;;EAyBf,oBAAA;EAvBA;EAyBA,WAAA,IAAe,OAAA;IAAW,OAAA;IAAiB,KAAA;EAAA;EApBD;;;;EAyB1C,gBAAA,IAAoB,OAAA;AAAA;AAAA,UAGL,iCAAA;EACf,MAAA;EACA,QAAA;EACA,cAAA,GAAiB,MAAA,SAAe,WAAA;EAChC,SAAA,GAAY,mBAAA;EACZ,gBAAA,IAAoB,GAAA,aAAgB,SAAA;EACpC,KAAA,GAAQ,IAAA,CAAK,sBAAA;EAjCb;;;;EAsCA,oBAAA;EAnCA;;;EAuCA,WAAA,IAAe,OAAA;IAAW,OAAA;IAAiB,KAAA;EAAA;EArCQ;;;;EA0CnD,aAAA,gBAA6B,OAAA;EA9B7B;;;;EAmCA,gBAAA,IAAoB,OAAA;AAAA;AAAA,KAGV,WAAA"}
|
|
@@ -1,10 +1,28 @@
|
|
|
1
|
+
const require_error = require("../error.cjs");
|
|
1
2
|
const require_queue = require("./queue.cjs");
|
|
2
3
|
const require_utils = require("./utils.cjs");
|
|
3
4
|
//#region src/client/stream/transport/websocket.ts
|
|
5
|
+
const WEB_SOCKET_CONNECTING = 0;
|
|
6
|
+
const WEB_SOCKET_OPEN = 1;
|
|
7
|
+
const WEB_SOCKET_CLOSED = 3;
|
|
8
|
+
/**
|
|
9
|
+
* Exponential backoff with jitter for WebSocket reconnect. Mirrors
|
|
10
|
+
* {@link streamWithRetry} in `utils/stream.ts` (capped at 5s + 1s jitter).
|
|
11
|
+
*/
|
|
12
|
+
function webSocketReconnectDelayMs(attempt) {
|
|
13
|
+
return Math.min(1e3 * 2 ** (attempt - 1), 5e3) + Math.random() * 1e3;
|
|
14
|
+
}
|
|
4
15
|
/**
|
|
5
16
|
* Transport adapter that speaks the thread-centric protocol over a
|
|
6
17
|
* bidirectional WebSocket. Bound to a specific `threadId` — the socket
|
|
7
18
|
* connects to `ws://.../threads/:thread_id/stream/events`.
|
|
19
|
+
*
|
|
20
|
+
* On unexpected disconnect the adapter reconnects with exponential
|
|
21
|
+
* backoff (see {@link ProtocolWebSocketTransportOptions.maxReconnectAttempts}).
|
|
22
|
+
* The server replays buffered events on the new socket; the SDK
|
|
23
|
+
* deduplicates by `event_id`. {@link ProtocolWebSocketTransportOptions.onReconnected}
|
|
24
|
+
* runs after each successful reconnect so `ThreadStream` can re-issue
|
|
25
|
+
* `subscription.subscribe` commands.
|
|
8
26
|
*/
|
|
9
27
|
var ProtocolWebSocketTransportAdapter = class {
|
|
10
28
|
threadId;
|
|
@@ -14,10 +32,15 @@ var ProtocolWebSocketTransportAdapter = class {
|
|
|
14
32
|
onRequest;
|
|
15
33
|
webSocketFactory;
|
|
16
34
|
streamUrl;
|
|
35
|
+
maxReconnectAttempts;
|
|
36
|
+
onReconnect;
|
|
37
|
+
reconnectDelayMs;
|
|
38
|
+
onReconnected;
|
|
17
39
|
pending = /* @__PURE__ */ new Map();
|
|
18
40
|
socket = null;
|
|
19
41
|
closed = false;
|
|
20
42
|
intentionalClose = false;
|
|
43
|
+
reconnectInFlight = null;
|
|
21
44
|
constructor(options) {
|
|
22
45
|
this.apiUrl = options.apiUrl;
|
|
23
46
|
this.threadId = options.threadId;
|
|
@@ -25,18 +48,32 @@ var ProtocolWebSocketTransportAdapter = class {
|
|
|
25
48
|
this.onRequest = options.onRequest;
|
|
26
49
|
this.webSocketFactory = options.webSocketFactory ?? ((url) => new WebSocket(url));
|
|
27
50
|
this.streamUrl = options.paths?.stream ?? `/threads/${this.threadId}/stream/events`;
|
|
51
|
+
this.maxReconnectAttempts = options.maxReconnectAttempts ?? 5;
|
|
52
|
+
this.onReconnect = options.onReconnect;
|
|
53
|
+
this.onReconnected = options.onReconnected;
|
|
54
|
+
this.reconnectDelayMs = options.reconnectDelayMs ?? webSocketReconnectDelayMs;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Register a callback invoked after each successful reconnect. Used
|
|
58
|
+
* by {@link ThreadStream} to re-send active `subscription.subscribe`
|
|
59
|
+
* commands.
|
|
60
|
+
*/
|
|
61
|
+
setOnReconnected(handler) {
|
|
62
|
+
this.onReconnected = handler;
|
|
28
63
|
}
|
|
29
64
|
async open() {
|
|
30
|
-
if (this.
|
|
65
|
+
if (this.closed) throw new Error("Protocol WebSocket transport is closed.");
|
|
66
|
+
if (this.socket?.readyState === WEB_SOCKET_OPEN) return;
|
|
67
|
+
if (this.socket != null) {
|
|
68
|
+
this.#detachSocket(this.socket);
|
|
69
|
+
this.socket = null;
|
|
70
|
+
}
|
|
31
71
|
this.assertBrowserSafeTransportConfig();
|
|
32
72
|
const wsUrl = require_utils.toWebSocketUrl(require_utils.toAbsoluteUrl(this.apiUrl, this.streamUrl).toString());
|
|
33
73
|
const socket = this.webSocketFactory(wsUrl);
|
|
34
74
|
this.socket = socket;
|
|
35
|
-
this.closed = false;
|
|
36
75
|
this.intentionalClose = false;
|
|
37
|
-
socket
|
|
38
|
-
socket.addEventListener("close", this.handleClose);
|
|
39
|
-
socket.addEventListener("error", this.handleSocketError);
|
|
76
|
+
this.#attachSocket(socket);
|
|
40
77
|
await new Promise((resolve, reject) => {
|
|
41
78
|
const onOpen = () => {
|
|
42
79
|
cleanup();
|
|
@@ -80,8 +117,9 @@ var ProtocolWebSocketTransportAdapter = class {
|
|
|
80
117
|
const socket = this.socket;
|
|
81
118
|
this.socket = null;
|
|
82
119
|
if (!socket) return;
|
|
120
|
+
this.#detachSocket(socket);
|
|
83
121
|
await new Promise((resolve) => {
|
|
84
|
-
if (socket.readyState ===
|
|
122
|
+
if (socket.readyState === WEB_SOCKET_CLOSED) {
|
|
85
123
|
resolve();
|
|
86
124
|
return;
|
|
87
125
|
}
|
|
@@ -90,7 +128,7 @@ var ProtocolWebSocketTransportAdapter = class {
|
|
|
90
128
|
resolve();
|
|
91
129
|
};
|
|
92
130
|
socket.addEventListener("close", onClose, { once: true });
|
|
93
|
-
if (socket.readyState ===
|
|
131
|
+
if (socket.readyState === WEB_SOCKET_OPEN || socket.readyState === WEB_SOCKET_CONNECTING) socket.close();
|
|
94
132
|
else resolve();
|
|
95
133
|
});
|
|
96
134
|
}
|
|
@@ -98,8 +136,12 @@ var ProtocolWebSocketTransportAdapter = class {
|
|
|
98
136
|
if (require_utils.hasHeaders(this.defaultHeaders) || this.onRequest != null) throw new Error("Browser WebSocket protocol transport does not support defaultHeaders or onRequest hooks. Supply a custom protocolWebSocketFactory if you need custom WebSocket setup.");
|
|
99
137
|
}
|
|
100
138
|
async sendCommand(command) {
|
|
101
|
-
|
|
102
|
-
if (socket == null || socket.readyState !==
|
|
139
|
+
let socket = this.socket;
|
|
140
|
+
if (this.reconnectInFlight != null && (socket == null || socket.readyState !== WEB_SOCKET_OPEN)) {
|
|
141
|
+
await this.reconnectInFlight.catch(() => void 0);
|
|
142
|
+
socket = this.socket;
|
|
143
|
+
}
|
|
144
|
+
if (socket == null || socket.readyState !== WEB_SOCKET_OPEN) throw new Error("Protocol WebSocket is not open.");
|
|
103
145
|
return await new Promise((resolve, reject) => {
|
|
104
146
|
this.pending.set(command.id, {
|
|
105
147
|
resolve,
|
|
@@ -113,6 +155,16 @@ var ProtocolWebSocketTransportAdapter = class {
|
|
|
113
155
|
}
|
|
114
156
|
});
|
|
115
157
|
}
|
|
158
|
+
#attachSocket(socket) {
|
|
159
|
+
socket.addEventListener("message", this.handleMessage);
|
|
160
|
+
socket.addEventListener("close", this.handleClose);
|
|
161
|
+
socket.addEventListener("error", this.handleSocketError);
|
|
162
|
+
}
|
|
163
|
+
#detachSocket(socket) {
|
|
164
|
+
socket.removeEventListener("message", this.handleMessage);
|
|
165
|
+
socket.removeEventListener("close", this.handleClose);
|
|
166
|
+
socket.removeEventListener("error", this.handleSocketError);
|
|
167
|
+
}
|
|
116
168
|
handleMessage = (event) => {
|
|
117
169
|
let payload;
|
|
118
170
|
try {
|
|
@@ -131,25 +183,62 @@ var ProtocolWebSocketTransportAdapter = class {
|
|
|
131
183
|
if (require_utils.isRecord(payload) && payload.type === "event") this.queue.push(payload);
|
|
132
184
|
};
|
|
133
185
|
handleClose = () => {
|
|
186
|
+
const socket = this.socket;
|
|
187
|
+
if (socket != null) this.#detachSocket(socket);
|
|
134
188
|
this.socket = null;
|
|
135
189
|
if (this.intentionalClose || this.closed) {
|
|
136
190
|
this.queue.close();
|
|
137
191
|
return;
|
|
138
192
|
}
|
|
139
|
-
|
|
140
|
-
for (const { reject } of this.pending.values()) reject(error);
|
|
141
|
-
this.pending.clear();
|
|
142
|
-
this.queue.close(error);
|
|
193
|
+
this.#handleUnexpectedDisconnect(/* @__PURE__ */ new Error("Protocol WebSocket closed unexpectedly."));
|
|
143
194
|
};
|
|
144
195
|
handleSocketError = () => {
|
|
145
196
|
if (this.closed || this.intentionalClose) return;
|
|
146
|
-
|
|
197
|
+
this.#handleUnexpectedDisconnect(/* @__PURE__ */ new Error("Protocol WebSocket encountered an error."));
|
|
198
|
+
};
|
|
199
|
+
#handleUnexpectedDisconnect(cause) {
|
|
200
|
+
const error = require_utils.toError(cause);
|
|
147
201
|
for (const { reject } of this.pending.values()) reject(error);
|
|
148
202
|
this.pending.clear();
|
|
149
|
-
this.
|
|
150
|
-
|
|
203
|
+
if (this.maxReconnectAttempts <= 0) {
|
|
204
|
+
this.queue.close(error);
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
this.#scheduleReconnect(cause);
|
|
208
|
+
}
|
|
209
|
+
#scheduleReconnect(cause) {
|
|
210
|
+
if (this.closed || this.intentionalClose) return;
|
|
211
|
+
if (this.reconnectInFlight != null) return;
|
|
212
|
+
this.reconnectInFlight = this.#runReconnectLoop(cause).finally(() => {
|
|
213
|
+
this.reconnectInFlight = null;
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
async #runReconnectLoop(initialCause) {
|
|
217
|
+
let lastError = initialCause;
|
|
218
|
+
for (let attempt = 1; attempt <= this.maxReconnectAttempts; attempt += 1) {
|
|
219
|
+
if (this.closed || this.intentionalClose) return;
|
|
220
|
+
this.onReconnect?.({
|
|
221
|
+
attempt,
|
|
222
|
+
cause: lastError
|
|
223
|
+
});
|
|
224
|
+
const delay = this.reconnectDelayMs(attempt);
|
|
225
|
+
if (delay > 0) await new Promise((resolve) => {
|
|
226
|
+
setTimeout(resolve, delay);
|
|
227
|
+
});
|
|
228
|
+
if (this.closed || this.intentionalClose) return;
|
|
229
|
+
try {
|
|
230
|
+
await this.open();
|
|
231
|
+
if (this.onReconnected) await this.onReconnected();
|
|
232
|
+
return;
|
|
233
|
+
} catch (error) {
|
|
234
|
+
lastError = error;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
this.queue.close(new require_error.MaxWebSocketReconnectAttemptsError(this.maxReconnectAttempts, lastError));
|
|
238
|
+
}
|
|
151
239
|
};
|
|
152
240
|
//#endregion
|
|
153
241
|
exports.ProtocolWebSocketTransportAdapter = ProtocolWebSocketTransportAdapter;
|
|
242
|
+
exports.webSocketReconnectDelayMs = webSocketReconnectDelayMs;
|
|
154
243
|
|
|
155
244
|
//# sourceMappingURL=websocket.cjs.map
|