import type { TransitionEvent } from "story-engine"; export interface SummaryBuilderOptions { apiKey: string; model?: string; // defaults to gpt-4o-mini baseUrl?: string; // defaults to /openai/v1/chat/completions } export class SummaryBuilder { private apiKey: string; private model: string; private baseUrl: string; constructor(opts: SummaryBuilderOptions) { this.apiKey = opts.apiKey; this.model = opts.model ?? "gpt-4o-mini"; this.baseUrl = opts.baseUrl ?? "/openai/v1/chat/completions"; } async summarize(events: TransitionEvent[]): Promise { if (events.length === 0) return "(The story is just beginning.)"; const log = events .map((e) => { switch (e.type) { case "storyEntered": return `Story "${e.storyId}" began.`; case "storyExited": return `User exited the story.`; case "sceneChanged": return `Scene changed: ${e.from ?? "?"} → ${e.to}.`; case "speakerChanged": return `Now speaking: ${e.to} (was ${e.from ?? "narrator"}).`; case "flagSet": return `Flag set: ${e.name} = ${e.value}.`; case "blocked": return `Blocked: ${e.reason}.`; } }) .join("\n"); const prompt = `Given these recent story events, write a 2-3 sentence summary suitable for handing off to a new character voice. Capture what just happened and what the user was doing. Plain spoken English, no markdown.\n\nEvents:\n${log}`; const res = await fetch(this.baseUrl, { method: "POST", headers: { "Content-Type": "application/json", Authorization: `Bearer ${this.apiKey}`, }, body: JSON.stringify({ model: this.model, messages: [{ role: "user", content: prompt }], max_tokens: 200, temperature: 0.4, }), }); if (!res.ok) { throw new Error(`Summary API ${res.status}: ${await res.text()}`); } const data = await res.json(); return data.choices[0]?.message?.content?.trim() ?? "(No summary available.)"; } }