diff --git a/frontend/server.ts b/frontend/server.ts index 4cba601..d81e7b3 100644 --- a/frontend/server.ts +++ b/frontend/server.ts @@ -11,6 +11,8 @@ import { Hono } from "hono"; const AGENT_URL = process.env.AGENT_URL ?? "http://backend:8000/api/v1/agent/agui"; +const BACKEND_URL = + process.env.BACKEND_URL ?? "http://localhost:8001"; const isProd = process.env.NODE_ENV === "production"; const PORT = parseInt(process.env.PORT ?? (isProd ? "3000" : "3001")); @@ -376,6 +378,32 @@ app.all("/api/copilotkit/*", async (c) => { app.get("/api/health", (c) => c.json({ ok: true })); +// Proxy backend API calls (FastAPI). In dev these hit Vite's proxy directly, +// but in prod the browser talks to this Hono server, which must forward +// `/api/v1/*` and `/api/auth/*` to the FastAPI container — otherwise the SPA +// fallback below swallows them and returns index.html. +const proxyToBackend = async (c: import("hono").Context) => { + const url = new URL(c.req.url); + const target = `${BACKEND_URL}${url.pathname}${url.search}`; + const method = c.req.method; + const headers = new Headers(c.req.raw.headers); + headers.delete("host"); + const init: RequestInit = { + method, + headers, + redirect: "manual", + }; + if (method !== "GET" && method !== "HEAD") { + init.body = c.req.raw.body; + // @ts-expect-error undici requires duplex for streamed bodies + init.duplex = "half"; + } + return fetch(target, init); +}; + +app.all("/api/v1/*", proxyToBackend); +app.all("/api/auth/*", proxyToBackend); + // In production, serve the Vite build output. if (isProd) { app.use("/*", serveStatic({ root: "./dist" }));