import { createReadStream } from "node:fs"; import { stat } from "node:fs/promises"; const MCP_PROTOCOL_VERSION = "2025-06-18"; const MAX_BODY_BYTES = 1024 * 1024; function sendJson(response, statusCode, payload, headers = {}) { response.writeHead(statusCode, { "cache-control": "no-store", "content-type": "application/json; charset=utf-8", ...headers, }); response.end(JSON.stringify(payload)); } async function readJsonBody(request, maxBytes = MAX_BODY_BYTES) { const chunks = []; let size = 0; for await (const chunk of request) { size += chunk.length; if (size > maxBytes) throw new Error("payload_too_large"); chunks.push(chunk); } if (!chunks.length) return {}; return JSON.parse(Buffer.concat(chunks).toString("utf8")); } function bearerToken(request) { const match = String(request.headers.authorization || "").match(/^Bearer\s+(.+)$/i); return match?.[1]?.trim() || ""; } function publicOrigin(request, configuredOrigin) { if (configuredOrigin) return String(configuredOrigin).replace(/\/+$/, ""); const forwardedProto = String(request.headers["x-forwarded-proto"] || "").split(",")[0].trim(); const protocol = forwardedProto === "https" ? "https" : "http"; const forwardedHost = String(request.headers["x-forwarded-host"] || "").split(",")[0].trim(); const host = forwardedHost || String(request.headers.host || "127.0.0.1"); return `${protocol}://${host}`.replace(/\/+$/, ""); } function actorFromProfile(profile) { const ownerId = String(profile?.id || "").trim(); const ownerKey = String(profile?.email || ownerId).trim().toLowerCase(); if (!ownerId || !ownerKey) throw new Error("foundry_agent_owner_required"); return { ownerId, ownerKey }; } function errorCode(error) { return String(error?.message || error || "foundry_agent_operation_failed").slice(0, 160); } function managementMatch(pathname) { const match = pathname.match(/^\/api\/foundry-agent-api\/agents(?:\/([^/]+)(?:\/(revoke|setup-code))?)?$/); if (!match) return null; return { agentId: match[1] ? decodeURIComponent(match[1]) : null, action: match[2] || null }; } export function createFoundryAgentGateway({ store, configuredPublicOrigin = "", installerPackagePath, ontologyCoreUrl = "", ontologyCoreAccessToken = "", }) { if (!store) throw new Error("foundry_agent_store_required"); const ontologyUrl = String(ontologyCoreUrl || "").trim().replace(/\/+$/, ""); const ontologyToken = String(ontologyCoreAccessToken || "").trim(); async function handleInstaller(request, response, url) { if (url.pathname !== "/api/foundry-agent/install/nodedc-foundry-codex-agent-setup.tgz") return false; if (request.method !== "GET") { response.writeHead(405, { allow: "GET" }); response.end(); return true; } try { const info = await stat(installerPackagePath); response.writeHead(200, { "cache-control": "no-store", "content-disposition": 'attachment; filename="nodedc-foundry-codex-agent-setup.tgz"', "content-length": info.size, "content-type": "application/octet-stream", }); createReadStream(installerPackagePath).pipe(response); } catch { sendJson(response, 404, { ok: false, error: "foundry_agent_installer_not_found" }); } return true; } async function handleSetupRedeem(request, response, url) { if (url.pathname !== "/api/foundry-agent/v1/setup/redeem") return false; if (request.method !== "POST") { response.writeHead(405, { allow: "POST" }); response.end(); return true; } try { const input = await readJsonBody(request, 16 * 1024); const redeemed = await store.redeemSetupCode(input.code, input.deviceName); const origin = publicOrigin(request, configuredPublicOrigin); sendJson(response, 200, { ok: true, agent: redeemed.agent, device: redeemed.device, foundry: { serverName: "nodedc_module_foundry", mcpUrl: `${origin}/api/mcp`, token: redeemed.foundryToken, }, ontology: { serverName: "nodedc_ontology", mcpUrl: `${origin}/api/ontology-mcp`, token: redeemed.ontologyToken, readOnly: true, }, }); } catch (error) { const code = errorCode(error); sendJson(response, code === "payload_too_large" ? 413 : 400, { ok: false, error: code }); } return true; } async function handleDoctor(request, response, url) { if (url.pathname !== "/api/foundry-agent/v1/doctor") return false; if (request.method !== "GET") { response.writeHead(405, { allow: "GET" }); response.end(); return true; } const session = await store.authenticateFoundryToken(bearerToken(request), { check: true }); if (!session) { sendJson(response, 401, { ok: false, error: "foundry_agent_unauthorized" }); return true; } sendJson(response, 200, { ok: true, agent: session.agent, device: session.device, foundryMcpUrl: `${publicOrigin(request, configuredPublicOrigin)}/api/mcp`, ontologyMcpUrl: `${publicOrigin(request, configuredPublicOrigin)}/api/ontology-mcp`, ontologyReadOnly: true, }); return true; } async function handlePublicRequest(request, response, url) { return await handleInstaller(request, response, url) || await handleSetupRedeem(request, response, url) || await handleDoctor(request, response, url); } async function handleManagementRequest(request, response, url, profile) { const match = managementMatch(url.pathname); if (!match) return false; const owner = actorFromProfile(profile); try { if (!match.agentId && request.method === "GET") { sendJson(response, 200, { ok: true, agents: await store.listAgents(owner.ownerKey) }); return true; } if (!match.agentId && request.method === "POST") { const input = await readJsonBody(request, 640 * 1024); const agent = await store.createAgent({ ...owner, name: input.name, avatarUrl: input.avatarUrl }); sendJson(response, 201, { ok: true, agent }); return true; } if (match.agentId && !match.action && request.method === "PATCH") { const input = await readJsonBody(request, 640 * 1024); const agent = await store.updateAgent(owner.ownerKey, match.agentId, input); sendJson(response, 200, { ok: true, agent }); return true; } if (match.agentId && match.action === "revoke" && request.method === "POST") { const agent = await store.revokeAgent(owner.ownerKey, match.agentId); sendJson(response, 200, { ok: true, agent }); return true; } if (match.agentId && match.action === "setup-code" && request.method === "POST") { const setup = await store.issueSetupCode(owner.ownerKey, match.agentId); const origin = publicOrigin(request, configuredPublicOrigin); const packageUrl = `${origin}/api/foundry-agent/install/nodedc-foundry-codex-agent-setup.tgz`; const command = `npm exec --yes --package=${packageUrl} nodedc-foundry-codex-agent -- --server ${origin} --code ${setup.code}`; sendJson(response, 201, { ok: true, install: { command, expiresAt: setup.expiresAt } }); return true; } response.writeHead(405, { allow: "GET, POST, PATCH" }); response.end(); } catch (error) { const code = errorCode(error); const status = code === "foundry_agent_not_found" ? 404 : code === "payload_too_large" ? 413 : 400; sendJson(response, status, { ok: false, error: code }); } return true; } async function authenticateFoundryToken(token, options) { const session = await store.authenticateFoundryToken(token, options); return session?.actor || null; } async function handleOntologyMcp(request, response, url) { if (url.pathname !== "/api/ontology-mcp") return false; if (request.method !== "POST") { response.writeHead(405, { allow: "POST" }); response.end(); return true; } const session = await store.authenticateOntologyToken(bearerToken(request), { check: true }); if (!session) { sendJson(response, 401, { ok: false, error: "ontology_agent_unauthorized" }); return true; } if (!ontologyUrl || !ontologyToken) { sendJson(response, 503, { ok: false, error: "ontology_agent_proxy_not_configured" }); return true; } let input; try { input = await readJsonBody(request); } catch (error) { sendJson(response, error?.message === "payload_too_large" ? 413 : 400, { ok: false, error: errorCode(error) }); return true; } let upstream; try { upstream = await fetch(`${ontologyUrl}/mcp`, { method: "POST", redirect: "manual", headers: { accept: String(request.headers.accept || "application/json, text/event-stream"), authorization: `Bearer ${ontologyToken}`, "content-type": "application/json", "mcp-protocol-version": String(request.headers["mcp-protocol-version"] || MCP_PROTOCOL_VERSION), }, body: JSON.stringify(input), signal: AbortSignal.timeout(60_000), }); } catch { sendJson(response, 503, { ok: false, error: "ontology_agent_proxy_unavailable" }); return true; } const payload = await upstream.text(); response.statusCode = upstream.status; response.setHeader("cache-control", "no-store"); response.setHeader("content-type", upstream.headers.get("content-type") || "application/json; charset=utf-8"); for (const header of ["mcp-protocol-version", "mcp-session-id", "vary"]) { const value = upstream.headers.get(header); if (value) response.setHeader(header, value); } response.end(payload); return true; } return { handlePublicRequest, handleManagementRequest, handleOntologyMcp, authenticateFoundryToken, health() { return { configured: Boolean(installerPackagePath), ontologyProxyConfigured: Boolean(ontologyUrl && ontologyToken), tokenLifecycle: "durable-until-revoke", }; }, }; }