chat-ui-agent-preview / server.mjs
Mike0021's picture
fix: retry transient preview route misses
246c163 verified
Raw History Blame Contribute Delete
33.6 kB
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 = /<head(?:\s[^>]*)?>/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 `<script>(()=>{"use strict";const P=${encodedPrefix},O=location.origin;const scoped=(input,ws=false)=>{try{const u=new URL(String(input),location.href);if(u.origin===O&&!u.pathname.startsWith(P+"/")&&u.pathname!==P)u.pathname=P+(u.pathname.startsWith("/")?u.pathname:"/"+u.pathname);if(ws){if(u.protocol==="http:")u.protocol="ws:";if(u.protocol==="https:")u.protocol="wss:"}return u.href}catch{return input}};const f=window.fetch;window.fetch=function(input,init){if(input instanceof Request)return f.call(this,new Request(scoped(input.url),input),init);return f.call(this,scoped(input),init)};const xo=XMLHttpRequest.prototype.open;XMLHttpRequest.prototype.open=function(method,url,...rest){return xo.call(this,method,scoped(url),...rest)};const W=window.WebSocket;function SW(url,protocols){return protocols===undefined?new W(scoped(url,true)):new W(scoped(url,true),protocols)}SW.prototype=W.prototype;Object.setPrototypeOf(SW,W);window.WebSocket=SW;const E=window.EventSource;function SE(url,options){return new E(scoped(url),options)}SE.prototype=E.prototype;Object.setPrototypeOf(SE,E);window.EventSource=SE;for(const name of ["pushState","replaceState"]){const original=history[name];history[name]=function(state,unused,url){return original.call(this,state,unused,url==null?url:scoped(url))}}const rewrite=(root=document)=>{for(const element of root.querySelectorAll?.("[src],[href],form[action]")??[]){for(const attr of ["src","href","action"]){const value=element.getAttribute(attr);if(value?.startsWith("/")&&!value.startsWith("//")&&!value.startsWith(P+"/"))element.setAttribute(attr,P+value)}}};document.addEventListener("submit",event=>{const form=event.target;if(form instanceof HTMLFormElement)form.action=scoped(form.getAttribute("action")||location.href)},true);if(document.readyState==="loading")document.addEventListener("DOMContentLoaded",()=>rewrite(),{once:true});else rewrite();new MutationObserver(records=>{for(const record of records)for(const node of record.addedNodes)if(node instanceof Element){rewrite(node);for(const attr of ["src","href","action"]){const value=node.getAttribute(attr);if(value?.startsWith("/")&&!value.startsWith("//")&&!value.startsWith(P+"/"))node.setAttribute(attr,P+value)}}}).observe(document.documentElement,{childList:true,subtree:true});try{const sw=Object.getPrototypeOf(navigator.serviceWorker);Object.defineProperty(sw,"register",{configurable:false,value:()=>Promise.reject(new DOMException("Service workers are disabled in previews","SecurityError"))})}catch{}document.currentScript?.remove()})();</script>`;
}
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`);
});
}