@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.
Files changed (47) hide show
  1. package/dist/client/stream/transport/http.cjs +9 -2
  2. package/dist/client/stream/transport/http.cjs.map +1 -1
  3. package/dist/client/stream/transport/http.d.cts +1 -0
  4. package/dist/client/stream/transport/http.d.cts.map +1 -1
  5. package/dist/client/stream/transport/http.d.ts +1 -0
  6. package/dist/client/stream/transport/http.d.ts.map +1 -1
  7. package/dist/client/stream/transport/http.js +9 -2
  8. package/dist/client/stream/transport/http.js.map +1 -1
  9. package/dist/client/stream/transport/types.d.cts +9 -12
  10. package/dist/client/stream/transport/types.d.cts.map +1 -1
  11. package/dist/client/stream/transport/types.d.ts +9 -12
  12. package/dist/client/stream/transport/types.d.ts.map +1 -1
  13. package/dist/client/stream/transport/websocket.cjs +15 -3
  14. package/dist/client/stream/transport/websocket.cjs.map +1 -1
  15. package/dist/client/stream/transport/websocket.d.cts +1 -0
  16. package/dist/client/stream/transport/websocket.d.cts.map +1 -1
  17. package/dist/client/stream/transport/websocket.d.ts +1 -0
  18. package/dist/client/stream/transport/websocket.d.ts.map +1 -1
  19. package/dist/client/stream/transport/websocket.js +15 -3
  20. package/dist/client/stream/transport/websocket.js.map +1 -1
  21. package/dist/client/stream/types.d.cts +5 -7
  22. package/dist/client/stream/types.d.cts.map +1 -1
  23. package/dist/client/stream/types.d.ts +5 -7
  24. package/dist/client/stream/types.d.ts.map +1 -1
  25. package/dist/client/threads/index.cjs +2 -1
  26. package/dist/client/threads/index.cjs.map +1 -1
  27. package/dist/client/threads/index.js +2 -1
  28. package/dist/client/threads/index.js.map +1 -1
  29. package/dist/schema.d.cts +5 -0
  30. package/dist/schema.d.cts.map +1 -1
  31. package/dist/schema.d.ts +5 -0
  32. package/dist/schema.d.ts.map +1 -1
  33. package/dist/stream/controller.cjs +32 -4
  34. package/dist/stream/controller.cjs.map +1 -1
  35. package/dist/stream/controller.js +32 -4
  36. package/dist/stream/controller.js.map +1 -1
  37. package/dist/stream/types.d.cts +5 -0
  38. package/dist/stream/types.d.cts.map +1 -1
  39. package/dist/stream/types.d.ts +5 -0
  40. package/dist/stream/types.d.ts.map +1 -1
  41. package/dist/utils/reconnect.cjs.map +1 -1
  42. package/dist/utils/reconnect.d.cts +18 -0
  43. package/dist/utils/reconnect.d.cts.map +1 -0
  44. package/dist/utils/reconnect.d.ts +18 -0
  45. package/dist/utils/reconnect.d.ts.map +1 -0
  46. package/dist/utils/reconnect.js.map +1 -1
  47. 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;EAgDjB,WAAA,CAAY,QAAA;EA5CK;;;;;EAAA,YAqDL,WAAA,CAAA;EAAA,YAQA,SAAA,CAAA;EAAA,YAQA,QAAA,CAAA;EArDK;;;;;;;EAoEX,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;EAmLpC,KAAA,CAAA,GAAS,OAAA;EAAA,QAWD,OAAA;AAAA"}
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;EAgDjB,WAAA,CAAY,QAAA;EA5CK;;;;;EAAA,YAqDL,WAAA,CAAA;EAAA,YAQA,SAAA,CAAA;EAAA,YAQA,QAAA,CAAA;EArDK;;;;;;;EAoEX,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;EAmLpC,KAAA,CAAA,GAAS,OAAA;EAAA,QAWD,OAAA;AAAA"}
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 (after backoff delay). */
73
- onReconnect?: (options: {
74
- attempt: number;
75
- cause: unknown;
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
- * Called before each reconnect attempt (after backoff delay).
102
- */
103
- onReconnect?: (options: {
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":";;;;;KAIY,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;EAtChB;EAwCA,WAAA,IAAe,OAAA;IAAW,OAAA;IAAiB,KAAA;EAAA;EAlC7B;;;;EAuCd,gBAAA,IAAoB,OAAA;AAAA;AAAA,UAGL,iCAAA;EACf,MAAA;EAT2C;;;;EAc3C,QAAA;EACA,cAAA,GAAiB,MAAA,SAAe,WAAA;EAChC,SAAA,GAAY,mBAAA;EACZ,gBAAA,IAAoB,GAAA,aAAgB,SAAA;EACpC,KAAA,GAAQ,IAAA,CAAK,sBAAA;EAHmB;;;;;EAShC,oBAAA;EAS6B;;;EAL7B,WAAA,IAAe,OAAA;IAAW,OAAA;IAAiB,KAAA;EAAA;EAbX;;;;EAkBhC,aAAA,gBAA6B,OAAA;EAhBO;;;;EAqBpC,gBAAA,IAAoB,OAAA;AAAA;AAAA,KAGV,WAAA"}
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 (after backoff delay). */
73
- onReconnect?: (options: {
74
- attempt: number;
75
- cause: unknown;
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
- * Called before each reconnect attempt (after backoff delay).
102
- */
103
- onReconnect?: (options: {
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":";;;;;KAIY,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;EAtChB;EAwCA,WAAA,IAAe,OAAA;IAAW,OAAA;IAAiB,KAAA;EAAA;EAlC7B;;;;EAuCd,gBAAA,IAAoB,OAAA;AAAA;AAAA,UAGL,iCAAA;EACf,MAAA;EAT2C;;;;EAc3C,QAAA;EACA,cAAA,GAAiB,MAAA,SAAe,WAAA;EAChC,SAAA,GAAY,mBAAA;EACZ,gBAAA,IAAoB,GAAA,aAAgB,SAAA;EACpC,KAAA,GAAQ,IAAA,CAAK,sBAAA;EAHmB;;;;;EAShC,oBAAA;EAS6B;;;EAL7B,WAAA,IAAe,OAAA;IAAW,OAAA;IAAiB,KAAA;EAAA;EAbX;;;;EAkBhC,aAAA,gBAA6B,OAAA;EAhBO;;;;EAqBpC,gBAAA,IAAoB,OAAA;AAAA;AAAA,KAGV,WAAA"}
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":";;;;;;;;;;;;;;;;;;cAgEa,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,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;EAgGlB;EAhFH,WAAA,CAAY,QAAA;EAgFiB;;;;;EAAA,YA5DjB,SAAA,CAAA;EA0HJ;;;;;EA7GR,gBAAA,CAAiB,OAAA,eAAsB,OAAA;EAIjC,IAAA,CAAA,GAAQ,OAAA;EAyCR,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"}
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"}