// Client for the daemon's brain proxy. Every call degrades to null — // the scripted engine is the floor, the model is the ceiling. Puck is // never dead, just less eloquent. import type { FairyState, WireEvent } from "../engine"; import { moodFor } from "../engine"; interface BrainFairyState { mood: string; mischief: number; obsession: string; } const forBrain = (fs: FairyState): BrainFairyState => ({ mood: moodFor(fs), mischief: fs.mischief, obsession: fs.obsession, }); async function post(path: string, body: unknown, timeoutMs: number): Promise { try { const res = await fetch(path, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(body), signal: AbortSignal.timeout(timeoutMs), }); if (!res.ok) return null; // 503 = no brain configured; scripted fallback is the design const data: unknown = await res.json(); const text = (data as { text?: unknown }).text; return typeof text === "string" && text.length > 0 ? text : null; } catch { return null; } } /** Fairy-voiced line for a real wire event. Null → use the template line. */ export function brainNarrate(event: WireEvent, fs: FairyState): Promise { return post("/api/brain/narrate", { event, fairy_state: forBrain(fs) }, 25000); } /** In-character chat reply. Null → use the scripted keyword reply. */ export function brainChat( message: string, fs: FairyState, history: { who: string; text: string }[], ): Promise { return post("/api/brain/chat", { message, fairy_state: forBrain(fs), history }, 25000); }