From d19928884c4df52e34534c28590f3ee89d334752 Mon Sep 17 00:00:00 2001 From: Dax Raad Date: Thu, 4 Jun 2026 01:26:04 -0400 Subject: [PATCH] fix(llm): avoid ambient websocket type dependency --- packages/llm/src/route/transport/websocket.ts | 44 ++++++++++++++----- 1 file changed, 32 insertions(+), 12 deletions(-) diff --git a/packages/llm/src/route/transport/websocket.ts b/packages/llm/src/route/transport/websocket.ts index 310121420c..bffbf2efc1 100644 --- a/packages/llm/src/route/transport/websocket.ts +++ b/packages/llm/src/route/transport/websocket.ts @@ -19,10 +19,30 @@ export interface Interface { readonly open: (input: WebSocketRequest) => Effect.Effect } -type WebSocketConstructorWithHeaders = new ( - url: string, - options?: { readonly headers?: Headers.Headers }, -) => globalThis.WebSocket +interface WebSocketLike { + readonly readyState: number + readonly send: (message: string) => void + readonly close: (code?: number) => void + readonly addEventListener: { + (type: "open" | "error", listener: (event: Event) => void, options?: { readonly once?: boolean }): void + (type: "close", listener: (event: CloseEvent) => void, options?: { readonly once?: boolean }): void + (type: "message", listener: (event: MessageEvent) => void, options?: { readonly once?: boolean }): void + } + readonly removeEventListener: { + (type: "open" | "error", listener: (event: Event) => void): void + (type: "close", listener: (event: CloseEvent) => void): void + (type: "message", listener: (event: MessageEvent) => void): void + } +} + +interface WebSocketConstructorWithHeaders { + readonly OPEN: number + readonly CLOSING: number + readonly CLOSED: number + new (url: string, options?: { readonly headers?: Headers.Headers }): WebSocketLike +} + +const WebSocketGlobal = globalThis as unknown as { readonly WebSocket: WebSocketConstructorWithHeaders } export class Service extends Context.Service()("@opencode/LLM/WebSocketExecutor") {} @@ -49,9 +69,9 @@ const binaryMessage = (data: unknown) => { return undefined } -const waitOpen = (ws: globalThis.WebSocket, input: WebSocketRequest) => { - if (ws.readyState === globalThis.WebSocket.OPEN) return Effect.void - if (ws.readyState === globalThis.WebSocket.CLOSING || ws.readyState === globalThis.WebSocket.CLOSED) { +const waitOpen = (ws: WebSocketLike, input: WebSocketRequest) => { + if (ws.readyState === WebSocketGlobal.WebSocket.OPEN) return Effect.void + if (ws.readyState === WebSocketGlobal.WebSocket.CLOSING || ws.readyState === WebSocketGlobal.WebSocket.CLOSED) { return Effect.fail( transportError("open", `WebSocket closed before opening (state ${ws.readyState})`, { url: input.url, @@ -68,7 +88,7 @@ const waitOpen = (ws: globalThis.WebSocket, input: WebSocketRequest) => { } const onAbort = () => { cleanup() - if (ws.readyState !== globalThis.WebSocket.CLOSED && ws.readyState !== globalThis.WebSocket.CLOSING) + if (ws.readyState !== WebSocketGlobal.WebSocket.CLOSED && ws.readyState !== WebSocketGlobal.WebSocket.CLOSING) ws.close(1000) } const onOpen = () => { @@ -124,8 +144,7 @@ const webSocketUrl = (value: string) => export const open = (input: WebSocketRequest) => Effect.try({ - try: () => - new (globalThis.WebSocket as unknown as WebSocketConstructorWithHeaders)(input.url, { headers: input.headers }), + try: () => new WebSocketGlobal.WebSocket(input.url, { headers: input.headers }), catch: (error) => transportError("open", error instanceof Error ? error.message : "Failed to construct WebSocket", { url: input.url, @@ -136,7 +155,7 @@ export const open = (input: WebSocketRequest) => export const layer: Layer.Layer = Layer.succeed(Service, Service.of({ open })) export const fromWebSocket = ( - ws: globalThis.WebSocket, + ws: WebSocketLike, input: WebSocketRequest, ): Effect.Effect => Effect.gen(function* () { @@ -195,7 +214,8 @@ export const fromWebSocket = ( close: cleanup.pipe( Effect.andThen( Effect.sync(() => { - if (ws.readyState === globalThis.WebSocket.CLOSED || ws.readyState === globalThis.WebSocket.CLOSING) return + if (ws.readyState === WebSocketGlobal.WebSocket.CLOSED || ws.readyState === WebSocketGlobal.WebSocket.CLOSING) + return ws.close(1000) }), ),