import http from "node:http"; import { Buffer } from "node:buffer"; import { Readable, Transform } from "node:stream"; import { pipeline } from "node:stream/promises"; import { pathToFileURL } from "node:url"; import WebSocket, { WebSocketServer } from "ws"; const CAPABILITY_PATTERN = /^[A-Za-z0-9_-]{43}$/; const SANDBOX_CONTROL_PORT = 49_983; const MIN_RESOLVER_SECRET_BYTES = 32; const MAX_RESOLVER_BODY_BYTES = 16 * 1024; const MAX_PREVIEW_REQUEST_BODY_BYTES = 10 * 1024 * 1024; const HTML_INJECTION_PROBE_BYTES = 64 * 1024; const MAX_WEBSOCKET_PAYLOAD_BYTES = 16 * 1024 * 1024; const PREVIEW_REVALIDATE_INTERVAL_MS = 10_000; const UPSTREAM_HANDSHAKE_TIMEOUT_MS = 10_000; // A healthy hf.jobs route can briefly return an edge 404 for one request. Retry // only bodyless safe methods so an application-level 404 remains observable // after a short, bounded propagation window. const UPSTREAM_ROUTE_RETRY_DELAYS_MS = [100, 250, 500, 1_000]; const BLOCKED_REQUEST_HEADERS = new Set([ "authorization", "cookie", "host", "connection", "content-length", "forwarded", "proxy-authenticate", "proxy-authorization", "referer", "te", "trailer", "transfer-encoding", "upgrade", ]); const BLOCKED_RESPONSE_HEADERS = new Set([ "access-control-allow-credentials", "access-control-allow-headers", "access-control-allow-methods", "access-control-allow-origin", "access-control-expose-headers", "access-control-max-age", "connection", "content-encoding", "content-length", "content-location", "content-security-policy", "content-security-policy-report-only", "cross-origin-embedder-policy", "cross-origin-opener-policy", "cross-origin-resource-policy", "keep-alive", "link", "permissions-policy", "proxy-authenticate", "proxy-authorization", "referrer-policy", "refresh", "service-worker-allowed", "strict-transport-security", "te", "trailer", "transfer-encoding", "upgrade", "www-authenticate", "x-content-type-options", "x-frame-options", ]); class PreviewRuntimeError extends Error { constructor(status, message) { super(message); this.name = "PreviewRuntimeError"; this.status = status; } } export function runtimeConfigFromEnv(env = process.env) { const chatBaseUrl = requireHttpsBaseUrl(env.CHAT_UI_ORIGIN, "CHAT_UI_ORIGIN"); const chatOrigin = new URL(chatBaseUrl).origin; const publicOrigin = requireHttpsOrigin(env.AGENT_PREVIEW_ORIGIN, "AGENT_PREVIEW_ORIGIN"); if (chatOrigin === publicOrigin) { throw new Error("AGENT_PREVIEW_ORIGIN must be distinct from CHAT_UI_ORIGIN"); } const resolverSecret = env.AGENT_PREVIEW_RESOLVER_SECRET ?? ""; if (Buffer.byteLength(resolverSecret, "utf8") < MIN_RESOLVER_SECRET_BYTES) { throw new Error("AGENT_PREVIEW_RESOLVER_SECRET must contain at least 32 bytes"); } const jobsToken = env.AGENT_HF_TOKEN || env.HF_TOKEN || ""; if ( !jobsToken || Buffer.byteLength(jobsToken, "utf8") > 4_096 || [...jobsToken].some((character) => { const code = character.charCodeAt(0); return code <= 31 || code === 127; }) ) { throw new Error("AGENT_HF_TOKEN must contain a valid server-side Hugging Face credential"); } const port = Number(env.PORT ?? 7860); if (!Number.isSafeInteger(port) || port < 1 || port > 65_535) { throw new Error("PORT must be a valid TCP port"); } return { chatOrigin, publicOrigin, resolverSecret, jobsToken, resolverUrl: `${chatBaseUrl}/api/agent/preview/resolve`, port, }; } export function createPreviewRuntime(options) { const config = options.config; const fetchImpl = options.fetchImpl ?? fetch; const resolveCapability = options.resolveCapability ?? ((capability, signal) => resolvePreviewCapability(capability, signal, config, fetchImpl)); const validateResolution = options.validateResolution ?? validatePreviewResolution; const WebSocketImpl = options.WebSocketImpl ?? WebSocket; const server = http.createServer( { maxHeaderSize: 16 * 1024, requestTimeout: 60_000, headersTimeout: 10_000, keepAliveTimeout: 5_000, }, (request, response) => { void handleHttpRequest({ request, response, config, fetchImpl, resolveCapability, validateResolution, }).catch((cause) => sendError(response, cause)); } ); server.maxHeadersCount = 100; server.maxRequestsPerSocket = 1_000; server.on("upgrade", (request, socket, head) => { void handleWebSocketUpgrade({ request, socket, head, config, resolveCapability, validateResolution, WebSocketImpl, }).catch((cause) => rejectUpgrade(socket, cause)); }); server.on("clientError", (_cause, socket) => { if (socket.writable) socket.end("HTTP/1.1 400 Bad Request\r\nConnection: close\r\n\r\n"); }); return server; } async function handleHttpRequest({ request, response, config, fetchImpl, resolveCapability, validateResolution, }) { const requestUrl = new URL(request.url ?? "/", config.publicOrigin); if (isServiceWorkerRequest(request)) { throw new PreviewRuntimeError(403, "Service workers are disabled for previews"); } if (requestUrl.pathname === "/" || requestUrl.pathname === "/healthcheck") { return sendLanding(response); } const recovered = recoverRootRelativeRequest(request, requestUrl, config.publicOrigin); if (recovered) { response.writeHead(307, { location: recovered, "cache-control": "no-store", "referrer-policy": "same-origin", }); response.end(); return; } const scoped = parseScopedRequest(requestUrl); if (!scoped) throw new PreviewRuntimeError(404, "Preview capability required"); if (scoped.canonicalLocation) { response.writeHead(308, { location: scoped.canonicalLocation, "cache-control": "no-store" }); response.end(); return; } const abort = new AbortController(); const onAborted = () => abort.abort(new Error("Preview client disconnected")); let clearResolutionGuard = () => {}; request.once("aborted", onAborted); try { const resolution = validateResolution(await resolveCapability(scoped.capability, abort.signal)); clearResolutionGuard = guardHttpResolution({ resolution, refresh: async () => validateResolution(await resolveCapability(scoped.capability, abort.signal)), abort, }); if (isCorsPreflight(request)) { return sendCorsPreflight(response, request); } const target = sandboxProxyUrl(resolution, scoped.upstreamPath, "https:"); const headers = sanitizedUpstreamHeaders(request.headers, resolution.port); headers.set("authorization", `Bearer ${config.jobsToken}`); headers.set("x-sandbox-token", resolution.sandboxToken); headers.set("accept-encoding", "identity"); const hasBody = !["GET", "HEAD"].includes(request.method ?? "GET"); const declaredLength = Number(request.headers["content-length"] ?? "0"); if ( hasBody && Number.isFinite(declaredLength) && declaredLength > MAX_PREVIEW_REQUEST_BODY_BYTES ) { throw new PreviewRuntimeError(413, "Preview request body is too large"); } const requestBody = hasBody ? request.pipe(new ByteLimitTransform(MAX_PREVIEW_REQUEST_BODY_BYTES)) : undefined; try { const upstream = await fetchPreviewUpstream(fetchImpl, target, { method: request.method, headers, ...(requestBody ? { body: Readable.toWeb(requestBody), duplex: "half" } : {}), redirect: "manual", signal: abort.signal, }); const responseHeaders = previewResponseHeaders( upstream, config, scoped.prefix, scoped.upstreamPath, resolution ); response.writeHead(upstream.status, upstream.statusText, responseHeaders); if (request.method === "HEAD" || !upstream.body) { response.end(); return; } const source = Readable.fromWeb(upstream.body); if (isHtml(upstream.headers.get("content-type"))) { await pipeline(source, new HtmlShimInjector(previewShim(scoped.prefix)), response); } else { await pipeline(source, response); } } finally { clearResolutionGuard(); } } finally { clearResolutionGuard(); request.off("aborted", onAborted); } } export async function fetchPreviewUpstream( fetchImpl, target, init, retryDelaysMs = UPSTREAM_ROUTE_RETRY_DELAYS_MS ) { const method = String(init.method ?? "GET").toUpperCase(); const canRetryRouteMiss = method === "GET" || method === "HEAD"; for (let attempt = 0; ; attempt += 1) { const upstream = await fetchImpl(target, init); const retryDelay = retryDelaysMs[attempt]; if (!canRetryRouteMiss || upstream.status !== 404 || retryDelay === undefined) { return upstream; } await upstream.body?.cancel().catch(() => undefined); await delay(retryDelay, init.signal); } } async function handleWebSocketUpgrade({ request, socket, head, config, resolveCapability, validateResolution, WebSocketImpl, }) { const requestUrl = new URL(request.url ?? "/", config.publicOrigin); const scoped = parseScopedRequest(requestUrl); if (!scoped || scoped.canonicalLocation) { throw new PreviewRuntimeError(404, "Preview capability required"); } const resolution = validateResolution(await resolveCapability(scoped.capability)); const target = sandboxProxyUrl(resolution, scoped.upstreamPath, "wss:"); const protocols = parseWebSocketProtocols(request.headers["sec-websocket-protocol"]); const headers = Object.fromEntries(sanitizedUpstreamHeaders(request.headers, resolution.port)); headers.authorization = `Bearer ${config.jobsToken}`; headers["x-sandbox-token"] = resolution.sandboxToken; const upstream = new WebSocketImpl(target, protocols, { headers, followRedirects: false, handshakeTimeout: UPSTREAM_HANDSHAKE_TIMEOUT_MS, maxPayload: MAX_WEBSOCKET_PAYLOAD_BYTES, perMessageDeflate: false, }); let browser; let completed = false; const reject = (cause) => { if (completed) return; completed = true; try { upstream.terminate(); } catch { // Best-effort cleanup of a failed upstream handshake. } rejectUpgrade(socket, cause); }; upstream.once("unexpected-response", (_request, response) => { response.resume(); reject(new PreviewRuntimeError(502, "Preview WebSocket refused the connection")); }); upstream.once("error", reject); upstream.once("open", () => { if (completed) return; void Promise.resolve(resolveCapability(scoped.capability)) .then(validateResolution) .then((current) => { if (completed) return; if (!samePreviewResolution(current, resolution)) { throw new PreviewRuntimeError(404, "Preview capability is no longer valid"); } completed = true; upstream.off("error", reject); const selectedProtocol = upstream.protocol; const wss = new WebSocketServer({ noServer: true, maxPayload: MAX_WEBSOCKET_PAYLOAD_BYTES, perMessageDeflate: false, handleProtocols: (offered) => selectedProtocol && offered.has(selectedProtocol) ? selectedProtocol : false, }); wss.handleUpgrade(request, socket, head, (accepted) => { browser = accepted; bridgeWebSockets(browser, upstream, resolution, async () => validateResolution(await resolveCapability(scoped.capability)) ); }); }) .catch(reject); }); socket.once("close", () => { if (!browser && upstream.readyState < WebSocketImpl.CLOSING) upstream.terminate(); }); } function bridgeWebSockets(browser, upstream, resolution, refreshResolution) { const closeBoth = (code = 1011, reason = "Preview connection closed") => { for (const ws of [browser, upstream]) { if (ws.readyState === WebSocket.OPEN) ws.close(code, reason.slice(0, 123)); else if (ws.readyState === WebSocket.CONNECTING) ws.terminate(); } }; const expiryDelay = Math.max(0, new Date(resolution.expiresAt).getTime() - Date.now()); const expiryTimer = setTimeout(() => closeBoth(1008, "Preview capability expired"), expiryDelay); expiryTimer.unref?.(); let refreshRunning = false; const refreshTimer = setInterval(() => { if (refreshRunning) return; refreshRunning = true; void refreshResolution() .then((current) => { if (!samePreviewResolution(current, resolution)) { closeBoth(1008, "Preview capability was replaced"); } }) .catch(() => closeBoth(1008, "Preview capability is no longer valid")) .finally(() => { refreshRunning = false; }); }, PREVIEW_REVALIDATE_INTERVAL_MS); refreshTimer.unref?.(); const clearGuards = () => { clearTimeout(expiryTimer); clearInterval(refreshTimer); }; browser.on("message", (data, isBinary) => { if (upstream.readyState === WebSocket.OPEN) upstream.send(data, { binary: isBinary }); }); upstream.on("message", (data, isBinary) => { if (browser.readyState === WebSocket.OPEN) browser.send(data, { binary: isBinary }); }); browser.on("ping", (data) => { if (upstream.readyState === WebSocket.OPEN) upstream.ping(data); }); upstream.on("ping", (data) => { if (browser.readyState === WebSocket.OPEN) browser.ping(data); }); browser.once("close", (code, reason) => { clearGuards(); if (upstream.readyState === WebSocket.OPEN) upstream.close(forwardableCloseCode(code), reason.toString()); else if (upstream.readyState === WebSocket.CONNECTING) upstream.terminate(); }); upstream.once("close", (code, reason) => { clearGuards(); if (browser.readyState === WebSocket.OPEN) browser.close(forwardableCloseCode(code), reason.toString()); else if (browser.readyState === WebSocket.CONNECTING) browser.terminate(); }); for (const ws of [browser, upstream]) { ws.on("error", () => closeBoth()); } } function samePreviewResolution(left, right) { return ( left.sandboxId === right.sandboxId && left.sandboxGeneration === right.sandboxGeneration && left.processId === right.processId && left.baseUrl === right.baseUrl && left.port === right.port && left.sandboxToken === right.sandboxToken && left.expiresAt === right.expiresAt ); } function guardHttpResolution({ resolution, refresh, abort }) { let refreshRunning = false; const expire = () => { if (!abort.signal.aborted) { abort.abort(new PreviewRuntimeError(404, "Preview capability expired")); } }; const expiryDelay = Math.max(0, new Date(resolution.expiresAt).getTime() - Date.now()); const expiryTimer = setTimeout(expire, expiryDelay); expiryTimer.unref?.(); const refreshTimer = setInterval(() => { if (refreshRunning || abort.signal.aborted) return; refreshRunning = true; void refresh() .then((current) => { if (!samePreviewResolution(current, resolution)) expire(); }) .catch(expire) .finally(() => { refreshRunning = false; }); }, PREVIEW_REVALIDATE_INTERVAL_MS); refreshTimer.unref?.(); return () => { clearTimeout(expiryTimer); clearInterval(refreshTimer); }; } function forwardableCloseCode(code) { return [1004, 1005, 1006, 1015].includes(code) ? 1000 : code; } async function resolvePreviewCapability(capability, signal, config, fetchImpl) { const resolverTimeout = AbortSignal.timeout(10_000); const response = await fetchImpl(config.resolverUrl, { method: "POST", headers: { authorization: `Bearer ${config.resolverSecret}`, "content-type": "application/json", accept: "application/json", }, body: JSON.stringify({ capability }), redirect: "error", signal: signal ? AbortSignal.any([signal, resolverTimeout]) : resolverTimeout, }); if (!response.ok) { await response.body?.cancel().catch(() => undefined); if ([400, 404, 410].includes(response.status)) { throw new PreviewRuntimeError(404, "Preview capability is no longer valid"); } throw new PreviewRuntimeError(502, "Preview resolver is unavailable"); } const declared = Number(response.headers.get("content-length") ?? "0"); if (Number.isFinite(declared) && declared > MAX_RESOLVER_BODY_BYTES) { await response.body?.cancel().catch(() => undefined); throw new PreviewRuntimeError(502, "Preview resolver returned an invalid response"); } const bytes = new Uint8Array(await response.arrayBuffer()); if (bytes.byteLength > MAX_RESOLVER_BODY_BYTES) { throw new PreviewRuntimeError(502, "Preview resolver returned an invalid response"); } try { return JSON.parse(new TextDecoder().decode(bytes)); } catch { throw new PreviewRuntimeError(502, "Preview resolver returned an invalid response"); } } export function validatePreviewResolution(value) { if (!value || typeof value !== "object" || Array.isArray(value)) { throw new PreviewRuntimeError(502, "Preview resolver returned an invalid target"); } const sandboxId = value.sandboxId; const sandboxGeneration = value.sandboxGeneration; const processId = value.processId; const baseUrl = value.baseUrl; const port = value.port; const sandboxToken = value.sandboxToken; const expiresAt = value.expiresAt; if ( typeof sandboxId !== "string" || !/^[a-z0-9](?:[a-z0-9-]{0,126}[a-z0-9])?$/.test(sandboxId) || typeof sandboxGeneration !== "string" || !sandboxGeneration || typeof processId !== "string" || !/^[A-Za-z0-9._~-]{1,256}$/.test(processId) || !Number.isSafeInteger(port) || port < 1_024 || port > 65_535 || port === SANDBOX_CONTROL_PORT || typeof sandboxToken !== "string" || !/^[a-f0-9]{64}$/.test(sandboxToken) || typeof expiresAt !== "string" || !Number.isFinite(Date.parse(expiresAt)) || Date.parse(expiresAt) <= Date.now() ) { throw new PreviewRuntimeError(502, "Preview resolver returned an invalid target"); } let target; try { target = new URL(baseUrl); } catch { throw new PreviewRuntimeError(502, "Preview resolver returned an invalid target"); } const expectedHostname = `${sandboxId}--${SANDBOX_CONTROL_PORT}.hf.jobs`; if ( target.protocol !== "https:" || target.username || target.password || target.hostname !== expectedHostname || target.port || target.pathname !== "/" || target.search || target.hash || (baseUrl !== target.origin && baseUrl !== `${target.origin}/`) ) { throw new PreviewRuntimeError(502, "Preview resolver returned an invalid target"); } return { sandboxId, sandboxGeneration, processId, baseUrl: target.origin, port, sandboxToken, expiresAt, }; } function sandboxProxyUrl(resolution, upstreamPath, protocol) { const target = new URL(resolution.baseUrl); target.protocol = protocol; const path = new URL(upstreamPath, "https://preview.invalid"); target.pathname = `/v1/proxy/${resolution.port}${path.pathname}`; target.search = path.search; return target.toString(); } function parseScopedRequest(url) { const match = /^\/p\/([A-Za-z0-9_-]{43})(\/.*)?$/.exec(url.pathname); if (!match || !CAPABILITY_PATTERN.test(match[1])) return undefined; const capability = match[1]; const prefix = `/p/${capability}`; if (!match[2]) { return { capability, prefix, canonicalLocation: `${prefix}/${url.search}` }; } const pathname = validateUpstreamPath(match[2]); return { capability, prefix, upstreamPath: `${pathname}${url.search}`, }; } function validateUpstreamPath(pathname) { if (pathname.length > 8_192 || pathname.includes("\\")) { throw new PreviewRuntimeError(400, "Invalid preview path"); } let decoded = pathname; for (let index = 0; index < 4; index += 1) { let next; try { next = decodeURIComponent(decoded); } catch { throw new PreviewRuntimeError(400, "Invalid preview path"); } if (next === decoded) break; decoded = next; } if ( decoded.includes("\\") || decoded.split("/").some((segment) => segment === "." || segment === "..") || [...decoded].some((character) => { const code = character.charCodeAt(0); return code === 0 || code < 32 || code === 127; }) ) { throw new PreviewRuntimeError(400, "Invalid preview path"); } return pathname; } function recoverRootRelativeRequest(request, url, publicOrigin) { if (url.pathname.startsWith("/p/")) return undefined; const referer = request.headers.referer; if (typeof referer !== "string") return undefined; let source; try { source = new URL(referer); } catch { return undefined; } if (source.origin !== publicOrigin) return undefined; const match = /^\/p\/([A-Za-z0-9_-]{43})(?:\/|$)/.exec(source.pathname); if (!match || !CAPABILITY_PATTERN.test(match[1])) return undefined; return `/p/${match[1]}${url.pathname}${url.search}`; } function isServiceWorkerRequest(request) { return ( String(request.headers["service-worker"] ?? "").toLowerCase() === "script" || String(request.headers["sec-fetch-dest"] ?? "").toLowerCase() === "serviceworker" ); } function isCorsPreflight(request) { return ( request.method === "OPTIONS" && typeof request.headers["access-control-request-method"] === "string" ); } function sendCorsPreflight(response, request) { if (request.headers.origin !== "null") { throw new PreviewRuntimeError(403, "Preview preflight requires an opaque origin"); } const method = String(request.headers["access-control-request-method"] ?? "").toUpperCase(); if (!/^[A-Z]+$/.test(method) || ["CONNECT", "TRACE", "TRACK"].includes(method)) { throw new PreviewRuntimeError(403, "Preview preflight method is not allowed"); } const requestedHeaders = String(request.headers["access-control-request-headers"] ?? "") .split(",") .map((value) => value.trim().toLowerCase()) .filter(Boolean); for (const name of requestedHeaders) { if ( !/^[a-z0-9!#$%&'*+.^_`|~-]+$/.test(name) || BLOCKED_REQUEST_HEADERS.has(name) || name.startsWith("x-forwarded-") || name.startsWith("x-sandbox-") || name.startsWith("x-hf-") || name.startsWith("x-chat-") || name.startsWith("x-agent-") || name.startsWith("sec-") ) { throw new PreviewRuntimeError(403, "Preview preflight header is not allowed"); } } response.writeHead(204, { "access-control-allow-origin": "null", "access-control-allow-credentials": "true", "access-control-allow-methods": method, ...(requestedHeaders.length ? { "access-control-allow-headers": requestedHeaders.join(", ") } : {}), "access-control-max-age": "0", "cache-control": "no-store", vary: "Origin, Access-Control-Request-Method, Access-Control-Request-Headers", }); response.end(); } function sanitizedUpstreamHeaders(source, port) { const headers = new Headers(); for (const [rawName, rawValue] of Object.entries(source)) { if (rawValue === undefined) continue; const name = rawName.toLowerCase(); if ( BLOCKED_REQUEST_HEADERS.has(name) || name.startsWith("x-forwarded-") || name.startsWith("x-sandbox-") || name.startsWith("x-hf-") || name.startsWith("x-chat-") || name.startsWith("x-agent-") || name.startsWith("sec-") ) { continue; } for (const value of Array.isArray(rawValue) ? rawValue : [rawValue]) { headers.append(name, value); } } if (source.origin) headers.set("origin", `http://localhost:${port}`); return headers; } function previewResponseHeaders(upstream, config, prefix, currentPath, resolution) { const headers = {}; for (const [name, value] of upstream.headers) { const lower = name.toLowerCase(); if (BLOCKED_RESPONSE_HEADERS.has(lower) || lower.startsWith("access-control-")) continue; if (lower === "set-cookie" || lower === "location") continue; headers[name] = value; } const location = upstream.headers.get("location"); if (location) headers.location = rewritePreviewLocation(location, prefix, currentPath, resolution); const cookies = upstream.headers.getSetCookie?.() ?? []; const scopedCookies = cookies .map((cookie) => scopeSetCookie(cookie, `${prefix}/`)) .filter(Boolean); if (scopedCookies.length) headers["set-cookie"] = scopedCookies; headers["content-security-policy"] = previewContentSecurityPolicy(config.chatOrigin); headers["referrer-policy"] = "same-origin"; headers["access-control-allow-origin"] = "null"; headers["access-control-allow-credentials"] = "true"; headers["cross-origin-resource-policy"] = "cross-origin"; headers["permissions-policy"] = "camera=(), geolocation=(), microphone=(), payment=(), usb=(), serial=(), bluetooth=()"; headers["x-content-type-options"] = "nosniff"; headers["x-robots-tag"] = "noindex, nofollow, noarchive"; headers["cache-control"] = "no-store"; headers.vary = mergeVary(headers.vary, "Origin"); return headers; } function mergeVary(value, token) { const names = String(value ?? "") .split(",") .map((name) => name.trim()) .filter(Boolean); if (!names.some((name) => name.toLowerCase() === token.toLowerCase())) names.push(token); return names.join(", "); } function delay(ms, signal) { if (signal?.aborted) return Promise.reject(signal.reason); return new Promise((resolve, reject) => { const timeout = setTimeout(done, ms); function done() { signal?.removeEventListener("abort", aborted); resolve(); } function aborted() { clearTimeout(timeout); signal?.removeEventListener("abort", aborted); reject(signal.reason); } signal?.addEventListener("abort", aborted, { once: true }); }); } function previewContentSecurityPolicy(chatOrigin) { return [ "sandbox allow-downloads allow-forms allow-modals allow-popups allow-scripts", "default-src * data: blob: 'unsafe-inline' 'unsafe-eval'", "connect-src * data: blob: ws: wss:", "img-src * data: blob:", "media-src * data: blob:", "style-src * 'unsafe-inline'", "script-src * 'unsafe-inline' 'unsafe-eval' blob:", "worker-src 'none'", "object-src 'none'", "base-uri 'none'", `frame-ancestors ${chatOrigin}`, ].join("; "); } function scopeSetCookie(value, path) { const parts = value.split(";"); const pair = parts.shift()?.trim(); if (!pair || !pair.includes("=")) return undefined; const attributes = []; let hasSecure = false; let hasSameSite = false; for (const raw of parts) { const attribute = raw.trim(); const name = attribute.split("=", 1)[0].toLowerCase(); if (name === "domain" || name === "path") continue; if (name === "secure") hasSecure = true; if (name === "samesite") hasSameSite = true; attributes.push(attribute); } attributes.push(`Path=${path}`); if (!hasSecure) attributes.push("Secure"); if (!hasSameSite) attributes.push("SameSite=Lax"); return [pair, ...attributes].join("; "); } function rewritePreviewLocation(location, prefix, currentPath, resolution) { let target; try { target = new URL(location, `https://preview.invalid${currentPath}`); } catch { return `${prefix}/`; } const localHosts = new Set([ "localhost", "127.0.0.1", "0.0.0.0", "[::1]", new URL(resolution.baseUrl).hostname, ]); if (target.origin === "https://preview.invalid" || localHosts.has(target.hostname)) { const marker = `/v1/proxy/${resolution.port}`; const path = target.pathname.startsWith(marker) ? target.pathname.slice(marker.length) || "/" : target.pathname; return `${prefix}${path}${target.search}${target.hash}`; } return location; } function isHtml(contentType) { return /(?:text\/html|application\/xhtml\+xml)/i.test(contentType ?? ""); } class HtmlShimInjector extends Transform { constructor(shim) { super(); this.shim = Buffer.from(shim, "utf8"); this.pending = Buffer.alloc(0); this.injected = false; } _transform(chunk, _encoding, callback) { if (this.injected) { this.push(chunk); callback(); return; } this.pending = Buffer.concat([this.pending, chunk]); const text = this.pending.toString("utf8"); const head = /]*)?>/i.exec(text); if (head) { const offset = Buffer.byteLength(text.slice(0, head.index + head[0].length), "utf8"); this.push(this.pending.subarray(0, offset)); this.push(this.shim); this.push(this.pending.subarray(offset)); this.pending = Buffer.alloc(0); this.injected = true; } else if (this.pending.byteLength >= HTML_INJECTION_PROBE_BYTES) { this.push(this.shim); this.push(this.pending); this.pending = Buffer.alloc(0); this.injected = true; } callback(); } _flush(callback) { if (!this.injected) this.push(this.shim); if (this.pending.byteLength) this.push(this.pending); callback(); } } class ByteLimitTransform extends Transform { constructor(limit) { super(); this.limit = limit; this.bytes = 0; } _transform(chunk, _encoding, callback) { this.bytes += chunk.byteLength; if (this.bytes > this.limit) { callback(new PreviewRuntimeError(413, "Preview request body is too large")); return; } callback(null, chunk); } } function previewShim(prefix) { const encodedPrefix = JSON.stringify(prefix).replaceAll("<", "\\u003c"); return ``; } function parseWebSocketProtocols(value) { if (typeof value !== "string") return []; return value .split(",") .map((protocol) => protocol.trim()) .filter(Boolean); } function sendLanding(response) { response.writeHead(200, { "content-type": "text/plain; charset=utf-8", "content-security-policy": "default-src 'none'; frame-ancestors 'none'", "cache-control": "no-store", "x-content-type-options": "nosniff", }); response.end("A fresh Agent preview link is required.\n"); } function sendError(response, cause) { if (response.headersSent || response.destroyed) { response.destroy(); return; } const status = cause instanceof PreviewRuntimeError ? cause.status : 502; const message = cause instanceof PreviewRuntimeError ? cause.message : "Preview proxy failed"; response.writeHead(status, { "content-type": "text/plain; charset=utf-8", "content-security-policy": "default-src 'none'; frame-ancestors 'none'", "cache-control": "no-store", "x-content-type-options": "nosniff", }); response.end(`${message}\n`); } function rejectUpgrade(socket, cause) { if (!socket.writable) return; const status = cause instanceof PreviewRuntimeError ? cause.status : 502; const phrase = status === 404 ? "Not Found" : status === 400 ? "Bad Request" : "Bad Gateway"; socket.end( `HTTP/1.1 ${status} ${phrase}\r\nConnection: close\r\nCache-Control: no-store\r\nContent-Length: 0\r\n\r\n` ); } function requireHttpsOrigin(candidate, name) { let url; try { url = new URL(candidate); } catch { throw new Error(`${name} must be an absolute HTTPS origin`); } if ( url.protocol !== "https:" || url.username || url.password || url.pathname !== "/" || url.search || url.hash || (candidate !== url.origin && candidate !== `${url.origin}/`) ) { throw new Error(`${name} must be a credential-free HTTPS origin`); } return url.origin; } function requireHttpsBaseUrl(candidate, name) { let url; try { url = new URL(candidate); } catch { throw new Error(`${name} must be an absolute HTTPS URL`); } if ( url.protocol !== "https:" || url.username || url.password || url.search || url.hash || url.pathname.includes("//") ) { throw new Error(`${name} must be a credential-free HTTPS URL with an optional base path`); } const pathname = url.pathname === "/" ? "" : url.pathname.replace(/\/$/, ""); return `${url.origin}${pathname}`; } if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) { const config = runtimeConfigFromEnv(); const server = createPreviewRuntime({ config }); server.listen(config.port, "0.0.0.0", () => { process.stdout.write(`Agent preview runtime listening on port ${config.port}\n`); }); }