/** * Pure state machine for a single voice session. * * The reducer has no DOM or WebRTC dependencies — it can be unit-tested by * driving events through `voiceReducer` and checking the resulting state. * * The companion `handleRealtimeMessage` parses an OpenAI Realtime data-channel * message and dispatches reducer events plus side effects (tool dispatch, * sending follow-up events back over the data channel). Side effects are * delegated to the injected `RealtimeMessageContext` so the function is * testable with a plain in-memory mock. */ /** * Lifecycle of a voice session, in order: * - `idle` — no session (the starting and stopped state). * - `connecting` — establishing the WebRTC connection. * - `listening` — connected and capturing the user's speech. * - `thinking` — the model is processing / generating a response. * - `speaking` — the assistant is playing audio back. * - `error` — the session failed; see {@link VoiceMachine.errorMessage}. */ export type VoiceState = "idle" | "connecting" | "listening" | "thinking" | "speaking" | "error"; export type VoiceEvent = { type: "start"; } | { type: "connected"; } /** An assistant-initiated turn was requested (e.g. the opening greeting). */ | { type: "response_requested"; } | { type: "speech_started"; } | { type: "speech_stopped"; } | { type: "audio_delta"; } | { type: "audio_stopped"; } | { type: "stop"; } | { type: "error"; message: string; }; /** Snapshot of a voice session's state machine. */ export interface VoiceMachine { /** Current lifecycle state of the session. */ state: VoiceState; /** Human-readable failure reason — set only when `state === "error"`. */ errorMessage?: string; } export declare const INITIAL_VOICE_MACHINE: VoiceMachine; export declare function voiceReducer(machine: VoiceMachine, event: VoiceEvent): VoiceMachine; /** * Per-`response.done` token usage emitted by OpenAI's Realtime API. * The SDK accumulates these across the session and ships the totals to the * `session-event` stop endpoint so billing has the dimensions it needs. */ export interface RealtimeResponseUsage { /** Total input tokens for this response (text + audio combined). */ inputTokens?: number; /** Cached input tokens — subset of `inputTokens`. */ cachedInputTokens?: number; /** Total output tokens for this response (text + audio combined). */ outputTokens?: number; /** Audio-input tokens (subset of `inputTokens`). */ audioInputTokens?: number; /** Audio-output tokens (subset of `outputTokens`). */ audioOutputTokens?: number; /** Text-input tokens (subset of `inputTokens`). */ textInputTokens?: number; /** Text-output tokens (subset of `outputTokens`). */ textOutputTokens?: number; } /** * Transcript-side events the session uses to persist a voice conversation in * the same shape as text chat. Distinct from the state-machine `VoiceEvent`s: * these carry text + ordering, not lifecycle state. * * - `user_turn` — a user utterance just finished (VAD `speech_stopped`). Emitted * *before* the user's transcript completes (which is async and can land after * the assistant has already replied), so the session reserves an ordered slot * here and back-fills the text when the `transcript` event arrives. * - `user_transcript_failed` — input transcription failed for an utterance; the * reserved slot must be voided so it doesn't block later turns. * - `transcript` — a settled user/assistant transcript, keyed by the OpenAI * conversation `item_id` (stable; used to derive the message id + dedupe). */ export type VoiceTranscriptEvent = { kind: "user_turn"; } | { kind: "user_transcript_failed"; } | { kind: "transcript"; itemId: string; role: "user" | "assistant"; text: string; }; export interface RealtimeMessageContext { send: (event: VoiceEvent) => void; sendData: (payload: unknown) => void; dispatchToolCall: (name: string, args: unknown) => Promise; isDispatched: (callId: string) => boolean; markDispatched: (callId: string) => void; /** Forward a per-response usage payload to the session for accumulation. */ recordUsage?: (usage: RealtimeResponseUsage) => void; /** Forward transcript/ordering events so the session can persist the conversation. */ onTranscriptEvent?: (event: VoiceTranscriptEvent) => void; } export declare function handleRealtimeMessage(raw: string, ctx: RealtimeMessageContext): Promise; //# sourceMappingURL=voice-machine.d.ts.map