@langchain/langgraph-sdk 1.12.4 → 1.12.5

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 (67) hide show
  1. package/dist/client/stream/handles/subagents.cjs +9 -2
  2. package/dist/client/stream/handles/subagents.cjs.map +1 -1
  3. package/dist/client/stream/handles/subagents.d.cts.map +1 -1
  4. package/dist/client/stream/handles/subagents.d.ts.map +1 -1
  5. package/dist/client/stream/handles/subagents.js +10 -3
  6. package/dist/client/stream/handles/subagents.js.map +1 -1
  7. package/dist/client/stream/handles/subgraphs.cjs +24 -2
  8. package/dist/client/stream/handles/subgraphs.cjs.map +1 -1
  9. package/dist/client/stream/handles/subgraphs.d.cts.map +1 -1
  10. package/dist/client/stream/handles/subgraphs.d.ts.map +1 -1
  11. package/dist/client/stream/handles/subgraphs.js +24 -3
  12. package/dist/client/stream/handles/subgraphs.js.map +1 -1
  13. package/dist/client/stream/index.cjs +17 -7
  14. package/dist/client/stream/index.cjs.map +1 -1
  15. package/dist/client/stream/index.d.cts.map +1 -1
  16. package/dist/client/stream/index.d.ts.map +1 -1
  17. package/dist/client/stream/index.js +17 -7
  18. package/dist/client/stream/index.js.map +1 -1
  19. package/dist/client/stream/messages.cjs +43 -0
  20. package/dist/client/stream/messages.cjs.map +1 -1
  21. package/dist/client/stream/messages.d.cts +8 -0
  22. package/dist/client/stream/messages.d.cts.map +1 -1
  23. package/dist/client/stream/messages.d.ts +8 -0
  24. package/dist/client/stream/messages.d.ts.map +1 -1
  25. package/dist/client/stream/messages.js +43 -0
  26. package/dist/client/stream/messages.js.map +1 -1
  27. package/dist/client/stream/transport/http.cjs +1 -12
  28. package/dist/client/stream/transport/http.cjs.map +1 -1
  29. package/dist/client/stream/transport/http.d.cts.map +1 -1
  30. package/dist/client/stream/transport/http.d.ts.map +1 -1
  31. package/dist/client/stream/transport/http.js +1 -12
  32. package/dist/client/stream/transport/http.js.map +1 -1
  33. package/dist/client/stream/transport/utils.cjs +12 -0
  34. package/dist/client/stream/transport/utils.cjs.map +1 -1
  35. package/dist/client/stream/transport/utils.js +12 -1
  36. package/dist/client/stream/transport/utils.js.map +1 -1
  37. package/dist/client/stream/transport/websocket.cjs +33 -3
  38. package/dist/client/stream/transport/websocket.cjs.map +1 -1
  39. package/dist/client/stream/transport/websocket.d.cts +4 -1
  40. package/dist/client/stream/transport/websocket.d.cts.map +1 -1
  41. package/dist/client/stream/transport/websocket.d.ts +4 -1
  42. package/dist/client/stream/transport/websocket.d.ts.map +1 -1
  43. package/dist/client/stream/transport/websocket.js +34 -4
  44. package/dist/client/stream/transport/websocket.js.map +1 -1
  45. package/dist/stream/controller.cjs +94 -3
  46. package/dist/stream/controller.cjs.map +1 -1
  47. package/dist/stream/controller.d.cts.map +1 -1
  48. package/dist/stream/controller.d.ts.map +1 -1
  49. package/dist/stream/controller.js +94 -3
  50. package/dist/stream/controller.js.map +1 -1
  51. package/dist/stream/lifecycle-loading-tracker.cjs +5 -0
  52. package/dist/stream/lifecycle-loading-tracker.cjs.map +1 -1
  53. package/dist/stream/lifecycle-loading-tracker.js +5 -0
  54. package/dist/stream/lifecycle-loading-tracker.js.map +1 -1
  55. package/dist/stream/projections/messages.cjs +45 -12
  56. package/dist/stream/projections/messages.cjs.map +1 -1
  57. package/dist/stream/projections/messages.js +45 -12
  58. package/dist/stream/projections/messages.js.map +1 -1
  59. package/dist/stream/root-message-projection.cjs +71 -8
  60. package/dist/stream/root-message-projection.cjs.map +1 -1
  61. package/dist/stream/root-message-projection.js +71 -8
  62. package/dist/stream/root-message-projection.js.map +1 -1
  63. package/dist/stream/submit-coordinator.cjs +7 -2
  64. package/dist/stream/submit-coordinator.cjs.map +1 -1
  65. package/dist/stream/submit-coordinator.js +7 -2
  66. package/dist/stream/submit-coordinator.js.map +1 -1
  67. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"utils.cjs","names":[],"sources":["../../../../src/client/stream/transport/utils.ts"],"sourcesContent":["import type { HeaderValue, ProtocolPath } from \"./types.js\";\nimport type { CommandResponse, ErrorResponse } from \"@langchain/protocol\";\n\nexport const isRecord = (value: unknown): value is Record<string, unknown> =>\n typeof value === \"object\" && value !== null;\n\n/**\n * Resolve a {@link ProtocolPath} against the transport's currently-bound\n * thread.\n *\n * - a fixed `string` is trusted and used verbatim, with no encoding\n * (back-compat: a baked path is independent of the bound thread);\n * - a function path and the default fallback are evaluated against\n * `threadId`, so late-bound / re-bound adapters target the right thread.\n *\n * Throws when neither a fixed path nor a bound thread is available — i.e.\n * a request was attempted before `client.threads.stream(threadId, …)` /\n * {@link TransportAdapter.setThreadId} bound a thread.\n */\nexport function resolveProtocolPath(\n path: ProtocolPath | undefined,\n threadId: string,\n fallback: (threadId: string) => string\n): string {\n if (typeof path === \"string\") return path;\n if (!threadId) {\n throw new Error(\n \"Protocol transport has no bound threadId. Bind one — the framework \" +\n \"calls client.threads.stream(threadId, { transport }) / \" +\n \"transport.setThreadId(threadId) — before issuing requests.\"\n );\n }\n return path ? path(threadId) : fallback(threadId);\n}\n\n/** Match {@link BaseClient.prepareFetchOptions}: preserve any apiUrl path prefix. */\nexport const toAbsoluteUrl = (apiUrl: string, path: string) =>\n new URL(`${apiUrl.replace(/\\/$/, \"\")}${path}`);\n\nexport const toError = (error: unknown) =>\n // oxlint-disable-next-line no-instanceof/no-instanceof\n error instanceof Error ? error : new Error(String(error));\n\nexport const toWebSocketUrl = (apiUrl: string): string => {\n // Extract path from the input (e.g. \"http://host/threads/X/stream\") and\n // swap the scheme to ws/wss. The caller passes a fully-formed URL\n // with the desired path.\n const url = new URL(apiUrl);\n url.protocol = url.protocol === \"https:\" ? \"wss:\" : \"ws:\";\n url.search = \"\";\n url.hash = \"\";\n return url.toString();\n};\n\nexport const hasHeaders = (headers?: Record<string, HeaderValue>) =>\n Object.values(headers ?? {}).some((value) => value != null);\n\nexport function mergeHeaders(\n ...headerGroups: Array<\n HeadersInit | Record<string, HeaderValue> | undefined | null\n >\n): Headers {\n const merged = new Headers();\n\n for (const group of headerGroups) {\n if (!group) {\n continue;\n }\n\n // oxlint-disable-next-line no-instanceof/no-instanceof\n if (group instanceof Headers) {\n group.forEach((value, key) => {\n merged.set(key, value);\n });\n continue;\n }\n\n if (Array.isArray(group)) {\n for (const [key, value] of group) {\n if (value == null) {\n merged.delete(key);\n } else {\n merged.set(key, value);\n }\n }\n continue;\n }\n\n for (const [key, value] of Object.entries(group)) {\n if (value == null) {\n merged.delete(key);\n } else {\n merged.set(key, value);\n }\n }\n }\n\n return merged;\n}\n\nexport function isProtocolResponse(\n value: unknown\n): value is CommandResponse | ErrorResponse {\n return (\n isRecord(value) &&\n typeof value.type === \"string\" &&\n (value.type === \"success\" || value.type === \"error\")\n );\n}\n"],"mappings":";AAGA,MAAa,YAAY,UACvB,OAAO,UAAU,YAAY,UAAU;;;;;;;;;;;;;;AAezC,SAAgB,oBACd,MACA,UACA,UACQ;AACR,KAAI,OAAO,SAAS,SAAU,QAAO;AACrC,KAAI,CAAC,SACH,OAAM,IAAI,MACR,uLAGD;AAEH,QAAO,OAAO,KAAK,SAAS,GAAG,SAAS,SAAS;;;AAInD,MAAa,iBAAiB,QAAgB,SAC5C,IAAI,IAAI,GAAG,OAAO,QAAQ,OAAO,GAAG,GAAG,OAAO;AAEhD,MAAa,WAAW,UAEtB,iBAAiB,QAAQ,QAAQ,IAAI,MAAM,OAAO,MAAM,CAAC;AAE3D,MAAa,kBAAkB,WAA2B;CAIxD,MAAM,MAAM,IAAI,IAAI,OAAO;AAC3B,KAAI,WAAW,IAAI,aAAa,WAAW,SAAS;AACpD,KAAI,SAAS;AACb,KAAI,OAAO;AACX,QAAO,IAAI,UAAU;;AAGvB,MAAa,cAAc,YACzB,OAAO,OAAO,WAAW,EAAE,CAAC,CAAC,MAAM,UAAU,SAAS,KAAK;AAE7D,SAAgB,aACd,GAAG,cAGM;CACT,MAAM,SAAS,IAAI,SAAS;AAE5B,MAAK,MAAM,SAAS,cAAc;AAChC,MAAI,CAAC,MACH;AAIF,MAAI,iBAAiB,SAAS;AAC5B,SAAM,SAAS,OAAO,QAAQ;AAC5B,WAAO,IAAI,KAAK,MAAM;KACtB;AACF;;AAGF,MAAI,MAAM,QAAQ,MAAM,EAAE;AACxB,QAAK,MAAM,CAAC,KAAK,UAAU,MACzB,KAAI,SAAS,KACX,QAAO,OAAO,IAAI;OAElB,QAAO,IAAI,KAAK,MAAM;AAG1B;;AAGF,OAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,MAAM,CAC9C,KAAI,SAAS,KACX,QAAO,OAAO,IAAI;MAElB,QAAO,IAAI,KAAK,MAAM;;AAK5B,QAAO;;AAGT,SAAgB,mBACd,OAC0C;AAC1C,QACE,SAAS,MAAM,IACf,OAAO,MAAM,SAAS,aACrB,MAAM,SAAS,aAAa,MAAM,SAAS"}
1
+ {"version":3,"file":"utils.cjs","names":[],"sources":["../../../../src/client/stream/transport/utils.ts"],"sourcesContent":["import type { HeaderValue, ProtocolPath } from \"./types.js\";\nimport type { CommandResponse, ErrorResponse } from \"@langchain/protocol\";\n\nexport const isRecord = (value: unknown): value is Record<string, unknown> =>\n typeof value === \"object\" && value !== null;\n\n/**\n * True for a 4xx HTTP error other than 408 (Request Timeout) and 429\n * (Too Many Requests): retrying the same request cannot succeed. Errors\n * with no `status` (network errors, idle timeouts, server closes) stay\n * retryable.\n */\nexport function isNonRetryableHttpError(error: unknown): boolean {\n if (!isRecord(error) || typeof error.status !== \"number\") return false;\n const { status } = error;\n return status >= 400 && status < 500 && status !== 408 && status !== 429;\n}\n\n/**\n * Resolve a {@link ProtocolPath} against the transport's currently-bound\n * thread.\n *\n * - a fixed `string` is trusted and used verbatim, with no encoding\n * (back-compat: a baked path is independent of the bound thread);\n * - a function path and the default fallback are evaluated against\n * `threadId`, so late-bound / re-bound adapters target the right thread.\n *\n * Throws when neither a fixed path nor a bound thread is available — i.e.\n * a request was attempted before `client.threads.stream(threadId, …)` /\n * {@link TransportAdapter.setThreadId} bound a thread.\n */\nexport function resolveProtocolPath(\n path: ProtocolPath | undefined,\n threadId: string,\n fallback: (threadId: string) => string\n): string {\n if (typeof path === \"string\") return path;\n if (!threadId) {\n throw new Error(\n \"Protocol transport has no bound threadId. Bind one — the framework \" +\n \"calls client.threads.stream(threadId, { transport }) / \" +\n \"transport.setThreadId(threadId) — before issuing requests.\"\n );\n }\n return path ? path(threadId) : fallback(threadId);\n}\n\n/** Match {@link BaseClient.prepareFetchOptions}: preserve any apiUrl path prefix. */\nexport const toAbsoluteUrl = (apiUrl: string, path: string) =>\n new URL(`${apiUrl.replace(/\\/$/, \"\")}${path}`);\n\nexport const toError = (error: unknown) =>\n // oxlint-disable-next-line no-instanceof/no-instanceof\n error instanceof Error ? error : new Error(String(error));\n\nexport const toWebSocketUrl = (apiUrl: string): string => {\n // Extract path from the input (e.g. \"http://host/threads/X/stream\") and\n // swap the scheme to ws/wss. The caller passes a fully-formed URL\n // with the desired path.\n const url = new URL(apiUrl);\n url.protocol = url.protocol === \"https:\" ? \"wss:\" : \"ws:\";\n url.search = \"\";\n url.hash = \"\";\n return url.toString();\n};\n\nexport const hasHeaders = (headers?: Record<string, HeaderValue>) =>\n Object.values(headers ?? {}).some((value) => value != null);\n\nexport function mergeHeaders(\n ...headerGroups: Array<\n HeadersInit | Record<string, HeaderValue> | undefined | null\n >\n): Headers {\n const merged = new Headers();\n\n for (const group of headerGroups) {\n if (!group) {\n continue;\n }\n\n // oxlint-disable-next-line no-instanceof/no-instanceof\n if (group instanceof Headers) {\n group.forEach((value, key) => {\n merged.set(key, value);\n });\n continue;\n }\n\n if (Array.isArray(group)) {\n for (const [key, value] of group) {\n if (value == null) {\n merged.delete(key);\n } else {\n merged.set(key, value);\n }\n }\n continue;\n }\n\n for (const [key, value] of Object.entries(group)) {\n if (value == null) {\n merged.delete(key);\n } else {\n merged.set(key, value);\n }\n }\n }\n\n return merged;\n}\n\nexport function isProtocolResponse(\n value: unknown\n): value is CommandResponse | ErrorResponse {\n return (\n isRecord(value) &&\n typeof value.type === \"string\" &&\n (value.type === \"success\" || value.type === \"error\")\n );\n}\n"],"mappings":";AAGA,MAAa,YAAY,UACvB,OAAO,UAAU,YAAY,UAAU;;;;;;;AAQzC,SAAgB,wBAAwB,OAAyB;AAC/D,KAAI,CAAC,SAAS,MAAM,IAAI,OAAO,MAAM,WAAW,SAAU,QAAO;CACjE,MAAM,EAAE,WAAW;AACnB,QAAO,UAAU,OAAO,SAAS,OAAO,WAAW,OAAO,WAAW;;;;;;;;;;;;;;;AAgBvE,SAAgB,oBACd,MACA,UACA,UACQ;AACR,KAAI,OAAO,SAAS,SAAU,QAAO;AACrC,KAAI,CAAC,SACH,OAAM,IAAI,MACR,uLAGD;AAEH,QAAO,OAAO,KAAK,SAAS,GAAG,SAAS,SAAS;;;AAInD,MAAa,iBAAiB,QAAgB,SAC5C,IAAI,IAAI,GAAG,OAAO,QAAQ,OAAO,GAAG,GAAG,OAAO;AAEhD,MAAa,WAAW,UAEtB,iBAAiB,QAAQ,QAAQ,IAAI,MAAM,OAAO,MAAM,CAAC;AAE3D,MAAa,kBAAkB,WAA2B;CAIxD,MAAM,MAAM,IAAI,IAAI,OAAO;AAC3B,KAAI,WAAW,IAAI,aAAa,WAAW,SAAS;AACpD,KAAI,SAAS;AACb,KAAI,OAAO;AACX,QAAO,IAAI,UAAU;;AAGvB,MAAa,cAAc,YACzB,OAAO,OAAO,WAAW,EAAE,CAAC,CAAC,MAAM,UAAU,SAAS,KAAK;AAE7D,SAAgB,aACd,GAAG,cAGM;CACT,MAAM,SAAS,IAAI,SAAS;AAE5B,MAAK,MAAM,SAAS,cAAc;AAChC,MAAI,CAAC,MACH;AAIF,MAAI,iBAAiB,SAAS;AAC5B,SAAM,SAAS,OAAO,QAAQ;AAC5B,WAAO,IAAI,KAAK,MAAM;KACtB;AACF;;AAGF,MAAI,MAAM,QAAQ,MAAM,EAAE;AACxB,QAAK,MAAM,CAAC,KAAK,UAAU,MACzB,KAAI,SAAS,KACX,QAAO,OAAO,IAAI;OAElB,QAAO,IAAI,KAAK,MAAM;AAG1B;;AAGF,OAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,MAAM,CAC9C,KAAI,SAAS,KACX,QAAO,OAAO,IAAI;MAElB,QAAO,IAAI,KAAK,MAAM;;AAK5B,QAAO;;AAGT,SAAgB,mBACd,OAC0C;AAC1C,QACE,SAAS,MAAM,IACf,OAAO,MAAM,SAAS,aACrB,MAAM,SAAS,aAAa,MAAM,SAAS"}
@@ -1,6 +1,17 @@
1
1
  //#region src/client/stream/transport/utils.ts
2
2
  const isRecord = (value) => typeof value === "object" && value !== null;
3
3
  /**
4
+ * True for a 4xx HTTP error other than 408 (Request Timeout) and 429
5
+ * (Too Many Requests): retrying the same request cannot succeed. Errors
6
+ * with no `status` (network errors, idle timeouts, server closes) stay
7
+ * retryable.
8
+ */
9
+ function isNonRetryableHttpError(error) {
10
+ if (!isRecord(error) || typeof error.status !== "number") return false;
11
+ const { status } = error;
12
+ return status >= 400 && status < 500 && status !== 408 && status !== 429;
13
+ }
14
+ /**
4
15
  * Resolve a {@link ProtocolPath} against the transport's currently-bound
5
16
  * thread.
6
17
  *
@@ -53,6 +64,6 @@ function isProtocolResponse(value) {
53
64
  return isRecord(value) && typeof value.type === "string" && (value.type === "success" || value.type === "error");
54
65
  }
55
66
  //#endregion
56
- export { hasHeaders, isProtocolResponse, isRecord, mergeHeaders, resolveProtocolPath, toAbsoluteUrl, toError, toWebSocketUrl };
67
+ export { hasHeaders, isNonRetryableHttpError, isProtocolResponse, isRecord, mergeHeaders, resolveProtocolPath, toAbsoluteUrl, toError, toWebSocketUrl };
57
68
 
58
69
  //# sourceMappingURL=utils.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"utils.js","names":[],"sources":["../../../../src/client/stream/transport/utils.ts"],"sourcesContent":["import type { HeaderValue, ProtocolPath } from \"./types.js\";\nimport type { CommandResponse, ErrorResponse } from \"@langchain/protocol\";\n\nexport const isRecord = (value: unknown): value is Record<string, unknown> =>\n typeof value === \"object\" && value !== null;\n\n/**\n * Resolve a {@link ProtocolPath} against the transport's currently-bound\n * thread.\n *\n * - a fixed `string` is trusted and used verbatim, with no encoding\n * (back-compat: a baked path is independent of the bound thread);\n * - a function path and the default fallback are evaluated against\n * `threadId`, so late-bound / re-bound adapters target the right thread.\n *\n * Throws when neither a fixed path nor a bound thread is available — i.e.\n * a request was attempted before `client.threads.stream(threadId, …)` /\n * {@link TransportAdapter.setThreadId} bound a thread.\n */\nexport function resolveProtocolPath(\n path: ProtocolPath | undefined,\n threadId: string,\n fallback: (threadId: string) => string\n): string {\n if (typeof path === \"string\") return path;\n if (!threadId) {\n throw new Error(\n \"Protocol transport has no bound threadId. Bind one — the framework \" +\n \"calls client.threads.stream(threadId, { transport }) / \" +\n \"transport.setThreadId(threadId) — before issuing requests.\"\n );\n }\n return path ? path(threadId) : fallback(threadId);\n}\n\n/** Match {@link BaseClient.prepareFetchOptions}: preserve any apiUrl path prefix. */\nexport const toAbsoluteUrl = (apiUrl: string, path: string) =>\n new URL(`${apiUrl.replace(/\\/$/, \"\")}${path}`);\n\nexport const toError = (error: unknown) =>\n // oxlint-disable-next-line no-instanceof/no-instanceof\n error instanceof Error ? error : new Error(String(error));\n\nexport const toWebSocketUrl = (apiUrl: string): string => {\n // Extract path from the input (e.g. \"http://host/threads/X/stream\") and\n // swap the scheme to ws/wss. The caller passes a fully-formed URL\n // with the desired path.\n const url = new URL(apiUrl);\n url.protocol = url.protocol === \"https:\" ? \"wss:\" : \"ws:\";\n url.search = \"\";\n url.hash = \"\";\n return url.toString();\n};\n\nexport const hasHeaders = (headers?: Record<string, HeaderValue>) =>\n Object.values(headers ?? {}).some((value) => value != null);\n\nexport function mergeHeaders(\n ...headerGroups: Array<\n HeadersInit | Record<string, HeaderValue> | undefined | null\n >\n): Headers {\n const merged = new Headers();\n\n for (const group of headerGroups) {\n if (!group) {\n continue;\n }\n\n // oxlint-disable-next-line no-instanceof/no-instanceof\n if (group instanceof Headers) {\n group.forEach((value, key) => {\n merged.set(key, value);\n });\n continue;\n }\n\n if (Array.isArray(group)) {\n for (const [key, value] of group) {\n if (value == null) {\n merged.delete(key);\n } else {\n merged.set(key, value);\n }\n }\n continue;\n }\n\n for (const [key, value] of Object.entries(group)) {\n if (value == null) {\n merged.delete(key);\n } else {\n merged.set(key, value);\n }\n }\n }\n\n return merged;\n}\n\nexport function isProtocolResponse(\n value: unknown\n): value is CommandResponse | ErrorResponse {\n return (\n isRecord(value) &&\n typeof value.type === \"string\" &&\n (value.type === \"success\" || value.type === \"error\")\n );\n}\n"],"mappings":";AAGA,MAAa,YAAY,UACvB,OAAO,UAAU,YAAY,UAAU;;;;;;;;;;;;;;AAezC,SAAgB,oBACd,MACA,UACA,UACQ;AACR,KAAI,OAAO,SAAS,SAAU,QAAO;AACrC,KAAI,CAAC,SACH,OAAM,IAAI,MACR,uLAGD;AAEH,QAAO,OAAO,KAAK,SAAS,GAAG,SAAS,SAAS;;;AAInD,MAAa,iBAAiB,QAAgB,SAC5C,IAAI,IAAI,GAAG,OAAO,QAAQ,OAAO,GAAG,GAAG,OAAO;AAEhD,MAAa,WAAW,UAEtB,iBAAiB,QAAQ,QAAQ,IAAI,MAAM,OAAO,MAAM,CAAC;AAE3D,MAAa,kBAAkB,WAA2B;CAIxD,MAAM,MAAM,IAAI,IAAI,OAAO;AAC3B,KAAI,WAAW,IAAI,aAAa,WAAW,SAAS;AACpD,KAAI,SAAS;AACb,KAAI,OAAO;AACX,QAAO,IAAI,UAAU;;AAGvB,MAAa,cAAc,YACzB,OAAO,OAAO,WAAW,EAAE,CAAC,CAAC,MAAM,UAAU,SAAS,KAAK;AAE7D,SAAgB,aACd,GAAG,cAGM;CACT,MAAM,SAAS,IAAI,SAAS;AAE5B,MAAK,MAAM,SAAS,cAAc;AAChC,MAAI,CAAC,MACH;AAIF,MAAI,iBAAiB,SAAS;AAC5B,SAAM,SAAS,OAAO,QAAQ;AAC5B,WAAO,IAAI,KAAK,MAAM;KACtB;AACF;;AAGF,MAAI,MAAM,QAAQ,MAAM,EAAE;AACxB,QAAK,MAAM,CAAC,KAAK,UAAU,MACzB,KAAI,SAAS,KACX,QAAO,OAAO,IAAI;OAElB,QAAO,IAAI,KAAK,MAAM;AAG1B;;AAGF,OAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,MAAM,CAC9C,KAAI,SAAS,KACX,QAAO,OAAO,IAAI;MAElB,QAAO,IAAI,KAAK,MAAM;;AAK5B,QAAO;;AAGT,SAAgB,mBACd,OAC0C;AAC1C,QACE,SAAS,MAAM,IACf,OAAO,MAAM,SAAS,aACrB,MAAM,SAAS,aAAa,MAAM,SAAS"}
1
+ {"version":3,"file":"utils.js","names":[],"sources":["../../../../src/client/stream/transport/utils.ts"],"sourcesContent":["import type { HeaderValue, ProtocolPath } from \"./types.js\";\nimport type { CommandResponse, ErrorResponse } from \"@langchain/protocol\";\n\nexport const isRecord = (value: unknown): value is Record<string, unknown> =>\n typeof value === \"object\" && value !== null;\n\n/**\n * True for a 4xx HTTP error other than 408 (Request Timeout) and 429\n * (Too Many Requests): retrying the same request cannot succeed. Errors\n * with no `status` (network errors, idle timeouts, server closes) stay\n * retryable.\n */\nexport function isNonRetryableHttpError(error: unknown): boolean {\n if (!isRecord(error) || typeof error.status !== \"number\") return false;\n const { status } = error;\n return status >= 400 && status < 500 && status !== 408 && status !== 429;\n}\n\n/**\n * Resolve a {@link ProtocolPath} against the transport's currently-bound\n * thread.\n *\n * - a fixed `string` is trusted and used verbatim, with no encoding\n * (back-compat: a baked path is independent of the bound thread);\n * - a function path and the default fallback are evaluated against\n * `threadId`, so late-bound / re-bound adapters target the right thread.\n *\n * Throws when neither a fixed path nor a bound thread is available — i.e.\n * a request was attempted before `client.threads.stream(threadId, …)` /\n * {@link TransportAdapter.setThreadId} bound a thread.\n */\nexport function resolveProtocolPath(\n path: ProtocolPath | undefined,\n threadId: string,\n fallback: (threadId: string) => string\n): string {\n if (typeof path === \"string\") return path;\n if (!threadId) {\n throw new Error(\n \"Protocol transport has no bound threadId. Bind one — the framework \" +\n \"calls client.threads.stream(threadId, { transport }) / \" +\n \"transport.setThreadId(threadId) — before issuing requests.\"\n );\n }\n return path ? path(threadId) : fallback(threadId);\n}\n\n/** Match {@link BaseClient.prepareFetchOptions}: preserve any apiUrl path prefix. */\nexport const toAbsoluteUrl = (apiUrl: string, path: string) =>\n new URL(`${apiUrl.replace(/\\/$/, \"\")}${path}`);\n\nexport const toError = (error: unknown) =>\n // oxlint-disable-next-line no-instanceof/no-instanceof\n error instanceof Error ? error : new Error(String(error));\n\nexport const toWebSocketUrl = (apiUrl: string): string => {\n // Extract path from the input (e.g. \"http://host/threads/X/stream\") and\n // swap the scheme to ws/wss. The caller passes a fully-formed URL\n // with the desired path.\n const url = new URL(apiUrl);\n url.protocol = url.protocol === \"https:\" ? \"wss:\" : \"ws:\";\n url.search = \"\";\n url.hash = \"\";\n return url.toString();\n};\n\nexport const hasHeaders = (headers?: Record<string, HeaderValue>) =>\n Object.values(headers ?? {}).some((value) => value != null);\n\nexport function mergeHeaders(\n ...headerGroups: Array<\n HeadersInit | Record<string, HeaderValue> | undefined | null\n >\n): Headers {\n const merged = new Headers();\n\n for (const group of headerGroups) {\n if (!group) {\n continue;\n }\n\n // oxlint-disable-next-line no-instanceof/no-instanceof\n if (group instanceof Headers) {\n group.forEach((value, key) => {\n merged.set(key, value);\n });\n continue;\n }\n\n if (Array.isArray(group)) {\n for (const [key, value] of group) {\n if (value == null) {\n merged.delete(key);\n } else {\n merged.set(key, value);\n }\n }\n continue;\n }\n\n for (const [key, value] of Object.entries(group)) {\n if (value == null) {\n merged.delete(key);\n } else {\n merged.set(key, value);\n }\n }\n }\n\n return merged;\n}\n\nexport function isProtocolResponse(\n value: unknown\n): value is CommandResponse | ErrorResponse {\n return (\n isRecord(value) &&\n typeof value.type === \"string\" &&\n (value.type === \"success\" || value.type === \"error\")\n );\n}\n"],"mappings":";AAGA,MAAa,YAAY,UACvB,OAAO,UAAU,YAAY,UAAU;;;;;;;AAQzC,SAAgB,wBAAwB,OAAyB;AAC/D,KAAI,CAAC,SAAS,MAAM,IAAI,OAAO,MAAM,WAAW,SAAU,QAAO;CACjE,MAAM,EAAE,WAAW;AACnB,QAAO,UAAU,OAAO,SAAS,OAAO,WAAW,OAAO,WAAW;;;;;;;;;;;;;;;AAgBvE,SAAgB,oBACd,MACA,UACA,UACQ;AACR,KAAI,OAAO,SAAS,SAAU,QAAO;AACrC,KAAI,CAAC,SACH,OAAM,IAAI,MACR,uLAGD;AAEH,QAAO,OAAO,KAAK,SAAS,GAAG,SAAS,SAAS;;;AAInD,MAAa,iBAAiB,QAAgB,SAC5C,IAAI,IAAI,GAAG,OAAO,QAAQ,OAAO,GAAG,GAAG,OAAO;AAEhD,MAAa,WAAW,UAEtB,iBAAiB,QAAQ,QAAQ,IAAI,MAAM,OAAO,MAAM,CAAC;AAE3D,MAAa,kBAAkB,WAA2B;CAIxD,MAAM,MAAM,IAAI,IAAI,OAAO;AAC3B,KAAI,WAAW,IAAI,aAAa,WAAW,SAAS;AACpD,KAAI,SAAS;AACb,KAAI,OAAO;AACX,QAAO,IAAI,UAAU;;AAGvB,MAAa,cAAc,YACzB,OAAO,OAAO,WAAW,EAAE,CAAC,CAAC,MAAM,UAAU,SAAS,KAAK;AAE7D,SAAgB,aACd,GAAG,cAGM;CACT,MAAM,SAAS,IAAI,SAAS;AAE5B,MAAK,MAAM,SAAS,cAAc;AAChC,MAAI,CAAC,MACH;AAIF,MAAI,iBAAiB,SAAS;AAC5B,SAAM,SAAS,OAAO,QAAQ;AAC5B,WAAO,IAAI,KAAK,MAAM;KACtB;AACF;;AAGF,MAAI,MAAM,QAAQ,MAAM,EAAE;AACxB,QAAK,MAAM,CAAC,KAAK,UAAU,MACzB,KAAI,SAAS,KACX,QAAO,OAAO,IAAI;OAElB,QAAO,IAAI,KAAK,MAAM;AAG1B;;AAGF,OAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,MAAM,CAC9C,KAAI,SAAS,KACX,QAAO,OAAO,IAAI;MAElB,QAAO,IAAI,KAAK,MAAM;;AAK5B,QAAO;;AAGT,SAAgB,mBACd,OAC0C;AAC1C,QACE,SAAS,MAAM,IACf,OAAO,MAAM,SAAS,aACrB,MAAM,SAAS,aAAa,MAAM,SAAS"}
@@ -8,13 +8,27 @@ const WEB_SOCKET_CONNECTING = 0;
8
8
  const WEB_SOCKET_OPEN = 1;
9
9
  const WEB_SOCKET_CLOSED = 3;
10
10
  /**
11
+ * A server that rejects a connection after accepting it closes with 4000
12
+ * plus the HTTP status the SSE endpoint would return (4403, 4404, 4422).
13
+ * Keeping that `status` on the error lets a 4xx end the stream the same
14
+ * way it does on the SSE transport.
15
+ */
16
+ function closeError(event) {
17
+ const { code, reason } = event;
18
+ if (typeof code !== "number" || code < 4e3 || code >= 5e3) return /* @__PURE__ */ new Error("Protocol WebSocket closed unexpectedly.");
19
+ return Object.assign(/* @__PURE__ */ new Error(`Protocol WebSocket closed by the server: ${code}${reason ? ` ${reason}` : ""}`), { status: code - 4e3 });
20
+ }
21
+ /**
11
22
  * Transport adapter that speaks the thread-centric protocol over a
12
23
  * bidirectional WebSocket. Bound to a `threadId` at construction or later
13
24
  * via {@link setThreadId} — the socket connects to
14
25
  * `ws://.../threads/:thread_id/stream/events`.
15
26
  *
16
27
  * On unexpected disconnect the adapter reconnects with exponential
17
- * backoff (see {@link ProtocolWebSocketTransportOptions.maxReconnectAttempts}).
28
+ * backoff (see {@link ProtocolWebSocketTransportOptions.maxReconnectAttempts}),
29
+ * except when the server closes with a code for a 4xx status other than
30
+ * 408 or 429 (4403, 4404, 4422), which ends the event stream with an error
31
+ * carrying that `status`.
18
32
  * The server replays buffered events on the new socket; the SDK
19
33
  * deduplicates by `event_id`. {@link ProtocolWebSocketTransportOptions.onReconnected}
20
34
  * runs after each successful reconnect so `ThreadStream` can re-issue
@@ -153,6 +167,7 @@ var ProtocolWebSocketTransportAdapter = class {
153
167
  if (require_utils.hasHeaders(this.defaultHeaders) || this.onRequest != null) throw new Error("Browser WebSocket protocol transport does not support defaultHeaders or onRequest hooks. Supply a custom protocolWebSocketFactory if you need custom WebSocket setup.");
154
168
  }
155
169
  async sendCommand(command) {
170
+ if (this.closed) throw new Error("Protocol WebSocket transport is closed.");
156
171
  let socket = this.socket;
157
172
  if (this.reconnectInFlight != null && (socket == null || socket.readyState !== WEB_SOCKET_OPEN)) {
158
173
  await this.reconnectInFlight.catch(() => void 0);
@@ -199,7 +214,7 @@ var ProtocolWebSocketTransportAdapter = class {
199
214
  }
200
215
  if (require_utils.isRecord(payload) && payload.type === "event") this.queue.push(payload);
201
216
  };
202
- handleClose = () => {
217
+ handleClose = (event) => {
203
218
  const socket = this.socket;
204
219
  if (socket != null) this.#detachSocket(socket);
205
220
  this.socket = null;
@@ -207,12 +222,27 @@ var ProtocolWebSocketTransportAdapter = class {
207
222
  this.queue.close();
208
223
  return;
209
224
  }
210
- this.#handleUnexpectedDisconnect(/* @__PURE__ */ new Error("Protocol WebSocket closed unexpectedly."));
225
+ const error = closeError(event);
226
+ if (require_utils.isNonRetryableHttpError(error)) {
227
+ this.#closeWithError(error);
228
+ return;
229
+ }
230
+ this.#handleUnexpectedDisconnect(error);
211
231
  };
212
232
  handleSocketError = () => {
213
233
  if (this.closed || this.intentionalClose) return;
214
234
  this.#handleUnexpectedDisconnect(/* @__PURE__ */ new Error("Protocol WebSocket encountered an error."));
215
235
  };
236
+ /**
237
+ * End the transport for good: a reconnect would be rejected the same way.
238
+ * Marking it closed also stops a reconnect loop that is in flight.
239
+ */
240
+ #closeWithError(error) {
241
+ this.closed = true;
242
+ for (const { reject } of this.pending.values()) reject(error);
243
+ this.pending.clear();
244
+ this.queue.close(error);
245
+ }
216
246
  #handleUnexpectedDisconnect(cause) {
217
247
  const error = require_utils.toError(cause);
218
248
  for (const { reject } of this.pending.values()) reject(error);
@@ -1 +1 @@
1
- {"version":3,"file":"websocket.cjs","names":["AsyncQueue","reconnectDelayMs","resolveProtocolPath","encodePathSegment","#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 { encodePathSegment } from \"../../base.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/${encodePathSegment(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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"}
1
+ {"version":3,"file":"websocket.cjs","names":["AsyncQueue","reconnectDelayMs","resolveProtocolPath","encodePathSegment","#detachSocket","toWebSocketUrl","toAbsoluteUrl","#attachSocket","hasHeaders","toError","isRecord","isNonRetryableHttpError","#closeWithError","#handleUnexpectedDisconnect","#scheduleReconnect","#runReconnectLoop","MaxWebSocketReconnectAttemptsError"],"sources":["../../../../src/client/stream/transport/websocket.ts"],"sourcesContent":["import { AsyncQueue } from \"./queue.js\";\nimport { encodePathSegment } from \"../../base.js\";\nimport type {\n Message,\n Command,\n CommandResponse,\n ErrorResponse,\n} from \"@langchain/protocol\";\n\nimport {\n toAbsoluteUrl,\n toWebSocketUrl,\n isRecord,\n isNonRetryableHttpError,\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 * A server that rejects a connection after accepting it closes with 4000\n * plus the HTTP status the SSE endpoint would return (4403, 4404, 4422).\n * Keeping that `status` on the error lets a 4xx end the stream the same\n * way it does on the SSE transport.\n */\nfunction closeError(event: CloseEvent): Error {\n const { code, reason } = event;\n if (typeof code !== \"number\" || code < 4000 || code >= 5000) {\n return new Error(\"Protocol WebSocket closed unexpectedly.\");\n }\n return Object.assign(\n new Error(\n `Protocol WebSocket closed by the server: ${code}${reason ? ` ${reason}` : \"\"}`\n ),\n { status: code - 4000 }\n );\n}\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 * except when the server closes with a code for a 4xx status other than\n * 408 or 429 (4403, 4404, 4422), which ends the event stream with an error\n * carrying that `status`.\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/${encodePathSegment(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 if (this.closed) {\n throw new Error(\"Protocol WebSocket transport is closed.\");\n }\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 = (event: CloseEvent): 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 const error = closeError(event);\n if (isNonRetryableHttpError(error)) {\n this.#closeWithError(error);\n return;\n }\n\n this.#handleUnexpectedDisconnect(error);\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 /**\n * End the transport for good: a reconnect would be rejected the same way.\n * Marking it closed also stops a reconnect loop that is in flight.\n */\n #closeWithError(error: Error): void {\n this.closed = true;\n for (const { reject } of this.pending.values()) {\n reject(error);\n }\n this.pending.clear();\n this.queue.close(error);\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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"}
@@ -10,7 +10,10 @@ import { Command, CommandResponse, ErrorResponse, Message } from "@langchain/pro
10
10
  * `ws://.../threads/:thread_id/stream/events`.
11
11
  *
12
12
  * On unexpected disconnect the adapter reconnects with exponential
13
- * backoff (see {@link ProtocolWebSocketTransportOptions.maxReconnectAttempts}).
13
+ * backoff (see {@link ProtocolWebSocketTransportOptions.maxReconnectAttempts}),
14
+ * except when the server closes with a code for a 4xx status other than
15
+ * 408 or 429 (4403, 4404, 4422), which ends the event stream with an error
16
+ * carrying that `status`.
14
17
  * The server replays buffered events on the new socket; the SDK
15
18
  * deduplicates by `event_id`. {@link ProtocolWebSocketTransportOptions.onReconnected}
16
19
  * runs after each successful reconnect so `ThreadStream` can re-issue
@@ -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,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"}
1
+ {"version":3,"file":"websocket.d.cts","names":[],"sources":["../../../../src/client/stream/transport/websocket.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;;cAuFa,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;EAqGnB;EApFF,WAAA,CAAY,QAAA;EAqFD;;;;;EAAA,YAjEC,SAAA,CAAA;EAkFG;;;;;EArEf,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,iBAiDG,aAAA;EAAA,iBA0BA,WAAA;EAAA,iBAqBA,iBAAA;AAAA"}
@@ -10,7 +10,10 @@ import { Command, CommandResponse, ErrorResponse, Message } from "@langchain/pro
10
10
  * `ws://.../threads/:thread_id/stream/events`.
11
11
  *
12
12
  * On unexpected disconnect the adapter reconnects with exponential
13
- * backoff (see {@link ProtocolWebSocketTransportOptions.maxReconnectAttempts}).
13
+ * backoff (see {@link ProtocolWebSocketTransportOptions.maxReconnectAttempts}),
14
+ * except when the server closes with a code for a 4xx status other than
15
+ * 408 or 429 (4403, 4404, 4422), which ends the event stream with an error
16
+ * carrying that `status`.
14
17
  * The server replays buffered events on the new socket; the SDK
15
18
  * deduplicates by `event_id`. {@link ProtocolWebSocketTransportOptions.onReconnected}
16
19
  * runs after each successful reconnect so `ThreadStream` can re-issue
@@ -1 +1 @@
1
- {"version":3,"file":"websocket.d.ts","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,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"}
1
+ {"version":3,"file":"websocket.d.ts","names":[],"sources":["../../../../src/client/stream/transport/websocket.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;;cAuFa,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;EAqGnB;EApFF,WAAA,CAAY,QAAA;EAqFD;;;;;EAAA,YAjEC,SAAA,CAAA;EAkFG;;;;;EArEf,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,iBAiDG,aAAA;EAAA,iBA0BA,WAAA;EAAA,iBAqBA,iBAAA;AAAA"}
@@ -2,19 +2,33 @@ import { reconnectDelayMs } from "../../../utils/reconnect.js";
2
2
  import { encodePathSegment } from "../../base.js";
3
3
  import { MaxWebSocketReconnectAttemptsError } from "../error.js";
4
4
  import { AsyncQueue } from "./queue.js";
5
- import { hasHeaders, isRecord, resolveProtocolPath, toAbsoluteUrl, toError, toWebSocketUrl } from "./utils.js";
5
+ import { hasHeaders, isNonRetryableHttpError, isRecord, resolveProtocolPath, toAbsoluteUrl, toError, toWebSocketUrl } from "./utils.js";
6
6
  //#region src/client/stream/transport/websocket.ts
7
7
  const WEB_SOCKET_CONNECTING = 0;
8
8
  const WEB_SOCKET_OPEN = 1;
9
9
  const WEB_SOCKET_CLOSED = 3;
10
10
  /**
11
+ * A server that rejects a connection after accepting it closes with 4000
12
+ * plus the HTTP status the SSE endpoint would return (4403, 4404, 4422).
13
+ * Keeping that `status` on the error lets a 4xx end the stream the same
14
+ * way it does on the SSE transport.
15
+ */
16
+ function closeError(event) {
17
+ const { code, reason } = event;
18
+ if (typeof code !== "number" || code < 4e3 || code >= 5e3) return /* @__PURE__ */ new Error("Protocol WebSocket closed unexpectedly.");
19
+ return Object.assign(/* @__PURE__ */ new Error(`Protocol WebSocket closed by the server: ${code}${reason ? ` ${reason}` : ""}`), { status: code - 4e3 });
20
+ }
21
+ /**
11
22
  * Transport adapter that speaks the thread-centric protocol over a
12
23
  * bidirectional WebSocket. Bound to a `threadId` at construction or later
13
24
  * via {@link setThreadId} — the socket connects to
14
25
  * `ws://.../threads/:thread_id/stream/events`.
15
26
  *
16
27
  * On unexpected disconnect the adapter reconnects with exponential
17
- * backoff (see {@link ProtocolWebSocketTransportOptions.maxReconnectAttempts}).
28
+ * backoff (see {@link ProtocolWebSocketTransportOptions.maxReconnectAttempts}),
29
+ * except when the server closes with a code for a 4xx status other than
30
+ * 408 or 429 (4403, 4404, 4422), which ends the event stream with an error
31
+ * carrying that `status`.
18
32
  * The server replays buffered events on the new socket; the SDK
19
33
  * deduplicates by `event_id`. {@link ProtocolWebSocketTransportOptions.onReconnected}
20
34
  * runs after each successful reconnect so `ThreadStream` can re-issue
@@ -153,6 +167,7 @@ var ProtocolWebSocketTransportAdapter = class {
153
167
  if (hasHeaders(this.defaultHeaders) || this.onRequest != null) throw new Error("Browser WebSocket protocol transport does not support defaultHeaders or onRequest hooks. Supply a custom protocolWebSocketFactory if you need custom WebSocket setup.");
154
168
  }
155
169
  async sendCommand(command) {
170
+ if (this.closed) throw new Error("Protocol WebSocket transport is closed.");
156
171
  let socket = this.socket;
157
172
  if (this.reconnectInFlight != null && (socket == null || socket.readyState !== WEB_SOCKET_OPEN)) {
158
173
  await this.reconnectInFlight.catch(() => void 0);
@@ -199,7 +214,7 @@ var ProtocolWebSocketTransportAdapter = class {
199
214
  }
200
215
  if (isRecord(payload) && payload.type === "event") this.queue.push(payload);
201
216
  };
202
- handleClose = () => {
217
+ handleClose = (event) => {
203
218
  const socket = this.socket;
204
219
  if (socket != null) this.#detachSocket(socket);
205
220
  this.socket = null;
@@ -207,12 +222,27 @@ var ProtocolWebSocketTransportAdapter = class {
207
222
  this.queue.close();
208
223
  return;
209
224
  }
210
- this.#handleUnexpectedDisconnect(/* @__PURE__ */ new Error("Protocol WebSocket closed unexpectedly."));
225
+ const error = closeError(event);
226
+ if (isNonRetryableHttpError(error)) {
227
+ this.#closeWithError(error);
228
+ return;
229
+ }
230
+ this.#handleUnexpectedDisconnect(error);
211
231
  };
212
232
  handleSocketError = () => {
213
233
  if (this.closed || this.intentionalClose) return;
214
234
  this.#handleUnexpectedDisconnect(/* @__PURE__ */ new Error("Protocol WebSocket encountered an error."));
215
235
  };
236
+ /**
237
+ * End the transport for good: a reconnect would be rejected the same way.
238
+ * Marking it closed also stops a reconnect loop that is in flight.
239
+ */
240
+ #closeWithError(error) {
241
+ this.closed = true;
242
+ for (const { reject } of this.pending.values()) reject(error);
243
+ this.pending.clear();
244
+ this.queue.close(error);
245
+ }
216
246
  #handleUnexpectedDisconnect(cause) {
217
247
  const error = toError(cause);
218
248
  for (const { reject } of this.pending.values()) reject(error);
@@ -1 +1 @@
1
- {"version":3,"file":"websocket.js","names":["#detachSocket","#attachSocket","#handleUnexpectedDisconnect","#scheduleReconnect","#runReconnectLoop"],"sources":["../../../../src/client/stream/transport/websocket.ts"],"sourcesContent":["import { AsyncQueue } from \"./queue.js\";\nimport { encodePathSegment } from \"../../base.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/${encodePathSegment(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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"}
1
+ {"version":3,"file":"websocket.js","names":["#detachSocket","#attachSocket","#closeWithError","#handleUnexpectedDisconnect","#scheduleReconnect","#runReconnectLoop"],"sources":["../../../../src/client/stream/transport/websocket.ts"],"sourcesContent":["import { AsyncQueue } from \"./queue.js\";\nimport { encodePathSegment } from \"../../base.js\";\nimport type {\n Message,\n Command,\n CommandResponse,\n ErrorResponse,\n} from \"@langchain/protocol\";\n\nimport {\n toAbsoluteUrl,\n toWebSocketUrl,\n isRecord,\n isNonRetryableHttpError,\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 * A server that rejects a connection after accepting it closes with 4000\n * plus the HTTP status the SSE endpoint would return (4403, 4404, 4422).\n * Keeping that `status` on the error lets a 4xx end the stream the same\n * way it does on the SSE transport.\n */\nfunction closeError(event: CloseEvent): Error {\n const { code, reason } = event;\n if (typeof code !== \"number\" || code < 4000 || code >= 5000) {\n return new Error(\"Protocol WebSocket closed unexpectedly.\");\n }\n return Object.assign(\n new Error(\n `Protocol WebSocket closed by the server: ${code}${reason ? ` ${reason}` : \"\"}`\n ),\n { status: code - 4000 }\n );\n}\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 * except when the server closes with a code for a 4xx status other than\n * 408 or 429 (4403, 4404, 4422), which ends the event stream with an error\n * carrying that `status`.\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/${encodePathSegment(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 if (this.closed) {\n throw new Error(\"Protocol WebSocket transport is closed.\");\n }\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 = (event: CloseEvent): 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 const error = closeError(event);\n if (isNonRetryableHttpError(error)) {\n this.#closeWithError(error);\n return;\n }\n\n this.#handleUnexpectedDisconnect(error);\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 /**\n * End the transport for good: a reconnect would be rejected the same way.\n * Marking it closed also stops a reconnect loop that is in flight.\n */\n #closeWithError(error: Error): void {\n this.closed = true;\n for (const { reject } of this.pending.values()) {\n reject(error);\n }\n this.pending.clear();\n this.queue.close(error);\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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"}