puck / frontend /src /lib /brain.ts
vu1n's picture
Puck β€” desktop fairy familiar (HF Build Small)
3c124f3
Raw
History Blame Contribute Delete
1.65 kB
// 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);
}