import { WebSocketServer, type WebSocket } from "ws"; import { parseHttpRequestFrame, parseJsonFrame, type HttpResponseFrame } from "./protocol.js"; export type OpencodeProxyOptions = { host: string; port: number; opencodeUrl: string; username?: string; password?: string; maxRequestMs?: number; }; const DEFAULT_MAX_REQUEST_MS = 600_000; // WebSocket endpoint where the ComputerCraft client is the requester and the // bridge is the responder: it performs the opencode http call server-side (no // short timeout) and returns the raw response. This lets the in-game client use // opencode's synchronous /message endpoint without hitting CC's ~60s http cap. export function startOpencodeProxy(options: OpencodeProxyOptions): WebSocketServer { const baseUrl = options.opencodeUrl.replace(/\/+$/, ""); const maxRequestMs = options.maxRequestMs ?? DEFAULT_MAX_REQUEST_MS; const authHeader = options.password ? "Basic " + Buffer.from(`${options.username ?? "opencode"}:${options.password}`).toString("base64") : undefined; const server = new WebSocketServer({ host: options.host, port: options.port }); server.on("connection", (ws) => { ws.on("message", (data) => { void handleFrame(ws, data); }); ws.on("error", () => { /* connection-level errors just drop the socket; nothing to clean up */ }); }); async function handleFrame(ws: WebSocket, data: unknown): Promise { const frame = parseHttpRequestFrame(parseJsonFrame(data)); if (!frame) { return; } const headers: Record = { "Content-Type": "application/json", Accept: "application/json", }; if (authHeader) { headers.Authorization = authHeader; } try { const res = await fetch(baseUrl + frame.path, { method: frame.method, headers, body: frame.method === "POST" ? frame.body : undefined, signal: AbortSignal.timeout(maxRequestMs), }); const body = await res.text(); send(ws, { type: "http-response", id: frame.id, status: res.status, body }); } catch (err) { const message = err instanceof Error ? err.message : String(err); send(ws, { type: "http-response", id: frame.id, status: 0, error: message }); } } return server; } function send(ws: WebSocket, frame: HttpResponseFrame): void { if (ws.readyState === ws.OPEN) { ws.send(JSON.stringify(frame)); } }