@langchain/langgraph-sdk 1.10.3-rc.1 → 1.11.0

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 (53) hide show
  1. package/dist/client/stream/index.cjs +23 -1
  2. package/dist/client/stream/index.cjs.map +1 -1
  3. package/dist/client/stream/index.d.cts +6 -0
  4. package/dist/client/stream/index.d.cts.map +1 -1
  5. package/dist/client/stream/index.d.ts +6 -0
  6. package/dist/client/stream/index.d.ts.map +1 -1
  7. package/dist/client/stream/index.js +23 -1
  8. package/dist/client/stream/index.js.map +1 -1
  9. package/dist/client/stream/transport/http.cjs +23 -5
  10. package/dist/client/stream/transport/http.cjs.map +1 -1
  11. package/dist/client/stream/transport/http.d.cts +1 -0
  12. package/dist/client/stream/transport/http.d.cts.map +1 -1
  13. package/dist/client/stream/transport/http.d.ts +1 -0
  14. package/dist/client/stream/transport/http.d.ts.map +1 -1
  15. package/dist/client/stream/transport/http.js +23 -5
  16. package/dist/client/stream/transport/http.js.map +1 -1
  17. package/dist/client/stream/transport/types.d.cts +9 -12
  18. package/dist/client/stream/transport/types.d.cts.map +1 -1
  19. package/dist/client/stream/transport/types.d.ts +9 -12
  20. package/dist/client/stream/transport/types.d.ts.map +1 -1
  21. package/dist/client/stream/transport/websocket.cjs +15 -3
  22. package/dist/client/stream/transport/websocket.cjs.map +1 -1
  23. package/dist/client/stream/transport/websocket.d.cts +1 -0
  24. package/dist/client/stream/transport/websocket.d.cts.map +1 -1
  25. package/dist/client/stream/transport/websocket.d.ts +1 -0
  26. package/dist/client/stream/transport/websocket.d.ts.map +1 -1
  27. package/dist/client/stream/transport/websocket.js +15 -3
  28. package/dist/client/stream/transport/websocket.js.map +1 -1
  29. package/dist/client/stream/types.d.cts +5 -7
  30. package/dist/client/stream/types.d.cts.map +1 -1
  31. package/dist/client/stream/types.d.ts +5 -7
  32. package/dist/client/stream/types.d.ts.map +1 -1
  33. package/dist/client/threads/index.cjs +2 -1
  34. package/dist/client/threads/index.cjs.map +1 -1
  35. package/dist/client/threads/index.js +2 -1
  36. package/dist/client/threads/index.js.map +1 -1
  37. package/dist/stream/controller.cjs +111 -31
  38. package/dist/stream/controller.cjs.map +1 -1
  39. package/dist/stream/controller.d.cts.map +1 -1
  40. package/dist/stream/controller.d.ts.map +1 -1
  41. package/dist/stream/controller.js +111 -31
  42. package/dist/stream/controller.js.map +1 -1
  43. package/dist/stream/types.d.cts +5 -0
  44. package/dist/stream/types.d.cts.map +1 -1
  45. package/dist/stream/types.d.ts +5 -0
  46. package/dist/stream/types.d.ts.map +1 -1
  47. package/dist/utils/reconnect.cjs.map +1 -1
  48. package/dist/utils/reconnect.d.cts +18 -0
  49. package/dist/utils/reconnect.d.cts.map +1 -0
  50. package/dist/utils/reconnect.d.ts +18 -0
  51. package/dist/utils/reconnect.d.ts.map +1 -0
  52. package/dist/utils/reconnect.js.map +1 -1
  53. 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;
@@ -139,6 +141,7 @@ var ProtocolSseTransportAdapter = class {
139
141
  let readySettled = false;
140
142
  const startStream = async () => {
141
143
  let attempt = 0;
144
+ let receivedEvent = false;
142
145
  while (!ac.signal.aborted && !this.closed) try {
143
146
  const response = await this.request(streamUrl, {
144
147
  method: "POST",
@@ -156,6 +159,10 @@ var ProtocolSseTransportAdapter = class {
156
159
  }, { stream: true });
157
160
  const contentType = response.headers.get("content-type")?.split(";")[0];
158
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
+ });
159
166
  if (!readySettled) {
160
167
  readySettled = true;
161
168
  resolveReady();
@@ -169,11 +176,21 @@ var ProtocolSseTransportAdapter = class {
169
176
  const iterable = require_stream.IterableReadableStream.fromReadableStream(stream);
170
177
  for await (const event of iterable) {
171
178
  if (ac.signal.aborted || this.closed) break;
172
- if (require_utils.isRecord(event.data)) streamQueue.push(event.data);
179
+ if (require_utils.isRecord(event.data)) {
180
+ receivedEvent = true;
181
+ streamQueue.push(event.data);
182
+ }
183
+ }
184
+ if (ac.signal.aborted || this.closed || this.maxReconnectAttempts <= 0) {
185
+ streamQueue.close();
186
+ return;
173
187
  }
174
- streamQueue.close();
175
- return;
188
+ throw new Error("Event stream closed by the server");
176
189
  } catch (error) {
190
+ if (receivedEvent) {
191
+ attempt = 0;
192
+ receivedEvent = false;
193
+ }
177
194
  if (ac.signal.aborted || this.closed) {
178
195
  if (!readySettled) rejectReady(error);
179
196
  streamQueue.close();
@@ -190,11 +207,12 @@ var ProtocolSseTransportAdapter = class {
190
207
  streamQueue.close(require_utils.toError(error));
191
208
  return;
192
209
  }
210
+ const delay = this.reconnectDelayMs(attempt);
193
211
  this.onReconnect?.({
194
212
  attempt,
195
- cause: error
213
+ cause: error,
214
+ delayMs: delay
196
215
  });
197
- const delay = this.reconnectDelayMs(attempt);
198
216
  if (delay > 0) await new Promise((resolve) => {
199
217
  setTimeout(resolve, delay);
200
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\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 streamQueue.push(event.data as Message);\n }\n }\n streamQueue.close();\n return;\n } catch (error) {\n if (ac.signal.aborted || this.closed) {\n if (!readySettled) {\n rejectReady(error);\n }\n streamQueue.close();\n return;\n }\n if (this.maxReconnectAttempts <= 0) {\n if (!readySettled) {\n rejectReady(error);\n }\n streamQueue.close(toError(error));\n return;\n }\n attempt += 1;\n if (attempt > this.maxReconnectAttempts) {\n if (!readySettled) {\n rejectReady(error);\n }\n streamQueue.close(toError(error));\n return;\n }\n this.onReconnect?.({ attempt, cause: error });\n const delay = this.reconnectDelayMs(attempt);\n if (delay > 0) {\n await new Promise<void>((resolve) => {\n setTimeout(resolve, delay);\n });\n }\n }\n }\n };\n\n void startStream();\n\n const cleanup = () => {\n this.eventStreams.delete(ac);\n ac.abort();\n streamQueue.close();\n };\n\n return {\n events: {\n [Symbol.asyncIterator]: () => ({\n next: async () => await streamQueue.shift(),\n return: async () => {\n cleanup();\n return { done: true, value: undefined };\n },\n }),\n },\n ready,\n close: cleanup,\n };\n }\n\n async close(): Promise<void> {\n if (this.closed) {\n return;\n }\n this.closed = true;\n this.sessionAbortController.abort();\n for (const ac of this.eventStreams) ac.abort();\n this.eventStreams.clear();\n this.queue.close();\n }\n\n private async request(\n path: string,\n init: RequestInit,\n options?: { stream?: boolean }\n ): Promise<Response> {\n const url = toAbsoluteUrl(this.apiUrl, path);\n let requestInit: RequestInit = {\n ...init,\n headers: mergeHeaders(this.defaultHeaders, init.headers),\n };\n\n if (this.onRequest) {\n requestInit = await this.onRequest(url, requestInit);\n }\n\n // Long-lived SSE event streams must not run through AsyncCaller: its\n // p-queue/p-retry semantics are designed for discrete request/response\n // calls, and wrapping a streaming response stalls the call (and can\n // leak retries). Stream resilience is handled separately by the\n // reconnect loop in `openEventStream`.\n const useAsyncCaller = this.asyncCaller != null && !options?.stream;\n\n const execute = async (): Promise<Response> => {\n const fetchImpl = await this.resolveFetch();\n const response = await fetchImpl(url.toString(), requestInit);\n if (!response.ok) {\n // Reject with the Response so AsyncCaller maps it to HTTPError and\n // applies STATUS_NO_RETRY / retry policy consistently with REST.\n if (useAsyncCaller) {\n throw response;\n }\n let detail = \"\";\n try {\n const body = await response.text();\n const parsed = JSON.parse(body);\n if (typeof parsed === \"object\" && parsed != null) {\n detail =\n ((parsed as Record<string, unknown>).message as string) ??\n ((parsed as Record<string, unknown>).error as string) ??\n \"\";\n }\n if (!detail) detail = body;\n } catch {\n // body unreadable or not JSON — fall through\n }\n const message = detail\n ? `Protocol request failed: ${response.status} ${response.statusText} — ${detail}`\n : `Protocol request failed: ${response.status} ${response.statusText}`;\n throw new Error(message);\n }\n return response;\n };\n\n try {\n return useAsyncCaller\n ? await this.asyncCaller!.call(execute)\n : await execute();\n } catch (error) {\n throw toError(error);\n }\n }\n}\n"],"mappings":"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"}
1
+ {"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;EAmKpC,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;EAmKpC,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;
@@ -139,6 +141,7 @@ var ProtocolSseTransportAdapter = class {
139
141
  let readySettled = false;
140
142
  const startStream = async () => {
141
143
  let attempt = 0;
144
+ let receivedEvent = false;
142
145
  while (!ac.signal.aborted && !this.closed) try {
143
146
  const response = await this.request(streamUrl, {
144
147
  method: "POST",
@@ -156,6 +159,10 @@ var ProtocolSseTransportAdapter = class {
156
159
  }, { stream: true });
157
160
  const contentType = response.headers.get("content-type")?.split(";")[0];
158
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
+ });
159
166
  if (!readySettled) {
160
167
  readySettled = true;
161
168
  resolveReady();
@@ -169,11 +176,21 @@ var ProtocolSseTransportAdapter = class {
169
176
  const iterable = IterableReadableStream.fromReadableStream(stream);
170
177
  for await (const event of iterable) {
171
178
  if (ac.signal.aborted || this.closed) break;
172
- if (isRecord(event.data)) streamQueue.push(event.data);
179
+ if (isRecord(event.data)) {
180
+ receivedEvent = true;
181
+ streamQueue.push(event.data);
182
+ }
183
+ }
184
+ if (ac.signal.aborted || this.closed || this.maxReconnectAttempts <= 0) {
185
+ streamQueue.close();
186
+ return;
173
187
  }
174
- streamQueue.close();
175
- return;
188
+ throw new Error("Event stream closed by the server");
176
189
  } catch (error) {
190
+ if (receivedEvent) {
191
+ attempt = 0;
192
+ receivedEvent = false;
193
+ }
177
194
  if (ac.signal.aborted || this.closed) {
178
195
  if (!readySettled) rejectReady(error);
179
196
  streamQueue.close();
@@ -190,11 +207,12 @@ var ProtocolSseTransportAdapter = class {
190
207
  streamQueue.close(toError(error));
191
208
  return;
192
209
  }
210
+ const delay = this.reconnectDelayMs(attempt);
193
211
  this.onReconnect?.({
194
212
  attempt,
195
- cause: error
213
+ cause: error,
214
+ delayMs: delay
196
215
  });
197
- const delay = this.reconnectDelayMs(attempt);
198
216
  if (delay > 0) await new Promise((resolve) => {
199
217
  setTimeout(resolve, delay);
200
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\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 streamQueue.push(event.data as Message);\n }\n }\n streamQueue.close();\n return;\n } catch (error) {\n if (ac.signal.aborted || this.closed) {\n if (!readySettled) {\n rejectReady(error);\n }\n streamQueue.close();\n return;\n }\n if (this.maxReconnectAttempts <= 0) {\n if (!readySettled) {\n rejectReady(error);\n }\n streamQueue.close(toError(error));\n return;\n }\n attempt += 1;\n if (attempt > this.maxReconnectAttempts) {\n if (!readySettled) {\n rejectReady(error);\n }\n streamQueue.close(toError(error));\n return;\n }\n this.onReconnect?.({ attempt, cause: error });\n const delay = this.reconnectDelayMs(attempt);\n if (delay > 0) {\n await new Promise<void>((resolve) => {\n setTimeout(resolve, delay);\n });\n }\n }\n }\n };\n\n void startStream();\n\n const cleanup = () => {\n this.eventStreams.delete(ac);\n ac.abort();\n streamQueue.close();\n };\n\n return {\n events: {\n [Symbol.asyncIterator]: () => ({\n next: async () => await streamQueue.shift(),\n return: async () => {\n cleanup();\n return { done: true, value: undefined };\n },\n }),\n },\n ready,\n close: cleanup,\n };\n }\n\n async close(): Promise<void> {\n if (this.closed) {\n return;\n }\n this.closed = true;\n this.sessionAbortController.abort();\n for (const ac of this.eventStreams) ac.abort();\n this.eventStreams.clear();\n this.queue.close();\n }\n\n private async request(\n path: string,\n init: RequestInit,\n options?: { stream?: boolean }\n ): Promise<Response> {\n const url = toAbsoluteUrl(this.apiUrl, path);\n let requestInit: RequestInit = {\n ...init,\n headers: mergeHeaders(this.defaultHeaders, init.headers),\n };\n\n if (this.onRequest) {\n requestInit = await this.onRequest(url, requestInit);\n }\n\n // Long-lived SSE event streams must not run through AsyncCaller: its\n // p-queue/p-retry semantics are designed for discrete request/response\n // calls, and wrapping a streaming response stalls the call (and can\n // leak retries). Stream resilience is handled separately by the\n // reconnect loop in `openEventStream`.\n const useAsyncCaller = this.asyncCaller != null && !options?.stream;\n\n const execute = async (): Promise<Response> => {\n const fetchImpl = await this.resolveFetch();\n const response = await fetchImpl(url.toString(), requestInit);\n if (!response.ok) {\n // Reject with the Response so AsyncCaller maps it to HTTPError and\n // applies STATUS_NO_RETRY / retry policy consistently with REST.\n if (useAsyncCaller) {\n throw response;\n }\n let detail = \"\";\n try {\n const body = await response.text();\n const parsed = JSON.parse(body);\n if (typeof parsed === \"object\" && parsed != null) {\n detail =\n ((parsed as Record<string, unknown>).message as string) ??\n ((parsed as Record<string, unknown>).error as string) ??\n \"\";\n }\n if (!detail) detail = body;\n } catch {\n // body unreadable or not JSON — fall through\n }\n const message = detail\n ? `Protocol request failed: ${response.status} ${response.statusText} — ${detail}`\n : `Protocol request failed: ${response.status} ${response.statusText}`;\n throw new Error(message);\n }\n return response;\n };\n\n try {\n return useAsyncCaller\n ? await this.asyncCaller!.call(execute)\n : await execute();\n } catch (error) {\n throw toError(error);\n }\n }\n}\n"],"mappings":"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"}
1
+ {"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;