{"version":3,"file":"websocket.cjs","names":["AsyncQueue","toWebSocketUrl","hasHeaders","toError","isRecord"],"sources":["../../../../src/client/stream/transport/websocket.ts"],"sourcesContent":["import { AsyncQueue } from \"./queue.js\";\nimport type {\n  Message,\n  Command,\n  CommandResponse,\n  ErrorResponse,\n} from \"@langchain/protocol\";\n\nimport { toWebSocketUrl, isRecord, hasHeaders, toError } from \"./utils.js\";\nimport type {\n  HeaderValue,\n  ProtocolRequestHook,\n  PendingResponse,\n  ProtocolWebSocketTransportOptions,\n} from \"./types.js\";\nimport type { TransportAdapter } from \"../transport.js\";\n\n/**\n * Transport adapter that speaks the thread-centric protocol over a\n * bidirectional WebSocket. Bound to a specific `threadId` — the socket\n * connects to `ws://.../threads/:thread_id/stream/events`.\n */\nexport class ProtocolWebSocketTransportAdapter implements TransportAdapter {\n  readonly 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 streamUrl: string;\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  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.streamUrl =\n      options.paths?.stream ?? `/threads/${this.threadId}/stream/events`;\n  }\n\n  async open(): Promise<void> {\n    if (this.socket != null) return;\n    this.assertBrowserSafeTransportConfig();\n\n    const wsUrl = toWebSocketUrl(\n      new URL(\n        this.streamUrl,\n        this.apiUrl.endsWith(\"/\") ? this.apiUrl : `${this.apiUrl}/`\n      ).toString()\n    );\n    const socket = this.webSocketFactory(wsUrl);\n    this.socket = socket;\n    this.closed = false;\n    this.intentionalClose = false;\n\n    socket.addEventListener(\"message\", this.handleMessage);\n    socket.addEventListener(\"close\", this.handleClose);\n    socket.addEventListener(\"error\", this.handleSocketError);\n\n    await new Promise<void>((resolve, reject) => {\n      const onOpen = () => {\n        cleanup();\n        resolve();\n      };\n      const onError = () => {\n        cleanup();\n        reject(new Error(\"Failed to open protocol WebSocket.\"));\n      };\n      const cleanup = () => {\n        socket.removeEventListener(\"open\", onOpen);\n        socket.removeEventListener(\"error\", onError);\n      };\n      socket.addEventListener(\"open\", onOpen, { once: true });\n      socket.addEventListener(\"error\", onError, { once: true });\n    });\n  }\n\n  async send(\n    command: Command\n  ): Promise<CommandResponse | ErrorResponse | void> {\n    return await this.sendCommand(command);\n  }\n\n  events(): AsyncIterable<Message> {\n    const queue = this.queue;\n    return {\n      [Symbol.asyncIterator]: () => ({\n        next: async () => await queue.shift(),\n        return: async () => {\n          queue.close();\n          return { done: true, value: undefined };\n        },\n      }),\n    };\n  }\n\n  async close(): Promise<void> {\n    if (this.closed) {\n      return;\n    }\n\n    this.closed = true;\n    this.intentionalClose = true;\n\n    for (const { reject } of this.pending.values()) {\n      reject(new Error(\"Protocol WebSocket connection closed.\"));\n    }\n    this.pending.clear();\n    this.queue.close();\n\n    const socket = this.socket;\n    this.socket = null;\n    if (!socket) {\n      return;\n    }\n\n    await new Promise<void>((resolve) => {\n      if (socket.readyState === WebSocket.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 === WebSocket.OPEN ||\n        socket.readyState === WebSocket.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    const socket = this.socket;\n    if (socket == null || socket.readyState !== WebSocket.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  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    this.socket = null;\n\n    if (this.intentionalClose || this.closed) {\n      this.queue.close();\n      return;\n    }\n\n    const error = new Error(\"Protocol WebSocket closed unexpectedly.\");\n    for (const { reject } of this.pending.values()) {\n      reject(error);\n    }\n    this.pending.clear();\n    this.queue.close(error);\n  };\n\n  private readonly handleSocketError = (): void => {\n    if (this.closed || this.intentionalClose) {\n      return;\n    }\n\n    const error = new Error(\"Protocol WebSocket encountered an error.\");\n    for (const { reject } of this.pending.values()) {\n      reject(error);\n    }\n    this.pending.clear();\n    this.queue.close(error);\n  };\n}\n"],"mappings":"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"}