import type { IncomingMessage, ServerResponse } from "node:http"; const DEFAULT_BODY_LIMIT_BYTES = 64 * 1024; export async function handleCaseRunHttp(request: IncomingMessage, response: ServerResponse, url: URL, options: any = {}) { const env = options.env ?? process.env; const baseUrl = String(env.HARNESSRL_API_URL ?? "").trim().replace(/\/+$/u, ""); if (!baseUrl) return sendJson(response, 503, { ok: false, error: { code: "harnessrl_api_url_required", message: "HARNESSRL_API_URL is required for CaseRun routes" } }); try { const target = new URL(`${url.pathname}${url.search}`, `${baseUrl}/`); const headers = new Headers(); for (const [name, value] of Object.entries(request.headers)) { if (value === undefined || name.toLowerCase() === "host" || name.toLowerCase() === "content-length") continue; headers.set(name, Array.isArray(value) ? value.join(",") : value); } headers.set("x-forwarded-host", String(request.headers["x-forwarded-host"] ?? request.headers.host ?? "")); headers.set("x-forwarded-proto", String(request.headers["x-forwarded-proto"] ?? "http")); const body = request.method === "GET" || request.method === "HEAD" ? undefined : await readBody(request, DEFAULT_BODY_LIMIT_BYTES); const upstream = await (options.fetchImpl ?? globalThis.fetch)(target, { method: request.method, headers, body }); response.statusCode = upstream.status; for (const [name, value] of upstream.headers) if (name.toLowerCase() !== "content-length" && name.toLowerCase() !== "transfer-encoding") response.setHeader(name, value); response.end(Buffer.from(await upstream.arrayBuffer())); } catch (error: any) { sendJson(response, 502, { ok: false, error: { code: error?.code ?? "harnessrl_api_unavailable", message: error?.message ?? String(error) } }); } } export function caseRunInternalFetch(fetchImpl: typeof fetch, internalApiUrl: string, env: Record) { const apiKey = String(env.HWLAB_BOOTSTRAP_ADMIN_API_KEY ?? "").trim(); if (!apiKey) return fetchImpl; const internalOrigin = new URL(internalApiUrl).origin; return async (input: URL | RequestInfo, init: RequestInit = {}) => { const target = new URL(typeof input === "string" || input instanceof URL ? input : input.url); if (target.origin !== internalOrigin) return fetchImpl(input, init); const headers = new Headers(input instanceof Request ? input.headers : undefined); new Headers(init.headers).forEach((value, name) => headers.set(name, value)); if (!headers.has("authorization")) headers.set("authorization", `Bearer ${apiKey}`); return fetchImpl(input, { ...init, headers }); }; } async function readBody(request: IncomingMessage, limitBytes: number) { let size = 0; const chunks: Buffer[] = []; for await (const chunk of request) { const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk); size += buffer.length; if (size > limitBytes) throw Object.assign(new Error(`body exceeds ${limitBytes} bytes`), { code: "body_too_large" }); chunks.push(buffer); } return Buffer.concat(chunks); } function sendJson(response: ServerResponse, status: number, body: unknown) { response.statusCode = status; response.setHeader("content-type", "application/json; charset=utf-8"); response.setHeader("cache-control", "no-store"); response.end(`${JSON.stringify(body)}\n`); }