@langchain/langgraph-sdk 1.10.3-rc.2 → 1.11.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/dist/client/stream/transport/http.cjs +9 -2
- package/dist/client/stream/transport/http.cjs.map +1 -1
- package/dist/client/stream/transport/http.d.cts +1 -0
- package/dist/client/stream/transport/http.d.cts.map +1 -1
- package/dist/client/stream/transport/http.d.ts +1 -0
- package/dist/client/stream/transport/http.d.ts.map +1 -1
- package/dist/client/stream/transport/http.js +9 -2
- package/dist/client/stream/transport/http.js.map +1 -1
- package/dist/client/stream/transport/types.d.cts +9 -12
- package/dist/client/stream/transport/types.d.cts.map +1 -1
- package/dist/client/stream/transport/types.d.ts +9 -12
- package/dist/client/stream/transport/types.d.ts.map +1 -1
- package/dist/client/stream/transport/websocket.cjs +15 -3
- package/dist/client/stream/transport/websocket.cjs.map +1 -1
- package/dist/client/stream/transport/websocket.d.cts +1 -0
- package/dist/client/stream/transport/websocket.d.cts.map +1 -1
- package/dist/client/stream/transport/websocket.d.ts +1 -0
- package/dist/client/stream/transport/websocket.d.ts.map +1 -1
- package/dist/client/stream/transport/websocket.js +15 -3
- package/dist/client/stream/transport/websocket.js.map +1 -1
- package/dist/client/stream/types.d.cts +5 -7
- package/dist/client/stream/types.d.cts.map +1 -1
- package/dist/client/stream/types.d.ts +5 -7
- package/dist/client/stream/types.d.ts.map +1 -1
- package/dist/client/threads/index.cjs +2 -1
- package/dist/client/threads/index.cjs.map +1 -1
- package/dist/client/threads/index.js +2 -1
- package/dist/client/threads/index.js.map +1 -1
- package/dist/schema.d.cts +5 -0
- package/dist/schema.d.cts.map +1 -1
- package/dist/schema.d.ts +5 -0
- package/dist/schema.d.ts.map +1 -1
- package/dist/stream/controller.cjs +32 -4
- package/dist/stream/controller.cjs.map +1 -1
- package/dist/stream/controller.js +32 -4
- package/dist/stream/controller.js.map +1 -1
- package/dist/stream/types.d.cts +5 -0
- package/dist/stream/types.d.cts.map +1 -1
- package/dist/stream/types.d.ts +5 -0
- package/dist/stream/types.d.ts.map +1 -1
- package/dist/utils/reconnect.cjs.map +1 -1
- package/dist/utils/reconnect.d.cts +18 -0
- package/dist/utils/reconnect.d.cts.map +1 -0
- package/dist/utils/reconnect.d.ts +18 -0
- package/dist/utils/reconnect.d.ts.map +1 -0
- package/dist/utils/reconnect.js.map +1 -1
- package/package.json +2 -2
|
@@ -24,6 +24,7 @@ var ProtocolSseTransportAdapter = class {
|
|
|
24
24
|
maxReconnectAttempts;
|
|
25
25
|
idleReconnect;
|
|
26
26
|
onReconnect;
|
|
27
|
+
onConnected;
|
|
27
28
|
reconnectDelayMs;
|
|
28
29
|
paths;
|
|
29
30
|
sessionAbortController = new AbortController();
|
|
@@ -39,6 +40,7 @@ var ProtocolSseTransportAdapter = class {
|
|
|
39
40
|
this.maxReconnectAttempts = options.maxReconnectAttempts ?? 5;
|
|
40
41
|
this.idleReconnect = options.idleReconnect ?? "auto";
|
|
41
42
|
this.onReconnect = options.onReconnect;
|
|
43
|
+
this.onConnected = options.onConnected;
|
|
42
44
|
this.reconnectDelayMs = options.reconnectDelayMs ?? require_reconnect.reconnectDelayMs;
|
|
43
45
|
this.threadId = options.threadId ?? "";
|
|
44
46
|
this.paths = options.paths;
|
|
@@ -157,6 +159,10 @@ var ProtocolSseTransportAdapter = class {
|
|
|
157
159
|
}, { stream: true });
|
|
158
160
|
const contentType = response.headers.get("content-type")?.split(";")[0];
|
|
159
161
|
if (contentType && !contentType.includes("text/event-stream")) throw new Error(`Expected response header Content-Type to contain 'text/event-stream', got '${contentType}'`);
|
|
162
|
+
await this.onConnected?.({
|
|
163
|
+
kind: attempt === 0 ? "initial" : "reconnected",
|
|
164
|
+
attempt
|
|
165
|
+
});
|
|
160
166
|
if (!readySettled) {
|
|
161
167
|
readySettled = true;
|
|
162
168
|
resolveReady();
|
|
@@ -201,11 +207,12 @@ var ProtocolSseTransportAdapter = class {
|
|
|
201
207
|
streamQueue.close(require_utils.toError(error));
|
|
202
208
|
return;
|
|
203
209
|
}
|
|
210
|
+
const delay = this.reconnectDelayMs(attempt);
|
|
204
211
|
this.onReconnect?.({
|
|
205
212
|
attempt,
|
|
206
|
-
cause: error
|
|
213
|
+
cause: error,
|
|
214
|
+
delayMs: delay
|
|
207
215
|
});
|
|
208
|
-
const delay = this.reconnectDelayMs(attempt);
|
|
209
216
|
if (delay > 0) await new Promise((resolve) => {
|
|
210
217
|
setTimeout(resolve, delay);
|
|
211
218
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"http.cjs","names":["AsyncQueue","reconnectDelayMs","resolveProtocolPath","toAbsoluteUrl","mergeHeaders","toError","isProtocolResponse","BytesLineDecoder","idleReconnectStream","SSEDecoder","IterableReadableStream","isRecord"],"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 ProtocolTransportPaths,\n} from \"./types.js\";\nimport type { TransportAdapter, EventStreamHandle } from \"../transport.js\";\nimport {\n toAbsoluteUrl,\n isRecord,\n mergeHeaders,\n toError,\n isProtocolResponse,\n resolveProtocolPath,\n} from \"./utils.js\";\nimport { BytesLineDecoder, SSEDecoder } from \"../../../utils/sse.js\";\nimport {\n IterableReadableStream,\n idleReconnectStream,\n type IdleReconnectMode,\n} from \"../../../utils/stream.js\";\nimport {\n DEFAULT_MAX_RECONNECT_ATTEMPTS,\n reconnectDelayMs,\n} from \"../../../utils/reconnect.js\";\nimport { DEFAULT_IDLE_RECONNECT } 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 `threadId` at construction\n * or later via {@link setThreadId}; request URLs derive from the\n * currently-bound thread. Each {@link openEventStream} call opens an\n * independent filtered SSE connection via\n * `POST /threads/:thread_id/stream/events`.\n */\nexport class ProtocolSseTransportAdapter implements TransportAdapter {\n 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 idleReconnect: IdleReconnectMode | null;\n\n private readonly onReconnect?: ProtocolSseTransportOptions[\"onReconnect\"];\n\n private readonly reconnectDelayMs: (attempt: number) => number;\n\n private readonly paths?: ProtocolTransportPaths;\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` (auth shims, proxies) must keep reconnect enabled — that is\n // the production path for tenant-aware browsers. Tests that need fail-fast\n // mocks should pass `maxReconnectAttempts: 0` (and `idleReconnect: 0`)\n // explicitly rather than relying on `fetch` presence.\n this.maxReconnectAttempts =\n options.maxReconnectAttempts ?? DEFAULT_MAX_RECONNECT_ATTEMPTS;\n // Default to heartbeat-adaptive idle reconnect, which stays dormant\n // unless the server actually emits keep-alive heartbeats. Pass `0` to\n // disable.\n this.idleReconnect = options.idleReconnect ?? DEFAULT_IDLE_RECONNECT;\n this.onReconnect = options.onReconnect;\n this.reconnectDelayMs = options.reconnectDelayMs ?? reconnectDelayMs;\n this.threadId = options.threadId ?? \"\";\n this.paths = options.paths;\n }\n\n /** {@inheritDoc TransportAdapter.setThreadId} */\n setThreadId(threadId: string): void {\n this.threadId = threadId;\n }\n\n /**\n * Command/stream/state URLs derive from the currently-bound thread so a\n * single adapter can follow {@link setThreadId} re-binds. A fixed\n * `paths.*` string overrides the default and is used as-is.\n */\n private get commandsUrl(): string {\n return resolveProtocolPath(\n this.paths?.commands,\n this.threadId,\n (id) => `/threads/${id}/commands`\n );\n }\n\n private get streamUrl(): string {\n return resolveProtocolPath(\n this.paths?.stream,\n this.threadId,\n (id) => `/threads/${id}/stream/events`\n );\n }\n\n private get stateUrl(): string {\n return resolveProtocolPath(\n this.paths?.state,\n this.threadId,\n (id) => `/threads/${id}/state`\n );\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 // Honor an explicit caller `since` until the stream has connected\n // once. Do not advance it from observed `seq` values — those are\n // connection-local, and carrying them onto a post-connect reconnect\n // POST filters out the full Redis replay (heartbeats only). Pre-ready\n // retries still send the caller cursor; only after a successful open\n // do reconnects omit `since` and rely on durable `event_id` dedup.\n const initialSince =\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 let receivedEvent = false;\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 ...(!readySettled && initialSince != null\n ? { since: initialSince }\n : {}),\n }),\n signal: ac.signal,\n },\n { stream: true }\n );\n\n const contentType = response.headers\n .get(\"content-type\")\n ?.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 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 // Idle watchdog on the line stream (between byte-line and SSE\n // decoding) so it can reset on any line and recognise `:` keep-alive\n // heartbeats to drive `\"auto\"` mode. On idle it errors the stream,\n // which the catch below treats like any other disconnect and\n // re-opens the SSE without carrying a connection-local `since`.\n const enableIdle =\n this.idleReconnect === \"auto\" ||\n (typeof this.idleReconnect === \"number\" && this.idleReconnect > 0);\n const lines = readable.pipeThrough(BytesLineDecoder());\n const watched = enableIdle\n ? lines.pipeThrough(\n idleReconnectStream({ mode: this.idleReconnect! })\n )\n : lines;\n const stream = watched.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 receivedEvent = true;\n streamQueue.push(event.data as Message);\n }\n }\n if (\n ac.signal.aborted ||\n this.closed ||\n this.maxReconnectAttempts <= 0\n ) {\n streamQueue.close();\n return;\n }\n // The thread stream is open-ended: the server only ends it when\n // its own upstream consumer died or it is shutting down, so a\n // clean close is a disconnect, not the end of the thread.\n throw new Error(\"Event stream closed by the server\");\n } catch (error) {\n if (receivedEvent) {\n attempt = 0;\n receivedEvent = false;\n }\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
|
+
{"version":3,"file":"http.cjs","names":["AsyncQueue","reconnectDelayMs","resolveProtocolPath","toAbsoluteUrl","mergeHeaders","toError","isProtocolResponse","BytesLineDecoder","idleReconnectStream","SSEDecoder","IterableReadableStream","isRecord"],"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 ProtocolTransportPaths,\n} from \"./types.js\";\nimport type { TransportAdapter, EventStreamHandle } from \"../transport.js\";\nimport {\n toAbsoluteUrl,\n isRecord,\n mergeHeaders,\n toError,\n isProtocolResponse,\n resolveProtocolPath,\n} from \"./utils.js\";\nimport { BytesLineDecoder, SSEDecoder } from \"../../../utils/sse.js\";\nimport {\n IterableReadableStream,\n idleReconnectStream,\n type IdleReconnectMode,\n} from \"../../../utils/stream.js\";\nimport {\n DEFAULT_MAX_RECONNECT_ATTEMPTS,\n reconnectDelayMs,\n} from \"../../../utils/reconnect.js\";\nimport { DEFAULT_IDLE_RECONNECT } 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 `threadId` at construction\n * or later via {@link setThreadId}; request URLs derive from the\n * currently-bound thread. Each {@link openEventStream} call opens an\n * independent filtered SSE connection via\n * `POST /threads/:thread_id/stream/events`.\n */\nexport class ProtocolSseTransportAdapter implements TransportAdapter {\n 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 idleReconnect: IdleReconnectMode | null;\n\n private readonly onReconnect?: ProtocolSseTransportOptions[\"onReconnect\"];\n\n private readonly onConnected?: ProtocolSseTransportOptions[\"onConnected\"];\n\n private readonly reconnectDelayMs: (attempt: number) => number;\n\n private readonly paths?: ProtocolTransportPaths;\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` (auth shims, proxies) must keep reconnect enabled — that is\n // the production path for tenant-aware browsers. Tests that need fail-fast\n // mocks should pass `maxReconnectAttempts: 0` (and `idleReconnect: 0`)\n // explicitly rather than relying on `fetch` presence.\n this.maxReconnectAttempts =\n options.maxReconnectAttempts ?? DEFAULT_MAX_RECONNECT_ATTEMPTS;\n // Default to heartbeat-adaptive idle reconnect, which stays dormant\n // unless the server actually emits keep-alive heartbeats. Pass `0` to\n // disable.\n this.idleReconnect = options.idleReconnect ?? DEFAULT_IDLE_RECONNECT;\n this.onReconnect = options.onReconnect;\n this.onConnected = options.onConnected;\n this.reconnectDelayMs = options.reconnectDelayMs ?? reconnectDelayMs;\n this.threadId = options.threadId ?? \"\";\n this.paths = options.paths;\n }\n\n /** {@inheritDoc TransportAdapter.setThreadId} */\n setThreadId(threadId: string): void {\n this.threadId = threadId;\n }\n\n /**\n * Command/stream/state URLs derive from the currently-bound thread so a\n * single adapter can follow {@link setThreadId} re-binds. A fixed\n * `paths.*` string overrides the default and is used as-is.\n */\n private get commandsUrl(): string {\n return resolveProtocolPath(\n this.paths?.commands,\n this.threadId,\n (id) => `/threads/${id}/commands`\n );\n }\n\n private get streamUrl(): string {\n return resolveProtocolPath(\n this.paths?.stream,\n this.threadId,\n (id) => `/threads/${id}/stream/events`\n );\n }\n\n private get stateUrl(): string {\n return resolveProtocolPath(\n this.paths?.state,\n this.threadId,\n (id) => `/threads/${id}/state`\n );\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 // Honor an explicit caller `since` until the stream has connected\n // once. Do not advance it from observed `seq` values — those are\n // connection-local, and carrying them onto a post-connect reconnect\n // POST filters out the full Redis replay (heartbeats only). Pre-ready\n // retries still send the caller cursor; only after a successful open\n // do reconnects omit `since` and rely on durable `event_id` dedup.\n const initialSince =\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 let receivedEvent = false;\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 ...(!readySettled && initialSince != null\n ? { since: initialSince }\n : {}),\n }),\n signal: ac.signal,\n },\n { stream: true }\n );\n\n const contentType = response.headers\n .get(\"content-type\")\n ?.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 await this.onConnected?.({\n kind: attempt === 0 ? \"initial\" : \"reconnected\",\n attempt,\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 // Idle watchdog on the line stream (between byte-line and SSE\n // decoding) so it can reset on any line and recognise `:` keep-alive\n // heartbeats to drive `\"auto\"` mode. On idle it errors the stream,\n // which the catch below treats like any other disconnect and\n // re-opens the SSE without carrying a connection-local `since`.\n const enableIdle =\n this.idleReconnect === \"auto\" ||\n (typeof this.idleReconnect === \"number\" && this.idleReconnect > 0);\n const lines = readable.pipeThrough(BytesLineDecoder());\n const watched = enableIdle\n ? lines.pipeThrough(\n idleReconnectStream({ mode: this.idleReconnect! })\n )\n : lines;\n const stream = watched.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 receivedEvent = true;\n streamQueue.push(event.data as Message);\n }\n }\n if (\n ac.signal.aborted ||\n this.closed ||\n this.maxReconnectAttempts <= 0\n ) {\n streamQueue.close();\n return;\n }\n // The thread stream is open-ended: the server only ends it when\n // its own upstream consumer died or it is shutting down, so a\n // clean close is a disconnect, not the end of the thread.\n throw new Error(\"Event stream closed by the server\");\n } catch (error) {\n if (receivedEvent) {\n attempt = 0;\n receivedEvent = false;\n }\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 const delay = this.reconnectDelayMs(attempt);\n this.onReconnect?.({ attempt, cause: error, delayMs: delay });\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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"}
|
|
@@ -23,6 +23,7 @@ declare class ProtocolSseTransportAdapter implements TransportAdapter {
|
|
|
23
23
|
private readonly maxReconnectAttempts;
|
|
24
24
|
private readonly idleReconnect;
|
|
25
25
|
private readonly onReconnect?;
|
|
26
|
+
private readonly onConnected?;
|
|
26
27
|
private readonly reconnectDelayMs;
|
|
27
28
|
private readonly paths?;
|
|
28
29
|
private readonly sessionAbortController;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"http.d.cts","names":[],"sources":["../../../../src/client/stream/transport/http.ts"],"mappings":";;;;;;;AA6CA;;;;;;cAAa,2BAAA,YAAuC,gBAAA;EAClD,QAAA;EAAA,SAES,MAAA;EAAA,iBAEQ,KAAA;EAAA,iBAEA,SAAA;EAAA,iBAEA,cAAA;EAAA,iBAEA,SAAA;EAAA,iBAEA,YAAA;EAAA,iBAEA,WAAA;EAAA,iBAEA,oBAAA;EAAA,iBAEA,aAAA;EAAA,iBAEA,WAAA;EAAA,iBAEA,gBAAA;EAAA,iBAEA,KAAA;EAAA,iBAEA,sBAAA;EAAA,iBAEA,YAAA;EAAA,QAET,MAAA;EAER,WAAA,CAAY,OAAA,EAAS,2BAAA;EAxBJ;
|
|
1
|
+
{"version":3,"file":"http.d.cts","names":[],"sources":["../../../../src/client/stream/transport/http.ts"],"mappings":";;;;;;;AA6CA;;;;;;cAAa,2BAAA,YAAuC,gBAAA;EAClD,QAAA;EAAA,SAES,MAAA;EAAA,iBAEQ,KAAA;EAAA,iBAEA,SAAA;EAAA,iBAEA,cAAA;EAAA,iBAEA,SAAA;EAAA,iBAEA,YAAA;EAAA,iBAEA,WAAA;EAAA,iBAEA,oBAAA;EAAA,iBAEA,aAAA;EAAA,iBAEA,WAAA;EAAA,iBAEA,WAAA;EAAA,iBAEA,gBAAA;EAAA,iBAEA,KAAA;EAAA,iBAEA,sBAAA;EAAA,iBAEA,YAAA;EAAA,QAET,MAAA;EAER,WAAA,CAAY,OAAA,EAAS,2BAAA;EAxBJ;EAiDjB,WAAA,CAAY,QAAA;EA7CK;;;;;EAAA,YAsDL,WAAA,CAAA;EAAA,YAQA,SAAA,CAAA;EAAA,YAQA,QAAA,CAAA;EAtDK;;;;;;;EAqEX,QAAA,qBAAA,CAAA,GAAiC,OAAA;IACrC,MAAA,EAAQ,SAAA;IACR,IAAA;IACA,KAAA;IACA,QAAA;IACA,UAAA;MAAe,aAAA;IAAA;IACf,iBAAA;MAAsB,aAAA;IAAA;EAAA;EAAA,QAmCV,YAAA;EAnCZ;;;;EA8CI,IAAA,CAAA,GAAQ,OAAA;EAIR,IAAA,CACJ,OAAA,EAAS,OAAA,GACR,OAAA,CAAQ,eAAA,GAAkB,aAAA;EADlB;;;;;EAyBX,MAAA,CAAA,GAAU,aAAA,CAAc,OAAA;EAaxB,eAAA,CAAgB,MAAA,EAAQ,eAAA,GAAkB,iBAAA;EAwLpC,KAAA,CAAA,GAAS,OAAA;EAAA,QAWD,OAAA;AAAA"}
|
|
@@ -23,6 +23,7 @@ declare class ProtocolSseTransportAdapter implements TransportAdapter {
|
|
|
23
23
|
private readonly maxReconnectAttempts;
|
|
24
24
|
private readonly idleReconnect;
|
|
25
25
|
private readonly onReconnect?;
|
|
26
|
+
private readonly onConnected?;
|
|
26
27
|
private readonly reconnectDelayMs;
|
|
27
28
|
private readonly paths?;
|
|
28
29
|
private readonly sessionAbortController;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"http.d.ts","names":[],"sources":["../../../../src/client/stream/transport/http.ts"],"mappings":";;;;;;;AA6CA;;;;;;cAAa,2BAAA,YAAuC,gBAAA;EAClD,QAAA;EAAA,SAES,MAAA;EAAA,iBAEQ,KAAA;EAAA,iBAEA,SAAA;EAAA,iBAEA,cAAA;EAAA,iBAEA,SAAA;EAAA,iBAEA,YAAA;EAAA,iBAEA,WAAA;EAAA,iBAEA,oBAAA;EAAA,iBAEA,aAAA;EAAA,iBAEA,WAAA;EAAA,iBAEA,gBAAA;EAAA,iBAEA,KAAA;EAAA,iBAEA,sBAAA;EAAA,iBAEA,YAAA;EAAA,QAET,MAAA;EAER,WAAA,CAAY,OAAA,EAAS,2BAAA;EAxBJ;
|
|
1
|
+
{"version":3,"file":"http.d.ts","names":[],"sources":["../../../../src/client/stream/transport/http.ts"],"mappings":";;;;;;;AA6CA;;;;;;cAAa,2BAAA,YAAuC,gBAAA;EAClD,QAAA;EAAA,SAES,MAAA;EAAA,iBAEQ,KAAA;EAAA,iBAEA,SAAA;EAAA,iBAEA,cAAA;EAAA,iBAEA,SAAA;EAAA,iBAEA,YAAA;EAAA,iBAEA,WAAA;EAAA,iBAEA,oBAAA;EAAA,iBAEA,aAAA;EAAA,iBAEA,WAAA;EAAA,iBAEA,WAAA;EAAA,iBAEA,gBAAA;EAAA,iBAEA,KAAA;EAAA,iBAEA,sBAAA;EAAA,iBAEA,YAAA;EAAA,QAET,MAAA;EAER,WAAA,CAAY,OAAA,EAAS,2BAAA;EAxBJ;EAiDjB,WAAA,CAAY,QAAA;EA7CK;;;;;EAAA,YAsDL,WAAA,CAAA;EAAA,YAQA,SAAA,CAAA;EAAA,YAQA,QAAA,CAAA;EAtDK;;;;;;;EAqEX,QAAA,qBAAA,CAAA,GAAiC,OAAA;IACrC,MAAA,EAAQ,SAAA;IACR,IAAA;IACA,KAAA;IACA,QAAA;IACA,UAAA;MAAe,aAAA;IAAA;IACf,iBAAA;MAAsB,aAAA;IAAA;EAAA;EAAA,QAmCV,YAAA;EAnCZ;;;;EA8CI,IAAA,CAAA,GAAQ,OAAA;EAIR,IAAA,CACJ,OAAA,EAAS,OAAA,GACR,OAAA,CAAQ,eAAA,GAAkB,aAAA;EADlB;;;;;EAyBX,MAAA,CAAA,GAAU,aAAA,CAAc,OAAA;EAaxB,eAAA,CAAgB,MAAA,EAAQ,eAAA,GAAkB,iBAAA;EAwLpC,KAAA,CAAA,GAAS,OAAA;EAAA,QAWD,OAAA;AAAA"}
|
|
@@ -24,6 +24,7 @@ var ProtocolSseTransportAdapter = class {
|
|
|
24
24
|
maxReconnectAttempts;
|
|
25
25
|
idleReconnect;
|
|
26
26
|
onReconnect;
|
|
27
|
+
onConnected;
|
|
27
28
|
reconnectDelayMs;
|
|
28
29
|
paths;
|
|
29
30
|
sessionAbortController = new AbortController();
|
|
@@ -39,6 +40,7 @@ var ProtocolSseTransportAdapter = class {
|
|
|
39
40
|
this.maxReconnectAttempts = options.maxReconnectAttempts ?? 5;
|
|
40
41
|
this.idleReconnect = options.idleReconnect ?? "auto";
|
|
41
42
|
this.onReconnect = options.onReconnect;
|
|
43
|
+
this.onConnected = options.onConnected;
|
|
42
44
|
this.reconnectDelayMs = options.reconnectDelayMs ?? reconnectDelayMs;
|
|
43
45
|
this.threadId = options.threadId ?? "";
|
|
44
46
|
this.paths = options.paths;
|
|
@@ -157,6 +159,10 @@ var ProtocolSseTransportAdapter = class {
|
|
|
157
159
|
}, { stream: true });
|
|
158
160
|
const contentType = response.headers.get("content-type")?.split(";")[0];
|
|
159
161
|
if (contentType && !contentType.includes("text/event-stream")) throw new Error(`Expected response header Content-Type to contain 'text/event-stream', got '${contentType}'`);
|
|
162
|
+
await this.onConnected?.({
|
|
163
|
+
kind: attempt === 0 ? "initial" : "reconnected",
|
|
164
|
+
attempt
|
|
165
|
+
});
|
|
160
166
|
if (!readySettled) {
|
|
161
167
|
readySettled = true;
|
|
162
168
|
resolveReady();
|
|
@@ -201,11 +207,12 @@ var ProtocolSseTransportAdapter = class {
|
|
|
201
207
|
streamQueue.close(toError(error));
|
|
202
208
|
return;
|
|
203
209
|
}
|
|
210
|
+
const delay = this.reconnectDelayMs(attempt);
|
|
204
211
|
this.onReconnect?.({
|
|
205
212
|
attempt,
|
|
206
|
-
cause: error
|
|
213
|
+
cause: error,
|
|
214
|
+
delayMs: delay
|
|
207
215
|
});
|
|
208
|
-
const delay = this.reconnectDelayMs(attempt);
|
|
209
216
|
if (delay > 0) await new Promise((resolve) => {
|
|
210
217
|
setTimeout(resolve, delay);
|
|
211
218
|
});
|
|
@@ -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 { AsyncCaller } from \"../../../utils/async_caller.js\";\nimport type {\n HeaderValue,\n ProtocolRequestHook,\n ProtocolSseTransportOptions,\n ProtocolTransportPaths,\n} from \"./types.js\";\nimport type { TransportAdapter, EventStreamHandle } from \"../transport.js\";\nimport {\n toAbsoluteUrl,\n isRecord,\n mergeHeaders,\n toError,\n isProtocolResponse,\n resolveProtocolPath,\n} from \"./utils.js\";\nimport { BytesLineDecoder, SSEDecoder } from \"../../../utils/sse.js\";\nimport {\n IterableReadableStream,\n idleReconnectStream,\n type IdleReconnectMode,\n} from \"../../../utils/stream.js\";\nimport {\n DEFAULT_MAX_RECONNECT_ATTEMPTS,\n reconnectDelayMs,\n} from \"../../../utils/reconnect.js\";\nimport { DEFAULT_IDLE_RECONNECT } 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 `threadId` at construction\n * or later via {@link setThreadId}; request URLs derive from the\n * currently-bound thread. Each {@link openEventStream} call opens an\n * independent filtered SSE connection via\n * `POST /threads/:thread_id/stream/events`.\n */\nexport class ProtocolSseTransportAdapter implements TransportAdapter {\n 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 idleReconnect: IdleReconnectMode | null;\n\n private readonly onReconnect?: ProtocolSseTransportOptions[\"onReconnect\"];\n\n private readonly reconnectDelayMs: (attempt: number) => number;\n\n private readonly paths?: ProtocolTransportPaths;\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` (auth shims, proxies) must keep reconnect enabled — that is\n // the production path for tenant-aware browsers. Tests that need fail-fast\n // mocks should pass `maxReconnectAttempts: 0` (and `idleReconnect: 0`)\n // explicitly rather than relying on `fetch` presence.\n this.maxReconnectAttempts =\n options.maxReconnectAttempts ?? DEFAULT_MAX_RECONNECT_ATTEMPTS;\n // Default to heartbeat-adaptive idle reconnect, which stays dormant\n // unless the server actually emits keep-alive heartbeats. Pass `0` to\n // disable.\n this.idleReconnect = options.idleReconnect ?? DEFAULT_IDLE_RECONNECT;\n this.onReconnect = options.onReconnect;\n this.reconnectDelayMs = options.reconnectDelayMs ?? reconnectDelayMs;\n this.threadId = options.threadId ?? \"\";\n this.paths = options.paths;\n }\n\n /** {@inheritDoc TransportAdapter.setThreadId} */\n setThreadId(threadId: string): void {\n this.threadId = threadId;\n }\n\n /**\n * Command/stream/state URLs derive from the currently-bound thread so a\n * single adapter can follow {@link setThreadId} re-binds. A fixed\n * `paths.*` string overrides the default and is used as-is.\n */\n private get commandsUrl(): string {\n return resolveProtocolPath(\n this.paths?.commands,\n this.threadId,\n (id) => `/threads/${id}/commands`\n );\n }\n\n private get streamUrl(): string {\n return resolveProtocolPath(\n this.paths?.stream,\n this.threadId,\n (id) => `/threads/${id}/stream/events`\n );\n }\n\n private get stateUrl(): string {\n return resolveProtocolPath(\n this.paths?.state,\n this.threadId,\n (id) => `/threads/${id}/state`\n );\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 // Honor an explicit caller `since` until the stream has connected\n // once. Do not advance it from observed `seq` values — those are\n // connection-local, and carrying them onto a post-connect reconnect\n // POST filters out the full Redis replay (heartbeats only). Pre-ready\n // retries still send the caller cursor; only after a successful open\n // do reconnects omit `since` and rely on durable `event_id` dedup.\n const initialSince =\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 let receivedEvent = false;\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 ...(!readySettled && initialSince != null\n ? { since: initialSince }\n : {}),\n }),\n signal: ac.signal,\n },\n { stream: true }\n );\n\n const contentType = response.headers\n .get(\"content-type\")\n ?.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 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 // Idle watchdog on the line stream (between byte-line and SSE\n // decoding) so it can reset on any line and recognise `:` keep-alive\n // heartbeats to drive `\"auto\"` mode. On idle it errors the stream,\n // which the catch below treats like any other disconnect and\n // re-opens the SSE without carrying a connection-local `since`.\n const enableIdle =\n this.idleReconnect === \"auto\" ||\n (typeof this.idleReconnect === \"number\" && this.idleReconnect > 0);\n const lines = readable.pipeThrough(BytesLineDecoder());\n const watched = enableIdle\n ? lines.pipeThrough(\n idleReconnectStream({ mode: this.idleReconnect! })\n )\n : lines;\n const stream = watched.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 receivedEvent = true;\n streamQueue.push(event.data as Message);\n }\n }\n if (\n ac.signal.aborted ||\n this.closed ||\n this.maxReconnectAttempts <= 0\n ) {\n streamQueue.close();\n return;\n }\n // The thread stream is open-ended: the server only ends it when\n // its own upstream consumer died or it is shutting down, so a\n // clean close is a disconnect, not the end of the thread.\n throw new Error(\"Event stream closed by the server\");\n } catch (error) {\n if (receivedEvent) {\n attempt = 0;\n receivedEvent = false;\n }\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
|
+
{"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 ProtocolTransportPaths,\n} from \"./types.js\";\nimport type { TransportAdapter, EventStreamHandle } from \"../transport.js\";\nimport {\n toAbsoluteUrl,\n isRecord,\n mergeHeaders,\n toError,\n isProtocolResponse,\n resolveProtocolPath,\n} from \"./utils.js\";\nimport { BytesLineDecoder, SSEDecoder } from \"../../../utils/sse.js\";\nimport {\n IterableReadableStream,\n idleReconnectStream,\n type IdleReconnectMode,\n} from \"../../../utils/stream.js\";\nimport {\n DEFAULT_MAX_RECONNECT_ATTEMPTS,\n reconnectDelayMs,\n} from \"../../../utils/reconnect.js\";\nimport { DEFAULT_IDLE_RECONNECT } 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 `threadId` at construction\n * or later via {@link setThreadId}; request URLs derive from the\n * currently-bound thread. Each {@link openEventStream} call opens an\n * independent filtered SSE connection via\n * `POST /threads/:thread_id/stream/events`.\n */\nexport class ProtocolSseTransportAdapter implements TransportAdapter {\n 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 idleReconnect: IdleReconnectMode | null;\n\n private readonly onReconnect?: ProtocolSseTransportOptions[\"onReconnect\"];\n\n private readonly onConnected?: ProtocolSseTransportOptions[\"onConnected\"];\n\n private readonly reconnectDelayMs: (attempt: number) => number;\n\n private readonly paths?: ProtocolTransportPaths;\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` (auth shims, proxies) must keep reconnect enabled — that is\n // the production path for tenant-aware browsers. Tests that need fail-fast\n // mocks should pass `maxReconnectAttempts: 0` (and `idleReconnect: 0`)\n // explicitly rather than relying on `fetch` presence.\n this.maxReconnectAttempts =\n options.maxReconnectAttempts ?? DEFAULT_MAX_RECONNECT_ATTEMPTS;\n // Default to heartbeat-adaptive idle reconnect, which stays dormant\n // unless the server actually emits keep-alive heartbeats. Pass `0` to\n // disable.\n this.idleReconnect = options.idleReconnect ?? DEFAULT_IDLE_RECONNECT;\n this.onReconnect = options.onReconnect;\n this.onConnected = options.onConnected;\n this.reconnectDelayMs = options.reconnectDelayMs ?? reconnectDelayMs;\n this.threadId = options.threadId ?? \"\";\n this.paths = options.paths;\n }\n\n /** {@inheritDoc TransportAdapter.setThreadId} */\n setThreadId(threadId: string): void {\n this.threadId = threadId;\n }\n\n /**\n * Command/stream/state URLs derive from the currently-bound thread so a\n * single adapter can follow {@link setThreadId} re-binds. A fixed\n * `paths.*` string overrides the default and is used as-is.\n */\n private get commandsUrl(): string {\n return resolveProtocolPath(\n this.paths?.commands,\n this.threadId,\n (id) => `/threads/${id}/commands`\n );\n }\n\n private get streamUrl(): string {\n return resolveProtocolPath(\n this.paths?.stream,\n this.threadId,\n (id) => `/threads/${id}/stream/events`\n );\n }\n\n private get stateUrl(): string {\n return resolveProtocolPath(\n this.paths?.state,\n this.threadId,\n (id) => `/threads/${id}/state`\n );\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 // Honor an explicit caller `since` until the stream has connected\n // once. Do not advance it from observed `seq` values — those are\n // connection-local, and carrying them onto a post-connect reconnect\n // POST filters out the full Redis replay (heartbeats only). Pre-ready\n // retries still send the caller cursor; only after a successful open\n // do reconnects omit `since` and rely on durable `event_id` dedup.\n const initialSince =\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 let receivedEvent = false;\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 ...(!readySettled && initialSince != null\n ? { since: initialSince }\n : {}),\n }),\n signal: ac.signal,\n },\n { stream: true }\n );\n\n const contentType = response.headers\n .get(\"content-type\")\n ?.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 await this.onConnected?.({\n kind: attempt === 0 ? \"initial\" : \"reconnected\",\n attempt,\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 // Idle watchdog on the line stream (between byte-line and SSE\n // decoding) so it can reset on any line and recognise `:` keep-alive\n // heartbeats to drive `\"auto\"` mode. On idle it errors the stream,\n // which the catch below treats like any other disconnect and\n // re-opens the SSE without carrying a connection-local `since`.\n const enableIdle =\n this.idleReconnect === \"auto\" ||\n (typeof this.idleReconnect === \"number\" && this.idleReconnect > 0);\n const lines = readable.pipeThrough(BytesLineDecoder());\n const watched = enableIdle\n ? lines.pipeThrough(\n idleReconnectStream({ mode: this.idleReconnect! })\n )\n : lines;\n const stream = watched.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 receivedEvent = true;\n streamQueue.push(event.data as Message);\n }\n }\n if (\n ac.signal.aborted ||\n this.closed ||\n this.maxReconnectAttempts <= 0\n ) {\n streamQueue.close();\n return;\n }\n // The thread stream is open-ended: the server only ends it when\n // its own upstream consumer died or it is shutting down, so a\n // clean close is a disconnect, not the end of the thread.\n throw new Error(\"Event stream closed by the server\");\n } catch (error) {\n if (receivedEvent) {\n attempt = 0;\n receivedEvent = false;\n }\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 const delay = this.reconnectDelayMs(attempt);\n this.onReconnect?.({ attempt, cause: error, delayMs: delay });\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,5 +1,6 @@
|
|
|
1
1
|
import { AsyncCaller } from "../../../utils/async_caller.cjs";
|
|
2
2
|
import { IdleReconnectMode } from "../../../utils/stream.cjs";
|
|
3
|
+
import { ConnectedInfo, ReconnectInfo } from "../../../utils/reconnect.cjs";
|
|
3
4
|
import { CommandResponse, ErrorResponse } from "@langchain/protocol";
|
|
4
5
|
|
|
5
6
|
//#region src/client/stream/transport/types.d.ts
|
|
@@ -69,11 +70,10 @@ interface ProtocolSseTransportOptions {
|
|
|
69
70
|
* @see {@link IdleReconnectMode}
|
|
70
71
|
*/
|
|
71
72
|
idleReconnect?: IdleReconnectMode;
|
|
72
|
-
/** Called before each SSE reconnect attempt
|
|
73
|
-
onReconnect?: (options:
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
}) => void;
|
|
73
|
+
/** Called before each SSE reconnect attempt with its scheduled delay. */
|
|
74
|
+
onReconnect?: (options: ReconnectInfo) => void;
|
|
75
|
+
/** Called after the initial SSE connection and every successful reconnect. */
|
|
76
|
+
onConnected?: (options: ConnectedInfo) => void | Promise<void>;
|
|
77
77
|
/**
|
|
78
78
|
* Backoff before each SSE reconnect attempt. Defaults to
|
|
79
79
|
* `reconnectDelayMs` from `utils/reconnect`.
|
|
@@ -97,13 +97,10 @@ interface ProtocolWebSocketTransportOptions {
|
|
|
97
97
|
* Set to 0 to disable automatic reconnection.
|
|
98
98
|
*/
|
|
99
99
|
maxReconnectAttempts?: number;
|
|
100
|
-
/**
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
attempt: number;
|
|
105
|
-
cause: unknown;
|
|
106
|
-
}) => void;
|
|
100
|
+
/** Called before each reconnect attempt with its scheduled delay. */
|
|
101
|
+
onReconnect?: (options: ReconnectInfo) => void;
|
|
102
|
+
/** Called after the initial connection and every successful reconnect. */
|
|
103
|
+
onConnected?: (options: ConnectedInfo) => void | Promise<void>;
|
|
107
104
|
/**
|
|
108
105
|
* Invoked after the socket has been re-established. Use to restore
|
|
109
106
|
* server-side subscription state (see `ThreadStream`).
|
|
@@ -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":";;;;;;KAKY,mBAAA,IACV,GAAA,EAAK,GAAA,EACL,IAAA,EAAM,WAAA,KACH,OAAA,CAAQ,WAAA,IAAe,WAAA;;AAH5B;;;;;;KAYY,YAAA,cAA0B,QAAA;AAAA,UAErB,sBAAA;EACf,QAAA,GAAW,YAAA;EACX,MAAA,GAAS,YAAA;EAfJ;EAiBL,KAAA,GAAQ,YAAA;AAAA;AAAA,UAGO,2BAAA;EACf,MAAA;EAnBW;;;;AASb;;EAiBE,QAAA;EACA,cAAA,GAAiB,MAAA,SAAe,WAAA;EAChC,SAAA,GAAY,mBAAA;EACZ,KAAA,UAAe,KAAA;EACf,YAAA,gBAA4B,KAAA,GAAQ,OAAA,QAAe,KAAA;;;;;;EAMnD,WAAA,GAAc,WAAA;EACd,KAAA,GAAQ,sBAAA;EAzBG;;;;;;;AAMb;;EA6BE,oBAAA;EApBgC;;;;;;;;;;;;;;;;;;;;EAyChC,aAAA,GAAgB,iBAAA;EAxCJ;EA0CZ,WAAA,IAAe,OAAA,EAAS,aAAA;EAzCT;EA2Cf,WAAA,IAAe,OAAA,EAAS,aAAA,YAAyB,OAAA;EA1CrB;;;;EA+C5B,gBAAA,IAAoB,OAAA;AAAA;AAAA,UAGL,iCAAA;EACf,MAAA;EAbA;;;;EAkBA,QAAA;EACA,cAAA,GAAiB,MAAA,SAAe,WAAA;EAChC,SAAA,GAAY,mBAAA;EACZ,gBAAA,IAAoB,GAAA,aAAgB,SAAA;EACpC,KAAA,GAAQ,IAAA,CAAK,sBAAA;EAbb;;;;AAGF;EAgBE,oBAAA;;EAEA,WAAA,IAAe,OAAA,EAAS,aAAA;EAXP;EAajB,WAAA,IAAe,OAAA,EAAS,aAAA,YAAyB,OAAA;EAXb;;;;EAgBpC,aAAA,gBAA6B,OAAA;EALoB;;;;EAUjD,gBAAA,IAAoB,OAAA;AAAA;AAAA,KAGV,WAAA"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { AsyncCaller } from "../../../utils/async_caller.js";
|
|
2
2
|
import { IdleReconnectMode } from "../../../utils/stream.js";
|
|
3
|
+
import { ConnectedInfo, ReconnectInfo } from "../../../utils/reconnect.js";
|
|
3
4
|
import { CommandResponse, ErrorResponse } from "@langchain/protocol";
|
|
4
5
|
|
|
5
6
|
//#region src/client/stream/transport/types.d.ts
|
|
@@ -69,11 +70,10 @@ interface ProtocolSseTransportOptions {
|
|
|
69
70
|
* @see {@link IdleReconnectMode}
|
|
70
71
|
*/
|
|
71
72
|
idleReconnect?: IdleReconnectMode;
|
|
72
|
-
/** Called before each SSE reconnect attempt
|
|
73
|
-
onReconnect?: (options:
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
}) => void;
|
|
73
|
+
/** Called before each SSE reconnect attempt with its scheduled delay. */
|
|
74
|
+
onReconnect?: (options: ReconnectInfo) => void;
|
|
75
|
+
/** Called after the initial SSE connection and every successful reconnect. */
|
|
76
|
+
onConnected?: (options: ConnectedInfo) => void | Promise<void>;
|
|
77
77
|
/**
|
|
78
78
|
* Backoff before each SSE reconnect attempt. Defaults to
|
|
79
79
|
* `reconnectDelayMs` from `utils/reconnect`.
|
|
@@ -97,13 +97,10 @@ interface ProtocolWebSocketTransportOptions {
|
|
|
97
97
|
* Set to 0 to disable automatic reconnection.
|
|
98
98
|
*/
|
|
99
99
|
maxReconnectAttempts?: number;
|
|
100
|
-
/**
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
attempt: number;
|
|
105
|
-
cause: unknown;
|
|
106
|
-
}) => void;
|
|
100
|
+
/** Called before each reconnect attempt with its scheduled delay. */
|
|
101
|
+
onReconnect?: (options: ReconnectInfo) => void;
|
|
102
|
+
/** Called after the initial connection and every successful reconnect. */
|
|
103
|
+
onConnected?: (options: ConnectedInfo) => void | Promise<void>;
|
|
107
104
|
/**
|
|
108
105
|
* Invoked after the socket has been re-established. Use to restore
|
|
109
106
|
* server-side subscription state (see `ThreadStream`).
|
|
@@ -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":";;;;;;KAKY,mBAAA,IACV,GAAA,EAAK,GAAA,EACL,IAAA,EAAM,WAAA,KACH,OAAA,CAAQ,WAAA,IAAe,WAAA;;AAH5B;;;;;;KAYY,YAAA,cAA0B,QAAA;AAAA,UAErB,sBAAA;EACf,QAAA,GAAW,YAAA;EACX,MAAA,GAAS,YAAA;EAfJ;EAiBL,KAAA,GAAQ,YAAA;AAAA;AAAA,UAGO,2BAAA;EACf,MAAA;EAnBW;;;;AASb;;EAiBE,QAAA;EACA,cAAA,GAAiB,MAAA,SAAe,WAAA;EAChC,SAAA,GAAY,mBAAA;EACZ,KAAA,UAAe,KAAA;EACf,YAAA,gBAA4B,KAAA,GAAQ,OAAA,QAAe,KAAA;;;;;;EAMnD,WAAA,GAAc,WAAA;EACd,KAAA,GAAQ,sBAAA;EAzBG;;;;;;;AAMb;;EA6BE,oBAAA;EApBgC;;;;;;;;;;;;;;;;;;;;EAyChC,aAAA,GAAgB,iBAAA;EAxCJ;EA0CZ,WAAA,IAAe,OAAA,EAAS,aAAA;EAzCT;EA2Cf,WAAA,IAAe,OAAA,EAAS,aAAA,YAAyB,OAAA;EA1CrB;;;;EA+C5B,gBAAA,IAAoB,OAAA;AAAA;AAAA,UAGL,iCAAA;EACf,MAAA;EAbA;;;;EAkBA,QAAA;EACA,cAAA,GAAiB,MAAA,SAAe,WAAA;EAChC,SAAA,GAAY,mBAAA;EACZ,gBAAA,IAAoB,GAAA,aAAgB,SAAA;EACpC,KAAA,GAAQ,IAAA,CAAK,sBAAA;EAbb;;;;AAGF;EAgBE,oBAAA;;EAEA,WAAA,IAAe,OAAA,EAAS,aAAA;EAXP;EAajB,WAAA,IAAe,OAAA,EAAS,aAAA,YAAyB,OAAA;EAXb;;;;EAgBpC,aAAA,gBAA6B,OAAA;EALoB;;;;EAUjD,gBAAA,IAAoB,OAAA;AAAA;AAAA,KAGV,WAAA"}
|
|
@@ -29,6 +29,7 @@ var ProtocolWebSocketTransportAdapter = class {
|
|
|
29
29
|
paths;
|
|
30
30
|
maxReconnectAttempts;
|
|
31
31
|
onReconnect;
|
|
32
|
+
onConnected;
|
|
32
33
|
reconnectDelayMs;
|
|
33
34
|
onReconnected;
|
|
34
35
|
pending = /* @__PURE__ */ new Map();
|
|
@@ -45,6 +46,7 @@ var ProtocolWebSocketTransportAdapter = class {
|
|
|
45
46
|
this.paths = options.paths;
|
|
46
47
|
this.maxReconnectAttempts = options.maxReconnectAttempts ?? 5;
|
|
47
48
|
this.onReconnect = options.onReconnect;
|
|
49
|
+
this.onConnected = options.onConnected;
|
|
48
50
|
this.onReconnected = options.onReconnected;
|
|
49
51
|
this.reconnectDelayMs = options.reconnectDelayMs ?? require_reconnect.reconnectDelayMs;
|
|
50
52
|
}
|
|
@@ -71,6 +73,7 @@ var ProtocolWebSocketTransportAdapter = class {
|
|
|
71
73
|
this.onReconnected = handler;
|
|
72
74
|
}
|
|
73
75
|
async open() {
|
|
76
|
+
const reconnecting = this.reconnectInFlight != null;
|
|
74
77
|
if (this.closed) throw new Error("Protocol WebSocket transport is closed.");
|
|
75
78
|
if (this.socket?.readyState === WEB_SOCKET_OPEN) return;
|
|
76
79
|
if (this.socket != null) {
|
|
@@ -99,6 +102,10 @@ var ProtocolWebSocketTransportAdapter = class {
|
|
|
99
102
|
socket.addEventListener("open", onOpen, { once: true });
|
|
100
103
|
socket.addEventListener("error", onError, { once: true });
|
|
101
104
|
});
|
|
105
|
+
if (!reconnecting) await this.onConnected?.({
|
|
106
|
+
kind: "initial",
|
|
107
|
+
attempt: 0
|
|
108
|
+
});
|
|
102
109
|
}
|
|
103
110
|
async send(command) {
|
|
104
111
|
return await this.sendCommand(command);
|
|
@@ -218,7 +225,7 @@ var ProtocolWebSocketTransportAdapter = class {
|
|
|
218
225
|
#scheduleReconnect(cause) {
|
|
219
226
|
if (this.closed || this.intentionalClose) return;
|
|
220
227
|
if (this.reconnectInFlight != null) return;
|
|
221
|
-
this.reconnectInFlight = this.#runReconnectLoop(cause).finally(() => {
|
|
228
|
+
this.reconnectInFlight = Promise.resolve().then(() => this.#runReconnectLoop(cause)).finally(() => {
|
|
222
229
|
this.reconnectInFlight = null;
|
|
223
230
|
});
|
|
224
231
|
}
|
|
@@ -226,11 +233,12 @@ var ProtocolWebSocketTransportAdapter = class {
|
|
|
226
233
|
let lastError = initialCause;
|
|
227
234
|
for (let attempt = 1; attempt <= this.maxReconnectAttempts; attempt += 1) {
|
|
228
235
|
if (this.closed || this.intentionalClose) return;
|
|
236
|
+
const delay = this.reconnectDelayMs(attempt);
|
|
229
237
|
this.onReconnect?.({
|
|
230
238
|
attempt,
|
|
231
|
-
cause: lastError
|
|
239
|
+
cause: lastError,
|
|
240
|
+
delayMs: delay
|
|
232
241
|
});
|
|
233
|
-
const delay = this.reconnectDelayMs(attempt);
|
|
234
242
|
if (delay > 0) await new Promise((resolve) => {
|
|
235
243
|
setTimeout(resolve, delay);
|
|
236
244
|
});
|
|
@@ -238,6 +246,10 @@ var ProtocolWebSocketTransportAdapter = class {
|
|
|
238
246
|
try {
|
|
239
247
|
await this.open();
|
|
240
248
|
if (this.onReconnected) await this.onReconnected();
|
|
249
|
+
await this.onConnected?.({
|
|
250
|
+
kind: "reconnected",
|
|
251
|
+
attempt
|
|
252
|
+
});
|
|
241
253
|
return;
|
|
242
254
|
} catch (error) {
|
|
243
255
|
lastError = error;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"websocket.cjs","names":["AsyncQueue","reconnectDelayMs","resolveProtocolPath","#detachSocket","toWebSocketUrl","toAbsoluteUrl","#attachSocket","hasHeaders","toError","isRecord","#handleUnexpectedDisconnect","#scheduleReconnect","#runReconnectLoop","MaxWebSocketReconnectAttemptsError"],"sources":["../../../../src/client/stream/transport/websocket.ts"],"sourcesContent":["import { AsyncQueue } from \"./queue.js\";\nimport type {\n Message,\n Command,\n CommandResponse,\n ErrorResponse,\n} from \"@langchain/protocol\";\n\nimport {\n toAbsoluteUrl,\n toWebSocketUrl,\n isRecord,\n hasHeaders,\n toError,\n resolveProtocolPath,\n} from \"./utils.js\";\nimport type {\n HeaderValue,\n ProtocolRequestHook,\n PendingResponse,\n ProtocolTransportPaths,\n ProtocolWebSocketTransportOptions,\n} from \"./types.js\";\nimport type { TransportAdapter } from \"../transport.js\";\nimport { MaxWebSocketReconnectAttemptsError } from \"../error.js\";\nimport {\n DEFAULT_MAX_RECONNECT_ATTEMPTS,\n reconnectDelayMs,\n} from \"../../../utils/reconnect.js\";\n\nconst WEB_SOCKET_CONNECTING = 0;\nconst WEB_SOCKET_OPEN = 1;\nconst WEB_SOCKET_CLOSED = 3;\n\n/**\n * Reconnect tuning for {@link ProtocolWebSocketTransportAdapter}. A subset\n * of {@link ProtocolWebSocketTransportOptions}.\n */\nexport interface WebSocketReconnectOptions {\n /**\n * Maximum reconnection attempts after an unexpected disconnect.\n * Defaults to {@link DEFAULT_MAX_RECONNECT_ATTEMPTS}.\n */\n maxReconnectAttempts?: number;\n\n /**\n * Invoked before each reconnect attempt (after backoff).\n */\n onReconnect?: (options: { attempt: number; cause: unknown }) => void;\n}\n\n/**\n * Transport adapter that speaks the thread-centric protocol over a\n * bidirectional WebSocket. Bound to a `threadId` at construction or later\n * via {@link setThreadId} — the socket connects to\n * `ws://.../threads/:thread_id/stream/events`.\n *\n * On unexpected disconnect the adapter reconnects with exponential\n * backoff (see {@link ProtocolWebSocketTransportOptions.maxReconnectAttempts}).\n * The server replays buffered events on the new socket; the SDK\n * deduplicates by `event_id`. {@link ProtocolWebSocketTransportOptions.onReconnected}\n * runs after each successful reconnect so `ThreadStream` can re-issue\n * `subscription.subscribe` commands.\n */\nexport class ProtocolWebSocketTransportAdapter implements TransportAdapter {\n threadId: string;\n\n private readonly queue = new AsyncQueue<Message>();\n\n private readonly apiUrl: string;\n\n private readonly defaultHeaders?: Record<string, HeaderValue>;\n\n private readonly onRequest?: ProtocolRequestHook;\n\n private readonly webSocketFactory: (url: string) => WebSocket;\n\n private readonly paths?: Pick<ProtocolTransportPaths, \"stream\">;\n\n private readonly maxReconnectAttempts: number;\n\n private readonly onReconnect?: ProtocolWebSocketTransportOptions[\"onReconnect\"];\n\n private readonly reconnectDelayMs: (attempt: number) => number;\n\n private onReconnected?: () => void | Promise<void>;\n\n private readonly pending = new Map<number, PendingResponse>();\n\n private socket: WebSocket | null = null;\n\n private closed = false;\n\n private intentionalClose = false;\n\n private reconnectInFlight: Promise<void> | null = null;\n\n constructor(options: ProtocolWebSocketTransportOptions) {\n this.apiUrl = options.apiUrl;\n this.threadId = options.threadId ?? \"\";\n this.defaultHeaders = options.defaultHeaders;\n this.onRequest = options.onRequest;\n this.webSocketFactory =\n options.webSocketFactory ?? ((url) => new WebSocket(url));\n this.paths = options.paths;\n this.maxReconnectAttempts =\n options.maxReconnectAttempts ?? DEFAULT_MAX_RECONNECT_ATTEMPTS;\n this.onReconnect = options.onReconnect;\n this.onReconnected = options.onReconnected;\n this.reconnectDelayMs = options.reconnectDelayMs ?? reconnectDelayMs;\n }\n\n /** {@inheritDoc TransportAdapter.setThreadId} */\n setThreadId(threadId: string): void {\n if (threadId === this.threadId) {\n return;\n }\n if (\n this.reconnectInFlight != null ||\n (this.socket != null && this.socket.readyState !== WEB_SOCKET_CLOSED)\n ) {\n throw new Error(\n \"Protocol WebSocket transport cannot be rebound to a different thread while the socket is open. Close the current stream and create a new WebSocket transport for the new thread.\"\n );\n }\n this.threadId = threadId;\n }\n\n /**\n * Socket URL derives from the currently-bound thread so a single adapter\n * can follow {@link setThreadId} re-binds; the next {@link open} connects\n * to the new thread. A fixed `paths.stream` string overrides the default.\n */\n private get streamUrl(): string {\n return resolveProtocolPath(\n this.paths?.stream,\n this.threadId,\n (id) => `/threads/${id}/stream/events`\n );\n }\n\n /**\n * Register a callback invoked after each successful reconnect. Used\n * by {@link ThreadStream} to re-send active `subscription.subscribe`\n * commands.\n */\n setOnReconnected(handler: () => void | Promise<void>): void {\n this.onReconnected = handler;\n }\n\n async open(): Promise<void> {\n if (this.closed) {\n throw new Error(\"Protocol WebSocket transport is closed.\");\n }\n if (this.socket?.readyState === WEB_SOCKET_OPEN) {\n return;\n }\n if (this.socket != null) {\n this.#detachSocket(this.socket);\n this.socket = null;\n }\n\n this.assertBrowserSafeTransportConfig();\n\n const wsUrl = toWebSocketUrl(\n toAbsoluteUrl(this.apiUrl, this.streamUrl).toString()\n );\n const socket = this.webSocketFactory(wsUrl);\n this.socket = socket;\n this.intentionalClose = false;\n\n this.#attachSocket(socket);\n\n await new Promise<void>((resolve, reject) => {\n const onOpen = () => {\n cleanup();\n resolve();\n };\n const onError = () => {\n cleanup();\n reject(new Error(\"Failed to open protocol WebSocket.\"));\n };\n const cleanup = () => {\n socket.removeEventListener(\"open\", onOpen);\n socket.removeEventListener(\"error\", onError);\n };\n socket.addEventListener(\"open\", onOpen, { once: true });\n socket.addEventListener(\"error\", onError, { once: true });\n });\n }\n\n async send(\n command: Command\n ): Promise<CommandResponse | ErrorResponse | void> {\n return await this.sendCommand(command);\n }\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 async close(): Promise<void> {\n if (this.closed) {\n return;\n }\n\n this.closed = true;\n this.intentionalClose = true;\n\n for (const { reject } of this.pending.values()) {\n reject(new Error(\"Protocol WebSocket connection closed.\"));\n }\n this.pending.clear();\n this.queue.close();\n\n const socket = this.socket;\n this.socket = null;\n if (!socket) {\n return;\n }\n\n this.#detachSocket(socket);\n\n await new Promise<void>((resolve) => {\n if (socket.readyState === WEB_SOCKET_CLOSED) {\n resolve();\n return;\n }\n\n const onClose = () => {\n socket.removeEventListener(\"close\", onClose);\n resolve();\n };\n\n socket.addEventListener(\"close\", onClose, { once: true });\n if (\n socket.readyState === WEB_SOCKET_OPEN ||\n socket.readyState === WEB_SOCKET_CONNECTING\n ) {\n socket.close();\n } else {\n resolve();\n }\n });\n }\n\n private assertBrowserSafeTransportConfig(): void {\n if (hasHeaders(this.defaultHeaders) || this.onRequest != null) {\n throw new Error(\n \"Browser WebSocket protocol transport does not support defaultHeaders or onRequest hooks. Supply a custom protocolWebSocketFactory if you need custom WebSocket setup.\"\n );\n }\n }\n\n private async sendCommand(\n command: Command\n ): Promise<CommandResponse | ErrorResponse> {\n // Wait for an in-flight reconnect only when the socket is not usable.\n // After `open()` succeeds, `#runReconnectLoop` may still be awaiting\n // `onReconnected` (e.g. ThreadStream resubscribe). Those callbacks call\n // `sendCommand` and must not await the same `reconnectInFlight` promise.\n let socket = this.socket;\n if (\n this.reconnectInFlight != null &&\n (socket == null || socket.readyState !== WEB_SOCKET_OPEN)\n ) {\n await this.reconnectInFlight.catch(() => undefined);\n socket = this.socket;\n }\n\n if (socket == null || socket.readyState !== WEB_SOCKET_OPEN) {\n throw new Error(\"Protocol WebSocket is not open.\");\n }\n\n return await new Promise<CommandResponse | ErrorResponse>(\n (resolve, reject) => {\n this.pending.set(command.id, { resolve, reject });\n\n try {\n socket.send(JSON.stringify(command));\n } catch (error) {\n this.pending.delete(command.id);\n reject(toError(error));\n }\n }\n );\n }\n\n #attachSocket(socket: WebSocket): void {\n socket.addEventListener(\"message\", this.handleMessage);\n socket.addEventListener(\"close\", this.handleClose);\n socket.addEventListener(\"error\", this.handleSocketError);\n }\n\n #detachSocket(socket: WebSocket): void {\n socket.removeEventListener(\"message\", this.handleMessage);\n socket.removeEventListener(\"close\", this.handleClose);\n socket.removeEventListener(\"error\", this.handleSocketError);\n }\n\n private readonly handleMessage = (event: MessageEvent): void => {\n let payload: unknown;\n try {\n payload = JSON.parse(String(event.data));\n } catch {\n return;\n }\n\n if (\n isRecord(payload) &&\n typeof payload.id === \"number\" &&\n (payload.type === \"success\" || payload.type === \"error\")\n ) {\n const pending = this.pending.get(payload.id);\n if (pending) {\n this.pending.delete(payload.id);\n pending.resolve(payload as CommandResponse | ErrorResponse);\n }\n return;\n }\n\n if (isRecord(payload) && payload.type === \"event\") {\n this.queue.push(payload as Message);\n }\n };\n\n private readonly handleClose = (): void => {\n const socket = this.socket;\n if (socket != null) {\n this.#detachSocket(socket);\n }\n this.socket = null;\n\n if (this.intentionalClose || this.closed) {\n this.queue.close();\n return;\n }\n\n this.#handleUnexpectedDisconnect(\n new Error(\"Protocol WebSocket closed unexpectedly.\")\n );\n };\n\n private readonly handleSocketError = (): void => {\n if (this.closed || this.intentionalClose) {\n return;\n }\n\n this.#handleUnexpectedDisconnect(\n new Error(\"Protocol WebSocket encountered an error.\")\n );\n };\n\n #handleUnexpectedDisconnect(cause: unknown): void {\n const error = toError(cause);\n for (const { reject } of this.pending.values()) {\n reject(error);\n }\n this.pending.clear();\n\n if (this.maxReconnectAttempts <= 0) {\n this.queue.close(error);\n return;\n }\n\n this.#scheduleReconnect(cause);\n }\n\n #scheduleReconnect(cause: unknown): void {\n if (this.closed || this.intentionalClose) {\n return;\n }\n if (this.reconnectInFlight != null) {\n return;\n }\n\n this.reconnectInFlight = this.#runReconnectLoop(cause).finally(() => {\n this.reconnectInFlight = null;\n });\n }\n\n async #runReconnectLoop(initialCause: unknown): Promise<void> {\n let lastError: unknown = initialCause;\n\n for (let attempt = 1; attempt <= this.maxReconnectAttempts; attempt += 1) {\n if (this.closed || this.intentionalClose) {\n return;\n }\n\n this.onReconnect?.({ attempt, cause: lastError });\n\n const delay = this.reconnectDelayMs(attempt);\n if (delay > 0) {\n await new Promise<void>((resolve) => {\n setTimeout(resolve, delay);\n });\n }\n\n if (this.closed || this.intentionalClose) {\n return;\n }\n\n try {\n await this.open();\n if (this.onReconnected) {\n await this.onReconnected();\n }\n return;\n } catch (error) {\n lastError = error;\n }\n }\n\n this.queue.close(\n new MaxWebSocketReconnectAttemptsError(\n this.maxReconnectAttempts,\n lastError\n )\n );\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"websocket.cjs","names":["AsyncQueue","reconnectDelayMs","resolveProtocolPath","#detachSocket","toWebSocketUrl","toAbsoluteUrl","#attachSocket","hasHeaders","toError","isRecord","#handleUnexpectedDisconnect","#scheduleReconnect","#runReconnectLoop","MaxWebSocketReconnectAttemptsError"],"sources":["../../../../src/client/stream/transport/websocket.ts"],"sourcesContent":["import { AsyncQueue } from \"./queue.js\";\nimport type {\n Message,\n Command,\n CommandResponse,\n ErrorResponse,\n} from \"@langchain/protocol\";\n\nimport {\n toAbsoluteUrl,\n toWebSocketUrl,\n isRecord,\n hasHeaders,\n toError,\n resolveProtocolPath,\n} from \"./utils.js\";\nimport type {\n HeaderValue,\n ProtocolRequestHook,\n PendingResponse,\n ProtocolTransportPaths,\n ProtocolWebSocketTransportOptions,\n} from \"./types.js\";\nimport type { TransportAdapter } from \"../transport.js\";\nimport { MaxWebSocketReconnectAttemptsError } from \"../error.js\";\nimport {\n DEFAULT_MAX_RECONNECT_ATTEMPTS,\n reconnectDelayMs,\n type ReconnectInfo,\n} from \"../../../utils/reconnect.js\";\n\nconst WEB_SOCKET_CONNECTING = 0;\nconst WEB_SOCKET_OPEN = 1;\nconst WEB_SOCKET_CLOSED = 3;\n\n/**\n * Reconnect tuning for {@link ProtocolWebSocketTransportAdapter}. A subset\n * of {@link ProtocolWebSocketTransportOptions}.\n */\nexport interface WebSocketReconnectOptions {\n /**\n * Maximum reconnection attempts after an unexpected disconnect.\n * Defaults to {@link DEFAULT_MAX_RECONNECT_ATTEMPTS}.\n */\n maxReconnectAttempts?: number;\n\n /** Invoked before each reconnect attempt with its scheduled delay. */\n onReconnect?: (options: ReconnectInfo) => void;\n}\n\n/**\n * Transport adapter that speaks the thread-centric protocol over a\n * bidirectional WebSocket. Bound to a `threadId` at construction or later\n * via {@link setThreadId} — the socket connects to\n * `ws://.../threads/:thread_id/stream/events`.\n *\n * On unexpected disconnect the adapter reconnects with exponential\n * backoff (see {@link ProtocolWebSocketTransportOptions.maxReconnectAttempts}).\n * The server replays buffered events on the new socket; the SDK\n * deduplicates by `event_id`. {@link ProtocolWebSocketTransportOptions.onReconnected}\n * runs after each successful reconnect so `ThreadStream` can re-issue\n * `subscription.subscribe` commands.\n */\nexport class ProtocolWebSocketTransportAdapter implements TransportAdapter {\n threadId: string;\n\n private readonly queue = new AsyncQueue<Message>();\n\n private readonly apiUrl: string;\n\n private readonly defaultHeaders?: Record<string, HeaderValue>;\n\n private readonly onRequest?: ProtocolRequestHook;\n\n private readonly webSocketFactory: (url: string) => WebSocket;\n\n private readonly paths?: Pick<ProtocolTransportPaths, \"stream\">;\n\n private readonly maxReconnectAttempts: number;\n\n private readonly onReconnect?: ProtocolWebSocketTransportOptions[\"onReconnect\"];\n\n private readonly onConnected?: ProtocolWebSocketTransportOptions[\"onConnected\"];\n\n private readonly reconnectDelayMs: (attempt: number) => number;\n\n private onReconnected?: () => void | Promise<void>;\n\n private readonly pending = new Map<number, PendingResponse>();\n\n private socket: WebSocket | null = null;\n\n private closed = false;\n\n private intentionalClose = false;\n\n private reconnectInFlight: Promise<void> | null = null;\n\n constructor(options: ProtocolWebSocketTransportOptions) {\n this.apiUrl = options.apiUrl;\n this.threadId = options.threadId ?? \"\";\n this.defaultHeaders = options.defaultHeaders;\n this.onRequest = options.onRequest;\n this.webSocketFactory =\n options.webSocketFactory ?? ((url) => new WebSocket(url));\n this.paths = options.paths;\n this.maxReconnectAttempts =\n options.maxReconnectAttempts ?? DEFAULT_MAX_RECONNECT_ATTEMPTS;\n this.onReconnect = options.onReconnect;\n this.onConnected = options.onConnected;\n this.onReconnected = options.onReconnected;\n this.reconnectDelayMs = options.reconnectDelayMs ?? reconnectDelayMs;\n }\n\n /** {@inheritDoc TransportAdapter.setThreadId} */\n setThreadId(threadId: string): void {\n if (threadId === this.threadId) {\n return;\n }\n if (\n this.reconnectInFlight != null ||\n (this.socket != null && this.socket.readyState !== WEB_SOCKET_CLOSED)\n ) {\n throw new Error(\n \"Protocol WebSocket transport cannot be rebound to a different thread while the socket is open. Close the current stream and create a new WebSocket transport for the new thread.\"\n );\n }\n this.threadId = threadId;\n }\n\n /**\n * Socket URL derives from the currently-bound thread so a single adapter\n * can follow {@link setThreadId} re-binds; the next {@link open} connects\n * to the new thread. A fixed `paths.stream` string overrides the default.\n */\n private get streamUrl(): string {\n return resolveProtocolPath(\n this.paths?.stream,\n this.threadId,\n (id) => `/threads/${id}/stream/events`\n );\n }\n\n /**\n * Register a callback invoked after each successful reconnect. Used\n * by {@link ThreadStream} to re-send active `subscription.subscribe`\n * commands.\n */\n setOnReconnected(handler: () => void | Promise<void>): void {\n this.onReconnected = handler;\n }\n\n async open(): Promise<void> {\n const reconnecting = this.reconnectInFlight != null;\n if (this.closed) {\n throw new Error(\"Protocol WebSocket transport is closed.\");\n }\n if (this.socket?.readyState === WEB_SOCKET_OPEN) {\n return;\n }\n if (this.socket != null) {\n this.#detachSocket(this.socket);\n this.socket = null;\n }\n\n this.assertBrowserSafeTransportConfig();\n\n const wsUrl = toWebSocketUrl(\n toAbsoluteUrl(this.apiUrl, this.streamUrl).toString()\n );\n const socket = this.webSocketFactory(wsUrl);\n this.socket = socket;\n this.intentionalClose = false;\n\n this.#attachSocket(socket);\n\n await new Promise<void>((resolve, reject) => {\n const onOpen = () => {\n cleanup();\n resolve();\n };\n const onError = () => {\n cleanup();\n reject(new Error(\"Failed to open protocol WebSocket.\"));\n };\n const cleanup = () => {\n socket.removeEventListener(\"open\", onOpen);\n socket.removeEventListener(\"error\", onError);\n };\n socket.addEventListener(\"open\", onOpen, { once: true });\n socket.addEventListener(\"error\", onError, { once: true });\n });\n\n if (!reconnecting) {\n await this.onConnected?.({ kind: \"initial\", attempt: 0 });\n }\n }\n\n async send(\n command: Command\n ): Promise<CommandResponse | ErrorResponse | void> {\n return await this.sendCommand(command);\n }\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 async close(): Promise<void> {\n if (this.closed) {\n return;\n }\n\n this.closed = true;\n this.intentionalClose = true;\n\n for (const { reject } of this.pending.values()) {\n reject(new Error(\"Protocol WebSocket connection closed.\"));\n }\n this.pending.clear();\n this.queue.close();\n\n const socket = this.socket;\n this.socket = null;\n if (!socket) {\n return;\n }\n\n this.#detachSocket(socket);\n\n await new Promise<void>((resolve) => {\n if (socket.readyState === WEB_SOCKET_CLOSED) {\n resolve();\n return;\n }\n\n const onClose = () => {\n socket.removeEventListener(\"close\", onClose);\n resolve();\n };\n\n socket.addEventListener(\"close\", onClose, { once: true });\n if (\n socket.readyState === WEB_SOCKET_OPEN ||\n socket.readyState === WEB_SOCKET_CONNECTING\n ) {\n socket.close();\n } else {\n resolve();\n }\n });\n }\n\n private assertBrowserSafeTransportConfig(): void {\n if (hasHeaders(this.defaultHeaders) || this.onRequest != null) {\n throw new Error(\n \"Browser WebSocket protocol transport does not support defaultHeaders or onRequest hooks. Supply a custom protocolWebSocketFactory if you need custom WebSocket setup.\"\n );\n }\n }\n\n private async sendCommand(\n command: Command\n ): Promise<CommandResponse | ErrorResponse> {\n // Wait for an in-flight reconnect only when the socket is not usable.\n // After `open()` succeeds, `#runReconnectLoop` may still be awaiting\n // `onReconnected` (e.g. ThreadStream resubscribe). Those callbacks call\n // `sendCommand` and must not await the same `reconnectInFlight` promise.\n let socket = this.socket;\n if (\n this.reconnectInFlight != null &&\n (socket == null || socket.readyState !== WEB_SOCKET_OPEN)\n ) {\n await this.reconnectInFlight.catch(() => undefined);\n socket = this.socket;\n }\n\n if (socket == null || socket.readyState !== WEB_SOCKET_OPEN) {\n throw new Error(\"Protocol WebSocket is not open.\");\n }\n\n return await new Promise<CommandResponse | ErrorResponse>(\n (resolve, reject) => {\n this.pending.set(command.id, { resolve, reject });\n\n try {\n socket.send(JSON.stringify(command));\n } catch (error) {\n this.pending.delete(command.id);\n reject(toError(error));\n }\n }\n );\n }\n\n #attachSocket(socket: WebSocket): void {\n socket.addEventListener(\"message\", this.handleMessage);\n socket.addEventListener(\"close\", this.handleClose);\n socket.addEventListener(\"error\", this.handleSocketError);\n }\n\n #detachSocket(socket: WebSocket): void {\n socket.removeEventListener(\"message\", this.handleMessage);\n socket.removeEventListener(\"close\", this.handleClose);\n socket.removeEventListener(\"error\", this.handleSocketError);\n }\n\n private readonly handleMessage = (event: MessageEvent): void => {\n let payload: unknown;\n try {\n payload = JSON.parse(String(event.data));\n } catch {\n return;\n }\n\n if (\n isRecord(payload) &&\n typeof payload.id === \"number\" &&\n (payload.type === \"success\" || payload.type === \"error\")\n ) {\n const pending = this.pending.get(payload.id);\n if (pending) {\n this.pending.delete(payload.id);\n pending.resolve(payload as CommandResponse | ErrorResponse);\n }\n return;\n }\n\n if (isRecord(payload) && payload.type === \"event\") {\n this.queue.push(payload as Message);\n }\n };\n\n private readonly handleClose = (): void => {\n const socket = this.socket;\n if (socket != null) {\n this.#detachSocket(socket);\n }\n this.socket = null;\n\n if (this.intentionalClose || this.closed) {\n this.queue.close();\n return;\n }\n\n this.#handleUnexpectedDisconnect(\n new Error(\"Protocol WebSocket closed unexpectedly.\")\n );\n };\n\n private readonly handleSocketError = (): void => {\n if (this.closed || this.intentionalClose) {\n return;\n }\n\n this.#handleUnexpectedDisconnect(\n new Error(\"Protocol WebSocket encountered an error.\")\n );\n };\n\n #handleUnexpectedDisconnect(cause: unknown): void {\n const error = toError(cause);\n for (const { reject } of this.pending.values()) {\n reject(error);\n }\n this.pending.clear();\n\n if (this.maxReconnectAttempts <= 0) {\n this.queue.close(error);\n return;\n }\n\n this.#scheduleReconnect(cause);\n }\n\n #scheduleReconnect(cause: unknown): void {\n if (this.closed || this.intentionalClose) {\n return;\n }\n if (this.reconnectInFlight != null) {\n return;\n }\n\n this.reconnectInFlight = Promise.resolve()\n .then(() => this.#runReconnectLoop(cause))\n .finally(() => {\n this.reconnectInFlight = null;\n });\n }\n\n async #runReconnectLoop(initialCause: unknown): Promise<void> {\n let lastError: unknown = initialCause;\n\n for (let attempt = 1; attempt <= this.maxReconnectAttempts; attempt += 1) {\n if (this.closed || this.intentionalClose) {\n return;\n }\n\n const delay = this.reconnectDelayMs(attempt);\n this.onReconnect?.({ attempt, cause: lastError, delayMs: delay });\n if (delay > 0) {\n await new Promise<void>((resolve) => {\n setTimeout(resolve, delay);\n });\n }\n\n if (this.closed || this.intentionalClose) {\n return;\n }\n\n try {\n await this.open();\n if (this.onReconnected) {\n await this.onReconnected();\n }\n await this.onConnected?.({ kind: \"reconnected\", attempt });\n return;\n } catch (error) {\n lastError = error;\n }\n }\n\n this.queue.close(\n new MaxWebSocketReconnectAttemptsError(\n this.maxReconnectAttempts,\n lastError\n )\n );\n }\n}\n"],"mappings":"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"}
|
|
@@ -27,6 +27,7 @@ declare class ProtocolWebSocketTransportAdapter implements TransportAdapter {
|
|
|
27
27
|
private readonly paths?;
|
|
28
28
|
private readonly maxReconnectAttempts;
|
|
29
29
|
private readonly onReconnect?;
|
|
30
|
+
private readonly onConnected?;
|
|
30
31
|
private readonly reconnectDelayMs;
|
|
31
32
|
private onReconnected?;
|
|
32
33
|
private readonly pending;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"websocket.d.cts","names":[],"sources":["../../../../src/client/stream/transport/websocket.ts"],"mappings":";;;;;;;;;;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"websocket.d.cts","names":[],"sources":["../../../../src/client/stream/transport/websocket.ts"],"mappings":";;;;;;;;;;;;;;;;;;cA+Da,iCAAA,YAA6C,gBAAA;EAAA;EACxD,QAAA;EAAA,iBAEiB,KAAA;EAAA,iBAEA,MAAA;EAAA,iBAEA,cAAA;EAAA,iBAEA,SAAA;EAAA,iBAEA,gBAAA;EAAA,iBAEA,KAAA;EAAA,iBAEA,oBAAA;EAAA,iBAEA,WAAA;EAAA,iBAEA,WAAA;EAAA,iBAEA,gBAAA;EAAA,QAET,aAAA;EAAA,iBAES,OAAA;EAAA,QAET,MAAA;EAAA,QAEA,MAAA;EAAA,QAEA,gBAAA;EAAA,QAEA,iBAAA;EAER,WAAA,CAAY,OAAA,EAAS,iCAAA;EAsDP;EArCd,WAAA,CAAY,QAAA;EAoFD;;;;;EAAA,YAhEC,SAAA,CAAA;EAqEF;;;;;EAxDV,gBAAA,CAAiB,OAAA,eAAsB,OAAA;EAIjC,IAAA,CAAA,GAAQ,OAAA;EA8CR,IAAA,CACJ,OAAA,EAAS,OAAA,GACR,OAAA,CAAQ,eAAA,GAAkB,aAAA;EAI7B,MAAA,CAAA,GAAU,aAAA,CAAc,OAAA;EAalB,KAAA,CAAA,GAAS,OAAA;EAAA,QA6CP,gCAAA;EAAA,QAQM,WAAA;EAAA,iBA8CG,aAAA;EAAA,iBA0BA,WAAA;EAAA,iBAiBA,iBAAA;AAAA"}
|