/** * OpenAI Realtime Voice Adapter * * The OpenAI implementation of the realtime transport axis (`realtime.ts`). * `OpenAIAdapter` stays the single provider object for chat; this companion * module holds the duplex-transport code so it stays OFF the chat path and * realtime model ids stay OFF `OPENAI_MODELS` (the chat registry). Realtime is * a separate axis — see `realtime.ts` and plan §0/§1.4. * * The transport is MOCKABLE: `openOpenAIRealtimeSession()` takes injectable * `webSocketFactory` + `fetchImpl` so a test can drive the full * mint-ephemeral-secret → open-WebSocket → echo-turn → usage flow without a * live endpoint. Real-endpoint audio verification is slice E (manual/founder); * the default factory lazily loads the `ws` package for the production path. * * SSOT: research/2026-07-10_realtime-adapter-implementation-plan-p0pp.md §1, §5. * * @module @holoscript/llm-provider */ import { OpenAIAdapter } from './openai'; import type { OpenAIProviderConfig } from '../types'; import type { EphemeralSecret, RealtimeSession, RealtimeSessionConfig } from '../realtime'; export declare const OPENAI_REALTIME_MODELS: readonly ["gpt-realtime-2.1", "gpt-realtime-2.1-mini", "gpt-realtime-2", "gpt-realtime-1.5", "gpt-realtime-mini"]; export type OpenAIRealtimeModel = (typeof OPENAI_REALTIME_MODELS)[number]; /** Default full-quality realtime model. */ export declare const DEFAULT_OPENAI_REALTIME_MODEL: OpenAIRealtimeModel; /** Default cost-optimized realtime model. */ export declare const DEFAULT_OPENAI_REALTIME_MINI_MODEL: OpenAIRealtimeModel; /** * Minimal WebSocket contract the session driver needs. Deliberately shaped like * the `ws` package's EventEmitter surface (`.on('open'|'message'|'close'| * 'error')`, `.send`, `.close`) so the default factory can hand a real `ws` * instance straight through, and a test can hand a fake with the same shape. */ export interface RealtimeWebSocketLike { send(data: string): void; close(code?: number, reason?: string): void; on(event: 'open' | 'message' | 'close' | 'error', handler: (arg?: unknown) => void): void; } export type RealtimeWebSocketFactory = (url: string, options: { headers: Record; }) => RealtimeWebSocketLike; /** Minimal fetch shape (avoids a DOM lib dependency under lib: ES2020). */ export type RealtimeFetchLike = (url: string, init?: { method?: string; headers?: Record; body?: string; }) => Promise<{ ok: boolean; status: number; json(): Promise; text(): Promise; }>; export interface OpenAIRealtimeDeps { apiKey: string; /** Defaults to https://api.openai.com. */ baseURL?: string; /** Inject for tests; production default lazily loads the `ws` package. */ webSocketFactory?: RealtimeWebSocketFactory; /** Inject for tests; defaults to `globalThis.fetch`. */ fetchImpl?: RealtimeFetchLike; } /** * Mint a short-lived client secret so a browser/Quest client can connect * directly to the vendor without the master `OPENAI_API_KEY` ever leaving the * server. Scoped per surface, short-TTL. For pure server-side WebSocket the * caller may skip this and use the master key directly — but the session-open * flow mints by default so the ephemeral path is exercised (plan §1.3, §5.1). */ export declare function mintOpenAIEphemeralSecret(surface: string, deps: OpenAIRealtimeDeps, opts?: { ttlSeconds?: number; model?: string; }): Promise; /** * Open a realtime voice session against OpenAI. Mints an ephemeral secret, * opens the WebSocket, and returns a live duplex handle. The event loop maps * OpenAI's wire events to the provider-neutral `RealtimeServerEvent` union. * * Inject `deps.webSocketFactory` + `deps.fetchImpl` to drive the whole flow in * a test without a live endpoint. */ export declare function openOpenAIRealtimeSession(config: RealtimeSessionConfig, deps: OpenAIRealtimeDeps): Promise; /** * The OpenAI realtime adapter. Extends `OpenAIAdapter` so it stays the SAME * provider object shape the capability router already reads (inherits * `OPENAI_CAPABILITIES`, which declares `realtimeVoice: true`), and only adds * the optional `openRealtimeSession()` method — making the `realtimeVoice` * flag honest for the first vendor. The chat adapter (`openai.ts`) is untouched. * * `realtimeDeps` lets a caller/test inject the transport seam through the * adapter path too, not just the standalone `openOpenAIRealtimeSession()`. */ export declare class OpenAIRealtimeAdapter extends OpenAIAdapter { private readonly realtimeDeps?; constructor(config: OpenAIProviderConfig, realtimeDeps?: Partial); openRealtimeSession(config: RealtimeSessionConfig): Promise; }