import type { BrowserTextRun, ChatInput, ChatOptions } from '../models/types.js'; import { CharacterEventBus, type CharacterEvent } from './action-bus.js'; import { type CharacterConfig } from './character-config.js'; export type RunStatus = 'ok' | 'aborted' | 'timed_out' | 'failed' | 'invalid_request' | 'invalid_response'; /** Minimal chat client required by character runtimes. */ export interface CharacterRuntimeClient { chat(input: ChatInput, options?: ChatOptions): BrowserTextRun; } export interface CharacterRuntimeOptions { /** * Maximum tokens the model may emit per turn. Defaults to 256, which is a * reasonable upper bound for conversational replies. */ readonly maxOutputTokens?: number; /** * Prebuilt CharacterEventBus. When omitted, the runtime creates one internally. * Injecting a shared bus lets multiple consumers observe the same character. */ readonly bus?: CharacterEventBus; /** Stable client context key prefix. Defaults to `character:${config.id}`. */ readonly contextKey?: string; } export interface ChatTurn { readonly role: 'user' | 'assistant'; readonly content: string; } export interface CharacterChooseResult { readonly selection: string | null; readonly status: RunStatus; readonly errorMessage?: string; readonly rawText: string; } /** A grammar-constrained choice that separates the model output id from UI text. */ export interface CharacterChoice { /** Stable id emitted by the model and returned as `CharacterChooseResult.selection`. */ readonly id: string; /** Human-readable text shown in the decision prompt. */ readonly label: string; /** Optional short detail shown after the label in the decision prompt. */ readonly description?: string; } export interface CharacterChooseOptions { /** Candidate choices; the model must emit one `id` exactly. */ readonly choices: readonly CharacterChoice[]; readonly signal?: AbortSignal; readonly timeoutMs?: number; readonly maxOutputTokens?: number; } /** * Turn-level chat event yielded by {@link CharacterRuntime.chat}. Mirrors the * CharacterEventBus event shape so consumers can choose either transport. */ export type ChatEvent = CharacterEvent; /** * A character-driven conversation runtime. Pair one with a SippClient and a * CharacterConfig to get a grammar-constrained, memory-aware chat loop. */ export declare class CharacterRuntime { private readonly client; private readonly config; private readonly maxOutputTokens; private readonly contextKey; private readonly systemPrompt; private readonly grammarSource; private readonly memoryLimitTurns; private readonly canonicalCueLabelsByActionId; private readonly turnHistory; private readonly eventBus; private currentTurn; constructor(client: CharacterRuntimeClient, config: CharacterConfig, options?: CharacterRuntimeOptions); /** Exposes the event bus for imperative subscribers (VRM bindings, logs). */ get bus(): CharacterEventBus; /** Read-only snapshot of the sliding-window memory. */ getMemory(): readonly ChatTurn[]; /** Clears the sliding-window memory. Does not reset the client's KV cache. */ clearMemory(): void; choose(userMessage: string, options: CharacterChooseOptions): Promise; /** * Processes a single user turn. Returns an async iterator that yields * `ChatEvent`s as they arrive, terminating with a `turn-end` event. * * The iterator is backed by a small internal queue so upstream token * emission never blocks on a slow consumer — if the consumer falls behind, * events buffer in memory rather than back-pressuring decode. */ chat(userMessage: string, options?: { signal?: AbortSignal; }): AsyncIterable; private executeTurn; /** * Builds the ordered ChatMessage[] for the current turn. This sequence is * rendered through the model's embedded chat template so the character * system prompt and turn history follow the model's native chat contract. */ private buildTurnMessages; private runTurn; private renderCanonicalActionCue; private pushTurnToMemory; }