import Fastify from "fastify"; import cors from "@fastify/cors"; import { z } from "zod"; import { handleChat } from "./ai/engine.js"; import { ChatRequest } from "./types.js"; const server = Fastify({ logger: true }); await server.register(cors, { origin: true, credentials: true }); server.get("/health", async () => ({ status: "ok" })); server.post("/api/chat", async (request, reply) => { const body = request.body as ChatRequest; const schema = z.object({ sessionId: z.string().min(1), message: z.string().min(1), mode: z.enum(["auto", "search", "no-search"]).optional() }); const parsed = schema.safeParse(body); if (!parsed.success) { reply.status(400); return { error: parsed.error.flatten() }; } const chunks: string[] = []; await handleChat(parsed.data, { onStatus: () => {}, onToken: (token) => chunks.push(token), onDone: () => {}, onError: () => {} }); return { message: chunks.join("") }; }); server.get("/api/chat/stream", async (request, reply) => { const query = request.query as Record; const schema = z.object({ sessionId: z.string().min(1), message: z.string().min(1), mode: z.enum(["auto", "search", "no-search"]).optional() }); const parsed = schema.safeParse(query); if (!parsed.success) { reply.status(400); return reply.send({ error: parsed.error.flatten() }); } reply.raw.writeHead(200, { "Content-Type": "text/event-stream", "Cache-Control": "no-cache, no-transform", Connection: "keep-alive", "X-Accel-Buffering": "no" }); reply.raw.flushHeaders?.(); const writeEvent = (event: string, data: string) => { reply.raw.write(`event: ${event}\n`); reply.raw.write(`data: ${data}\n\n`); }; const requestPayload: ChatRequest = { sessionId: parsed.data.sessionId, message: parsed.data.message, mode: parsed.data.mode }; let closed = false; request.raw.on("close", () => { closed = true; }); await handleChat(requestPayload, { onStatus: (message) => !closed && writeEvent("status", JSON.stringify({ message })), onToken: (token) => !closed && writeEvent("delta", JSON.stringify({ token })), onDone: () => !closed && writeEvent("done", JSON.stringify({ ok: true })), onError: (message) => !closed && writeEvent("error", JSON.stringify({ message })) }); reply.raw.end(); }); const port = Number(process.env.PORT ?? 8080); server.listen({ port, host: "0.0.0.0" }).catch((err) => { server.log.error(err); process.exit(1); });