#!/usr/bin/env node import { createServer, request as httpRequest } from "node:http"; import { request as httpsRequest } from "node:https"; const serviceId = "hwlab-codex-api-responses-forwarder"; const defaultPort = 49280; const port = parsePositiveInteger(process.env.HWLAB_CODE_AGENT_CODEX_API_FORWARDER_PORT || process.env.PORT, defaultPort); const host = firstNonEmpty(process.env.HWLAB_CODE_AGENT_CODEX_API_FORWARDER_HOST, "127.0.0.1"); const upstreamBaseUrl = new URL(firstNonEmpty(process.env.HWLAB_CODE_AGENT_CODEX_API_UPSTREAM_BASE_URL, "https://hyueapi.com")); const upstreamNoProxyEntries = noProxyEntriesForHost(upstreamBaseUrl.hostname); const maxBodyBytes = parsePositiveInteger(process.env.HWLAB_CODE_AGENT_CODEX_API_FORWARDER_MAX_BODY_BYTES, 64 * 1024 * 1024); const hopByHopHeaders = new Set([ "connection", "keep-alive", "proxy-authenticate", "proxy-authorization", "te", "trailer", "transfer-encoding", "upgrade" ]); ensureNoProxyEntries(upstreamNoProxyEntries); const server = createServer(async (clientReq, clientRes) => { try { const url = new URL(clientReq.url || "/", "http://hwlab-codex-api-forwarder.local"); if (url.pathname === "/health" || url.pathname === "/health/live" || url.pathname === "/health/liveliness") { sendJson(clientRes, 200, healthPayload("ok")); return; } if (url.pathname === "/health/readiness") { sendJson(clientRes, 200, healthPayload("ready")); return; } await proxyRequest(clientReq, clientRes, url); } catch (error) { sendJson(clientRes, 502, { error: { code: "codex_api_forwarder_failed", message: "Codex API Responses forwarder failed to proxy request", reason: error.message } }); } }); server.listen(port, host, () => { process.stdout.write(JSON.stringify({ serviceId, status: "listening", host, port, upstreamHost: upstreamBaseUrl.hostname, noProxyRequired: upstreamNoProxyEntries }) + "\n"); }); async function proxyRequest(clientReq, clientRes, url) { const startedAt = Date.now(); const targetUrl = targetUrlForRequest(url); const headers = filterRequestHeaders(clientReq.headers, targetUrl); const requestImpl = targetUrl.protocol === "https:" ? httpsRequest : httpRequest; const upstreamReq = requestImpl({ protocol: targetUrl.protocol, hostname: targetUrl.hostname, port: targetUrl.port || (targetUrl.protocol === "https:" ? 443 : 80), method: clientReq.method, path: `${targetUrl.pathname}${targetUrl.search}`, headers }, (upstreamRes) => { const responseHeaders = filterResponseHeaders(upstreamRes.headers); clientRes.writeHead(upstreamRes.statusCode || 502, responseHeaders); let responseBytes = 0; upstreamRes.on("data", (chunk) => { responseBytes += chunk.length; }); upstreamRes.on("end", () => { process.stderr.write(JSON.stringify({ serviceId, method: clientReq.method, path: url.pathname, upstreamPath: targetUrl.pathname, status: upstreamRes.statusCode || null, responseBytes, elapsedMs: Date.now() - startedAt }) + "\n"); }); upstreamRes.pipe(clientRes); }); upstreamReq.on("error", (error) => { if (!clientRes.headersSent) { sendJson(clientRes, 502, { error: { code: "codex_api_forwarder_upstream_error", message: "Codex API forwarder upstream request failed", reason: error.message } }); } else { clientRes.end(); } }); await pipeRequestBody(clientReq, upstreamReq); } async function pipeRequestBody(clientReq, upstreamReq) { let bytes = 0; for await (const chunk of clientReq) { bytes += chunk.length; if (bytes > maxBodyBytes) { upstreamReq.destroy(new Error(`request body exceeded ${maxBodyBytes} bytes`)); return; } if (!upstreamReq.write(chunk)) { await new Promise((resolve) => upstreamReq.once("drain", resolve)); } } upstreamReq.end(); } function targetUrlForRequest(url) { const target = new URL(upstreamBaseUrl.toString()); target.pathname = upstreamPathFor(url.pathname); target.search = url.search; target.hash = ""; return target; } function upstreamPathFor(inboundPath) { const basePath = upstreamBaseUrl.pathname.replace(/\/+$/u, ""); const requestPath = normalizeCodexResponsesPath(inboundPath || "/"); if (!basePath) return requestPath; if (basePath === "/v1" && requestPath.startsWith("/v1/")) return requestPath; if (basePath === "/v1" && requestPath === "/v1") return requestPath; if (basePath.endsWith("/responses") && (requestPath === "/responses" || requestPath === "/v1/responses")) return basePath; if (basePath.endsWith("/models") && (requestPath === "/models" || requestPath === "/v1/models")) return basePath; if (requestPath.startsWith(`${basePath}/`) || requestPath === basePath) return requestPath; return `${basePath}${requestPath}`; } function normalizeCodexResponsesPath(pathname) { if (upstreamBaseUrl.pathname.replace(/\/+$/u, "") === "/v1") return pathname; if (pathname === "/v1/responses") return "/responses"; if (pathname === "/v1/models") return "/models"; return pathname; } function filterRequestHeaders(headers, targetUrl) { const filtered = {}; for (const [name, value] of Object.entries(headers)) { const lower = name.toLowerCase(); if (hopByHopHeaders.has(lower) || lower === "host") continue; filtered[name] = value; } filtered.host = targetUrl.host; return filtered; } function filterResponseHeaders(headers) { const filtered = {}; for (const [name, value] of Object.entries(headers)) { if (hopByHopHeaders.has(name.toLowerCase())) continue; filtered[name] = value; } return filtered; } function sendJson(res, status, payload) { const body = JSON.stringify(payload); res.writeHead(status, { "content-type": "application/json", "content-length": Buffer.byteLength(body) }); res.end(body); } function healthPayload(status) { return { serviceId, status, listen: { host, port }, upstream: { origin: upstreamBaseUrl.origin, host: upstreamBaseUrl.hostname, directNoProxyRequired: true, noProxyRequired: upstreamNoProxyEntries } }; } function ensureNoProxyEntries(entries) { for (const name of ["NO_PROXY", "no_proxy"]) { process.env[name] = mergeNoProxyEntries(process.env[name], entries); } } function noProxyEntriesForHost(hostname) { const hostText = String(hostname || "").trim().toLowerCase(); if (!hostText) return ["127.0.0.1", "localhost", "::1"]; const entries = [hostText, "127.0.0.1", "localhost", "::1"]; if (!/^\d+(?:\.\d+){3}$/u.test(hostText) && !hostText.includes(":")) entries.push(`.${hostText}`); return uniqueStrings(entries); } function mergeNoProxyEntries(current, additions = []) { return uniqueStrings([...String(current ?? "").split(/[,;\s]+/u), ...additions]).join(","); } function uniqueStrings(values) { const seen = new Set(); const result = []; for (const value of values) { const text = String(value ?? "").trim(); if (!text) continue; const key = text.toLowerCase(); if (seen.has(key)) continue; seen.add(key); result.push(text); } return result; } function firstNonEmpty(...values) { for (const value of values) { const text = String(value ?? "").trim(); if (text) return text; } return ""; } function parsePositiveInteger(value, fallback) { const parsed = Number.parseInt(value || "", 10); return Number.isInteger(parsed) && parsed > 0 ? parsed : fallback; }