Spaces:
Running
Running
| // 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<string | null> { | |
| 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<string | null> { | |
| 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<string | null> { | |
| return post("/api/brain/chat", { message, fairy_state: forBrain(fs), history }, 25000); | |
| } | |