import type { APIRoute } from "astro"; import { resetIdle, throttledSend, releaseSlot } from "../../lib/web-bridges"; import { upsertThread, appendLogLine } from "../../lib/thread-store"; import { notifyThreadDone } from "../../lib/push"; export const POST: APIRoute = async ({ request }) => { const headers = { "Content-Type": "application/json" }; try { const body = await request.json(); const message = (body.message || "").trim(); if (!message) { return new Response(JSON.stringify({ error: "message required" }), { status: 400, headers, }); } const tid = body.threadId || `code-${Date.now()}`; const subject = message.length > 60 ? message.slice(0, 57) + "..." : message; await upsertThread({ channel: "code", threadId: tid, subject, from: "code", account: "", status: "queued", startedAt: Date.now(), endedAt: null, detail: "", }); await appendLogLine(tid, `from code: ${subject}`); const { text: response, tokIn, tokOut, cost } = await new Promise<{ text: string; tokIn: number | null; tokOut: number | null; cost: number | null }>((resolve, reject) => { throttledSend(tid, message, (bridge) => { upsertThread({ threadId: tid, status: "processing" }).catch(() => {}); const sessionReadyHandler = (sessionId: string) => { upsertThread({ threadId: tid, sessionFile: `palacecode-${sessionId}.log`, }).catch(() => {}); }; bridge.once("sessionReady", sessionReadyHandler); bridge.once("result", () => { bridge.removeListener("sessionReady", sessionReadyHandler); releaseSlot(tid); const full = (bridge as any).responseBuffer?.trim() || ""; const u = bridge.usage; resolve({ text: full, tokIn: u ? u.input_tokens + (u as any).cache_read_input_tokens + (u as any).cache_creation_input_tokens : null, tokOut: u ? u.output_tokens : null, cost: u ? u.total_cost_usd : null, }); }); bridge.once("error", (err: Error) => { bridge.removeListener("sessionReady", sessionReadyHandler); releaseSlot(tid); reject(err); }); bridge.once("exit", (code: number | null) => { bridge.removeListener("sessionReady", sessionReadyHandler); releaseSlot(tid); if (code && code !== 0) { reject(new Error(`Process exited with code ${code}`)); } else { resolve({ text: "", tokIn: null, tokOut: null, cost: null }); } }); }); }); await upsertThread({ threadId: tid, status: "replied", endedAt: Date.now(), ...(tokIn !== null ? { tokIn, tokOut, cost } : {}), } as any); await appendLogLine( tid, response.length > 200 ? response.slice(0, 197) + "..." : response ); notifyThreadDone(subject).catch(() => {}); resetIdle(tid); return new Response(JSON.stringify({ threadId: tid, response }), { headers, }); } catch (err: any) { return new Response(JSON.stringify({ error: err.message }), { status: 500, headers, }); } };