@speechos/core 0.2.4 → 0.2.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs","names":["DEFAULT_HOST: string","defaultConfig: ResolvedConfig","userConfig: SpeechOSCoreConfig","currentConfig: ResolvedConfig","config: SpeechOSCoreConfig","userId: string","event: K","callback: EventCallback<K>","payload: SpeechOSEventMap[K]","event?: keyof SpeechOSEventMap","event: keyof SpeechOSEventMap","events: SpeechOSEventEmitter","initialState: SpeechOSState","initialState","partial: Partial<SpeechOSState>","callback: StateChangeCallback","element: HTMLElement | null","action: SpeechOSState[\"activeAction\"]","recordingState: SpeechOSState[\"recordingState\"]","isConnected: boolean","isMicEnabled: boolean","message: string","state: StateManager","initial?: Partial<SpeechOSState>","MESSAGE_TYPE_REQUEST_TRANSCRIPT","MESSAGE_TYPE_TRANSCRIPT","MESSAGE_TYPE_EDIT_TEXT","MESSAGE_TYPE_EDITED_TEXT","MESSAGE_TYPE_EXECUTE_COMMAND","MESSAGE_TYPE_COMMAND_RESULT","MESSAGE_TYPE_ERROR","ms: number","errorMessage: string","errorCode: string","errorSource: ErrorSource","value: T","error: Error","data","Room","RoomEvent","data: Uint8Array","participant?: RemoteParticipant","_participant?: RemoteParticipant","commandResult: CommandResult | null","trackOptions: Parameters<typeof createLocalAudioTrack>[0]","Track","message: object","options?: VoiceSessionOptions","constraints: MediaTrackConstraints","originalText: string","commands: CommandDefinition[]","timeUntilRefresh: number","livekit: LiveKitManager","onChunk: AudioChunkCallback","deviceId?: string","constraints: MediaStreamConstraints","recorderOptions: MediaRecorderOptions","chunk: Blob","event: BlobEvent","Deferred","ms: number","errorMessage: string","errorCode: string","errorSource: ErrorSource","value: T","error: Error","options?: VoiceSessionOptions","chunk: Blob","data: string","message: { session_id: string }","message: {\n transcript: string;\n is_final: boolean;\n }","message: { transcript: string }","message: { text: string }","message: { command: CommandResult | null; transcript?: string }","message: ServerErrorMessage","_originalText: string","_commands: CommandDefinition[]","message: object","websocket: WebSocketManager","getBackend","config: SpeechOSCoreConfig","currentConfig","originalText: string","commands: CommandDefinition[]","speechOS: SpeechOSCore","websocketBackend: VoiceBackend"],"sources":["../src/config.ts","../src/events.ts","../src/state.ts","../src/livekit.ts","../src/audio-capture.ts","../src/websocket.ts","../src/speechos.ts","../src/backend.ts","../src/index.ts"],"sourcesContent":["/**\n * Configuration management for SpeechOS Core SDK\n */\n\nimport type { SpeechOSCoreConfig } from \"./types.js\";\n\n/**\n * Default host - can be overridden by SPEECHOS_HOST env var at build time\n */\nexport const DEFAULT_HOST: string =\n (typeof process !== \"undefined\" && process.env?.SPEECHOS_HOST) ||\n \"https://app.speechos.ai\";\n\n/**\n * Configuration with defaults applied (all fields required internally)\n */\ninterface ResolvedConfig {\n apiKey: string;\n userId: string;\n host: string;\n debug: boolean;\n}\n\n/**\n * Default configuration values\n */\nconst defaultConfig: ResolvedConfig = {\n apiKey: \"\",\n userId: \"\",\n host: DEFAULT_HOST,\n debug: false,\n};\n\n/**\n * Validates and merges user config with defaults\n * @param userConfig - User-provided configuration\n * @returns Validated and merged configuration\n */\nexport function validateConfig(userConfig: SpeechOSCoreConfig): ResolvedConfig {\n // Validate apiKey is provided\n if (!userConfig.apiKey) {\n throw new Error(\n \"SpeechOS requires an apiKey. Get one from your team dashboard at /a/<team-slug>/.\"\n );\n }\n\n return {\n apiKey: userConfig.apiKey,\n userId: userConfig.userId ?? defaultConfig.userId,\n host: userConfig.host ?? defaultConfig.host,\n debug: userConfig.debug ?? defaultConfig.debug,\n };\n}\n\n/**\n * Current active configuration (singleton)\n */\nlet currentConfig: ResolvedConfig = { ...defaultConfig };\n\n/**\n * Get the current configuration\n */\nexport function getConfig(): ResolvedConfig {\n return { ...currentConfig };\n}\n\n/**\n * Set the current configuration\n * @param config - Configuration to set\n */\nexport function setConfig(config: SpeechOSCoreConfig): void {\n currentConfig = validateConfig(config);\n}\n\n/**\n * Reset configuration to defaults\n */\nexport function resetConfig(): void {\n currentConfig = { ...defaultConfig };\n}\n\n/**\n * Update the userId in the current configuration\n * @param userId - The user identifier to set\n */\nexport function updateUserId(userId: string): void {\n currentConfig = { ...currentConfig, userId };\n}\n\n/**\n * LocalStorage key for anonymous ID persistence\n */\nconst ANONYMOUS_ID_KEY = 'speechos_anonymous_id';\n\n/**\n * Get or generate a persistent anonymous ID for Mixpanel tracking.\n *\n * This ID is stored in localStorage to persist across sessions,\n * allowing consistent anonymous user tracking without identifying\n * the account owner's customers.\n *\n * @returns A UUID string for anonymous identification\n */\nexport function getAnonymousId(): string {\n // Only available in browser environments with localStorage\n if (typeof localStorage === 'undefined') {\n return crypto.randomUUID();\n }\n\n let anonymousId = localStorage.getItem(ANONYMOUS_ID_KEY);\n if (!anonymousId) {\n anonymousId = crypto.randomUUID();\n localStorage.setItem(ANONYMOUS_ID_KEY, anonymousId);\n }\n return anonymousId;\n}\n","/**\n * Typed event system for SpeechOS SDK\n * Provides a type-safe event emitter for cross-component communication\n */\n\nimport type { SpeechOSEventMap, UnsubscribeFn } from \"./types.js\";\n\ntype EventCallback<K extends keyof SpeechOSEventMap> = (\n payload: SpeechOSEventMap[K]\n) => void;\n\n/**\n * Type-safe event emitter for SpeechOS events\n */\nexport class SpeechOSEventEmitter {\n private listeners: Map<keyof SpeechOSEventMap, Set<EventCallback<any>>> =\n new Map();\n\n /**\n * Subscribe to an event\n * @param event - Event name to listen to\n * @param callback - Function to call when event is emitted\n * @returns Unsubscribe function\n */\n on<K extends keyof SpeechOSEventMap>(\n event: K,\n callback: EventCallback<K>\n ): UnsubscribeFn {\n if (!this.listeners.has(event)) {\n this.listeners.set(event, new Set());\n }\n\n this.listeners.get(event)!.add(callback);\n\n // Return unsubscribe function\n return () => {\n const callbacks = this.listeners.get(event);\n if (callbacks) {\n callbacks.delete(callback);\n if (callbacks.size === 0) {\n this.listeners.delete(event);\n }\n }\n };\n }\n\n /**\n * Subscribe to an event once (automatically unsubscribes after first call)\n * @param event - Event name to listen to\n * @param callback - Function to call when event is emitted\n * @returns Unsubscribe function\n */\n once<K extends keyof SpeechOSEventMap>(\n event: K,\n callback: EventCallback<K>\n ): UnsubscribeFn {\n const unsubscribe = this.on(event, (payload) => {\n unsubscribe();\n callback(payload);\n });\n return unsubscribe;\n }\n\n /**\n * Emit an event to all subscribers\n * @param event - Event name to emit\n * @param payload - Event payload data\n */\n emit<K extends keyof SpeechOSEventMap>(\n event: K,\n payload: SpeechOSEventMap[K]\n ): void {\n const callbacks = this.listeners.get(event);\n if (callbacks) {\n callbacks.forEach((callback) => {\n try {\n callback(payload);\n } catch (error) {\n console.error(\n `Error in event listener for \"${String(event)}\":`,\n error\n );\n }\n });\n }\n }\n\n /**\n * Remove all listeners for a specific event or all events\n * @param event - Optional event name to clear listeners for\n */\n clear(event?: keyof SpeechOSEventMap): void {\n if (event) {\n this.listeners.delete(event);\n } else {\n this.listeners.clear();\n }\n }\n\n /**\n * Get the number of listeners for an event\n * @param event - Event name\n * @returns Number of listeners\n */\n listenerCount(event: keyof SpeechOSEventMap): number {\n return this.listeners.get(event)?.size ?? 0;\n }\n}\n\n// Export singleton instance\nexport const events: SpeechOSEventEmitter = new SpeechOSEventEmitter();\n","/**\n * Reactive state management for SpeechOS SDK\n * Provides a centralized state store with subscriptions (similar to Zustand pattern)\n */\n\nimport type {\n SpeechOSState,\n StateChangeCallback,\n UnsubscribeFn,\n} from \"./types.js\";\nimport { events } from \"./events.js\";\n\n/**\n * Initial state\n */\nconst initialState: SpeechOSState = {\n isVisible: false,\n isExpanded: false,\n isConnected: false,\n isMicEnabled: false,\n activeAction: null,\n focusedElement: null,\n recordingState: \"idle\",\n errorMessage: null,\n};\n\n/**\n * State manager class\n */\nclass StateManager {\n private state: SpeechOSState;\n private subscribers: Set<StateChangeCallback> = new Set();\n /** Cached immutable snapshot for useSyncExternalStore compatibility */\n private snapshot: SpeechOSState;\n\n constructor(initialState: SpeechOSState) {\n this.state = { ...initialState };\n this.snapshot = Object.freeze({ ...this.state });\n }\n\n /**\n * Get the current state snapshot (returns a stable reference for React)\n * This returns an immutable frozen object that only changes when setState is called.\n */\n getState(): SpeechOSState {\n return this.snapshot;\n }\n\n /**\n * Update state with partial values\n * @param partial - Partial state to merge with current state\n */\n setState(partial: Partial<SpeechOSState>): void {\n const prevState = this.snapshot;\n this.state = { ...this.state, ...partial };\n // Create a new frozen snapshot\n this.snapshot = Object.freeze({ ...this.state });\n\n // Notify subscribers\n this.subscribers.forEach((callback) => {\n try {\n callback(this.snapshot, prevState);\n } catch (error) {\n console.error(\"Error in state change callback:\", error);\n }\n });\n\n // Emit state change event\n events.emit(\"state:change\", { state: this.snapshot });\n }\n\n /**\n * Subscribe to state changes\n * @param callback - Function to call when state changes\n * @returns Unsubscribe function\n */\n subscribe(callback: StateChangeCallback): UnsubscribeFn {\n this.subscribers.add(callback);\n\n // Return unsubscribe function\n return () => {\n this.subscribers.delete(callback);\n };\n }\n\n /**\n * Reset state to initial values\n */\n reset(): void {\n const prevState = this.snapshot;\n this.state = { ...initialState };\n this.snapshot = Object.freeze({ ...this.state });\n\n // Notify subscribers\n this.subscribers.forEach((callback) => {\n try {\n callback(this.snapshot, prevState);\n } catch (error) {\n console.error(\"Error in state change callback:\", error);\n }\n });\n\n // Emit state change event\n events.emit(\"state:change\", { state: this.snapshot });\n }\n\n /**\n * Show the widget\n */\n show(): void {\n this.setState({ isVisible: true });\n events.emit(\"widget:show\", undefined);\n }\n\n /**\n * Hide the widget and reset expanded state\n */\n hide(): void {\n this.setState({\n isVisible: false,\n isExpanded: false,\n activeAction: null,\n });\n events.emit(\"widget:hide\", undefined);\n }\n\n /**\n * Toggle the action bubbles expansion\n */\n toggleExpanded(): void {\n this.setState({ isExpanded: !this.state.isExpanded });\n }\n\n /**\n * Set the focused form element\n * @param element - The form element that has focus\n */\n setFocusedElement(element: HTMLElement | null): void {\n this.setState({ focusedElement: element });\n }\n\n /**\n * Set the active action\n * @param action - The action to set as active\n */\n setActiveAction(action: SpeechOSState[\"activeAction\"]): void {\n this.setState({ activeAction: action });\n }\n\n /**\n * Set the recording state\n * @param recordingState - The recording state to set\n */\n setRecordingState(recordingState: SpeechOSState[\"recordingState\"]): void {\n this.setState({ recordingState });\n }\n\n /**\n * Set the connection state\n * @param isConnected - Whether connected to LiveKit\n */\n setConnected(isConnected: boolean): void {\n this.setState({ isConnected });\n }\n\n /**\n * Set the microphone enabled state\n * @param isMicEnabled - Whether microphone is enabled\n */\n setMicEnabled(isMicEnabled: boolean): void {\n this.setState({ isMicEnabled });\n }\n\n /**\n * Start recording flow (connecting → recording)\n */\n startRecording(): void {\n this.setState({\n recordingState: \"connecting\",\n isExpanded: false, // Collapse bubbles when recording starts\n });\n }\n\n /**\n * Stop recording and start processing\n */\n stopRecording(): void {\n this.setState({ recordingState: \"processing\", isMicEnabled: false });\n }\n\n /**\n * Complete the recording flow and return to idle\n */\n completeRecording(): void {\n this.setState({\n recordingState: \"idle\",\n activeAction: null,\n isConnected: false,\n isMicEnabled: false,\n });\n }\n\n /**\n * Cancel recording and return to idle\n */\n cancelRecording(): void {\n this.setState({\n recordingState: \"idle\",\n activeAction: null,\n errorMessage: null,\n isConnected: false,\n isMicEnabled: false,\n });\n }\n\n /**\n * Set error state with a message\n * @param message - Error message to display\n */\n setError(message: string): void {\n this.setState({\n recordingState: \"error\",\n errorMessage: message,\n });\n }\n\n /**\n * Clear error state and return to idle\n */\n clearError(): void {\n this.setState({\n recordingState: \"idle\",\n errorMessage: null,\n });\n }\n}\n\n// Export singleton instance\nexport const state: StateManager = new StateManager(initialState);\n\n/**\n * Create a new state manager instance (useful for testing)\n */\nexport function createStateManager(\n initial?: Partial<SpeechOSState>\n): StateManager {\n return new StateManager({ ...initialState, ...initial });\n}\n","/**\n * LiveKit integration for SpeechOS SDK\n * Handles room connections, audio streaming, and transcription requests\n */\n\nimport {\n Room,\n RoomEvent,\n Track,\n createLocalAudioTrack,\n type LocalAudioTrack,\n type RemoteParticipant,\n} from \"livekit-client\";\nimport type {\n LiveKitTokenResponse,\n ServerErrorMessage,\n ErrorSource,\n CommandDefinition,\n CommandResult,\n SpeechOSAction,\n VoiceSessionOptions,\n SessionSettings,\n} from \"./types.js\";\nimport { getConfig } from \"./config.js\";\nimport { events } from \"./events.js\";\nimport { state } from \"./state.js\";\n\n// Protocol constants (matching backend TranscriptionManager)\nconst MESSAGE_TYPE_REQUEST_TRANSCRIPT = \"request_transcript\";\nconst MESSAGE_TYPE_TRANSCRIPT = \"transcript\";\nconst MESSAGE_TYPE_EDIT_TEXT = \"edit_text\";\nconst MESSAGE_TYPE_EDITED_TEXT = \"edited_text\";\nconst MESSAGE_TYPE_EXECUTE_COMMAND = \"execute_command\";\nconst MESSAGE_TYPE_COMMAND_RESULT = \"command_result\";\nconst MESSAGE_TYPE_ERROR = \"error\";\nconst TOPIC_SPEECHOS = \"speechos\";\n\n// Token cache TTL (4 minutes in milliseconds)\n// LiveKit tokens are valid for longer, but we cache for 4 minutes to ensure\n// freshness of session settings (language, vocabulary) while still providing\n// a latency benefit when user clicks an action shortly after expanding the widget\nconst TOKEN_CACHE_TTL_MS = 4 * 60 * 1000;\n\n/**\n * A deferred promise with timeout support.\n * Encapsulates resolve/reject/timeout in a single object for cleaner async handling.\n */\nexport class Deferred<T> {\n readonly promise: Promise<T>;\n private _resolve!: (value: T) => void;\n private _reject!: (error: Error) => void;\n private _timeoutId: ReturnType<typeof setTimeout> | null = null;\n private _settled = false;\n\n constructor() {\n this.promise = new Promise<T>((resolve, reject) => {\n this._resolve = resolve;\n this._reject = reject;\n });\n }\n\n /**\n * Set a timeout that will reject the promise with the given error\n */\n setTimeout(\n ms: number,\n errorMessage: string,\n errorCode: string,\n errorSource: ErrorSource\n ): void {\n this._timeoutId = setTimeout(() => {\n if (!this._settled) {\n console.error(`[SpeechOS] Error: ${errorMessage} (${errorCode})`);\n events.emit(\"error\", {\n code: errorCode,\n message: errorMessage,\n source: errorSource,\n });\n this.reject(new Error(errorMessage));\n }\n }, ms);\n }\n\n resolve(value: T): void {\n if (!this._settled) {\n this._settled = true;\n this.clearTimeout();\n this._resolve(value);\n }\n }\n\n reject(error: Error): void {\n if (!this._settled) {\n this._settled = true;\n this.clearTimeout();\n this._reject(error);\n }\n }\n\n private clearTimeout(): void {\n if (this._timeoutId !== null) {\n clearTimeout(this._timeoutId);\n this._timeoutId = null;\n }\n }\n\n get isSettled(): boolean {\n return this._settled;\n }\n}\n\n/**\n * LiveKit connection manager\n */\nclass LiveKitManager {\n private room: Room | null = null;\n private tokenData: LiveKitTokenResponse | null = null;\n private micTrack: LocalAudioTrack | null = null;\n\n // Token cache for pre-fetching optimization\n private cachedTokenData: LiveKitTokenResponse | null = null;\n private tokenCacheTimestamp: number | null = null;\n private tokenPrefetchPromise: Promise<LiveKitTokenResponse> | null = null;\n\n // Auto-refresh timer for keeping token fresh while widget is expanded\n private tokenRefreshTimer: ReturnType<typeof setTimeout> | null = null;\n private autoRefreshEnabled = false;\n\n // Pending async operations using Deferred pattern\n private pendingTranscript: Deferred<string> | null = null;\n private pendingEditText: Deferred<string> | null = null;\n private pendingCommand: Deferred<CommandResult | null> | null = null;\n private pendingTrackSubscribed: Deferred<void> | null = null;\n\n // Track original text for edit operations (to include in events)\n private editOriginalText: string | null = null;\n\n // Session settings passed from options\n private sessionSettings: SessionSettings = {};\n\n /**\n * Check if the cached token is still valid (within TTL)\n */\n private isCachedTokenValid(): boolean {\n if (!this.cachedTokenData || !this.tokenCacheTimestamp) {\n return false;\n }\n const age = Date.now() - this.tokenCacheTimestamp;\n return age < TOKEN_CACHE_TTL_MS;\n }\n\n /**\n * Pre-fetch a LiveKit token for later use\n * Call this early (e.g., when widget expands) to reduce latency when starting a voice session.\n * If a prefetch is already in progress, returns the existing promise.\n * If a valid cached token exists, returns it immediately.\n */\n async prefetchToken(): Promise<LiveKitTokenResponse> {\n const config = getConfig();\n\n // If we have a valid cached token, return it\n if (this.isCachedTokenValid() && this.cachedTokenData) {\n if (config.debug) {\n console.log(\"[SpeechOS] Using cached token (prefetch hit)\");\n }\n return this.cachedTokenData;\n }\n\n // If a prefetch is already in progress, return the existing promise\n if (this.tokenPrefetchPromise) {\n if (config.debug) {\n console.log(\"[SpeechOS] Prefetch already in progress, awaiting...\");\n }\n return this.tokenPrefetchPromise;\n }\n\n // Start a new prefetch\n if (config.debug) {\n console.log(\"[SpeechOS] Starting token prefetch...\");\n }\n\n this.tokenPrefetchPromise = this.fetchTokenFromServer()\n .then((data) => {\n // Cache the token\n this.cachedTokenData = data;\n this.tokenCacheTimestamp = Date.now();\n this.tokenPrefetchPromise = null;\n return data;\n })\n .catch((error) => {\n this.tokenPrefetchPromise = null;\n throw error;\n });\n\n return this.tokenPrefetchPromise;\n }\n\n /**\n * Fetch a LiveKit token from the backend\n * Uses cached token if valid, otherwise fetches a fresh one.\n * Includes language settings and user vocabulary which are stored in the VoiceSession.\n */\n async fetchToken(): Promise<LiveKitTokenResponse> {\n const config = getConfig();\n\n // Check if we have a valid cached token\n if (this.isCachedTokenValid() && this.cachedTokenData) {\n if (config.debug) {\n console.log(\"[SpeechOS] Using cached token\");\n }\n this.tokenData = this.cachedTokenData;\n return this.cachedTokenData;\n }\n\n // If a prefetch is in progress, wait for it\n if (this.tokenPrefetchPromise) {\n if (config.debug) {\n console.log(\"[SpeechOS] Waiting for prefetch to complete...\");\n }\n const data = await this.tokenPrefetchPromise;\n this.tokenData = data;\n return data;\n }\n\n // No valid cache, fetch fresh token\n const data = await this.fetchTokenFromServer();\n\n // Cache the token\n this.cachedTokenData = data;\n this.tokenCacheTimestamp = Date.now();\n this.tokenData = data;\n\n return data;\n }\n\n /**\n * Internal method to fetch a fresh token from the server\n */\n private async fetchTokenFromServer(): Promise<LiveKitTokenResponse> {\n const config = getConfig();\n const url = `${config.host}/livekit/api/token/`;\n\n // Use session settings (with defaults)\n const settings = this.sessionSettings;\n const inputLanguage = settings.inputLanguageCode ?? \"en-US\";\n const outputLanguage = settings.outputLanguageCode ?? \"en-US\";\n const smartFormat = settings.smartFormat ?? true;\n const vocabulary = settings.vocabulary ?? [];\n const snippets = settings.snippets ?? [];\n\n if (config.debug) {\n console.log(\"[SpeechOS] Fetching LiveKit token from:\", url);\n console.log(\"[SpeechOS] Session settings:\", {\n inputLanguage,\n outputLanguage,\n smartFormat,\n snippetsCount: snippets.length,\n vocabularyCount: vocabulary.length,\n });\n }\n\n const response = await fetch(url, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n ...(config.apiKey ? { Authorization: `Api-Key ${config.apiKey}` } : {}),\n },\n body: JSON.stringify({\n user_id: config.userId || null,\n input_language: inputLanguage,\n output_language: outputLanguage,\n smart_format: smartFormat,\n custom_vocabulary: vocabulary,\n custom_snippets: snippets,\n }),\n });\n\n if (!response.ok) {\n throw new Error(\n `Failed to fetch LiveKit token: ${response.status} ${response.statusText}`\n );\n }\n\n const data = (await response.json()) as LiveKitTokenResponse;\n\n if (config.debug) {\n console.log(\"[SpeechOS] LiveKit token received:\", {\n room: data.room,\n identity: data.identity,\n ws_url: data.ws_url,\n });\n }\n\n return data;\n }\n\n /**\n * Connect to a LiveKit room (fresh connection each time)\n */\n async connect(): Promise<Room> {\n const config = getConfig();\n\n // Fetch a fresh token for this connection\n await this.fetchToken();\n\n if (!this.tokenData) {\n throw new Error(\"No token available for LiveKit connection\");\n }\n\n // Create a new room instance\n this.room = new Room({\n adaptiveStream: true,\n dynacast: true,\n });\n\n // Set up event listeners\n this.setupRoomEvents();\n\n if (config.debug) {\n console.log(\n \"[SpeechOS] Connecting to LiveKit room:\",\n this.tokenData.room\n );\n }\n\n // Connect to the room\n await this.room.connect(this.tokenData.ws_url, this.tokenData.token);\n\n // Update state\n state.setConnected(true);\n\n if (config.debug) {\n console.log(\"[SpeechOS] Connected to LiveKit room:\", this.room.name);\n }\n\n return this.room;\n }\n\n /**\n * Wait until the agent is ready to receive audio\n * Resolves when LocalTrackSubscribed event is received\n */\n async waitUntilReady(): Promise<void> {\n if (!this.room || this.room.state !== \"connected\") {\n throw new Error(\"Not connected to room\");\n }\n\n // If already set up (from startVoiceSession), just return the existing promise\n if (this.pendingTrackSubscribed) {\n return this.pendingTrackSubscribed.promise;\n }\n\n // Create a new deferred\n this.pendingTrackSubscribed = new Deferred<void>();\n this.pendingTrackSubscribed.setTimeout(\n 15000,\n \"Connection timed out - agent not available\",\n \"connection_timeout\",\n \"connection\"\n );\n\n return this.pendingTrackSubscribed.promise;\n }\n\n /**\n * Set up LiveKit room event listeners\n */\n private setupRoomEvents(): void {\n if (!this.room) return;\n\n const config = getConfig();\n\n this.room.on(RoomEvent.Connected, () => {\n if (config.debug) {\n console.log(\"[SpeechOS] Room connected\");\n }\n state.setConnected(true);\n });\n\n this.room.on(RoomEvent.Disconnected, (reason) => {\n if (config.debug) {\n console.log(\"[SpeechOS] Room disconnected:\", reason);\n }\n state.setConnected(false);\n state.setMicEnabled(false);\n });\n\n this.room.on(RoomEvent.ParticipantConnected, (participant) => {\n if (config.debug) {\n console.log(\"[SpeechOS] Participant connected:\", participant.identity);\n }\n });\n\n // Fired when a remote participant subscribes to our local track\n // This confirms the agent is ready to receive our audio\n this.room.on(RoomEvent.LocalTrackSubscribed, (publication) => {\n if (config.debug) {\n console.log(\n \"[SpeechOS] LocalTrackSubscribed event fired:\",\n publication.trackSid\n );\n }\n\n // Resolve the promise when our track is subscribed to\n if (this.pendingTrackSubscribed) {\n this.pendingTrackSubscribed.resolve();\n this.pendingTrackSubscribed = null;\n }\n });\n\n // Also log all room events for debugging\n this.room.on(RoomEvent.LocalTrackPublished, (publication) => {\n if (config.debug) {\n console.log(\n \"[SpeechOS] LocalTrackPublished:\",\n publication.trackSid,\n publication.source\n );\n }\n });\n\n // Handle incoming data messages (transcriptions, acks, etc.)\n this.room.on(\n RoomEvent.DataReceived,\n (data: Uint8Array, participant?: RemoteParticipant) => {\n this.handleDataMessage(data, participant);\n }\n );\n }\n\n /**\n * Handle incoming data messages from the agent\n */\n private handleDataMessage(\n data: Uint8Array,\n _participant?: RemoteParticipant\n ): void {\n const config = getConfig();\n\n try {\n const message = JSON.parse(new TextDecoder().decode(data));\n\n if (config.debug) {\n console.log(\"[SpeechOS] Data received:\", message);\n }\n\n if (message.type === MESSAGE_TYPE_TRANSCRIPT) {\n // Transcript received from agent\n const transcript = message.transcript || \"\";\n\n if (config.debug) {\n console.log(\"[SpeechOS] Transcript received:\", transcript);\n }\n\n // Emit transcription:complete event\n events.emit(\"transcription:complete\", { text: transcript });\n\n // Resolve the pending transcript promise\n if (this.pendingTranscript) {\n this.pendingTranscript.resolve(transcript);\n this.pendingTranscript = null;\n }\n } else if (message.type === MESSAGE_TYPE_EDITED_TEXT) {\n // Edited text received from agent\n const editedText = message.text || \"\";\n\n if (config.debug) {\n console.log(\"[SpeechOS] Edited text received:\", editedText);\n }\n\n // Emit edit:complete event\n events.emit(\"edit:complete\", {\n text: editedText,\n originalText: this.editOriginalText || \"\",\n });\n\n // Resolve the pending edit text promise\n if (this.pendingEditText) {\n this.pendingEditText.resolve(editedText);\n this.pendingEditText = null;\n }\n\n // Clear stored original text\n this.editOriginalText = null;\n } else if (message.type === MESSAGE_TYPE_COMMAND_RESULT) {\n // Command result received from agent\n const commandResult: CommandResult | null = message.command || null;\n\n if (config.debug) {\n console.log(\"[SpeechOS] Command result received:\", commandResult);\n }\n\n // Emit command:complete event\n events.emit(\"command:complete\", { command: commandResult });\n\n // Resolve the pending command promise\n if (this.pendingCommand) {\n this.pendingCommand.resolve(commandResult);\n this.pendingCommand = null;\n }\n } else if (message.type === MESSAGE_TYPE_ERROR) {\n // Server error received from agent\n const serverError = message as ServerErrorMessage;\n const errorCode = serverError.code || \"server_error\";\n const errorMessage = serverError.message || \"A server error occurred\";\n\n console.error(`[SpeechOS] Error: ${errorMessage} (${errorCode})`);\n\n if (config.debug && serverError.details) {\n console.error(\"[SpeechOS] Error details:\", serverError.details);\n }\n\n // Emit error event for UI to handle\n events.emit(\"error\", {\n code: errorCode,\n message: errorMessage,\n source: \"server\",\n });\n\n // Reject any pending operations\n const error = new Error(errorMessage);\n if (this.pendingTranscript) {\n this.pendingTranscript.reject(error);\n this.pendingTranscript = null;\n }\n if (this.pendingEditText) {\n this.pendingEditText.reject(error);\n this.pendingEditText = null;\n }\n if (this.pendingCommand) {\n this.pendingCommand.reject(error);\n this.pendingCommand = null;\n }\n }\n } catch (error) {\n console.error(\"[SpeechOS] Failed to parse data message:\", error);\n }\n }\n\n /**\n * Publish microphone audio track\n * Uses the device ID from session settings if set\n */\n async enableMicrophone(): Promise<void> {\n if (!this.room || this.room.state !== \"connected\") {\n throw new Error(\"Not connected to room\");\n }\n\n const config = getConfig();\n\n if (!this.micTrack) {\n if (config.debug) {\n console.log(\"[SpeechOS] Creating microphone track...\");\n }\n\n // Get selected device ID from session settings\n const deviceId = this.sessionSettings.audioDeviceId;\n const trackOptions: Parameters<typeof createLocalAudioTrack>[0] = {\n echoCancellation: true,\n noiseSuppression: true,\n };\n\n // Only set deviceId if user has selected a specific device\n // Use { exact: deviceId } for strict matching per LiveKit best practices\n if (deviceId) {\n trackOptions.deviceId = { exact: deviceId };\n if (config.debug) {\n console.log(\"[SpeechOS] Using audio device:\", deviceId);\n }\n }\n\n try {\n this.micTrack = await createLocalAudioTrack(trackOptions);\n } catch (error) {\n // If the selected device is unavailable, fall back to default\n if (deviceId && error instanceof Error) {\n console.warn(\n \"[SpeechOS] Selected audio device unavailable, falling back to default:\",\n error.message\n );\n this.micTrack = await createLocalAudioTrack({\n echoCancellation: true,\n noiseSuppression: true,\n });\n } else {\n throw error;\n }\n }\n\n // Log microphone info\n this.logMicrophoneInfo();\n }\n\n // Publish the track if not already published\n const existingPub = this.room.localParticipant.getTrackPublication(\n Track.Source.Microphone\n );\n if (!existingPub) {\n await this.room.localParticipant.publishTrack(this.micTrack, {\n source: Track.Source.Microphone,\n });\n\n // Update state\n state.setMicEnabled(true);\n\n if (config.debug) {\n console.log(\"[SpeechOS] Microphone track published\");\n }\n }\n }\n\n /**\n * Log information about the current microphone track\n */\n private logMicrophoneInfo(): void {\n if (!this.micTrack) return;\n\n const config = getConfig();\n const mediaTrack = this.micTrack.mediaStreamTrack;\n const settings = mediaTrack.getSettings();\n\n // Always log basic mic info (useful for debugging)\n console.log(\"[SpeechOS] Microphone active:\", {\n deviceId: settings.deviceId || \"unknown\",\n label: mediaTrack.label || \"Unknown device\",\n sampleRate: settings.sampleRate,\n channelCount: settings.channelCount,\n echoCancellation: settings.echoCancellation,\n noiseSuppression: settings.noiseSuppression,\n });\n\n if (config.debug) {\n // Log full settings in debug mode\n console.log(\"[SpeechOS] Full audio track settings:\", settings);\n }\n }\n\n /**\n * Disable microphone audio track\n */\n async disableMicrophone(): Promise<void> {\n const config = getConfig();\n\n if (this.micTrack) {\n if (config.debug) {\n console.log(\"[SpeechOS] Disabling microphone track...\");\n }\n\n // Unpublish from room if connected\n if (this.room?.state === \"connected\") {\n try {\n await this.room.localParticipant.unpublishTrack(this.micTrack);\n if (config.debug) {\n console.log(\"[SpeechOS] Microphone track unpublished\");\n }\n } catch (error) {\n console.warn(\"[SpeechOS] Error unpublishing track:\", error);\n }\n }\n\n // Stop the track to release the microphone\n this.micTrack.stop();\n\n // Detach from any elements (per LiveKit best practices)\n this.micTrack.detach();\n\n this.micTrack = null;\n\n // Update state\n state.setMicEnabled(false);\n\n if (config.debug) {\n console.log(\"[SpeechOS] Microphone track stopped and detached\");\n }\n }\n }\n\n /**\n * Send a data message to the room\n */\n async sendDataMessage(message: object): Promise<void> {\n if (!this.room || this.room.state !== \"connected\") {\n throw new Error(\"Not connected to room\");\n }\n\n const data = new TextEncoder().encode(JSON.stringify(message));\n await this.room.localParticipant.publishData(data, {\n reliable: true,\n topic: TOPIC_SPEECHOS,\n });\n }\n\n /**\n * Start a voice session with pre-connect audio buffering\n * Fetches a fresh token, then enables mic with preConnectBuffer to capture audio while connecting.\n * Agent subscription happens in the background - we don't block on it.\n *\n * @param options - Session options including action type and parameters\n */\n async startVoiceSession(options?: VoiceSessionOptions): Promise<void> {\n const config = getConfig();\n if (config.debug) {\n console.log(\"[SpeechOS] Starting voice session...\");\n }\n\n // Store session settings from options\n this.sessionSettings = options?.settings || {};\n\n // Fetch a fresh token for this session\n await this.fetchToken();\n\n if (!this.tokenData) {\n throw new Error(\"No token available for LiveKit connection\");\n }\n\n // Set up the deferred BEFORE connecting so we don't miss the event\n // Agent subscription happens in the background - we don't block on it\n this.pendingTrackSubscribed = new Deferred<void>();\n this.pendingTrackSubscribed.setTimeout(\n 15000,\n \"Connection timed out - agent not available\",\n \"connection_timeout\",\n \"connection\"\n );\n\n // Create the room instance\n this.room = new Room({\n adaptiveStream: true,\n dynacast: true,\n });\n\n // Set up event listeners before enabling mic\n this.setupRoomEvents();\n\n if (config.debug) {\n console.log(\n \"[SpeechOS] Connecting to LiveKit room:\",\n this.tokenData.room,\n \"at\",\n this.tokenData.ws_url\n );\n }\n\n // Connect to the room first\n await this.room.connect(this.tokenData.ws_url, this.tokenData.token);\n\n if (config.debug) {\n console.log(\n \"[SpeechOS] Connected, enabling microphone with preConnectBuffer...\"\n );\n }\n\n // Enable microphone with preConnectBuffer after connection\n // This buffers audio until the agent subscribes to our track\n await this.enableMicrophoneWithPreConnectBuffer();\n\n // Notify that mic is ready - UI can show \"recording\" state now\n // Audio is being captured even though room connection may still be in progress\n if (options?.onMicReady) {\n options.onMicReady();\n }\n\n // Update state\n state.setConnected(true);\n\n if (config.debug) {\n console.log(\"[SpeechOS] Voice session ready - microphone active\");\n }\n\n // Wait for agent subscription in the background\n // This allows the UI to show \"recording\" immediately while the agent connects\n this.waitForAgentSubscription();\n }\n\n /**\n * Wait for the agent to subscribe to our audio track in the background\n * Handles timeout errors without blocking the main flow\n */\n private waitForAgentSubscription(): void {\n const config = getConfig();\n\n if (!this.pendingTrackSubscribed) {\n return;\n }\n\n // Handle the subscription in the background\n this.pendingTrackSubscribed.promise\n .then(() => {\n if (config.debug) {\n console.log(\n \"[SpeechOS] Agent subscribed to audio track - full duplex established\"\n );\n }\n this.pendingTrackSubscribed = null;\n })\n .catch((error) => {\n // Agent subscription timed out - the buffered audio will still be sent\n // but ongoing audio may not be processed if agent never connects\n console.warn(\"[SpeechOS] Agent subscription timeout:\", error.message);\n this.pendingTrackSubscribed = null;\n // Note: We don't set error state here because the session may still work\n // if the agent connects late. The timeout error event was already emitted\n // by the Deferred class.\n });\n }\n\n /**\n * Enable microphone with pre-connect buffering\n * This starts capturing audio locally before the room is connected,\n * buffering it until the connection is established.\n */\n private async enableMicrophoneWithPreConnectBuffer(): Promise<void> {\n if (!this.room) {\n throw new Error(\"Room not initialized\");\n }\n\n const config = getConfig();\n\n // Get selected device ID from session settings\n const deviceId = this.sessionSettings.audioDeviceId;\n\n // Build constraints for the microphone\n const constraints: MediaTrackConstraints = {\n echoCancellation: true,\n noiseSuppression: true,\n };\n\n // Only set deviceId if user has selected a specific device\n if (deviceId) {\n constraints.deviceId = { exact: deviceId };\n if (config.debug) {\n console.log(\"[SpeechOS] Using audio device:\", deviceId);\n }\n }\n\n try {\n // Enable microphone with preConnectBuffer: true\n // This tells LiveKit to start capturing audio locally and buffer it\n // until the room connection is established\n await this.room.localParticipant.setMicrophoneEnabled(true, constraints, {\n preConnectBuffer: true,\n });\n\n // Update state\n state.setMicEnabled(true);\n\n // Get the mic track for logging\n const micPub = this.room.localParticipant.getTrackPublication(\n Track.Source.Microphone\n );\n if (micPub?.track) {\n this.micTrack = micPub.track as LocalAudioTrack;\n this.logMicrophoneInfo();\n }\n\n if (config.debug) {\n console.log(\n \"[SpeechOS] Microphone enabled with pre-connect buffer - audio is being captured\"\n );\n }\n } catch (error) {\n // If the selected device is unavailable, fall back to default\n if (deviceId && error instanceof Error) {\n console.warn(\n \"[SpeechOS] Selected audio device unavailable, falling back to default:\",\n error.message\n );\n await this.room.localParticipant.setMicrophoneEnabled(\n true,\n {\n echoCancellation: true,\n noiseSuppression: true,\n },\n {\n preConnectBuffer: true,\n }\n );\n state.setMicEnabled(true);\n } else {\n throw error;\n }\n }\n }\n\n /**\n * Stop the voice session and request the transcript\n * Returns a promise that resolves with the transcript text\n * @throws Error if timeout occurs waiting for transcript\n */\n async stopVoiceSession(): Promise<string> {\n const config = getConfig();\n const settings = this.sessionSettings;\n const inputLanguage = settings.inputLanguageCode ?? \"en-US\";\n const outputLanguage = settings.outputLanguageCode ?? \"en-US\";\n\n // Always log dictate command with language settings\n console.log(\"[SpeechOS] Dictate command:\", {\n inputLanguage,\n outputLanguage,\n });\n\n if (config.debug) {\n console.log(\n \"[SpeechOS] Stopping voice session, requesting transcript...\"\n );\n }\n\n // Disable microphone\n await this.disableMicrophone();\n\n if (config.debug) {\n console.log(\"[SpeechOS] Requesting transcript from agent...\");\n }\n\n // Create deferred for transcript (resolved when transcript message is received)\n this.pendingTranscript = new Deferred<string>();\n this.pendingTranscript.setTimeout(\n 10000,\n \"Transcription timed out. Please try again.\",\n \"transcription_timeout\",\n \"timeout\"\n );\n\n // Request the transcript from the agent\n await this.sendDataMessage({\n type: MESSAGE_TYPE_REQUEST_TRANSCRIPT,\n });\n\n const result = await this.pendingTranscript.promise;\n this.pendingTranscript = null;\n return result;\n }\n\n /**\n * Alias for stopVoiceSession - granular API naming\n */\n async stopAndGetTranscript(): Promise<string> {\n return this.stopVoiceSession();\n }\n\n /**\n * Request text editing using the transcript as instructions\n * Sends the original text to the backend, which applies the spoken instructions\n * Returns a promise that resolves with the edited text\n * @throws Error if timeout occurs waiting for edited text\n */\n async requestEditText(originalText: string): Promise<string> {\n const config = getConfig();\n const settings = this.sessionSettings;\n const inputLanguage = settings.inputLanguageCode ?? \"en-US\";\n const outputLanguage = settings.outputLanguageCode ?? \"en-US\";\n\n // Always log edit command with language settings\n console.log(\"[SpeechOS] Edit command:\", {\n inputLanguage,\n outputLanguage,\n originalTextLength: originalText.length,\n });\n\n if (config.debug) {\n console.log(\"[SpeechOS] Requesting text edit...\");\n }\n\n // Store original text for the event\n this.editOriginalText = originalText;\n\n // Disable microphone first\n await this.disableMicrophone();\n\n if (config.debug) {\n console.log(\"[SpeechOS] Sending edit_text request to agent...\");\n }\n\n // Create deferred for edited text (resolved when edited_text message is received)\n this.pendingEditText = new Deferred<string>();\n this.pendingEditText.setTimeout(\n 15000,\n \"Edit request timed out. Please try again.\",\n \"edit_timeout\",\n \"timeout\"\n );\n\n // Send the edit request to the agent\n await this.sendDataMessage({\n type: MESSAGE_TYPE_EDIT_TEXT,\n text: originalText,\n });\n\n const result = await this.pendingEditText.promise;\n this.pendingEditText = null;\n return result;\n }\n\n /**\n * Alias for requestEditText - granular API naming\n */\n async stopAndEdit(originalText: string): Promise<string> {\n return this.requestEditText(originalText);\n }\n\n /**\n * Request command matching using the transcript as input\n * Sends command definitions to the backend, which matches the user's speech against them\n * Returns a promise that resolves with the matched command or null if no match\n * @throws Error if timeout occurs waiting for command result\n */\n async requestCommand(\n commands: CommandDefinition[]\n ): Promise<CommandResult | null> {\n const config = getConfig();\n const settings = this.sessionSettings;\n const inputLanguage = settings.inputLanguageCode ?? \"en-US\";\n\n // Always log command request\n console.log(\"[SpeechOS] Command request:\", {\n inputLanguage,\n commandCount: commands.length,\n });\n\n if (config.debug) {\n console.log(\"[SpeechOS] Requesting command match...\");\n }\n\n // Disable microphone first\n await this.disableMicrophone();\n\n if (config.debug) {\n console.log(\"[SpeechOS] Sending execute_command request to agent...\");\n }\n\n // Create deferred for command result (resolved when command_result message is received)\n this.pendingCommand = new Deferred<CommandResult | null>();\n this.pendingCommand.setTimeout(\n 15000,\n \"Command request timed out. Please try again.\",\n \"command_timeout\",\n \"timeout\"\n );\n\n // Send the command request to the agent with command definitions\n await this.sendDataMessage({\n type: MESSAGE_TYPE_EXECUTE_COMMAND,\n commands: commands,\n });\n\n const result = await this.pendingCommand.promise;\n this.pendingCommand = null;\n return result;\n }\n\n /**\n * Alias for requestCommand - granular API naming\n */\n async stopAndCommand(\n commands: CommandDefinition[]\n ): Promise<CommandResult | null> {\n return this.requestCommand(commands);\n }\n\n /**\n * Disconnect from the current room\n * Clears the token so a fresh one is fetched for the next session\n */\n async disconnect(): Promise<void> {\n const config = getConfig();\n\n if (config.debug) {\n console.log(\"[SpeechOS] Disconnecting from room...\");\n }\n\n // Disable and cleanup microphone track\n await this.disableMicrophone();\n\n if (this.room) {\n // Remove all event listeners before disconnecting\n this.room.removeAllListeners();\n\n // Disconnect from the room\n await this.room.disconnect();\n this.room = null;\n\n // Update state\n state.setConnected(false);\n\n if (config.debug) {\n console.log(\"[SpeechOS] Room disconnected and cleaned up\");\n }\n }\n\n // Reject any pending operations (so callers don't hang)\n if (this.pendingTranscript) {\n this.pendingTranscript.reject(new Error(\"Disconnected\"));\n this.pendingTranscript = null;\n }\n if (this.pendingEditText) {\n this.pendingEditText.reject(new Error(\"Disconnected\"));\n this.pendingEditText = null;\n }\n if (this.pendingCommand) {\n this.pendingCommand.reject(new Error(\"Disconnected\"));\n this.pendingCommand = null;\n }\n if (this.pendingTrackSubscribed) {\n this.pendingTrackSubscribed.reject(new Error(\"Disconnected\"));\n this.pendingTrackSubscribed = null;\n }\n\n // Clear all session state including token\n // Note: We intentionally keep the cached token (cachedTokenData) valid\n // so it can be reused for the next session if within TTL.\n // The cache will naturally expire based on TOKEN_CACHE_TTL_MS.\n this.tokenData = null;\n this.editOriginalText = null;\n this.sessionSettings = {};\n\n if (config.debug) {\n console.log(\"[SpeechOS] Session state cleared\");\n }\n }\n\n /**\n * Invalidate the cached token\n * Call this when settings change that would affect the token (language, vocabulary)\n */\n invalidateTokenCache(): void {\n const config = getConfig();\n if (config.debug) {\n console.log(\"[SpeechOS] Token cache invalidated\");\n }\n this.cachedTokenData = null;\n this.tokenCacheTimestamp = null;\n }\n\n /**\n * Start auto-refreshing the token while the widget is expanded.\n * Call this after a voice session completes to immediately fetch a fresh token\n * (since each command requires its own token) and keep it fresh for subsequent commands.\n */\n startAutoRefresh(): void {\n const config = getConfig();\n this.autoRefreshEnabled = true;\n\n if (config.debug) {\n console.log(\"[SpeechOS] Token auto-refresh enabled\");\n }\n\n // Invalidate the token we just used (each command needs a fresh token)\n this.invalidateTokenCache();\n\n // Immediately fetch a fresh token for the next command, then schedule refresh\n this.prefetchToken()\n .then(() => {\n // Now that we have a fresh token, schedule the next refresh\n this.scheduleTokenRefresh();\n })\n .catch((error) => {\n if (config.debug) {\n console.warn(\n \"[SpeechOS] Failed to prefetch token after command:\",\n error\n );\n }\n // Even on failure, try again later\n if (this.autoRefreshEnabled) {\n this.tokenRefreshTimer = setTimeout(() => {\n this.performAutoRefresh();\n }, 5 * 1000); // Retry in 5 seconds\n }\n });\n }\n\n /**\n * Stop auto-refreshing the token.\n * Call this when the widget collapses or user navigates away.\n */\n stopAutoRefresh(): void {\n const config = getConfig();\n this.autoRefreshEnabled = false;\n\n if (this.tokenRefreshTimer) {\n clearTimeout(this.tokenRefreshTimer);\n this.tokenRefreshTimer = null;\n }\n\n if (config.debug) {\n console.log(\"[SpeechOS] Token auto-refresh disabled\");\n }\n }\n\n /**\n * Schedule a token refresh before the current cache expires.\n * Handles computer sleep by checking elapsed time on each refresh attempt.\n */\n private scheduleTokenRefresh(): void {\n if (!this.autoRefreshEnabled) {\n return;\n }\n\n // Clear any existing timer\n if (this.tokenRefreshTimer) {\n clearTimeout(this.tokenRefreshTimer);\n this.tokenRefreshTimer = null;\n }\n\n const config = getConfig();\n\n // Calculate time until refresh needed\n // Refresh 30 seconds before expiry to ensure smooth transition\n const refreshBuffer = 30 * 1000;\n let timeUntilRefresh: number;\n\n if (this.tokenCacheTimestamp) {\n const age = Date.now() - this.tokenCacheTimestamp;\n const timeRemaining = TOKEN_CACHE_TTL_MS - age;\n timeUntilRefresh = Math.max(0, timeRemaining - refreshBuffer);\n } else {\n // No cached token, refresh immediately\n timeUntilRefresh = 0;\n }\n\n if (config.debug) {\n console.log(\n `[SpeechOS] Scheduling token refresh in ${Math.round(\n timeUntilRefresh / 1000\n )}s`\n );\n }\n\n this.tokenRefreshTimer = setTimeout(() => {\n this.performAutoRefresh();\n }, timeUntilRefresh);\n }\n\n /**\n * Perform the auto-refresh, handling computer sleep scenarios.\n */\n private async performAutoRefresh(): Promise<void> {\n if (!this.autoRefreshEnabled) {\n return;\n }\n\n const config = getConfig();\n\n // Check if token is still valid (handles computer sleep where timer fired late)\n // If token expired during sleep, we'll fetch a fresh one\n if (this.isCachedTokenValid()) {\n if (config.debug) {\n console.log(\n \"[SpeechOS] Token still valid on refresh check, rescheduling\"\n );\n }\n this.scheduleTokenRefresh();\n return;\n }\n\n if (config.debug) {\n console.log(\"[SpeechOS] Auto-refreshing token...\");\n }\n\n try {\n // Fetch fresh token\n const data = await this.fetchTokenFromServer();\n this.cachedTokenData = data;\n this.tokenCacheTimestamp = Date.now();\n\n if (config.debug) {\n console.log(\"[SpeechOS] Token auto-refreshed successfully\");\n }\n\n // Schedule next refresh\n this.scheduleTokenRefresh();\n } catch (error) {\n // Log but don't show error to user - they haven't started an action\n console.warn(\"[SpeechOS] Token auto-refresh failed:\", error);\n\n // Retry in 30 seconds\n if (this.autoRefreshEnabled) {\n this.tokenRefreshTimer = setTimeout(() => {\n this.performAutoRefresh();\n }, 30 * 1000);\n }\n }\n }\n\n /**\n * Get the current room instance\n */\n getRoom(): Room | null {\n return this.room;\n }\n\n /**\n * Get the current token data\n */\n getTokenData(): LiveKitTokenResponse | null {\n return this.tokenData;\n }\n\n /**\n * Check if connected to a room\n */\n isConnected(): boolean {\n return this.room?.state === \"connected\";\n }\n\n /**\n * Check if microphone is enabled\n */\n isMicrophoneEnabled(): boolean {\n return this.micTrack !== null;\n }\n}\n\n// Export singleton instance\nexport const livekit: LiveKitManager = new LiveKitManager();\n\n// Invalidate token cache when settings change (language, snippets, vocabulary)\n// These settings are embedded in the token, so a new token is needed if they change\nevents.on(\"settings:changed\", () => {\n livekit.invalidateTokenCache();\n});\n","/**\n * Audio capture module for SpeechOS WebSocket integration.\n *\n * Provides MediaRecorder-based audio capture with:\n * - Format detection for cross-browser compatibility\n * - Buffering for instant start (audio captured before connection is ready)\n * - Atomic buffer swap pattern to prevent chunk reordering\n */\n\nimport { getConfig } from './config.js';\n\n/**\n * Supported audio formats with their MIME types and whether\n * Deepgram needs explicit encoding parameters.\n */\nexport interface AudioFormat {\n /** MIME type for MediaRecorder */\n mimeType: string;\n /** Short identifier for the format */\n format: 'webm' | 'mp4' | 'pcm';\n /** Whether Deepgram needs encoding/sample_rate params */\n needsEncodingParams: boolean;\n}\n\n/**\n * Detect if running in Safari.\n */\nfunction isSafari(): boolean {\n const ua = navigator.userAgent.toLowerCase();\n const vendor = navigator.vendor?.toLowerCase() || '';\n const hasSafariUA = ua.includes('safari') && !ua.includes('chrome') && !ua.includes('chromium');\n const isAppleVendor = vendor.includes('apple');\n return hasSafariUA && isAppleVendor;\n}\n\n/**\n * Detect the best supported audio format for the current browser.\n *\n * IMPORTANT: Safari must use MP4/AAC. Its WebM/Opus implementation is buggy\n * and produces truncated/incomplete audio.\n */\nexport function getSupportedAudioFormat(): AudioFormat {\n // Safari: Force MP4/AAC\n if (isSafari()) {\n if (MediaRecorder.isTypeSupported('audio/mp4')) {\n return {\n mimeType: 'audio/mp4',\n format: 'mp4',\n needsEncodingParams: false,\n };\n }\n return {\n mimeType: '',\n format: 'mp4',\n needsEncodingParams: true,\n };\n }\n\n // Chrome, Firefox, Edge: Use WebM/Opus\n if (MediaRecorder.isTypeSupported('audio/webm;codecs=opus')) {\n return {\n mimeType: 'audio/webm;codecs=opus',\n format: 'webm',\n needsEncodingParams: false,\n };\n }\n\n // Fallback to WebM without codec spec\n if (MediaRecorder.isTypeSupported('audio/webm')) {\n return {\n mimeType: 'audio/webm',\n format: 'webm',\n needsEncodingParams: false,\n };\n }\n\n // Fallback to MP4 (for browsers that support MP4 but not WebM)\n if (MediaRecorder.isTypeSupported('audio/mp4')) {\n return {\n mimeType: 'audio/mp4',\n format: 'mp4',\n needsEncodingParams: false,\n };\n }\n\n // Last resort - let browser choose\n return {\n mimeType: '',\n format: 'webm',\n needsEncodingParams: true,\n };\n}\n\n/**\n * Callback for receiving audio chunks.\n */\nexport type AudioChunkCallback = (chunk: Blob) => void;\n\n/**\n * Audio capture manager with buffering support.\n *\n * Usage:\n * 1. Create instance with onChunk callback\n * 2. Call start() - immediately begins capturing\n * 3. Call setReady() when connection is established - flushes buffer\n * 4. Call stop() when done\n */\nexport class AudioCapture {\n private mediaStream: MediaStream | null = null;\n private recorder: MediaRecorder | null = null;\n private buffer: Blob[] = [];\n private isReady = false;\n private isRecording = false;\n private onChunk: AudioChunkCallback;\n private audioFormat: AudioFormat;\n private deviceId: string | undefined;\n\n /**\n * Time slice for MediaRecorder in milliseconds.\n *\n * Safari requires a larger timeslice (1000ms) to properly flush its internal\n * audio buffers. Smaller values cause Safari to drop or truncate audio data.\n * See: https://community.openai.com/t/whisper-problem-with-audio-mp4-blobs-from-safari/\n *\n * Other browsers (Chrome, Firefox, Edge) work well with smaller timeslices\n * which provide lower latency for real-time transcription.\n */\n private static readonly TIME_SLICE_MS = 100;\n private static readonly SAFARI_TIME_SLICE_MS = 1000;\n\n /**\n * @param onChunk - Callback for receiving audio chunks\n * @param deviceId - Optional audio device ID (empty string or undefined for system default)\n */\n constructor(onChunk: AudioChunkCallback, deviceId?: string) {\n this.onChunk = onChunk;\n this.audioFormat = getSupportedAudioFormat();\n this.deviceId = deviceId;\n }\n\n /**\n * Get the appropriate timeslice for the current browser.\n * Safari needs a larger timeslice to avoid dropping audio data.\n */\n private getTimeSlice(): number {\n return isSafari() ? AudioCapture.SAFARI_TIME_SLICE_MS : AudioCapture.TIME_SLICE_MS;\n }\n\n /**\n * Get the timeslice being used (in milliseconds).\n * Useful for callers that need to wait for audio processing.\n */\n getTimeSliceMs(): number {\n return this.getTimeSlice();\n }\n\n /**\n * Get the audio format being used.\n */\n getFormat(): AudioFormat {\n return this.audioFormat;\n }\n\n /**\n * Start capturing audio immediately.\n *\n * Audio chunks will be buffered until setReady() is called.\n */\n async start(): Promise<void> {\n const config = getConfig();\n\n if (this.isRecording) {\n if (config.debug) {\n console.log('[SpeechOS] AudioCapture already recording');\n }\n return;\n }\n\n // Reset state\n this.buffer = [];\n this.isReady = false;\n\n // Build constraints using deviceId from constructor\n const constraints: MediaStreamConstraints = {\n audio: {\n echoCancellation: true,\n noiseSuppression: true,\n ...(this.deviceId ? { deviceId: { exact: this.deviceId } } : {}),\n },\n };\n\n if (config.debug) {\n console.log('[SpeechOS] AudioCapture starting with format:', this.audioFormat.mimeType);\n console.log('[SpeechOS] Detected Safari:', isSafari());\n if (this.deviceId) {\n console.log('[SpeechOS] Using audio device:', this.deviceId);\n }\n }\n\n try {\n // Get microphone access\n this.mediaStream = await navigator.mediaDevices.getUserMedia(constraints);\n\n // Create MediaRecorder with detected format\n const recorderOptions: MediaRecorderOptions = {};\n if (this.audioFormat.mimeType) {\n recorderOptions.mimeType = this.audioFormat.mimeType;\n }\n\n this.recorder = new MediaRecorder(this.mediaStream, recorderOptions);\n\n // Handle audio data\n this.recorder.ondataavailable = (event) => {\n if (event.data && event.data.size > 0) {\n this.handleChunk(event.data);\n }\n };\n\n this.recorder.onerror = (event) => {\n console.error('[SpeechOS] MediaRecorder error:', event);\n };\n\n // Start recording with time slicing\n // Safari needs larger timeslice (1000ms) to avoid dropping audio\n const timeSlice = this.getTimeSlice();\n this.recorder.start(timeSlice);\n this.isRecording = true;\n\n if (config.debug) {\n console.log(`[SpeechOS] AudioCapture started with ${timeSlice}ms timeslice, buffering until ready`);\n }\n } catch (error) {\n // If specific device failed, try without device constraint\n if (this.deviceId && error instanceof Error) {\n console.warn('[SpeechOS] Selected device unavailable, trying default:', error.message);\n\n this.mediaStream = await navigator.mediaDevices.getUserMedia({\n audio: { echoCancellation: true, noiseSuppression: true },\n });\n\n const recorderOptions: MediaRecorderOptions = {};\n if (this.audioFormat.mimeType) {\n recorderOptions.mimeType = this.audioFormat.mimeType;\n }\n\n this.recorder = new MediaRecorder(this.mediaStream, recorderOptions);\n\n this.recorder.ondataavailable = (event) => {\n if (event.data && event.data.size > 0) {\n this.handleChunk(event.data);\n }\n };\n\n this.recorder.start(this.getTimeSlice());\n this.isRecording = true;\n } else {\n throw error;\n }\n }\n }\n\n /**\n * Handle an audio chunk with atomic buffer swap pattern.\n *\n * If not ready: buffer the chunk.\n * If ready: send directly via callback.\n */\n private handleChunk(chunk: Blob): void {\n if (this.isReady) {\n // Direct send mode\n this.onChunk(chunk);\n } else {\n // Buffer mode\n this.buffer.push(chunk);\n }\n }\n\n /**\n * Mark the capture as ready (connection established).\n *\n * This flushes any buffered chunks and switches to direct mode.\n * Uses atomic swap to prevent chunk reordering.\n */\n setReady(): void {\n const config = getConfig();\n\n if (this.isReady) {\n return;\n }\n\n // Atomic swap: grab buffer, clear it, then set ready flag\n const toFlush = this.buffer;\n this.buffer = [];\n\n // Flush all buffered chunks in order\n for (const chunk of toFlush) {\n this.onChunk(chunk);\n }\n\n // Now enable direct mode\n this.isReady = true;\n\n if (config.debug) {\n console.log(`[SpeechOS] AudioCapture ready, flushed ${toFlush.length} buffered chunks`);\n }\n }\n\n /**\n * Stop capturing audio and wait for final chunk.\n *\n * Uses requestData() before stop() to force the MediaRecorder to flush\n * any buffered audio immediately. This is critical for Safari which\n * may hold audio data in internal buffers.\n *\n * Safari requires an additional delay after stopping to ensure all audio\n * from its internal encoding pipeline has been fully processed and emitted.\n */\n async stop(): Promise<void> {\n const config = getConfig();\n const safari = isSafari();\n\n if (this.recorder && this.recorder.state !== 'inactive') {\n // Force flush any buffered audio before stopping\n // This is critical for Safari which may hold data in internal buffers\n if (this.recorder.state === 'recording') {\n try {\n // Set up promise to wait for the dataavailable event (event-driven)\n const dataPromise = new Promise<void>((resolve) => {\n const handler = (event: BlobEvent) => {\n this.recorder?.removeEventListener('dataavailable', handler);\n if (config.debug) {\n console.log(`[SpeechOS] requestData flush received: ${event.data.size} bytes`);\n }\n resolve();\n };\n this.recorder?.addEventListener('dataavailable', handler);\n });\n\n // requestData() forces an immediate dataavailable event with buffered data\n this.recorder.requestData();\n if (config.debug) {\n console.log('[SpeechOS] Requested data flush before stop');\n }\n\n // Wait for the actual dataavailable event\n await dataPromise;\n } catch (e) {\n // requestData() may not be supported on all browsers, continue anyway\n if (config.debug) {\n console.log('[SpeechOS] requestData() not supported or failed:', e);\n }\n }\n }\n\n // Create a promise that resolves when the recorder fully stops\n const stopPromise = new Promise<void>((resolve) => {\n if (!this.recorder) {\n resolve();\n return;\n }\n\n // onstop fires AFTER the final ondataavailable, so we wait for it\n this.recorder.onstop = () => {\n if (config.debug) {\n console.log('[SpeechOS] MediaRecorder onstop fired');\n }\n resolve();\n };\n });\n\n // Stop the recorder\n this.recorder.stop();\n\n // Wait for onstop event\n await stopPromise;\n\n // Safari needs extra time for its internal encoding pipeline to fully flush\n // Without this delay, the last portion of audio may be truncated\n if (safari) {\n if (config.debug) {\n console.log('[SpeechOS] Safari: waiting 2s for encoding pipeline to flush');\n }\n await new Promise((resolve) => setTimeout(resolve, 2000));\n }\n }\n\n // Now safe to clean up\n if (this.mediaStream) {\n for (const track of this.mediaStream.getTracks()) {\n track.stop();\n }\n this.mediaStream = null;\n }\n\n this.recorder = null;\n this.isRecording = false;\n this.isReady = false;\n this.buffer = [];\n\n if (config.debug) {\n console.log('[SpeechOS] AudioCapture stopped');\n }\n }\n\n /**\n * Check if currently recording.\n */\n get recording(): boolean {\n return this.isRecording;\n }\n\n /**\n * Check if ready (connection established, direct mode active).\n */\n get ready(): boolean {\n return this.isReady;\n }\n\n /**\n * Get the number of buffered chunks waiting to be sent.\n */\n get bufferedChunks(): number {\n return this.buffer.length;\n }\n}\n\n/**\n * Factory function to create an AudioCapture instance.\n * @param onChunk - Callback for receiving audio chunks\n * @param deviceId - Optional audio device ID (empty string or undefined for system default)\n */\nexport function createAudioCapture(onChunk: AudioChunkCallback, deviceId?: string): AudioCapture {\n return new AudioCapture(onChunk, deviceId);\n}\n","/**\n * WebSocket integration for SpeechOS SDK.\n *\n * Provides a direct WebSocket connection to the backend for voice sessions,\n * bypassing LiveKit for lower latency. Uses audio buffering to capture\n * audio immediately while the connection is being established.\n */\n\nimport type {\n CommandDefinition,\n CommandResult,\n ServerErrorMessage,\n ErrorSource,\n SpeechOSAction,\n VoiceSessionOptions,\n SessionSettings,\n} from './types.js';\nimport { getConfig, getAnonymousId } from './config.js';\nimport { events } from './events.js';\nimport { state } from './state.js';\nimport { AudioCapture, createAudioCapture, getSupportedAudioFormat } from './audio-capture.js';\n\n// Protocol message types (matching backend)\nconst MESSAGE_TYPE_AUTH = 'auth';\nconst MESSAGE_TYPE_READY = 'ready';\nconst MESSAGE_TYPE_TRANSCRIPTION = 'transcription';\nconst MESSAGE_TYPE_REQUEST_TRANSCRIPT = 'request_transcript';\nconst MESSAGE_TYPE_TRANSCRIPT = 'transcript';\nconst MESSAGE_TYPE_EDIT_TEXT = 'edit_text';\nconst MESSAGE_TYPE_EDITED_TEXT = 'edited_text';\nconst MESSAGE_TYPE_EXECUTE_COMMAND = 'execute_command';\nconst MESSAGE_TYPE_COMMAND_RESULT = 'command_result';\nconst MESSAGE_TYPE_ERROR = 'error';\n\n/**\n * Response timeout in milliseconds.\n */\nconst RESPONSE_TIMEOUT_MS = 15000;\n\n/**\n * A deferred promise with timeout support.\n */\nexport class Deferred<T> {\n readonly promise: Promise<T>;\n private _resolve!: (value: T) => void;\n private _reject!: (error: Error) => void;\n private _timeoutId: ReturnType<typeof setTimeout> | null = null;\n private _settled = false;\n\n constructor() {\n this.promise = new Promise<T>((resolve, reject) => {\n this._resolve = resolve;\n this._reject = reject;\n });\n }\n\n setTimeout(ms: number, errorMessage: string, errorCode: string, errorSource: ErrorSource): void {\n this._timeoutId = setTimeout(() => {\n if (!this._settled) {\n console.error(`[SpeechOS] Error: ${errorMessage} (${errorCode})`);\n events.emit('error', {\n code: errorCode,\n message: errorMessage,\n source: errorSource,\n });\n this.reject(new Error(errorMessage));\n }\n }, ms);\n }\n\n resolve(value: T): void {\n if (!this._settled) {\n this._settled = true;\n this.clearTimeout();\n this._resolve(value);\n }\n }\n\n reject(error: Error): void {\n if (!this._settled) {\n this._settled = true;\n this.clearTimeout();\n this._reject(error);\n }\n }\n\n private clearTimeout(): void {\n if (this._timeoutId !== null) {\n clearTimeout(this._timeoutId);\n this._timeoutId = null;\n }\n }\n\n get isSettled(): boolean {\n return this._settled;\n }\n}\n\n/**\n * Maximum time to wait for WebSocket buffer to drain.\n */\nconst BUFFER_DRAIN_TIMEOUT_MS = 5000;\n\n/**\n * Polling interval for checking WebSocket buffer.\n */\nconst BUFFER_CHECK_INTERVAL_MS = 50;\n\n/**\n * WebSocket connection manager for voice sessions.\n */\nclass WebSocketManager {\n private ws: WebSocket | null = null;\n private audioCapture: AudioCapture | null = null;\n private sessionId: string | null = null;\n\n // Pending operations\n private pendingAuth: Deferred<void> | null = null;\n private pendingTranscript: Deferred<string> | null = null;\n private pendingEditText: Deferred<string> | null = null;\n private pendingCommand: Deferred<CommandResult | null> | null = null;\n\n // Track pending audio chunk sends (for waiting before transcript request)\n private pendingAudioSends: Set<Promise<void>> = new Set();\n\n // Track original text for edit operations\n private editOriginalText: string | null = null;\n\n // Track the last input text from command results\n private lastInputText: string | undefined = undefined;\n\n // Session parameters (set at start, used in auth message)\n private sessionAction: SpeechOSAction = 'dictate';\n private sessionInputText: string = '';\n private sessionCommands: CommandDefinition[] = [];\n private sessionSettings: SessionSettings = {};\n\n /**\n * Get the WebSocket URL for voice sessions.\n */\n private getWebSocketUrl(): string {\n const config = getConfig();\n const host = config.host || 'https://app.speechos.ai';\n\n // Convert HTTP(S) to WS(S)\n const wsUrl = host.replace(/^http/, 'ws');\n\n return `${wsUrl}/ws/voice/`;\n }\n\n /**\n * Start a voice session with the WebSocket backend.\n *\n * This method:\n * 1. Starts audio capture immediately (buffering)\n * 2. Opens WebSocket connection\n * 3. Authenticates with API key and action parameters\n * 4. Flushes buffered audio and continues streaming\n *\n * @param options - Session options including action type and parameters\n */\n async startVoiceSession(options?: VoiceSessionOptions): Promise<void> {\n const config = getConfig();\n\n // Store session parameters for auth message\n this.sessionAction = options?.action || 'dictate';\n this.sessionInputText = options?.inputText || '';\n this.sessionCommands = options?.commands || [];\n this.sessionSettings = options?.settings || {};\n\n // Also store for event emission\n if (this.sessionAction === 'edit') {\n this.editOriginalText = this.sessionInputText;\n }\n\n if (config.debug) {\n console.log('[SpeechOS] Starting WebSocket voice session...');\n }\n\n // Create audio capture that buffers until ready\n this.audioCapture = createAudioCapture(\n (chunk) => {\n this.sendAudioChunk(chunk);\n },\n this.sessionSettings.audioDeviceId\n );\n\n // Start capturing immediately (will buffer)\n await this.audioCapture.start();\n\n // Notify that mic is ready (recording to buffer)\n if (options?.onMicReady) {\n options.onMicReady();\n }\n\n state.setMicEnabled(true);\n\n // Connect to WebSocket\n const wsUrl = this.getWebSocketUrl();\n\n if (config.debug) {\n console.log('[SpeechOS] Connecting to WebSocket:', wsUrl);\n }\n\n this.ws = new WebSocket(wsUrl);\n\n // Set up event handlers\n this.ws.onopen = () => {\n if (config.debug) {\n console.log('[SpeechOS] WebSocket connected, authenticating...');\n }\n this.authenticate();\n };\n\n this.ws.onmessage = (event) => {\n this.handleMessage(event.data);\n };\n\n this.ws.onerror = (event) => {\n console.error('[SpeechOS] WebSocket error:', event);\n events.emit('error', {\n code: 'websocket_error',\n message: 'WebSocket connection error',\n source: 'connection',\n });\n };\n\n this.ws.onclose = (event) => {\n if (config.debug) {\n console.log('[SpeechOS] WebSocket closed:', event.code, event.reason);\n }\n state.setConnected(false);\n };\n\n // Wait for authentication\n this.pendingAuth = new Deferred<void>();\n this.pendingAuth.setTimeout(\n RESPONSE_TIMEOUT_MS,\n 'Connection timed out',\n 'connection_timeout',\n 'connection'\n );\n\n await this.pendingAuth.promise;\n this.pendingAuth = null;\n\n // Now ready - flush buffered audio\n if (this.audioCapture) {\n this.audioCapture.setReady();\n }\n\n state.setConnected(true);\n\n if (config.debug) {\n console.log('[SpeechOS] WebSocket voice session ready');\n }\n }\n\n /**\n * Send authentication message with action parameters.\n * All session parameters are now sent upfront in the auth message.\n */\n private authenticate(): void {\n const config = getConfig();\n const audioFormat = getSupportedAudioFormat();\n const settings = this.sessionSettings;\n const anonymousId = getAnonymousId();\n\n const authMessage = {\n type: MESSAGE_TYPE_AUTH,\n api_key: config.apiKey,\n user_id: config.userId || null,\n anonymous_id: anonymousId,\n input_language: settings.inputLanguageCode ?? 'en-US',\n output_language: settings.outputLanguageCode ?? 'en-US',\n smart_format: settings.smartFormat ?? true,\n custom_vocabulary: settings.vocabulary ?? [],\n custom_snippets: settings.snippets ?? [],\n audio_format: audioFormat.format,\n // Action parameters (sent upfront in auth)\n action: this.sessionAction,\n input_text: this.sessionInputText,\n commands: this.sessionCommands,\n };\n\n if (config.debug) {\n console.log('[SpeechOS] Sending auth message with action:', this.sessionAction);\n }\n\n this.ws?.send(JSON.stringify(authMessage));\n }\n\n /**\n * Send an audio chunk over the WebSocket.\n * Tracks the promise so we can wait for all sends to complete.\n */\n private sendAudioChunk(chunk: Blob): void {\n const sendPromise = this.doSendAudioChunk(chunk);\n this.pendingAudioSends.add(sendPromise);\n sendPromise.finally(() => {\n this.pendingAudioSends.delete(sendPromise);\n });\n }\n\n /**\n * Actually send the audio chunk (async operation).\n */\n private async doSendAudioChunk(chunk: Blob): Promise<void> {\n if (this.ws && this.ws.readyState === WebSocket.OPEN) {\n const arrayBuffer = await chunk.arrayBuffer();\n this.ws.send(arrayBuffer);\n }\n }\n\n /**\n * Handle incoming WebSocket messages.\n */\n private handleMessage(data: string): void {\n const config = getConfig();\n\n try {\n const message = JSON.parse(data);\n\n if (config.debug) {\n console.log('[SpeechOS] WebSocket message:', message);\n }\n\n switch (message.type) {\n case MESSAGE_TYPE_READY:\n this.handleReady(message);\n break;\n\n case MESSAGE_TYPE_TRANSCRIPTION:\n this.handleIntermediateTranscription(message);\n break;\n\n case MESSAGE_TYPE_TRANSCRIPT:\n this.handleFinalTranscript(message);\n break;\n\n case MESSAGE_TYPE_EDITED_TEXT:\n this.handleEditedText(message);\n break;\n\n case MESSAGE_TYPE_COMMAND_RESULT:\n this.handleCommandResult(message);\n break;\n\n case MESSAGE_TYPE_ERROR:\n this.handleError(message);\n break;\n\n default:\n if (config.debug) {\n console.log('[SpeechOS] Unknown message type:', message.type);\n }\n }\n } catch (error) {\n console.error('[SpeechOS] Failed to parse message:', error);\n }\n }\n\n private handleReady(message: { session_id: string }): void {\n const config = getConfig();\n\n this.sessionId = message.session_id;\n\n if (config.debug) {\n console.log('[SpeechOS] Session ready:', this.sessionId);\n }\n\n // Resolve auth promise\n if (this.pendingAuth) {\n this.pendingAuth.resolve();\n }\n }\n\n private handleIntermediateTranscription(message: {\n transcript: string;\n is_final: boolean;\n }): void {\n // Intermediate transcriptions for UI feedback\n // Currently not exposed to the public API, but could be used for live preview\n const config = getConfig();\n\n if (config.debug) {\n console.log(\n '[SpeechOS] Intermediate transcription:',\n message.transcript,\n 'final:',\n message.is_final\n );\n }\n }\n\n private handleFinalTranscript(message: { transcript: string }): void {\n const transcript = message.transcript || '';\n\n // Emit transcription:complete event\n events.emit('transcription:complete', { text: transcript });\n\n // Resolve pending promise\n if (this.pendingTranscript) {\n this.pendingTranscript.resolve(transcript);\n this.pendingTranscript = null;\n }\n }\n\n private handleEditedText(message: { text: string }): void {\n const editedText = message.text || '';\n\n // Emit edit:complete event\n events.emit('edit:complete', {\n text: editedText,\n originalText: this.editOriginalText || '',\n });\n\n // Resolve pending promise\n if (this.pendingEditText) {\n this.pendingEditText.resolve(editedText);\n this.pendingEditText = null;\n }\n\n this.editOriginalText = null;\n }\n\n private handleCommandResult(message: { command: CommandResult | null; transcript?: string }): void {\n const commandResult = message.command || null;\n\n // Store the input text (what the user said) if provided by the backend\n this.lastInputText = message.transcript;\n\n // Emit command:complete event\n events.emit('command:complete', { command: commandResult });\n\n // Resolve pending promise\n if (this.pendingCommand) {\n this.pendingCommand.resolve(commandResult);\n this.pendingCommand = null;\n }\n }\n\n private handleError(message: ServerErrorMessage): void {\n const errorCode = message.code || 'server_error';\n const errorMessage = message.message || 'A server error occurred';\n\n console.error(`[SpeechOS] Error: ${errorMessage} (${errorCode})`);\n\n events.emit('error', {\n code: errorCode,\n message: errorMessage,\n source: 'server',\n });\n\n // Reject any pending operations\n const error = new Error(errorMessage);\n if (this.pendingAuth) {\n this.pendingAuth.reject(error);\n this.pendingAuth = null;\n }\n if (this.pendingTranscript) {\n this.pendingTranscript.reject(error);\n this.pendingTranscript = null;\n }\n if (this.pendingEditText) {\n this.pendingEditText.reject(error);\n this.pendingEditText = null;\n }\n if (this.pendingCommand) {\n this.pendingCommand.reject(error);\n this.pendingCommand = null;\n }\n }\n\n /**\n * Stop the voice session and request the transcript.\n */\n async stopVoiceSession(): Promise<string> {\n const config = getConfig();\n\n if (config.debug) {\n console.log('[SpeechOS] Stopping voice session, requesting transcript...');\n }\n\n // Stop audio capture and wait for final chunk to be sent\n await this.stopAudioCapture();\n\n // Create deferred for transcript\n this.pendingTranscript = new Deferred<string>();\n this.pendingTranscript.setTimeout(\n RESPONSE_TIMEOUT_MS,\n 'Transcription timed out. Please try again.',\n 'transcription_timeout',\n 'timeout'\n );\n\n // Request transcript\n this.sendMessage({ type: MESSAGE_TYPE_REQUEST_TRANSCRIPT });\n\n const result = await this.pendingTranscript.promise;\n this.pendingTranscript = null;\n\n return result;\n }\n\n /**\n * Request text editing using the transcript as instructions.\n * Note: The input text was already sent in the auth message via startVoiceSession.\n */\n async requestEditText(_originalText: string): Promise<string> {\n const config = getConfig();\n\n if (config.debug) {\n console.log('[SpeechOS] Requesting text edit...');\n }\n\n // Stop audio capture and wait for final chunk\n await this.stopAudioCapture();\n\n // Create deferred for edited text\n this.pendingEditText = new Deferred<string>();\n this.pendingEditText.setTimeout(\n RESPONSE_TIMEOUT_MS,\n 'Edit request timed out. Please try again.',\n 'edit_timeout',\n 'timeout'\n );\n\n // Send edit request (params already sent in auth message)\n this.sendMessage({\n type: MESSAGE_TYPE_EDIT_TEXT,\n });\n\n const result = await this.pendingEditText.promise;\n this.pendingEditText = null;\n\n return result;\n }\n\n /**\n * Request command matching using the transcript as input.\n * Note: The command definitions were already sent in the auth message via startVoiceSession.\n */\n async requestCommand(_commands: CommandDefinition[]): Promise<CommandResult | null> {\n const config = getConfig();\n\n if (config.debug) {\n console.log('[SpeechOS] Requesting command match...');\n }\n\n // Stop audio capture and wait for final chunk\n await this.stopAudioCapture();\n\n // Create deferred for command result\n this.pendingCommand = new Deferred<CommandResult | null>();\n this.pendingCommand.setTimeout(\n RESPONSE_TIMEOUT_MS,\n 'Command request timed out. Please try again.',\n 'command_timeout',\n 'timeout'\n );\n\n // Send command request (params already sent in auth message)\n this.sendMessage({\n type: MESSAGE_TYPE_EXECUTE_COMMAND,\n });\n\n const result = await this.pendingCommand.promise;\n this.pendingCommand = null;\n\n return result;\n }\n\n /**\n * Stop audio capture and wait for all data to be sent.\n *\n * Waits for:\n * 1. All pending sendAudioChunk calls to complete (arrayBuffer conversion)\n * 2. WebSocket buffer to drain (all data transmitted)\n *\n * WebSocket message ordering ensures server receives all audio before transcript request.\n */\n private async stopAudioCapture(): Promise<void> {\n const config = getConfig();\n const startTime = Date.now();\n\n if (config.debug) {\n console.log('[SpeechOS] stopAudioCapture: starting...');\n }\n\n if (this.audioCapture) {\n await this.audioCapture.stop();\n this.audioCapture = null;\n if (config.debug) {\n console.log(`[SpeechOS] stopAudioCapture: recorder stopped after ${Date.now() - startTime}ms`);\n }\n }\n state.setMicEnabled(false);\n\n // Wait for all pending audio chunk sends to complete\n // This ensures all arrayBuffer() conversions finish and data is queued to WebSocket\n if (this.pendingAudioSends.size > 0) {\n if (config.debug) {\n console.log(`[SpeechOS] stopAudioCapture: waiting for ${this.pendingAudioSends.size} pending audio sends...`);\n }\n await Promise.all(this.pendingAudioSends);\n if (config.debug) {\n console.log(`[SpeechOS] stopAudioCapture: all sends complete after ${Date.now() - startTime}ms`);\n }\n } else if (config.debug) {\n console.log('[SpeechOS] stopAudioCapture: no pending sends');\n }\n\n // Wait for WebSocket buffer to drain (all audio data sent)\n await this.waitForBufferDrain();\n\n if (config.debug) {\n console.log(`[SpeechOS] stopAudioCapture: complete after ${Date.now() - startTime}ms`);\n }\n }\n\n /**\n * Wait for the WebSocket send buffer to drain.\n *\n * This ensures all audio data has been transmitted before we request\n * the transcript. Uses the same pattern as LiveKit's ReadableStream approach.\n */\n private async waitForBufferDrain(): Promise<void> {\n if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {\n return;\n }\n\n const config = getConfig();\n const startTime = Date.now();\n\n while (this.ws.bufferedAmount > 0) {\n if (Date.now() - startTime > BUFFER_DRAIN_TIMEOUT_MS) {\n console.warn(\n `[SpeechOS] Buffer drain timeout, ${this.ws.bufferedAmount} bytes still pending`\n );\n break;\n }\n await new Promise((resolve) => setTimeout(resolve, BUFFER_CHECK_INTERVAL_MS));\n }\n\n if (config.debug) {\n console.log(`[SpeechOS] Buffer drained in ${Date.now() - startTime}ms`);\n }\n }\n\n /**\n * Send a JSON message over the WebSocket.\n */\n private sendMessage(message: object): void {\n if (this.ws && this.ws.readyState === WebSocket.OPEN) {\n this.ws.send(JSON.stringify(message));\n }\n }\n\n /**\n * Disconnect from the WebSocket.\n */\n async disconnect(): Promise<void> {\n const config = getConfig();\n\n if (config.debug) {\n console.log('[SpeechOS] Disconnecting WebSocket...');\n }\n\n // Stop audio capture (don't wait for final chunk on disconnect)\n await this.stopAudioCapture();\n\n // Close WebSocket\n if (this.ws) {\n this.ws.close();\n this.ws = null;\n }\n\n // Reject any pending operations\n const error = new Error('Disconnected');\n if (this.pendingAuth) {\n this.pendingAuth.reject(error);\n this.pendingAuth = null;\n }\n if (this.pendingTranscript) {\n this.pendingTranscript.reject(error);\n this.pendingTranscript = null;\n }\n if (this.pendingEditText) {\n this.pendingEditText.reject(error);\n this.pendingEditText = null;\n }\n if (this.pendingCommand) {\n this.pendingCommand.reject(error);\n this.pendingCommand = null;\n }\n\n // Reset state\n this.sessionId = null;\n this.editOriginalText = null;\n this.lastInputText = undefined;\n this.sessionSettings = {};\n\n state.setConnected(false);\n state.setMicEnabled(false);\n\n if (config.debug) {\n console.log('[SpeechOS] WebSocket disconnected');\n }\n }\n\n /**\n * Check if connected to WebSocket.\n */\n isConnected(): boolean {\n return this.ws !== null && this.ws.readyState === WebSocket.OPEN;\n }\n\n /**\n * Get the last input text from a command result.\n * This is the raw transcript of what the user said.\n */\n getLastInputText(): string | undefined {\n return this.lastInputText;\n }\n}\n\n// Export singleton instance\nexport const websocket: WebSocketManager = new WebSocketManager();\n","/**\n * SpeechOS Core SDK\n *\n * Provides both low-level and high-level APIs for voice interaction.\n * This is the main entry point for headless usage of SpeechOS.\n */\n\nimport type {\n SpeechOSCoreConfig,\n CommandDefinition,\n CommandResult,\n VoiceSessionOptions,\n} from \"./types.js\";\nimport { setConfig, getConfig, resetConfig } from \"./config.js\";\nimport { livekit } from \"./livekit.js\";\nimport { websocket } from \"./websocket.js\";\nimport { state } from \"./state.js\";\nimport { events } from \"./events.js\";\n\n/**\n * Voice backend interface - common methods between LiveKit and WebSocket backends\n */\ninterface VoiceBackendInterface {\n startVoiceSession(options?: VoiceSessionOptions): Promise<void>;\n stopVoiceSession(): Promise<string>;\n requestEditText(originalText: string): Promise<string>;\n requestCommand(commands: CommandDefinition[]): Promise<CommandResult | null>;\n disconnect(): Promise<void>;\n}\n\n/**\n * Get the active voice backend (always websocket now)\n */\nfunction getBackend(): VoiceBackendInterface {\n // Always use websocket backend (livekit is legacy)\n return websocket;\n}\n\n/**\n * SpeechOS Core SDK\n *\n * Provides two API layers:\n * 1. Low-level API: Granular control over LiveKit connection lifecycle\n * 2. High-level API: One-shot methods for common voice tasks\n */\nclass SpeechOSCore {\n private initialized = false;\n\n /**\n * Initialize the SDK with configuration\n * @param config - Configuration options including apiKey\n */\n init(config: SpeechOSCoreConfig): void {\n setConfig(config);\n this.initialized = true;\n\n const currentConfig = getConfig();\n if (currentConfig.debug) {\n console.log(\"[SpeechOS] Initialized with config:\", {\n host: currentConfig.host,\n debug: currentConfig.debug,\n });\n }\n }\n\n /**\n * Check if the SDK is initialized\n */\n isInitialized(): boolean {\n return this.initialized;\n }\n\n // ============================================\n // Low-level API (granular control)\n // ============================================\n\n /**\n * Connect to LiveKit (fetches token, establishes connection)\n * Call this before other low-level methods\n */\n async connect(): Promise<void> {\n this.ensureInitialized();\n await livekit.connect();\n }\n\n /**\n * Wait until the agent is ready to receive audio\n * Resolves when the agent subscribes to our audio track\n */\n async waitUntilReady(): Promise<void> {\n return livekit.waitUntilReady();\n }\n\n /**\n * Enable microphone (user is now being recorded)\n */\n async enableMicrophone(): Promise<void> {\n await livekit.enableMicrophone();\n state.setRecordingState(\"recording\");\n }\n\n /**\n * Stop recording and get the transcript\n * @returns The transcribed text\n */\n async stopAndGetTranscript(): Promise<string> {\n state.setRecordingState(\"processing\");\n try {\n const transcript = await livekit.stopAndGetTranscript();\n state.completeRecording();\n return transcript;\n } catch (error) {\n state.setError(\n error instanceof Error ? error.message : \"Transcription failed\"\n );\n throw error;\n }\n }\n\n /**\n * Stop recording and get edited text\n * @param originalText - The original text to edit based on voice instructions\n * @returns The edited text\n */\n async stopAndEdit(originalText: string): Promise<string> {\n state.setRecordingState(\"processing\");\n try {\n const editedText = await livekit.stopAndEdit(originalText);\n state.completeRecording();\n return editedText;\n } catch (error) {\n state.setError(\n error instanceof Error ? error.message : \"Edit request failed\"\n );\n throw error;\n }\n }\n\n /**\n * Disconnect from LiveKit\n */\n async disconnect(): Promise<void> {\n await livekit.disconnect();\n state.completeRecording();\n }\n\n // ============================================\n // High-level API (convenience methods)\n // ============================================\n\n /**\n * One-shot dictation: connect, wait for agent, record, and get transcript\n * Automatically handles the full voice session lifecycle\n *\n * @returns The transcribed text\n */\n async dictate(): Promise<string> {\n this.ensureInitialized();\n\n state.setActiveAction(\"dictate\");\n state.startRecording();\n\n try {\n const backend = getBackend();\n\n // Start voice session with action=dictate\n await backend.startVoiceSession({\n action: \"dictate\",\n onMicReady: () => {\n // Transition to recording state as soon as mic is capturing\n state.setRecordingState(\"recording\");\n },\n });\n\n // User is now being recorded...\n // The caller should call stopDictation() when done\n // Or they can just await this if they want to handle it themselves\n\n // For this high-level API, we return immediately after setup\n // The UI should handle when to stop\n return new Promise<string>((resolve, reject) => {\n // Store resolvers for stopDictation to use\n this._dictateResolve = resolve;\n this._dictateReject = reject;\n });\n } catch (error) {\n state.setError(\n error instanceof Error ? error.message : \"Failed to start dictation\"\n );\n await this.cleanup();\n throw error;\n }\n }\n\n private _dictateResolve?: (transcript: string) => void;\n private _dictateReject?: (error: Error) => void;\n\n /**\n * Stop dictation and get the transcript\n * Call this after dictate() when user stops speaking\n */\n async stopDictation(): Promise<string> {\n state.setRecordingState(\"processing\");\n\n try {\n const backend = getBackend();\n const transcript = await backend.stopVoiceSession();\n\n state.completeRecording();\n\n // Resolve the dictate promise if it exists\n if (this._dictateResolve) {\n this._dictateResolve(transcript);\n this._dictateResolve = undefined;\n this._dictateReject = undefined;\n }\n\n return transcript;\n } catch (error) {\n const err =\n error instanceof Error ? error : new Error(\"Transcription failed\");\n state.setError(err.message);\n\n // Reject the dictate promise if it exists\n if (this._dictateReject) {\n this._dictateReject(err);\n this._dictateResolve = undefined;\n this._dictateReject = undefined;\n }\n\n throw err;\n } finally {\n await this.cleanup();\n }\n }\n\n /**\n * One-shot edit: connect, wait for agent, record voice instructions, apply to text\n * Automatically handles the full voice session lifecycle\n *\n * @param originalText - The text to edit\n * @returns The edited text\n */\n async edit(originalText: string): Promise<string> {\n this.ensureInitialized();\n\n state.setActiveAction(\"edit\");\n state.startRecording();\n this._editOriginalText = originalText;\n\n try {\n const backend = getBackend();\n\n // Start voice session with action=edit and inputText\n await backend.startVoiceSession({\n action: \"edit\",\n inputText: originalText,\n onMicReady: () => {\n // Transition to recording state as soon as mic is capturing\n state.setRecordingState(\"recording\");\n },\n });\n\n // Return a promise that will be resolved when stopEdit is called\n return new Promise<string>((resolve, reject) => {\n this._editResolve = resolve;\n this._editReject = reject;\n });\n } catch (error) {\n state.setError(\n error instanceof Error ? error.message : \"Failed to start edit\"\n );\n await this.cleanup();\n throw error;\n }\n }\n\n private _editOriginalText?: string;\n private _editResolve?: (editedText: string) => void;\n private _editReject?: (error: Error) => void;\n\n /**\n * Stop edit recording and get the edited text\n * Call this after edit() when user stops speaking\n */\n async stopEdit(): Promise<string> {\n state.setRecordingState(\"processing\");\n\n try {\n const backend = getBackend();\n const originalText = this._editOriginalText || \"\";\n const editedText = await backend.requestEditText(originalText);\n\n state.completeRecording();\n\n // Resolve the edit promise if it exists\n if (this._editResolve) {\n this._editResolve(editedText);\n this._editResolve = undefined;\n this._editReject = undefined;\n }\n\n return editedText;\n } catch (error) {\n const err =\n error instanceof Error ? error : new Error(\"Edit request failed\");\n state.setError(err.message);\n\n // Reject the edit promise if it exists\n if (this._editReject) {\n this._editReject(err);\n this._editResolve = undefined;\n this._editReject = undefined;\n }\n\n throw err;\n } finally {\n this._editOriginalText = undefined;\n await this.cleanup();\n }\n }\n\n /**\n * One-shot command: connect, wait for agent, record voice, match against commands\n * Automatically handles the full voice session lifecycle\n *\n * @param commands - Array of command definitions to match against\n * @returns The matched command result or null if no match\n */\n async command(commands: CommandDefinition[]): Promise<CommandResult | null> {\n this.ensureInitialized();\n\n state.setActiveAction(\"command\");\n state.startRecording();\n this._commandCommands = commands;\n\n try {\n const backend = getBackend();\n\n // Start voice session with action=command and command definitions\n await backend.startVoiceSession({\n action: \"command\",\n commands: commands,\n onMicReady: () => {\n // Transition to recording state as soon as mic is capturing\n state.setRecordingState(\"recording\");\n },\n });\n\n // Return a promise that will be resolved when stopCommand is called\n return new Promise<CommandResult | null>((resolve, reject) => {\n this._commandResolve = resolve;\n this._commandReject = reject;\n });\n } catch (error) {\n state.setError(\n error instanceof Error ? error.message : \"Failed to start command\"\n );\n await this.cleanup();\n throw error;\n }\n }\n\n private _commandCommands?: CommandDefinition[];\n private _commandResolve?: (result: CommandResult | null) => void;\n private _commandReject?: (error: Error) => void;\n\n /**\n * Stop command recording and get the matched command\n * Call this after command() when user stops speaking\n */\n async stopCommand(): Promise<CommandResult | null> {\n state.setRecordingState(\"processing\");\n\n try {\n const backend = getBackend();\n const commands = this._commandCommands || [];\n const result = await backend.requestCommand(commands);\n\n state.completeRecording();\n\n // Resolve the command promise if it exists\n if (this._commandResolve) {\n this._commandResolve(result);\n this._commandResolve = undefined;\n this._commandReject = undefined;\n }\n\n return result;\n } catch (error) {\n const err =\n error instanceof Error ? error : new Error(\"Command request failed\");\n state.setError(err.message);\n\n // Reject the command promise if it exists\n if (this._commandReject) {\n this._commandReject(err);\n this._commandResolve = undefined;\n this._commandReject = undefined;\n }\n\n throw err;\n } finally {\n this._commandCommands = undefined;\n await this.cleanup();\n }\n }\n\n /**\n * Cancel the current operation\n */\n async cancel(): Promise<void> {\n const err = new Error(\"Operation cancelled\");\n\n if (this._dictateReject) {\n this._dictateReject(err);\n this._dictateResolve = undefined;\n this._dictateReject = undefined;\n }\n\n if (this._editReject) {\n this._editReject(err);\n this._editResolve = undefined;\n this._editReject = undefined;\n }\n\n if (this._commandReject) {\n this._commandReject(err);\n this._commandResolve = undefined;\n this._commandReject = undefined;\n }\n\n this._editOriginalText = undefined;\n this._commandCommands = undefined;\n\n await this.cleanup();\n state.cancelRecording();\n }\n\n // ============================================\n // State and Events access\n // ============================================\n\n /**\n * Access the state manager for subscribing to state changes\n */\n get state(): typeof state {\n return state;\n }\n\n /**\n * Access the event emitter for listening to events\n */\n get events(): typeof events {\n return events;\n }\n\n /**\n * Get the current config\n */\n getConfig(): SpeechOSCoreConfig {\n return getConfig();\n }\n\n // ============================================\n // Private helpers\n // ============================================\n\n private ensureInitialized(): void {\n if (!this.initialized) {\n throw new Error(\n \"SpeechOS not initialized. Call speechOS.init({ apiKey: ... }) first.\"\n );\n }\n }\n\n private async cleanup(): Promise<void> {\n try {\n const backend = getBackend();\n await backend.disconnect();\n } catch (error) {\n // Ignore disconnect errors during cleanup\n const config = getConfig();\n if (config.debug) {\n console.warn(\"[SpeechOS] Cleanup disconnect error:\", error);\n }\n }\n }\n\n /**\n * Reset the SDK (useful for testing)\n */\n reset(): void {\n this.initialized = false;\n this._dictateResolve = undefined;\n this._dictateReject = undefined;\n this._editResolve = undefined;\n this._editReject = undefined;\n this._editOriginalText = undefined;\n this._commandResolve = undefined;\n this._commandReject = undefined;\n this._commandCommands = undefined;\n resetConfig();\n state.reset();\n events.clear();\n }\n}\n\n// Export singleton instance\nexport const speechOS: SpeechOSCore = new SpeechOSCore();\n","/**\n * Backend abstraction for voice sessions.\n *\n * Provides a unified interface for voice backends.\n * Currently always uses WebSocket backend.\n */\n\nimport type { CommandDefinition, CommandResult, VoiceSessionOptions } from './types.js';\nimport { livekit } from './livekit.js';\nimport { websocket } from './websocket.js';\n\n/**\n * Voice backend interface - common methods between backends\n */\nexport interface VoiceBackend {\n startVoiceSession(options?: VoiceSessionOptions): Promise<void>;\n stopVoiceSession(): Promise<string>;\n requestEditText(originalText: string): Promise<string>;\n requestCommand(commands: CommandDefinition[]): Promise<CommandResult | null>;\n disconnect(): Promise<void>;\n isConnected(): boolean;\n\n /** Get the last input text (transcript) from a command result */\n getLastInputText?(): string | undefined;\n\n // LiveKit-specific methods (available on both, but may be no-ops on websocket)\n prefetchToken?(): Promise<unknown>;\n startAutoRefresh?(): void;\n stopAutoRefresh?(): void;\n invalidateTokenCache?(): void;\n}\n\n/**\n * LiveKit backend adapter - wraps the livekit module to match the VoiceBackend interface\n * @internal Legacy backend, kept for potential future use\n */\nconst livekitBackend: VoiceBackend = {\n startVoiceSession: (options) => livekit.startVoiceSession(options),\n stopVoiceSession: () => livekit.stopVoiceSession(),\n requestEditText: (text) => livekit.requestEditText(text),\n requestCommand: (commands) => livekit.requestCommand(commands),\n disconnect: () => livekit.disconnect(),\n isConnected: () => livekit.isConnected(),\n prefetchToken: () => livekit.prefetchToken(),\n startAutoRefresh: () => livekit.startAutoRefresh(),\n stopAutoRefresh: () => livekit.stopAutoRefresh(),\n invalidateTokenCache: () => livekit.invalidateTokenCache(),\n};\n\n/**\n * WebSocket backend adapter - wraps the websocket module to match the VoiceBackend interface\n */\nconst websocketBackend: VoiceBackend = {\n startVoiceSession: (options) => websocket.startVoiceSession(options),\n stopVoiceSession: () => websocket.stopVoiceSession(),\n requestEditText: (text) => websocket.requestEditText(text),\n requestCommand: (commands) => websocket.requestCommand(commands),\n disconnect: () => websocket.disconnect(),\n isConnected: () => websocket.isConnected(),\n getLastInputText: () => websocket.getLastInputText(),\n // No-op methods for LiveKit-specific features\n prefetchToken: () => Promise.resolve({}),\n startAutoRefresh: () => {},\n stopAutoRefresh: () => {},\n invalidateTokenCache: () => {},\n};\n\n/**\n * Get the active voice backend.\n * Always returns WebSocket backend (LiveKit is legacy).\n *\n * @returns The websocket backend\n */\nexport function getBackend(): VoiceBackend {\n return websocketBackend;\n}\n\n/**\n * Check if the current backend is LiveKit.\n * @deprecated Always returns false - LiveKit is legacy\n */\nexport function isLiveKitBackend(): boolean {\n return false;\n}\n\n/**\n * Check if the current backend is WebSocket.\n * @deprecated Always returns true - WebSocket is the only backend\n */\nexport function isWebSocketBackend(): boolean {\n return true;\n}\n\n// Keep livekitBackend reference to prevent unused variable warning\nvoid livekitBackend;\n","/**\n * @speechos/core\n *\n * Headless core SDK for SpeechOS - state management, events, and backend integration.\n * No DOM dependencies - can be used in any JavaScript environment.\n */\n\n// Main SDK class\nexport { speechOS } from \"./speechos.js\";\n\n// Core modules\nexport { events, SpeechOSEventEmitter } from \"./events.js\";\nexport { state, createStateManager } from \"./state.js\";\nexport {\n getConfig,\n setConfig,\n resetConfig,\n updateUserId,\n validateConfig,\n DEFAULT_HOST,\n} from \"./config.js\";\nexport { livekit, Deferred } from \"./livekit.js\";\nexport { websocket } from \"./websocket.js\";\nexport { getBackend } from \"./backend.js\";\nexport type { VoiceBackend } from \"./backend.js\";\n\n// Types\nexport type {\n SpeechOSCoreConfig,\n SpeechOSState,\n SpeechOSAction,\n SpeechOSEventMap,\n StateChangeCallback,\n UnsubscribeFn,\n RecordingState,\n LiveKitTokenResponse,\n ServerErrorMessage,\n ErrorSource,\n UserVocabularyData,\n CommandArgument,\n CommandDefinition,\n CommandResult,\n SessionSettings,\n VoiceSessionOptions,\n} from \"./types.js\";\n\n// Version\nexport const VERSION = \"0.1.0\";\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.cjs","names":["DEFAULT_HOST: string","defaultConfig: ResolvedConfig","userConfig: SpeechOSCoreConfig","currentConfig: ResolvedConfig","config: SpeechOSCoreConfig","userId: string","event: K","callback: EventCallback<K>","payload: SpeechOSEventMap[K]","event?: keyof SpeechOSEventMap","event: keyof SpeechOSEventMap","events: SpeechOSEventEmitter","initialState: SpeechOSState","initialState","partial: Partial<SpeechOSState>","callback: StateChangeCallback","element: HTMLElement | null","action: SpeechOSState[\"activeAction\"]","recordingState: SpeechOSState[\"recordingState\"]","isConnected: boolean","isMicEnabled: boolean","message: string","state: StateManager","initial?: Partial<SpeechOSState>","MESSAGE_TYPE_REQUEST_TRANSCRIPT","MESSAGE_TYPE_TRANSCRIPT","MESSAGE_TYPE_EDIT_TEXT","MESSAGE_TYPE_EDITED_TEXT","MESSAGE_TYPE_EXECUTE_COMMAND","MESSAGE_TYPE_COMMAND_RESULT","MESSAGE_TYPE_ERROR","ms: number","errorMessage: string","errorCode: string","errorSource: ErrorSource","value: T","error: Error","data","Room","RoomEvent","data: Uint8Array","participant?: RemoteParticipant","_participant?: RemoteParticipant","commandResult: CommandResult | null","trackOptions: Parameters<typeof createLocalAudioTrack>[0]","Track","message: object","options?: VoiceSessionOptions","constraints: MediaTrackConstraints","originalText: string","commands: CommandDefinition[]","timeUntilRefresh: number","livekit: LiveKitManager","onChunk: AudioChunkCallback","deviceId?: string","constraints: MediaStreamConstraints","recorderOptions: MediaRecorderOptions","chunk: Blob","event: BlobEvent","Deferred","ms: number","errorMessage: string","errorCode: string","errorSource: ErrorSource","value: T","error: Error","options?: VoiceSessionOptions","url: string","chunk: Blob","data: string","message: { session_id: string }","message: {\n transcript: string;\n is_final: boolean;\n }","message: { transcript: string }","message: { text: string }","message: { command: CommandResult | null; transcript?: string }","message: ServerErrorMessage","_originalText: string","_commands: CommandDefinition[]","message: object","websocket: WebSocketManager","getBackend","config: SpeechOSCoreConfig","currentConfig","originalText: string","commands: CommandDefinition[]","speechOS: SpeechOSCore","websocketBackend: VoiceBackend"],"sources":["../src/config.ts","../src/events.ts","../src/state.ts","../src/livekit.ts","../src/audio-capture.ts","../src/websocket.ts","../src/speechos.ts","../src/backend.ts","../src/index.ts"],"sourcesContent":["/**\n * Configuration management for SpeechOS Core SDK\n */\n\nimport type { SpeechOSCoreConfig, WebSocketFactory } from \"./types.js\";\n\n/**\n * Default host - can be overridden by SPEECHOS_HOST env var at build time\n */\nexport const DEFAULT_HOST: string =\n (typeof process !== \"undefined\" && process.env?.SPEECHOS_HOST) ||\n \"https://app.speechos.ai\";\n\n/**\n * Configuration with defaults applied (all fields required internally)\n */\ninterface ResolvedConfig {\n apiKey: string;\n userId: string;\n host: string;\n debug: boolean;\n /** Custom WebSocket factory (undefined means use native WebSocket) */\n webSocketFactory: WebSocketFactory | undefined;\n}\n\n/**\n * Default configuration values\n */\nconst defaultConfig: ResolvedConfig = {\n apiKey: \"\",\n userId: \"\",\n host: DEFAULT_HOST,\n debug: false,\n webSocketFactory: undefined,\n};\n\n/**\n * Validates and merges user config with defaults\n * @param userConfig - User-provided configuration\n * @returns Validated and merged configuration\n */\nexport function validateConfig(userConfig: SpeechOSCoreConfig): ResolvedConfig {\n // Validate apiKey is provided\n if (!userConfig.apiKey) {\n throw new Error(\n \"SpeechOS requires an apiKey. Get one from your team dashboard at /a/<team-slug>/.\"\n );\n }\n\n return {\n apiKey: userConfig.apiKey,\n userId: userConfig.userId ?? defaultConfig.userId,\n host: userConfig.host ?? defaultConfig.host,\n debug: userConfig.debug ?? defaultConfig.debug,\n webSocketFactory: userConfig.webSocketFactory ?? defaultConfig.webSocketFactory,\n };\n}\n\n/**\n * Current active configuration (singleton)\n */\nlet currentConfig: ResolvedConfig = { ...defaultConfig };\n\n/**\n * Get the current configuration\n */\nexport function getConfig(): ResolvedConfig {\n return { ...currentConfig };\n}\n\n/**\n * Set the current configuration\n * @param config - Configuration to set\n */\nexport function setConfig(config: SpeechOSCoreConfig): void {\n currentConfig = validateConfig(config);\n}\n\n/**\n * Reset configuration to defaults\n */\nexport function resetConfig(): void {\n currentConfig = { ...defaultConfig };\n}\n\n/**\n * Update the userId in the current configuration\n * @param userId - The user identifier to set\n */\nexport function updateUserId(userId: string): void {\n currentConfig = { ...currentConfig, userId };\n}\n\n/**\n * LocalStorage key for anonymous ID persistence\n */\nconst ANONYMOUS_ID_KEY = 'speechos_anonymous_id';\n\n/**\n * Get or generate a persistent anonymous ID for Mixpanel tracking.\n *\n * This ID is stored in localStorage to persist across sessions,\n * allowing consistent anonymous user tracking without identifying\n * the account owner's customers.\n *\n * @returns A UUID string for anonymous identification\n */\nexport function getAnonymousId(): string {\n // Only available in browser environments with localStorage\n if (typeof localStorage === 'undefined') {\n return crypto.randomUUID();\n }\n\n let anonymousId = localStorage.getItem(ANONYMOUS_ID_KEY);\n if (!anonymousId) {\n anonymousId = crypto.randomUUID();\n localStorage.setItem(ANONYMOUS_ID_KEY, anonymousId);\n }\n return anonymousId;\n}\n","/**\n * Typed event system for SpeechOS SDK\n * Provides a type-safe event emitter for cross-component communication\n */\n\nimport type { SpeechOSEventMap, UnsubscribeFn } from \"./types.js\";\n\ntype EventCallback<K extends keyof SpeechOSEventMap> = (\n payload: SpeechOSEventMap[K]\n) => void;\n\n/**\n * Type-safe event emitter for SpeechOS events\n */\nexport class SpeechOSEventEmitter {\n private listeners: Map<keyof SpeechOSEventMap, Set<EventCallback<any>>> =\n new Map();\n\n /**\n * Subscribe to an event\n * @param event - Event name to listen to\n * @param callback - Function to call when event is emitted\n * @returns Unsubscribe function\n */\n on<K extends keyof SpeechOSEventMap>(\n event: K,\n callback: EventCallback<K>\n ): UnsubscribeFn {\n if (!this.listeners.has(event)) {\n this.listeners.set(event, new Set());\n }\n\n this.listeners.get(event)!.add(callback);\n\n // Return unsubscribe function\n return () => {\n const callbacks = this.listeners.get(event);\n if (callbacks) {\n callbacks.delete(callback);\n if (callbacks.size === 0) {\n this.listeners.delete(event);\n }\n }\n };\n }\n\n /**\n * Subscribe to an event once (automatically unsubscribes after first call)\n * @param event - Event name to listen to\n * @param callback - Function to call when event is emitted\n * @returns Unsubscribe function\n */\n once<K extends keyof SpeechOSEventMap>(\n event: K,\n callback: EventCallback<K>\n ): UnsubscribeFn {\n const unsubscribe = this.on(event, (payload) => {\n unsubscribe();\n callback(payload);\n });\n return unsubscribe;\n }\n\n /**\n * Emit an event to all subscribers\n * @param event - Event name to emit\n * @param payload - Event payload data\n */\n emit<K extends keyof SpeechOSEventMap>(\n event: K,\n payload: SpeechOSEventMap[K]\n ): void {\n const callbacks = this.listeners.get(event);\n if (callbacks) {\n callbacks.forEach((callback) => {\n try {\n callback(payload);\n } catch (error) {\n console.error(\n `Error in event listener for \"${String(event)}\":`,\n error\n );\n }\n });\n }\n }\n\n /**\n * Remove all listeners for a specific event or all events\n * @param event - Optional event name to clear listeners for\n */\n clear(event?: keyof SpeechOSEventMap): void {\n if (event) {\n this.listeners.delete(event);\n } else {\n this.listeners.clear();\n }\n }\n\n /**\n * Get the number of listeners for an event\n * @param event - Event name\n * @returns Number of listeners\n */\n listenerCount(event: keyof SpeechOSEventMap): number {\n return this.listeners.get(event)?.size ?? 0;\n }\n}\n\n// Export singleton instance\nexport const events: SpeechOSEventEmitter = new SpeechOSEventEmitter();\n","/**\n * Reactive state management for SpeechOS SDK\n * Provides a centralized state store with subscriptions (similar to Zustand pattern)\n */\n\nimport type {\n SpeechOSState,\n StateChangeCallback,\n UnsubscribeFn,\n} from \"./types.js\";\nimport { events } from \"./events.js\";\n\n/**\n * Initial state\n */\nconst initialState: SpeechOSState = {\n isVisible: false,\n isExpanded: false,\n isConnected: false,\n isMicEnabled: false,\n activeAction: null,\n focusedElement: null,\n recordingState: \"idle\",\n errorMessage: null,\n};\n\n/**\n * State manager class\n */\nclass StateManager {\n private state: SpeechOSState;\n private subscribers: Set<StateChangeCallback> = new Set();\n /** Cached immutable snapshot for useSyncExternalStore compatibility */\n private snapshot: SpeechOSState;\n\n constructor(initialState: SpeechOSState) {\n this.state = { ...initialState };\n this.snapshot = Object.freeze({ ...this.state });\n }\n\n /**\n * Get the current state snapshot (returns a stable reference for React)\n * This returns an immutable frozen object that only changes when setState is called.\n */\n getState(): SpeechOSState {\n return this.snapshot;\n }\n\n /**\n * Update state with partial values\n * @param partial - Partial state to merge with current state\n */\n setState(partial: Partial<SpeechOSState>): void {\n const prevState = this.snapshot;\n this.state = { ...this.state, ...partial };\n // Create a new frozen snapshot\n this.snapshot = Object.freeze({ ...this.state });\n\n // Notify subscribers\n this.subscribers.forEach((callback) => {\n try {\n callback(this.snapshot, prevState);\n } catch (error) {\n console.error(\"Error in state change callback:\", error);\n }\n });\n\n // Emit state change event\n events.emit(\"state:change\", { state: this.snapshot });\n }\n\n /**\n * Subscribe to state changes\n * @param callback - Function to call when state changes\n * @returns Unsubscribe function\n */\n subscribe(callback: StateChangeCallback): UnsubscribeFn {\n this.subscribers.add(callback);\n\n // Return unsubscribe function\n return () => {\n this.subscribers.delete(callback);\n };\n }\n\n /**\n * Reset state to initial values\n */\n reset(): void {\n const prevState = this.snapshot;\n this.state = { ...initialState };\n this.snapshot = Object.freeze({ ...this.state });\n\n // Notify subscribers\n this.subscribers.forEach((callback) => {\n try {\n callback(this.snapshot, prevState);\n } catch (error) {\n console.error(\"Error in state change callback:\", error);\n }\n });\n\n // Emit state change event\n events.emit(\"state:change\", { state: this.snapshot });\n }\n\n /**\n * Show the widget\n */\n show(): void {\n this.setState({ isVisible: true });\n events.emit(\"widget:show\", undefined);\n }\n\n /**\n * Hide the widget and reset expanded state\n */\n hide(): void {\n this.setState({\n isVisible: false,\n isExpanded: false,\n activeAction: null,\n });\n events.emit(\"widget:hide\", undefined);\n }\n\n /**\n * Toggle the action bubbles expansion\n */\n toggleExpanded(): void {\n this.setState({ isExpanded: !this.state.isExpanded });\n }\n\n /**\n * Set the focused form element\n * @param element - The form element that has focus\n */\n setFocusedElement(element: HTMLElement | null): void {\n this.setState({ focusedElement: element });\n }\n\n /**\n * Set the active action\n * @param action - The action to set as active\n */\n setActiveAction(action: SpeechOSState[\"activeAction\"]): void {\n this.setState({ activeAction: action });\n }\n\n /**\n * Set the recording state\n * @param recordingState - The recording state to set\n */\n setRecordingState(recordingState: SpeechOSState[\"recordingState\"]): void {\n this.setState({ recordingState });\n }\n\n /**\n * Set the connection state\n * @param isConnected - Whether connected to LiveKit\n */\n setConnected(isConnected: boolean): void {\n this.setState({ isConnected });\n }\n\n /**\n * Set the microphone enabled state\n * @param isMicEnabled - Whether microphone is enabled\n */\n setMicEnabled(isMicEnabled: boolean): void {\n this.setState({ isMicEnabled });\n }\n\n /**\n * Start recording flow (connecting → recording)\n */\n startRecording(): void {\n this.setState({\n recordingState: \"connecting\",\n isExpanded: false, // Collapse bubbles when recording starts\n });\n }\n\n /**\n * Stop recording and start processing\n */\n stopRecording(): void {\n this.setState({ recordingState: \"processing\", isMicEnabled: false });\n }\n\n /**\n * Complete the recording flow and return to idle\n */\n completeRecording(): void {\n this.setState({\n recordingState: \"idle\",\n activeAction: null,\n isConnected: false,\n isMicEnabled: false,\n });\n }\n\n /**\n * Cancel recording and return to idle\n */\n cancelRecording(): void {\n this.setState({\n recordingState: \"idle\",\n activeAction: null,\n errorMessage: null,\n isConnected: false,\n isMicEnabled: false,\n });\n }\n\n /**\n * Set error state with a message\n * @param message - Error message to display\n */\n setError(message: string): void {\n this.setState({\n recordingState: \"error\",\n errorMessage: message,\n });\n }\n\n /**\n * Clear error state and return to idle\n */\n clearError(): void {\n this.setState({\n recordingState: \"idle\",\n errorMessage: null,\n });\n }\n}\n\n// Export singleton instance\nexport const state: StateManager = new StateManager(initialState);\n\n/**\n * Create a new state manager instance (useful for testing)\n */\nexport function createStateManager(\n initial?: Partial<SpeechOSState>\n): StateManager {\n return new StateManager({ ...initialState, ...initial });\n}\n","/**\n * LiveKit integration for SpeechOS SDK\n * Handles room connections, audio streaming, and transcription requests\n */\n\nimport {\n Room,\n RoomEvent,\n Track,\n createLocalAudioTrack,\n type LocalAudioTrack,\n type RemoteParticipant,\n} from \"livekit-client\";\nimport type {\n LiveKitTokenResponse,\n ServerErrorMessage,\n ErrorSource,\n CommandDefinition,\n CommandResult,\n SpeechOSAction,\n VoiceSessionOptions,\n SessionSettings,\n} from \"./types.js\";\nimport { getConfig } from \"./config.js\";\nimport { events } from \"./events.js\";\nimport { state } from \"./state.js\";\n\n// Protocol constants (matching backend TranscriptionManager)\nconst MESSAGE_TYPE_REQUEST_TRANSCRIPT = \"request_transcript\";\nconst MESSAGE_TYPE_TRANSCRIPT = \"transcript\";\nconst MESSAGE_TYPE_EDIT_TEXT = \"edit_text\";\nconst MESSAGE_TYPE_EDITED_TEXT = \"edited_text\";\nconst MESSAGE_TYPE_EXECUTE_COMMAND = \"execute_command\";\nconst MESSAGE_TYPE_COMMAND_RESULT = \"command_result\";\nconst MESSAGE_TYPE_ERROR = \"error\";\nconst TOPIC_SPEECHOS = \"speechos\";\n\n// Token cache TTL (4 minutes in milliseconds)\n// LiveKit tokens are valid for longer, but we cache for 4 minutes to ensure\n// freshness of session settings (language, vocabulary) while still providing\n// a latency benefit when user clicks an action shortly after expanding the widget\nconst TOKEN_CACHE_TTL_MS = 4 * 60 * 1000;\n\n/**\n * A deferred promise with timeout support.\n * Encapsulates resolve/reject/timeout in a single object for cleaner async handling.\n */\nexport class Deferred<T> {\n readonly promise: Promise<T>;\n private _resolve!: (value: T) => void;\n private _reject!: (error: Error) => void;\n private _timeoutId: ReturnType<typeof setTimeout> | null = null;\n private _settled = false;\n\n constructor() {\n this.promise = new Promise<T>((resolve, reject) => {\n this._resolve = resolve;\n this._reject = reject;\n });\n }\n\n /**\n * Set a timeout that will reject the promise with the given error\n */\n setTimeout(\n ms: number,\n errorMessage: string,\n errorCode: string,\n errorSource: ErrorSource\n ): void {\n this._timeoutId = setTimeout(() => {\n if (!this._settled) {\n console.error(`[SpeechOS] Error: ${errorMessage} (${errorCode})`);\n events.emit(\"error\", {\n code: errorCode,\n message: errorMessage,\n source: errorSource,\n });\n this.reject(new Error(errorMessage));\n }\n }, ms);\n }\n\n resolve(value: T): void {\n if (!this._settled) {\n this._settled = true;\n this.clearTimeout();\n this._resolve(value);\n }\n }\n\n reject(error: Error): void {\n if (!this._settled) {\n this._settled = true;\n this.clearTimeout();\n this._reject(error);\n }\n }\n\n private clearTimeout(): void {\n if (this._timeoutId !== null) {\n clearTimeout(this._timeoutId);\n this._timeoutId = null;\n }\n }\n\n get isSettled(): boolean {\n return this._settled;\n }\n}\n\n/**\n * LiveKit connection manager\n */\nclass LiveKitManager {\n private room: Room | null = null;\n private tokenData: LiveKitTokenResponse | null = null;\n private micTrack: LocalAudioTrack | null = null;\n\n // Token cache for pre-fetching optimization\n private cachedTokenData: LiveKitTokenResponse | null = null;\n private tokenCacheTimestamp: number | null = null;\n private tokenPrefetchPromise: Promise<LiveKitTokenResponse> | null = null;\n\n // Auto-refresh timer for keeping token fresh while widget is expanded\n private tokenRefreshTimer: ReturnType<typeof setTimeout> | null = null;\n private autoRefreshEnabled = false;\n\n // Pending async operations using Deferred pattern\n private pendingTranscript: Deferred<string> | null = null;\n private pendingEditText: Deferred<string> | null = null;\n private pendingCommand: Deferred<CommandResult | null> | null = null;\n private pendingTrackSubscribed: Deferred<void> | null = null;\n\n // Track original text for edit operations (to include in events)\n private editOriginalText: string | null = null;\n\n // Session settings passed from options\n private sessionSettings: SessionSettings = {};\n\n /**\n * Check if the cached token is still valid (within TTL)\n */\n private isCachedTokenValid(): boolean {\n if (!this.cachedTokenData || !this.tokenCacheTimestamp) {\n return false;\n }\n const age = Date.now() - this.tokenCacheTimestamp;\n return age < TOKEN_CACHE_TTL_MS;\n }\n\n /**\n * Pre-fetch a LiveKit token for later use\n * Call this early (e.g., when widget expands) to reduce latency when starting a voice session.\n * If a prefetch is already in progress, returns the existing promise.\n * If a valid cached token exists, returns it immediately.\n */\n async prefetchToken(): Promise<LiveKitTokenResponse> {\n const config = getConfig();\n\n // If we have a valid cached token, return it\n if (this.isCachedTokenValid() && this.cachedTokenData) {\n if (config.debug) {\n console.log(\"[SpeechOS] Using cached token (prefetch hit)\");\n }\n return this.cachedTokenData;\n }\n\n // If a prefetch is already in progress, return the existing promise\n if (this.tokenPrefetchPromise) {\n if (config.debug) {\n console.log(\"[SpeechOS] Prefetch already in progress, awaiting...\");\n }\n return this.tokenPrefetchPromise;\n }\n\n // Start a new prefetch\n if (config.debug) {\n console.log(\"[SpeechOS] Starting token prefetch...\");\n }\n\n this.tokenPrefetchPromise = this.fetchTokenFromServer()\n .then((data) => {\n // Cache the token\n this.cachedTokenData = data;\n this.tokenCacheTimestamp = Date.now();\n this.tokenPrefetchPromise = null;\n return data;\n })\n .catch((error) => {\n this.tokenPrefetchPromise = null;\n throw error;\n });\n\n return this.tokenPrefetchPromise;\n }\n\n /**\n * Fetch a LiveKit token from the backend\n * Uses cached token if valid, otherwise fetches a fresh one.\n * Includes language settings and user vocabulary which are stored in the VoiceSession.\n */\n async fetchToken(): Promise<LiveKitTokenResponse> {\n const config = getConfig();\n\n // Check if we have a valid cached token\n if (this.isCachedTokenValid() && this.cachedTokenData) {\n if (config.debug) {\n console.log(\"[SpeechOS] Using cached token\");\n }\n this.tokenData = this.cachedTokenData;\n return this.cachedTokenData;\n }\n\n // If a prefetch is in progress, wait for it\n if (this.tokenPrefetchPromise) {\n if (config.debug) {\n console.log(\"[SpeechOS] Waiting for prefetch to complete...\");\n }\n const data = await this.tokenPrefetchPromise;\n this.tokenData = data;\n return data;\n }\n\n // No valid cache, fetch fresh token\n const data = await this.fetchTokenFromServer();\n\n // Cache the token\n this.cachedTokenData = data;\n this.tokenCacheTimestamp = Date.now();\n this.tokenData = data;\n\n return data;\n }\n\n /**\n * Internal method to fetch a fresh token from the server\n */\n private async fetchTokenFromServer(): Promise<LiveKitTokenResponse> {\n const config = getConfig();\n const url = `${config.host}/livekit/api/token/`;\n\n // Use session settings (with defaults)\n const settings = this.sessionSettings;\n const inputLanguage = settings.inputLanguageCode ?? \"en-US\";\n const outputLanguage = settings.outputLanguageCode ?? \"en-US\";\n const smartFormat = settings.smartFormat ?? true;\n const vocabulary = settings.vocabulary ?? [];\n const snippets = settings.snippets ?? [];\n\n if (config.debug) {\n console.log(\"[SpeechOS] Fetching LiveKit token from:\", url);\n console.log(\"[SpeechOS] Session settings:\", {\n inputLanguage,\n outputLanguage,\n smartFormat,\n snippetsCount: snippets.length,\n vocabularyCount: vocabulary.length,\n });\n }\n\n const response = await fetch(url, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n ...(config.apiKey ? { Authorization: `Api-Key ${config.apiKey}` } : {}),\n },\n body: JSON.stringify({\n user_id: config.userId || null,\n input_language: inputLanguage,\n output_language: outputLanguage,\n smart_format: smartFormat,\n custom_vocabulary: vocabulary,\n custom_snippets: snippets,\n }),\n });\n\n if (!response.ok) {\n throw new Error(\n `Failed to fetch LiveKit token: ${response.status} ${response.statusText}`\n );\n }\n\n const data = (await response.json()) as LiveKitTokenResponse;\n\n if (config.debug) {\n console.log(\"[SpeechOS] LiveKit token received:\", {\n room: data.room,\n identity: data.identity,\n ws_url: data.ws_url,\n });\n }\n\n return data;\n }\n\n /**\n * Connect to a LiveKit room (fresh connection each time)\n */\n async connect(): Promise<Room> {\n const config = getConfig();\n\n // Fetch a fresh token for this connection\n await this.fetchToken();\n\n if (!this.tokenData) {\n throw new Error(\"No token available for LiveKit connection\");\n }\n\n // Create a new room instance\n this.room = new Room({\n adaptiveStream: true,\n dynacast: true,\n });\n\n // Set up event listeners\n this.setupRoomEvents();\n\n if (config.debug) {\n console.log(\n \"[SpeechOS] Connecting to LiveKit room:\",\n this.tokenData.room\n );\n }\n\n // Connect to the room\n await this.room.connect(this.tokenData.ws_url, this.tokenData.token);\n\n // Update state\n state.setConnected(true);\n\n if (config.debug) {\n console.log(\"[SpeechOS] Connected to LiveKit room:\", this.room.name);\n }\n\n return this.room;\n }\n\n /**\n * Wait until the agent is ready to receive audio\n * Resolves when LocalTrackSubscribed event is received\n */\n async waitUntilReady(): Promise<void> {\n if (!this.room || this.room.state !== \"connected\") {\n throw new Error(\"Not connected to room\");\n }\n\n // If already set up (from startVoiceSession), just return the existing promise\n if (this.pendingTrackSubscribed) {\n return this.pendingTrackSubscribed.promise;\n }\n\n // Create a new deferred\n this.pendingTrackSubscribed = new Deferred<void>();\n this.pendingTrackSubscribed.setTimeout(\n 15000,\n \"Connection timed out - agent not available\",\n \"connection_timeout\",\n \"connection\"\n );\n\n return this.pendingTrackSubscribed.promise;\n }\n\n /**\n * Set up LiveKit room event listeners\n */\n private setupRoomEvents(): void {\n if (!this.room) return;\n\n const config = getConfig();\n\n this.room.on(RoomEvent.Connected, () => {\n if (config.debug) {\n console.log(\"[SpeechOS] Room connected\");\n }\n state.setConnected(true);\n });\n\n this.room.on(RoomEvent.Disconnected, (reason) => {\n if (config.debug) {\n console.log(\"[SpeechOS] Room disconnected:\", reason);\n }\n state.setConnected(false);\n state.setMicEnabled(false);\n });\n\n this.room.on(RoomEvent.ParticipantConnected, (participant) => {\n if (config.debug) {\n console.log(\"[SpeechOS] Participant connected:\", participant.identity);\n }\n });\n\n // Fired when a remote participant subscribes to our local track\n // This confirms the agent is ready to receive our audio\n this.room.on(RoomEvent.LocalTrackSubscribed, (publication) => {\n if (config.debug) {\n console.log(\n \"[SpeechOS] LocalTrackSubscribed event fired:\",\n publication.trackSid\n );\n }\n\n // Resolve the promise when our track is subscribed to\n if (this.pendingTrackSubscribed) {\n this.pendingTrackSubscribed.resolve();\n this.pendingTrackSubscribed = null;\n }\n });\n\n // Also log all room events for debugging\n this.room.on(RoomEvent.LocalTrackPublished, (publication) => {\n if (config.debug) {\n console.log(\n \"[SpeechOS] LocalTrackPublished:\",\n publication.trackSid,\n publication.source\n );\n }\n });\n\n // Handle incoming data messages (transcriptions, acks, etc.)\n this.room.on(\n RoomEvent.DataReceived,\n (data: Uint8Array, participant?: RemoteParticipant) => {\n this.handleDataMessage(data, participant);\n }\n );\n }\n\n /**\n * Handle incoming data messages from the agent\n */\n private handleDataMessage(\n data: Uint8Array,\n _participant?: RemoteParticipant\n ): void {\n const config = getConfig();\n\n try {\n const message = JSON.parse(new TextDecoder().decode(data));\n\n if (config.debug) {\n console.log(\"[SpeechOS] Data received:\", message);\n }\n\n if (message.type === MESSAGE_TYPE_TRANSCRIPT) {\n // Transcript received from agent\n const transcript = message.transcript || \"\";\n\n if (config.debug) {\n console.log(\"[SpeechOS] Transcript received:\", transcript);\n }\n\n // Emit transcription:complete event\n events.emit(\"transcription:complete\", { text: transcript });\n\n // Resolve the pending transcript promise\n if (this.pendingTranscript) {\n this.pendingTranscript.resolve(transcript);\n this.pendingTranscript = null;\n }\n } else if (message.type === MESSAGE_TYPE_EDITED_TEXT) {\n // Edited text received from agent\n const editedText = message.text || \"\";\n\n if (config.debug) {\n console.log(\"[SpeechOS] Edited text received:\", editedText);\n }\n\n // Emit edit:complete event\n events.emit(\"edit:complete\", {\n text: editedText,\n originalText: this.editOriginalText || \"\",\n });\n\n // Resolve the pending edit text promise\n if (this.pendingEditText) {\n this.pendingEditText.resolve(editedText);\n this.pendingEditText = null;\n }\n\n // Clear stored original text\n this.editOriginalText = null;\n } else if (message.type === MESSAGE_TYPE_COMMAND_RESULT) {\n // Command result received from agent\n const commandResult: CommandResult | null = message.command || null;\n\n if (config.debug) {\n console.log(\"[SpeechOS] Command result received:\", commandResult);\n }\n\n // Emit command:complete event\n events.emit(\"command:complete\", { command: commandResult });\n\n // Resolve the pending command promise\n if (this.pendingCommand) {\n this.pendingCommand.resolve(commandResult);\n this.pendingCommand = null;\n }\n } else if (message.type === MESSAGE_TYPE_ERROR) {\n // Server error received from agent\n const serverError = message as ServerErrorMessage;\n const errorCode = serverError.code || \"server_error\";\n const errorMessage = serverError.message || \"A server error occurred\";\n\n console.error(`[SpeechOS] Error: ${errorMessage} (${errorCode})`);\n\n if (config.debug && serverError.details) {\n console.error(\"[SpeechOS] Error details:\", serverError.details);\n }\n\n // Emit error event for UI to handle\n events.emit(\"error\", {\n code: errorCode,\n message: errorMessage,\n source: \"server\",\n });\n\n // Reject any pending operations\n const error = new Error(errorMessage);\n if (this.pendingTranscript) {\n this.pendingTranscript.reject(error);\n this.pendingTranscript = null;\n }\n if (this.pendingEditText) {\n this.pendingEditText.reject(error);\n this.pendingEditText = null;\n }\n if (this.pendingCommand) {\n this.pendingCommand.reject(error);\n this.pendingCommand = null;\n }\n }\n } catch (error) {\n console.error(\"[SpeechOS] Failed to parse data message:\", error);\n }\n }\n\n /**\n * Publish microphone audio track\n * Uses the device ID from session settings if set\n */\n async enableMicrophone(): Promise<void> {\n if (!this.room || this.room.state !== \"connected\") {\n throw new Error(\"Not connected to room\");\n }\n\n const config = getConfig();\n\n if (!this.micTrack) {\n if (config.debug) {\n console.log(\"[SpeechOS] Creating microphone track...\");\n }\n\n // Get selected device ID from session settings\n const deviceId = this.sessionSettings.audioDeviceId;\n const trackOptions: Parameters<typeof createLocalAudioTrack>[0] = {\n echoCancellation: true,\n noiseSuppression: true,\n };\n\n // Only set deviceId if user has selected a specific device\n // Use { exact: deviceId } for strict matching per LiveKit best practices\n if (deviceId) {\n trackOptions.deviceId = { exact: deviceId };\n if (config.debug) {\n console.log(\"[SpeechOS] Using audio device:\", deviceId);\n }\n }\n\n try {\n this.micTrack = await createLocalAudioTrack(trackOptions);\n } catch (error) {\n // If the selected device is unavailable, fall back to default\n if (deviceId && error instanceof Error) {\n console.warn(\n \"[SpeechOS] Selected audio device unavailable, falling back to default:\",\n error.message\n );\n this.micTrack = await createLocalAudioTrack({\n echoCancellation: true,\n noiseSuppression: true,\n });\n } else {\n throw error;\n }\n }\n\n // Log microphone info\n this.logMicrophoneInfo();\n }\n\n // Publish the track if not already published\n const existingPub = this.room.localParticipant.getTrackPublication(\n Track.Source.Microphone\n );\n if (!existingPub) {\n await this.room.localParticipant.publishTrack(this.micTrack, {\n source: Track.Source.Microphone,\n });\n\n // Update state\n state.setMicEnabled(true);\n\n if (config.debug) {\n console.log(\"[SpeechOS] Microphone track published\");\n }\n }\n }\n\n /**\n * Log information about the current microphone track\n */\n private logMicrophoneInfo(): void {\n if (!this.micTrack) return;\n\n const config = getConfig();\n const mediaTrack = this.micTrack.mediaStreamTrack;\n const settings = mediaTrack.getSettings();\n\n // Always log basic mic info (useful for debugging)\n console.log(\"[SpeechOS] Microphone active:\", {\n deviceId: settings.deviceId || \"unknown\",\n label: mediaTrack.label || \"Unknown device\",\n sampleRate: settings.sampleRate,\n channelCount: settings.channelCount,\n echoCancellation: settings.echoCancellation,\n noiseSuppression: settings.noiseSuppression,\n });\n\n if (config.debug) {\n // Log full settings in debug mode\n console.log(\"[SpeechOS] Full audio track settings:\", settings);\n }\n }\n\n /**\n * Disable microphone audio track\n */\n async disableMicrophone(): Promise<void> {\n const config = getConfig();\n\n if (this.micTrack) {\n if (config.debug) {\n console.log(\"[SpeechOS] Disabling microphone track...\");\n }\n\n // Unpublish from room if connected\n if (this.room?.state === \"connected\") {\n try {\n await this.room.localParticipant.unpublishTrack(this.micTrack);\n if (config.debug) {\n console.log(\"[SpeechOS] Microphone track unpublished\");\n }\n } catch (error) {\n console.warn(\"[SpeechOS] Error unpublishing track:\", error);\n }\n }\n\n // Stop the track to release the microphone\n this.micTrack.stop();\n\n // Detach from any elements (per LiveKit best practices)\n this.micTrack.detach();\n\n this.micTrack = null;\n\n // Update state\n state.setMicEnabled(false);\n\n if (config.debug) {\n console.log(\"[SpeechOS] Microphone track stopped and detached\");\n }\n }\n }\n\n /**\n * Send a data message to the room\n */\n async sendDataMessage(message: object): Promise<void> {\n if (!this.room || this.room.state !== \"connected\") {\n throw new Error(\"Not connected to room\");\n }\n\n const data = new TextEncoder().encode(JSON.stringify(message));\n await this.room.localParticipant.publishData(data, {\n reliable: true,\n topic: TOPIC_SPEECHOS,\n });\n }\n\n /**\n * Start a voice session with pre-connect audio buffering\n * Fetches a fresh token, then enables mic with preConnectBuffer to capture audio while connecting.\n * Agent subscription happens in the background - we don't block on it.\n *\n * @param options - Session options including action type and parameters\n */\n async startVoiceSession(options?: VoiceSessionOptions): Promise<void> {\n const config = getConfig();\n if (config.debug) {\n console.log(\"[SpeechOS] Starting voice session...\");\n }\n\n // Store session settings from options\n this.sessionSettings = options?.settings || {};\n\n // Fetch a fresh token for this session\n await this.fetchToken();\n\n if (!this.tokenData) {\n throw new Error(\"No token available for LiveKit connection\");\n }\n\n // Set up the deferred BEFORE connecting so we don't miss the event\n // Agent subscription happens in the background - we don't block on it\n this.pendingTrackSubscribed = new Deferred<void>();\n this.pendingTrackSubscribed.setTimeout(\n 15000,\n \"Connection timed out - agent not available\",\n \"connection_timeout\",\n \"connection\"\n );\n\n // Create the room instance\n this.room = new Room({\n adaptiveStream: true,\n dynacast: true,\n });\n\n // Set up event listeners before enabling mic\n this.setupRoomEvents();\n\n if (config.debug) {\n console.log(\n \"[SpeechOS] Connecting to LiveKit room:\",\n this.tokenData.room,\n \"at\",\n this.tokenData.ws_url\n );\n }\n\n // Connect to the room first\n await this.room.connect(this.tokenData.ws_url, this.tokenData.token);\n\n if (config.debug) {\n console.log(\n \"[SpeechOS] Connected, enabling microphone with preConnectBuffer...\"\n );\n }\n\n // Enable microphone with preConnectBuffer after connection\n // This buffers audio until the agent subscribes to our track\n await this.enableMicrophoneWithPreConnectBuffer();\n\n // Notify that mic is ready - UI can show \"recording\" state now\n // Audio is being captured even though room connection may still be in progress\n if (options?.onMicReady) {\n options.onMicReady();\n }\n\n // Update state\n state.setConnected(true);\n\n if (config.debug) {\n console.log(\"[SpeechOS] Voice session ready - microphone active\");\n }\n\n // Wait for agent subscription in the background\n // This allows the UI to show \"recording\" immediately while the agent connects\n this.waitForAgentSubscription();\n }\n\n /**\n * Wait for the agent to subscribe to our audio track in the background\n * Handles timeout errors without blocking the main flow\n */\n private waitForAgentSubscription(): void {\n const config = getConfig();\n\n if (!this.pendingTrackSubscribed) {\n return;\n }\n\n // Handle the subscription in the background\n this.pendingTrackSubscribed.promise\n .then(() => {\n if (config.debug) {\n console.log(\n \"[SpeechOS] Agent subscribed to audio track - full duplex established\"\n );\n }\n this.pendingTrackSubscribed = null;\n })\n .catch((error) => {\n // Agent subscription timed out - the buffered audio will still be sent\n // but ongoing audio may not be processed if agent never connects\n console.warn(\"[SpeechOS] Agent subscription timeout:\", error.message);\n this.pendingTrackSubscribed = null;\n // Note: We don't set error state here because the session may still work\n // if the agent connects late. The timeout error event was already emitted\n // by the Deferred class.\n });\n }\n\n /**\n * Enable microphone with pre-connect buffering\n * This starts capturing audio locally before the room is connected,\n * buffering it until the connection is established.\n */\n private async enableMicrophoneWithPreConnectBuffer(): Promise<void> {\n if (!this.room) {\n throw new Error(\"Room not initialized\");\n }\n\n const config = getConfig();\n\n // Get selected device ID from session settings\n const deviceId = this.sessionSettings.audioDeviceId;\n\n // Build constraints for the microphone\n const constraints: MediaTrackConstraints = {\n echoCancellation: true,\n noiseSuppression: true,\n };\n\n // Only set deviceId if user has selected a specific device\n if (deviceId) {\n constraints.deviceId = { exact: deviceId };\n if (config.debug) {\n console.log(\"[SpeechOS] Using audio device:\", deviceId);\n }\n }\n\n try {\n // Enable microphone with preConnectBuffer: true\n // This tells LiveKit to start capturing audio locally and buffer it\n // until the room connection is established\n await this.room.localParticipant.setMicrophoneEnabled(true, constraints, {\n preConnectBuffer: true,\n });\n\n // Update state\n state.setMicEnabled(true);\n\n // Get the mic track for logging\n const micPub = this.room.localParticipant.getTrackPublication(\n Track.Source.Microphone\n );\n if (micPub?.track) {\n this.micTrack = micPub.track as LocalAudioTrack;\n this.logMicrophoneInfo();\n }\n\n if (config.debug) {\n console.log(\n \"[SpeechOS] Microphone enabled with pre-connect buffer - audio is being captured\"\n );\n }\n } catch (error) {\n // If the selected device is unavailable, fall back to default\n if (deviceId && error instanceof Error) {\n console.warn(\n \"[SpeechOS] Selected audio device unavailable, falling back to default:\",\n error.message\n );\n await this.room.localParticipant.setMicrophoneEnabled(\n true,\n {\n echoCancellation: true,\n noiseSuppression: true,\n },\n {\n preConnectBuffer: true,\n }\n );\n state.setMicEnabled(true);\n } else {\n throw error;\n }\n }\n }\n\n /**\n * Stop the voice session and request the transcript\n * Returns a promise that resolves with the transcript text\n * @throws Error if timeout occurs waiting for transcript\n */\n async stopVoiceSession(): Promise<string> {\n const config = getConfig();\n const settings = this.sessionSettings;\n const inputLanguage = settings.inputLanguageCode ?? \"en-US\";\n const outputLanguage = settings.outputLanguageCode ?? \"en-US\";\n\n // Always log dictate command with language settings\n console.log(\"[SpeechOS] Dictate command:\", {\n inputLanguage,\n outputLanguage,\n });\n\n if (config.debug) {\n console.log(\n \"[SpeechOS] Stopping voice session, requesting transcript...\"\n );\n }\n\n // Disable microphone\n await this.disableMicrophone();\n\n if (config.debug) {\n console.log(\"[SpeechOS] Requesting transcript from agent...\");\n }\n\n // Create deferred for transcript (resolved when transcript message is received)\n this.pendingTranscript = new Deferred<string>();\n this.pendingTranscript.setTimeout(\n 10000,\n \"Transcription timed out. Please try again.\",\n \"transcription_timeout\",\n \"timeout\"\n );\n\n // Request the transcript from the agent\n await this.sendDataMessage({\n type: MESSAGE_TYPE_REQUEST_TRANSCRIPT,\n });\n\n const result = await this.pendingTranscript.promise;\n this.pendingTranscript = null;\n return result;\n }\n\n /**\n * Alias for stopVoiceSession - granular API naming\n */\n async stopAndGetTranscript(): Promise<string> {\n return this.stopVoiceSession();\n }\n\n /**\n * Request text editing using the transcript as instructions\n * Sends the original text to the backend, which applies the spoken instructions\n * Returns a promise that resolves with the edited text\n * @throws Error if timeout occurs waiting for edited text\n */\n async requestEditText(originalText: string): Promise<string> {\n const config = getConfig();\n const settings = this.sessionSettings;\n const inputLanguage = settings.inputLanguageCode ?? \"en-US\";\n const outputLanguage = settings.outputLanguageCode ?? \"en-US\";\n\n // Always log edit command with language settings\n console.log(\"[SpeechOS] Edit command:\", {\n inputLanguage,\n outputLanguage,\n originalTextLength: originalText.length,\n });\n\n if (config.debug) {\n console.log(\"[SpeechOS] Requesting text edit...\");\n }\n\n // Store original text for the event\n this.editOriginalText = originalText;\n\n // Disable microphone first\n await this.disableMicrophone();\n\n if (config.debug) {\n console.log(\"[SpeechOS] Sending edit_text request to agent...\");\n }\n\n // Create deferred for edited text (resolved when edited_text message is received)\n this.pendingEditText = new Deferred<string>();\n this.pendingEditText.setTimeout(\n 15000,\n \"Edit request timed out. Please try again.\",\n \"edit_timeout\",\n \"timeout\"\n );\n\n // Send the edit request to the agent\n await this.sendDataMessage({\n type: MESSAGE_TYPE_EDIT_TEXT,\n text: originalText,\n });\n\n const result = await this.pendingEditText.promise;\n this.pendingEditText = null;\n return result;\n }\n\n /**\n * Alias for requestEditText - granular API naming\n */\n async stopAndEdit(originalText: string): Promise<string> {\n return this.requestEditText(originalText);\n }\n\n /**\n * Request command matching using the transcript as input\n * Sends command definitions to the backend, which matches the user's speech against them\n * Returns a promise that resolves with the matched command or null if no match\n * @throws Error if timeout occurs waiting for command result\n */\n async requestCommand(\n commands: CommandDefinition[]\n ): Promise<CommandResult | null> {\n const config = getConfig();\n const settings = this.sessionSettings;\n const inputLanguage = settings.inputLanguageCode ?? \"en-US\";\n\n // Always log command request\n console.log(\"[SpeechOS] Command request:\", {\n inputLanguage,\n commandCount: commands.length,\n });\n\n if (config.debug) {\n console.log(\"[SpeechOS] Requesting command match...\");\n }\n\n // Disable microphone first\n await this.disableMicrophone();\n\n if (config.debug) {\n console.log(\"[SpeechOS] Sending execute_command request to agent...\");\n }\n\n // Create deferred for command result (resolved when command_result message is received)\n this.pendingCommand = new Deferred<CommandResult | null>();\n this.pendingCommand.setTimeout(\n 15000,\n \"Command request timed out. Please try again.\",\n \"command_timeout\",\n \"timeout\"\n );\n\n // Send the command request to the agent with command definitions\n await this.sendDataMessage({\n type: MESSAGE_TYPE_EXECUTE_COMMAND,\n commands: commands,\n });\n\n const result = await this.pendingCommand.promise;\n this.pendingCommand = null;\n return result;\n }\n\n /**\n * Alias for requestCommand - granular API naming\n */\n async stopAndCommand(\n commands: CommandDefinition[]\n ): Promise<CommandResult | null> {\n return this.requestCommand(commands);\n }\n\n /**\n * Disconnect from the current room\n * Clears the token so a fresh one is fetched for the next session\n */\n async disconnect(): Promise<void> {\n const config = getConfig();\n\n if (config.debug) {\n console.log(\"[SpeechOS] Disconnecting from room...\");\n }\n\n // Disable and cleanup microphone track\n await this.disableMicrophone();\n\n if (this.room) {\n // Remove all event listeners before disconnecting\n this.room.removeAllListeners();\n\n // Disconnect from the room\n await this.room.disconnect();\n this.room = null;\n\n // Update state\n state.setConnected(false);\n\n if (config.debug) {\n console.log(\"[SpeechOS] Room disconnected and cleaned up\");\n }\n }\n\n // Reject any pending operations (so callers don't hang)\n if (this.pendingTranscript) {\n this.pendingTranscript.reject(new Error(\"Disconnected\"));\n this.pendingTranscript = null;\n }\n if (this.pendingEditText) {\n this.pendingEditText.reject(new Error(\"Disconnected\"));\n this.pendingEditText = null;\n }\n if (this.pendingCommand) {\n this.pendingCommand.reject(new Error(\"Disconnected\"));\n this.pendingCommand = null;\n }\n if (this.pendingTrackSubscribed) {\n this.pendingTrackSubscribed.reject(new Error(\"Disconnected\"));\n this.pendingTrackSubscribed = null;\n }\n\n // Clear all session state including token\n // Note: We intentionally keep the cached token (cachedTokenData) valid\n // so it can be reused for the next session if within TTL.\n // The cache will naturally expire based on TOKEN_CACHE_TTL_MS.\n this.tokenData = null;\n this.editOriginalText = null;\n this.sessionSettings = {};\n\n if (config.debug) {\n console.log(\"[SpeechOS] Session state cleared\");\n }\n }\n\n /**\n * Invalidate the cached token\n * Call this when settings change that would affect the token (language, vocabulary)\n */\n invalidateTokenCache(): void {\n const config = getConfig();\n if (config.debug) {\n console.log(\"[SpeechOS] Token cache invalidated\");\n }\n this.cachedTokenData = null;\n this.tokenCacheTimestamp = null;\n }\n\n /**\n * Start auto-refreshing the token while the widget is expanded.\n * Call this after a voice session completes to immediately fetch a fresh token\n * (since each command requires its own token) and keep it fresh for subsequent commands.\n */\n startAutoRefresh(): void {\n const config = getConfig();\n this.autoRefreshEnabled = true;\n\n if (config.debug) {\n console.log(\"[SpeechOS] Token auto-refresh enabled\");\n }\n\n // Invalidate the token we just used (each command needs a fresh token)\n this.invalidateTokenCache();\n\n // Immediately fetch a fresh token for the next command, then schedule refresh\n this.prefetchToken()\n .then(() => {\n // Now that we have a fresh token, schedule the next refresh\n this.scheduleTokenRefresh();\n })\n .catch((error) => {\n if (config.debug) {\n console.warn(\n \"[SpeechOS] Failed to prefetch token after command:\",\n error\n );\n }\n // Even on failure, try again later\n if (this.autoRefreshEnabled) {\n this.tokenRefreshTimer = setTimeout(() => {\n this.performAutoRefresh();\n }, 5 * 1000); // Retry in 5 seconds\n }\n });\n }\n\n /**\n * Stop auto-refreshing the token.\n * Call this when the widget collapses or user navigates away.\n */\n stopAutoRefresh(): void {\n const config = getConfig();\n this.autoRefreshEnabled = false;\n\n if (this.tokenRefreshTimer) {\n clearTimeout(this.tokenRefreshTimer);\n this.tokenRefreshTimer = null;\n }\n\n if (config.debug) {\n console.log(\"[SpeechOS] Token auto-refresh disabled\");\n }\n }\n\n /**\n * Schedule a token refresh before the current cache expires.\n * Handles computer sleep by checking elapsed time on each refresh attempt.\n */\n private scheduleTokenRefresh(): void {\n if (!this.autoRefreshEnabled) {\n return;\n }\n\n // Clear any existing timer\n if (this.tokenRefreshTimer) {\n clearTimeout(this.tokenRefreshTimer);\n this.tokenRefreshTimer = null;\n }\n\n const config = getConfig();\n\n // Calculate time until refresh needed\n // Refresh 30 seconds before expiry to ensure smooth transition\n const refreshBuffer = 30 * 1000;\n let timeUntilRefresh: number;\n\n if (this.tokenCacheTimestamp) {\n const age = Date.now() - this.tokenCacheTimestamp;\n const timeRemaining = TOKEN_CACHE_TTL_MS - age;\n timeUntilRefresh = Math.max(0, timeRemaining - refreshBuffer);\n } else {\n // No cached token, refresh immediately\n timeUntilRefresh = 0;\n }\n\n if (config.debug) {\n console.log(\n `[SpeechOS] Scheduling token refresh in ${Math.round(\n timeUntilRefresh / 1000\n )}s`\n );\n }\n\n this.tokenRefreshTimer = setTimeout(() => {\n this.performAutoRefresh();\n }, timeUntilRefresh);\n }\n\n /**\n * Perform the auto-refresh, handling computer sleep scenarios.\n */\n private async performAutoRefresh(): Promise<void> {\n if (!this.autoRefreshEnabled) {\n return;\n }\n\n const config = getConfig();\n\n // Check if token is still valid (handles computer sleep where timer fired late)\n // If token expired during sleep, we'll fetch a fresh one\n if (this.isCachedTokenValid()) {\n if (config.debug) {\n console.log(\n \"[SpeechOS] Token still valid on refresh check, rescheduling\"\n );\n }\n this.scheduleTokenRefresh();\n return;\n }\n\n if (config.debug) {\n console.log(\"[SpeechOS] Auto-refreshing token...\");\n }\n\n try {\n // Fetch fresh token\n const data = await this.fetchTokenFromServer();\n this.cachedTokenData = data;\n this.tokenCacheTimestamp = Date.now();\n\n if (config.debug) {\n console.log(\"[SpeechOS] Token auto-refreshed successfully\");\n }\n\n // Schedule next refresh\n this.scheduleTokenRefresh();\n } catch (error) {\n // Log but don't show error to user - they haven't started an action\n console.warn(\"[SpeechOS] Token auto-refresh failed:\", error);\n\n // Retry in 30 seconds\n if (this.autoRefreshEnabled) {\n this.tokenRefreshTimer = setTimeout(() => {\n this.performAutoRefresh();\n }, 30 * 1000);\n }\n }\n }\n\n /**\n * Get the current room instance\n */\n getRoom(): Room | null {\n return this.room;\n }\n\n /**\n * Get the current token data\n */\n getTokenData(): LiveKitTokenResponse | null {\n return this.tokenData;\n }\n\n /**\n * Check if connected to a room\n */\n isConnected(): boolean {\n return this.room?.state === \"connected\";\n }\n\n /**\n * Check if microphone is enabled\n */\n isMicrophoneEnabled(): boolean {\n return this.micTrack !== null;\n }\n}\n\n// Export singleton instance\nexport const livekit: LiveKitManager = new LiveKitManager();\n\n// Invalidate token cache when settings change (language, snippets, vocabulary)\n// These settings are embedded in the token, so a new token is needed if they change\nevents.on(\"settings:changed\", () => {\n livekit.invalidateTokenCache();\n});\n","/**\n * Audio capture module for SpeechOS WebSocket integration.\n *\n * Provides MediaRecorder-based audio capture with:\n * - Format detection for cross-browser compatibility\n * - Buffering for instant start (audio captured before connection is ready)\n * - Atomic buffer swap pattern to prevent chunk reordering\n */\n\nimport { getConfig } from './config.js';\n\n/**\n * Supported audio formats with their MIME types and whether\n * Deepgram needs explicit encoding parameters.\n */\nexport interface AudioFormat {\n /** MIME type for MediaRecorder */\n mimeType: string;\n /** Short identifier for the format */\n format: 'webm' | 'mp4' | 'pcm';\n /** Whether Deepgram needs encoding/sample_rate params */\n needsEncodingParams: boolean;\n}\n\n/**\n * Detect if running in Safari.\n */\nfunction isSafari(): boolean {\n const ua = navigator.userAgent.toLowerCase();\n const vendor = navigator.vendor?.toLowerCase() || '';\n const hasSafariUA = ua.includes('safari') && !ua.includes('chrome') && !ua.includes('chromium');\n const isAppleVendor = vendor.includes('apple');\n return hasSafariUA && isAppleVendor;\n}\n\n/**\n * Detect the best supported audio format for the current browser.\n *\n * IMPORTANT: Safari must use MP4/AAC. Its WebM/Opus implementation is buggy\n * and produces truncated/incomplete audio.\n */\nexport function getSupportedAudioFormat(): AudioFormat {\n // Safari: Force MP4/AAC\n if (isSafari()) {\n if (MediaRecorder.isTypeSupported('audio/mp4')) {\n return {\n mimeType: 'audio/mp4',\n format: 'mp4',\n needsEncodingParams: false,\n };\n }\n return {\n mimeType: '',\n format: 'mp4',\n needsEncodingParams: true,\n };\n }\n\n // Chrome, Firefox, Edge: Use WebM/Opus\n if (MediaRecorder.isTypeSupported('audio/webm;codecs=opus')) {\n return {\n mimeType: 'audio/webm;codecs=opus',\n format: 'webm',\n needsEncodingParams: false,\n };\n }\n\n // Fallback to WebM without codec spec\n if (MediaRecorder.isTypeSupported('audio/webm')) {\n return {\n mimeType: 'audio/webm',\n format: 'webm',\n needsEncodingParams: false,\n };\n }\n\n // Fallback to MP4 (for browsers that support MP4 but not WebM)\n if (MediaRecorder.isTypeSupported('audio/mp4')) {\n return {\n mimeType: 'audio/mp4',\n format: 'mp4',\n needsEncodingParams: false,\n };\n }\n\n // Last resort - let browser choose\n return {\n mimeType: '',\n format: 'webm',\n needsEncodingParams: true,\n };\n}\n\n/**\n * Callback for receiving audio chunks.\n */\nexport type AudioChunkCallback = (chunk: Blob) => void;\n\n/**\n * Audio capture manager with buffering support.\n *\n * Usage:\n * 1. Create instance with onChunk callback\n * 2. Call start() - immediately begins capturing\n * 3. Call setReady() when connection is established - flushes buffer\n * 4. Call stop() when done\n */\nexport class AudioCapture {\n private mediaStream: MediaStream | null = null;\n private recorder: MediaRecorder | null = null;\n private buffer: Blob[] = [];\n private isReady = false;\n private isRecording = false;\n private onChunk: AudioChunkCallback;\n private audioFormat: AudioFormat;\n private deviceId: string | undefined;\n\n /**\n * Time slice for MediaRecorder in milliseconds.\n *\n * Safari requires a larger timeslice (1000ms) to properly flush its internal\n * audio buffers. Smaller values cause Safari to drop or truncate audio data.\n * See: https://community.openai.com/t/whisper-problem-with-audio-mp4-blobs-from-safari/\n *\n * Other browsers (Chrome, Firefox, Edge) work well with smaller timeslices\n * which provide lower latency for real-time transcription.\n */\n private static readonly TIME_SLICE_MS = 100;\n private static readonly SAFARI_TIME_SLICE_MS = 1000;\n\n /**\n * @param onChunk - Callback for receiving audio chunks\n * @param deviceId - Optional audio device ID (empty string or undefined for system default)\n */\n constructor(onChunk: AudioChunkCallback, deviceId?: string) {\n this.onChunk = onChunk;\n this.audioFormat = getSupportedAudioFormat();\n this.deviceId = deviceId;\n }\n\n /**\n * Get the appropriate timeslice for the current browser.\n * Safari needs a larger timeslice to avoid dropping audio data.\n */\n private getTimeSlice(): number {\n return isSafari() ? AudioCapture.SAFARI_TIME_SLICE_MS : AudioCapture.TIME_SLICE_MS;\n }\n\n /**\n * Get the timeslice being used (in milliseconds).\n * Useful for callers that need to wait for audio processing.\n */\n getTimeSliceMs(): number {\n return this.getTimeSlice();\n }\n\n /**\n * Get the audio format being used.\n */\n getFormat(): AudioFormat {\n return this.audioFormat;\n }\n\n /**\n * Start capturing audio immediately.\n *\n * Audio chunks will be buffered until setReady() is called.\n */\n async start(): Promise<void> {\n const config = getConfig();\n\n if (this.isRecording) {\n if (config.debug) {\n console.log('[SpeechOS] AudioCapture already recording');\n }\n return;\n }\n\n // Reset state\n this.buffer = [];\n this.isReady = false;\n\n // Build constraints using deviceId from constructor\n const constraints: MediaStreamConstraints = {\n audio: {\n echoCancellation: true,\n noiseSuppression: true,\n ...(this.deviceId ? { deviceId: { exact: this.deviceId } } : {}),\n },\n };\n\n if (config.debug) {\n console.log('[SpeechOS] AudioCapture starting with format:', this.audioFormat.mimeType);\n console.log('[SpeechOS] Detected Safari:', isSafari());\n if (this.deviceId) {\n console.log('[SpeechOS] Using audio device:', this.deviceId);\n }\n }\n\n try {\n // Get microphone access\n this.mediaStream = await navigator.mediaDevices.getUserMedia(constraints);\n\n // Create MediaRecorder with detected format\n const recorderOptions: MediaRecorderOptions = {};\n if (this.audioFormat.mimeType) {\n recorderOptions.mimeType = this.audioFormat.mimeType;\n }\n\n this.recorder = new MediaRecorder(this.mediaStream, recorderOptions);\n\n // Handle audio data\n this.recorder.ondataavailable = (event) => {\n if (event.data && event.data.size > 0) {\n this.handleChunk(event.data);\n }\n };\n\n this.recorder.onerror = (event) => {\n console.error('[SpeechOS] MediaRecorder error:', event);\n };\n\n // Start recording with time slicing\n // Safari needs larger timeslice (1000ms) to avoid dropping audio\n const timeSlice = this.getTimeSlice();\n this.recorder.start(timeSlice);\n this.isRecording = true;\n\n if (config.debug) {\n console.log(`[SpeechOS] AudioCapture started with ${timeSlice}ms timeslice, buffering until ready`);\n }\n } catch (error) {\n // If specific device failed, try without device constraint\n if (this.deviceId && error instanceof Error) {\n console.warn('[SpeechOS] Selected device unavailable, trying default:', error.message);\n\n this.mediaStream = await navigator.mediaDevices.getUserMedia({\n audio: { echoCancellation: true, noiseSuppression: true },\n });\n\n const recorderOptions: MediaRecorderOptions = {};\n if (this.audioFormat.mimeType) {\n recorderOptions.mimeType = this.audioFormat.mimeType;\n }\n\n this.recorder = new MediaRecorder(this.mediaStream, recorderOptions);\n\n this.recorder.ondataavailable = (event) => {\n if (event.data && event.data.size > 0) {\n this.handleChunk(event.data);\n }\n };\n\n this.recorder.start(this.getTimeSlice());\n this.isRecording = true;\n } else {\n throw error;\n }\n }\n }\n\n /**\n * Handle an audio chunk with atomic buffer swap pattern.\n *\n * If not ready: buffer the chunk.\n * If ready: send directly via callback.\n */\n private handleChunk(chunk: Blob): void {\n if (this.isReady) {\n // Direct send mode\n this.onChunk(chunk);\n } else {\n // Buffer mode\n this.buffer.push(chunk);\n }\n }\n\n /**\n * Mark the capture as ready (connection established).\n *\n * This flushes any buffered chunks and switches to direct mode.\n * Uses atomic swap to prevent chunk reordering.\n */\n setReady(): void {\n const config = getConfig();\n\n if (this.isReady) {\n return;\n }\n\n // Atomic swap: grab buffer, clear it, then set ready flag\n const toFlush = this.buffer;\n this.buffer = [];\n\n // Flush all buffered chunks in order\n for (const chunk of toFlush) {\n this.onChunk(chunk);\n }\n\n // Now enable direct mode\n this.isReady = true;\n\n if (config.debug) {\n console.log(`[SpeechOS] AudioCapture ready, flushed ${toFlush.length} buffered chunks`);\n }\n }\n\n /**\n * Stop capturing audio and wait for final chunk.\n *\n * Uses requestData() before stop() to force the MediaRecorder to flush\n * any buffered audio immediately. This is critical for Safari which\n * may hold audio data in internal buffers.\n *\n * Safari requires an additional delay after stopping to ensure all audio\n * from its internal encoding pipeline has been fully processed and emitted.\n */\n async stop(): Promise<void> {\n const config = getConfig();\n const safari = isSafari();\n\n if (this.recorder && this.recorder.state !== 'inactive') {\n // Force flush any buffered audio before stopping\n // This is critical for Safari which may hold data in internal buffers\n if (this.recorder.state === 'recording') {\n try {\n // Set up promise to wait for the dataavailable event (event-driven)\n const dataPromise = new Promise<void>((resolve) => {\n const handler = (event: BlobEvent) => {\n this.recorder?.removeEventListener('dataavailable', handler);\n if (config.debug) {\n console.log(`[SpeechOS] requestData flush received: ${event.data.size} bytes`);\n }\n resolve();\n };\n this.recorder?.addEventListener('dataavailable', handler);\n });\n\n // requestData() forces an immediate dataavailable event with buffered data\n this.recorder.requestData();\n if (config.debug) {\n console.log('[SpeechOS] Requested data flush before stop');\n }\n\n // Wait for the actual dataavailable event\n await dataPromise;\n } catch (e) {\n // requestData() may not be supported on all browsers, continue anyway\n if (config.debug) {\n console.log('[SpeechOS] requestData() not supported or failed:', e);\n }\n }\n }\n\n // Create a promise that resolves when the recorder fully stops\n const stopPromise = new Promise<void>((resolve) => {\n if (!this.recorder) {\n resolve();\n return;\n }\n\n // onstop fires AFTER the final ondataavailable, so we wait for it\n this.recorder.onstop = () => {\n if (config.debug) {\n console.log('[SpeechOS] MediaRecorder onstop fired');\n }\n resolve();\n };\n });\n\n // Stop the recorder\n this.recorder.stop();\n\n // Wait for onstop event\n await stopPromise;\n\n // Safari needs extra time for its internal encoding pipeline to fully flush\n // Without this delay, the last portion of audio may be truncated\n if (safari) {\n if (config.debug) {\n console.log('[SpeechOS] Safari: waiting 2s for encoding pipeline to flush');\n }\n await new Promise((resolve) => setTimeout(resolve, 2000));\n }\n }\n\n // Now safe to clean up\n if (this.mediaStream) {\n for (const track of this.mediaStream.getTracks()) {\n track.stop();\n }\n this.mediaStream = null;\n }\n\n this.recorder = null;\n this.isRecording = false;\n this.isReady = false;\n this.buffer = [];\n\n if (config.debug) {\n console.log('[SpeechOS] AudioCapture stopped');\n }\n }\n\n /**\n * Check if currently recording.\n */\n get recording(): boolean {\n return this.isRecording;\n }\n\n /**\n * Check if ready (connection established, direct mode active).\n */\n get ready(): boolean {\n return this.isReady;\n }\n\n /**\n * Get the number of buffered chunks waiting to be sent.\n */\n get bufferedChunks(): number {\n return this.buffer.length;\n }\n}\n\n/**\n * Factory function to create an AudioCapture instance.\n * @param onChunk - Callback for receiving audio chunks\n * @param deviceId - Optional audio device ID (empty string or undefined for system default)\n */\nexport function createAudioCapture(onChunk: AudioChunkCallback, deviceId?: string): AudioCapture {\n return new AudioCapture(onChunk, deviceId);\n}\n","/**\n * WebSocket integration for SpeechOS SDK.\n *\n * Provides a direct WebSocket connection to the backend for voice sessions,\n * bypassing LiveKit for lower latency. Uses audio buffering to capture\n * audio immediately while the connection is being established.\n */\n\nimport type {\n CommandDefinition,\n CommandResult,\n ServerErrorMessage,\n ErrorSource,\n SpeechOSAction,\n VoiceSessionOptions,\n SessionSettings,\n WebSocketLike,\n} from './types.js';\nimport { getConfig, getAnonymousId } from './config.js';\nimport { events } from './events.js';\nimport { state } from './state.js';\nimport { AudioCapture, createAudioCapture, getSupportedAudioFormat } from './audio-capture.js';\n\n// Protocol message types (matching backend)\nconst MESSAGE_TYPE_AUTH = 'auth';\nconst MESSAGE_TYPE_READY = 'ready';\nconst MESSAGE_TYPE_TRANSCRIPTION = 'transcription';\nconst MESSAGE_TYPE_REQUEST_TRANSCRIPT = 'request_transcript';\nconst MESSAGE_TYPE_TRANSCRIPT = 'transcript';\nconst MESSAGE_TYPE_EDIT_TEXT = 'edit_text';\nconst MESSAGE_TYPE_EDITED_TEXT = 'edited_text';\nconst MESSAGE_TYPE_EXECUTE_COMMAND = 'execute_command';\nconst MESSAGE_TYPE_COMMAND_RESULT = 'command_result';\nconst MESSAGE_TYPE_ERROR = 'error';\n\n// WebSocket readyState constants (for use with WebSocketLike interface)\nconst WS_CONNECTING = 0;\nconst WS_OPEN = 1;\nconst WS_CLOSING = 2;\nconst WS_CLOSED = 3;\n\n/**\n * Response timeout in milliseconds.\n */\nconst RESPONSE_TIMEOUT_MS = 15000;\n\n/**\n * A deferred promise with timeout support.\n */\nexport class Deferred<T> {\n readonly promise: Promise<T>;\n private _resolve!: (value: T) => void;\n private _reject!: (error: Error) => void;\n private _timeoutId: ReturnType<typeof setTimeout> | null = null;\n private _settled = false;\n\n constructor() {\n this.promise = new Promise<T>((resolve, reject) => {\n this._resolve = resolve;\n this._reject = reject;\n });\n }\n\n setTimeout(ms: number, errorMessage: string, errorCode: string, errorSource: ErrorSource): void {\n this._timeoutId = setTimeout(() => {\n if (!this._settled) {\n console.error(`[SpeechOS] Error: ${errorMessage} (${errorCode})`);\n events.emit('error', {\n code: errorCode,\n message: errorMessage,\n source: errorSource,\n });\n this.reject(new Error(errorMessage));\n }\n }, ms);\n }\n\n resolve(value: T): void {\n if (!this._settled) {\n this._settled = true;\n this.clearTimeout();\n this._resolve(value);\n }\n }\n\n reject(error: Error): void {\n if (!this._settled) {\n this._settled = true;\n this.clearTimeout();\n this._reject(error);\n }\n }\n\n private clearTimeout(): void {\n if (this._timeoutId !== null) {\n clearTimeout(this._timeoutId);\n this._timeoutId = null;\n }\n }\n\n get isSettled(): boolean {\n return this._settled;\n }\n}\n\n/**\n * Maximum time to wait for WebSocket buffer to drain.\n */\nconst BUFFER_DRAIN_TIMEOUT_MS = 5000;\n\n/**\n * Polling interval for checking WebSocket buffer.\n */\nconst BUFFER_CHECK_INTERVAL_MS = 50;\n\n/**\n * WebSocket connection manager for voice sessions.\n */\nclass WebSocketManager {\n private ws: WebSocketLike | null = null;\n private audioCapture: AudioCapture | null = null;\n private sessionId: string | null = null;\n\n // Pending operations\n private pendingAuth: Deferred<void> | null = null;\n private pendingTranscript: Deferred<string> | null = null;\n private pendingEditText: Deferred<string> | null = null;\n private pendingCommand: Deferred<CommandResult | null> | null = null;\n\n // Track pending audio chunk sends (for waiting before transcript request)\n private pendingAudioSends: Set<Promise<void>> = new Set();\n\n // Track original text for edit operations\n private editOriginalText: string | null = null;\n\n // Track the last input text from command results\n private lastInputText: string | undefined = undefined;\n\n // Session parameters (set at start, used in auth message)\n private sessionAction: SpeechOSAction = 'dictate';\n private sessionInputText: string = '';\n private sessionCommands: CommandDefinition[] = [];\n private sessionSettings: SessionSettings = {};\n\n /**\n * Get the WebSocket URL for voice sessions.\n */\n private getWebSocketUrl(): string {\n const config = getConfig();\n const host = config.host || 'https://app.speechos.ai';\n\n // Convert HTTP(S) to WS(S)\n const wsUrl = host.replace(/^http/, 'ws');\n\n return `${wsUrl}/ws/voice/`;\n }\n\n /**\n * Start a voice session with the WebSocket backend.\n *\n * This method:\n * 1. Starts audio capture immediately (buffering)\n * 2. Opens WebSocket connection\n * 3. Authenticates with API key and action parameters\n * 4. Flushes buffered audio and continues streaming\n *\n * @param options - Session options including action type and parameters\n */\n async startVoiceSession(options?: VoiceSessionOptions): Promise<void> {\n const config = getConfig();\n\n // Store session parameters for auth message\n this.sessionAction = options?.action || 'dictate';\n this.sessionInputText = options?.inputText || '';\n this.sessionCommands = options?.commands || [];\n this.sessionSettings = options?.settings || {};\n\n // Also store for event emission\n if (this.sessionAction === 'edit') {\n this.editOriginalText = this.sessionInputText;\n }\n\n if (config.debug) {\n console.log('[SpeechOS] Starting WebSocket voice session...');\n }\n\n // Create audio capture that buffers until ready\n this.audioCapture = createAudioCapture(\n (chunk) => {\n this.sendAudioChunk(chunk);\n },\n this.sessionSettings.audioDeviceId\n );\n\n // Start capturing immediately (will buffer)\n await this.audioCapture.start();\n\n // Notify that mic is ready (recording to buffer)\n if (options?.onMicReady) {\n options.onMicReady();\n }\n\n state.setMicEnabled(true);\n\n // Connect to WebSocket\n const wsUrl = this.getWebSocketUrl();\n\n if (config.debug) {\n console.log('[SpeechOS] Connecting to WebSocket:', wsUrl);\n }\n\n // Prepare auth promise before creating WebSocket so early errors can reject it\n this.pendingAuth = new Deferred<void>();\n this.pendingAuth.setTimeout(\n RESPONSE_TIMEOUT_MS,\n 'Connection timed out',\n 'connection_timeout',\n 'connection'\n );\n\n // Use custom factory if provided (for extension CSP bypass), otherwise native WebSocket\n const factory = config.webSocketFactory ?? ((url: string) => new WebSocket(url));\n this.ws = factory(wsUrl);\n\n // Set up event handlers\n this.ws.onopen = () => {\n if (config.debug) {\n console.log('[SpeechOS] WebSocket connected, authenticating...');\n }\n this.authenticate();\n };\n\n this.ws.onmessage = (event) => {\n this.handleMessage(event.data);\n };\n\n this.ws.onerror = (event) => {\n // Check if connection was blocked (e.g., by CSP) - readyState will be CLOSED\n // without ever successfully connecting\n const isConnectionBlocked = this.ws?.readyState === WS_CLOSED;\n const errorCode = isConnectionBlocked ? 'connection_blocked' : 'websocket_error';\n const errorMessage = isConnectionBlocked\n ? \"This site's CSP blocks the extension. Try embedded mode instead.\"\n : 'WebSocket connection error';\n\n console.error('[SpeechOS] WebSocket error:', event, { isConnectionBlocked });\n events.emit('error', {\n code: errorCode,\n message: errorMessage,\n source: 'connection',\n });\n\n // Immediately reject pending auth to stop the connection attempt\n if (this.pendingAuth) {\n this.pendingAuth.reject(new Error(errorMessage));\n }\n };\n\n this.ws.onclose = (event) => {\n if (config.debug) {\n console.log('[SpeechOS] WebSocket closed:', event.code, event.reason);\n }\n state.setConnected(false);\n };\n\n await this.pendingAuth.promise;\n this.pendingAuth = null;\n\n // Now ready - flush buffered audio\n if (this.audioCapture) {\n this.audioCapture.setReady();\n }\n\n state.setConnected(true);\n\n if (config.debug) {\n console.log('[SpeechOS] WebSocket voice session ready');\n }\n }\n\n /**\n * Send authentication message with action parameters.\n * All session parameters are now sent upfront in the auth message.\n */\n private authenticate(): void {\n const config = getConfig();\n const audioFormat = getSupportedAudioFormat();\n const settings = this.sessionSettings;\n const anonymousId = getAnonymousId();\n\n const authMessage = {\n type: MESSAGE_TYPE_AUTH,\n api_key: config.apiKey,\n user_id: config.userId || null,\n anonymous_id: anonymousId,\n input_language: settings.inputLanguageCode ?? 'en-US',\n output_language: settings.outputLanguageCode ?? 'en-US',\n smart_format: settings.smartFormat ?? true,\n custom_vocabulary: settings.vocabulary ?? [],\n custom_snippets: settings.snippets ?? [],\n audio_format: audioFormat.format,\n // Action parameters (sent upfront in auth)\n action: this.sessionAction,\n input_text: this.sessionInputText,\n commands: this.sessionCommands,\n };\n\n if (config.debug) {\n console.log('[SpeechOS] Sending auth message with action:', this.sessionAction);\n }\n\n this.ws?.send(JSON.stringify(authMessage));\n }\n\n /**\n * Send an audio chunk over the WebSocket.\n * Tracks the promise so we can wait for all sends to complete.\n */\n private sendAudioChunk(chunk: Blob): void {\n const sendPromise = this.doSendAudioChunk(chunk);\n this.pendingAudioSends.add(sendPromise);\n sendPromise.finally(() => {\n this.pendingAudioSends.delete(sendPromise);\n });\n }\n\n /**\n * Actually send the audio chunk (async operation).\n */\n private async doSendAudioChunk(chunk: Blob): Promise<void> {\n if (this.ws && this.ws.readyState === WS_OPEN) {\n const arrayBuffer = await chunk.arrayBuffer();\n this.ws.send(arrayBuffer);\n }\n }\n\n /**\n * Handle incoming WebSocket messages.\n */\n private handleMessage(data: string): void {\n const config = getConfig();\n\n try {\n const message = JSON.parse(data);\n\n if (config.debug) {\n console.log('[SpeechOS] WebSocket message:', message);\n }\n\n switch (message.type) {\n case MESSAGE_TYPE_READY:\n this.handleReady(message);\n break;\n\n case MESSAGE_TYPE_TRANSCRIPTION:\n this.handleIntermediateTranscription(message);\n break;\n\n case MESSAGE_TYPE_TRANSCRIPT:\n this.handleFinalTranscript(message);\n break;\n\n case MESSAGE_TYPE_EDITED_TEXT:\n this.handleEditedText(message);\n break;\n\n case MESSAGE_TYPE_COMMAND_RESULT:\n this.handleCommandResult(message);\n break;\n\n case MESSAGE_TYPE_ERROR:\n this.handleError(message);\n break;\n\n default:\n if (config.debug) {\n console.log('[SpeechOS] Unknown message type:', message.type);\n }\n }\n } catch (error) {\n console.error('[SpeechOS] Failed to parse message:', error);\n }\n }\n\n private handleReady(message: { session_id: string }): void {\n const config = getConfig();\n\n this.sessionId = message.session_id;\n\n if (config.debug) {\n console.log('[SpeechOS] Session ready:', this.sessionId);\n }\n\n // Resolve auth promise\n if (this.pendingAuth) {\n this.pendingAuth.resolve();\n }\n }\n\n private handleIntermediateTranscription(message: {\n transcript: string;\n is_final: boolean;\n }): void {\n const config = getConfig();\n\n // Emit transcription:interim event for UI feedback (e.g., no-audio warning detection)\n events.emit('transcription:interim', {\n transcript: message.transcript,\n isFinal: message.is_final,\n });\n\n if (config.debug) {\n console.log(\n '[SpeechOS] Intermediate transcription:',\n message.transcript,\n 'final:',\n message.is_final\n );\n }\n }\n\n private handleFinalTranscript(message: { transcript: string }): void {\n const transcript = message.transcript || '';\n\n // Emit transcription:complete event\n events.emit('transcription:complete', { text: transcript });\n\n // Resolve pending promise\n if (this.pendingTranscript) {\n this.pendingTranscript.resolve(transcript);\n this.pendingTranscript = null;\n }\n }\n\n private handleEditedText(message: { text: string }): void {\n const editedText = message.text || '';\n\n // Emit edit:complete event\n events.emit('edit:complete', {\n text: editedText,\n originalText: this.editOriginalText || '',\n });\n\n // Resolve pending promise\n if (this.pendingEditText) {\n this.pendingEditText.resolve(editedText);\n this.pendingEditText = null;\n }\n\n this.editOriginalText = null;\n }\n\n private handleCommandResult(message: { command: CommandResult | null; transcript?: string }): void {\n const commandResult = message.command || null;\n\n // Store the input text (what the user said) if provided by the backend\n this.lastInputText = message.transcript;\n\n // Emit command:complete event\n events.emit('command:complete', { command: commandResult });\n\n // Resolve pending promise\n if (this.pendingCommand) {\n this.pendingCommand.resolve(commandResult);\n this.pendingCommand = null;\n }\n }\n\n private handleError(message: ServerErrorMessage): void {\n const errorCode = message.code || 'server_error';\n const errorMessage = message.message || 'A server error occurred';\n\n console.error(`[SpeechOS] Error: ${errorMessage} (${errorCode})`);\n\n events.emit('error', {\n code: errorCode,\n message: errorMessage,\n source: 'server',\n });\n\n // Reject any pending operations\n const error = new Error(errorMessage);\n if (this.pendingAuth) {\n this.pendingAuth.reject(error);\n this.pendingAuth = null;\n }\n if (this.pendingTranscript) {\n this.pendingTranscript.reject(error);\n this.pendingTranscript = null;\n }\n if (this.pendingEditText) {\n this.pendingEditText.reject(error);\n this.pendingEditText = null;\n }\n if (this.pendingCommand) {\n this.pendingCommand.reject(error);\n this.pendingCommand = null;\n }\n }\n\n /**\n * Stop the voice session and request the transcript.\n */\n async stopVoiceSession(): Promise<string> {\n const config = getConfig();\n\n if (config.debug) {\n console.log('[SpeechOS] Stopping voice session, requesting transcript...');\n }\n\n // Stop audio capture and wait for final chunk to be sent\n await this.stopAudioCapture();\n\n // Create deferred for transcript\n this.pendingTranscript = new Deferred<string>();\n this.pendingTranscript.setTimeout(\n RESPONSE_TIMEOUT_MS,\n 'Transcription timed out. Please try again.',\n 'transcription_timeout',\n 'timeout'\n );\n\n // Request transcript\n this.sendMessage({ type: MESSAGE_TYPE_REQUEST_TRANSCRIPT });\n\n const result = await this.pendingTranscript.promise;\n this.pendingTranscript = null;\n\n return result;\n }\n\n /**\n * Request text editing using the transcript as instructions.\n * Note: The input text was already sent in the auth message via startVoiceSession.\n */\n async requestEditText(_originalText: string): Promise<string> {\n const config = getConfig();\n\n if (config.debug) {\n console.log('[SpeechOS] Requesting text edit...');\n }\n\n // Stop audio capture and wait for final chunk\n await this.stopAudioCapture();\n\n // Create deferred for edited text\n this.pendingEditText = new Deferred<string>();\n this.pendingEditText.setTimeout(\n RESPONSE_TIMEOUT_MS,\n 'Edit request timed out. Please try again.',\n 'edit_timeout',\n 'timeout'\n );\n\n // Send edit request (params already sent in auth message)\n this.sendMessage({\n type: MESSAGE_TYPE_EDIT_TEXT,\n });\n\n const result = await this.pendingEditText.promise;\n this.pendingEditText = null;\n\n return result;\n }\n\n /**\n * Request command matching using the transcript as input.\n * Note: The command definitions were already sent in the auth message via startVoiceSession.\n */\n async requestCommand(_commands: CommandDefinition[]): Promise<CommandResult | null> {\n const config = getConfig();\n\n if (config.debug) {\n console.log('[SpeechOS] Requesting command match...');\n }\n\n // Stop audio capture and wait for final chunk\n await this.stopAudioCapture();\n\n // Create deferred for command result\n this.pendingCommand = new Deferred<CommandResult | null>();\n this.pendingCommand.setTimeout(\n RESPONSE_TIMEOUT_MS,\n 'Command request timed out. Please try again.',\n 'command_timeout',\n 'timeout'\n );\n\n // Send command request (params already sent in auth message)\n this.sendMessage({\n type: MESSAGE_TYPE_EXECUTE_COMMAND,\n });\n\n const result = await this.pendingCommand.promise;\n this.pendingCommand = null;\n\n return result;\n }\n\n /**\n * Stop audio capture and wait for all data to be sent.\n *\n * Waits for:\n * 1. All pending sendAudioChunk calls to complete (arrayBuffer conversion)\n * 2. WebSocket buffer to drain (all data transmitted)\n *\n * WebSocket message ordering ensures server receives all audio before transcript request.\n */\n private async stopAudioCapture(): Promise<void> {\n const config = getConfig();\n const startTime = Date.now();\n\n if (config.debug) {\n console.log('[SpeechOS] stopAudioCapture: starting...');\n }\n\n if (this.audioCapture) {\n await this.audioCapture.stop();\n this.audioCapture = null;\n if (config.debug) {\n console.log(`[SpeechOS] stopAudioCapture: recorder stopped after ${Date.now() - startTime}ms`);\n }\n }\n state.setMicEnabled(false);\n\n // Wait for all pending audio chunk sends to complete\n // This ensures all arrayBuffer() conversions finish and data is queued to WebSocket\n if (this.pendingAudioSends.size > 0) {\n if (config.debug) {\n console.log(`[SpeechOS] stopAudioCapture: waiting for ${this.pendingAudioSends.size} pending audio sends...`);\n }\n await Promise.all(this.pendingAudioSends);\n if (config.debug) {\n console.log(`[SpeechOS] stopAudioCapture: all sends complete after ${Date.now() - startTime}ms`);\n }\n } else if (config.debug) {\n console.log('[SpeechOS] stopAudioCapture: no pending sends');\n }\n\n // Wait for WebSocket buffer to drain (all audio data sent)\n await this.waitForBufferDrain();\n\n if (config.debug) {\n console.log(`[SpeechOS] stopAudioCapture: complete after ${Date.now() - startTime}ms`);\n }\n }\n\n /**\n * Wait for the WebSocket send buffer to drain.\n *\n * This ensures all audio data has been transmitted before we request\n * the transcript. Uses the same pattern as LiveKit's ReadableStream approach.\n */\n private async waitForBufferDrain(): Promise<void> {\n if (!this.ws || this.ws.readyState !== WS_OPEN) {\n return;\n }\n\n const config = getConfig();\n const startTime = Date.now();\n\n while (this.ws.bufferedAmount > 0) {\n if (Date.now() - startTime > BUFFER_DRAIN_TIMEOUT_MS) {\n console.warn(\n `[SpeechOS] Buffer drain timeout, ${this.ws.bufferedAmount} bytes still pending`\n );\n break;\n }\n await new Promise((resolve) => setTimeout(resolve, BUFFER_CHECK_INTERVAL_MS));\n }\n\n if (config.debug) {\n console.log(`[SpeechOS] Buffer drained in ${Date.now() - startTime}ms`);\n }\n }\n\n /**\n * Send a JSON message over the WebSocket.\n */\n private sendMessage(message: object): void {\n if (this.ws && this.ws.readyState === WS_OPEN) {\n this.ws.send(JSON.stringify(message));\n }\n }\n\n /**\n * Disconnect from the WebSocket.\n */\n async disconnect(): Promise<void> {\n const config = getConfig();\n\n if (config.debug) {\n console.log('[SpeechOS] Disconnecting WebSocket...');\n }\n\n // Stop audio capture (don't wait for final chunk on disconnect)\n await this.stopAudioCapture();\n\n // Close WebSocket\n if (this.ws) {\n this.ws.close();\n this.ws = null;\n }\n\n // Reject any pending operations\n const error = new Error('Disconnected');\n if (this.pendingAuth) {\n this.pendingAuth.reject(error);\n this.pendingAuth = null;\n }\n if (this.pendingTranscript) {\n this.pendingTranscript.reject(error);\n this.pendingTranscript = null;\n }\n if (this.pendingEditText) {\n this.pendingEditText.reject(error);\n this.pendingEditText = null;\n }\n if (this.pendingCommand) {\n this.pendingCommand.reject(error);\n this.pendingCommand = null;\n }\n\n // Reset state\n this.sessionId = null;\n this.editOriginalText = null;\n this.lastInputText = undefined;\n this.sessionSettings = {};\n\n state.setConnected(false);\n state.setMicEnabled(false);\n\n if (config.debug) {\n console.log('[SpeechOS] WebSocket disconnected');\n }\n }\n\n /**\n * Check if connected to WebSocket.\n */\n isConnected(): boolean {\n return this.ws !== null && this.ws.readyState === WS_OPEN;\n }\n\n /**\n * Get the last input text from a command result.\n * This is the raw transcript of what the user said.\n */\n getLastInputText(): string | undefined {\n return this.lastInputText;\n }\n}\n\n// Export singleton instance\nexport const websocket: WebSocketManager = new WebSocketManager();\n","/**\n * SpeechOS Core SDK\n *\n * Provides both low-level and high-level APIs for voice interaction.\n * This is the main entry point for headless usage of SpeechOS.\n */\n\nimport type {\n SpeechOSCoreConfig,\n CommandDefinition,\n CommandResult,\n VoiceSessionOptions,\n} from \"./types.js\";\nimport { setConfig, getConfig, resetConfig } from \"./config.js\";\nimport { livekit } from \"./livekit.js\";\nimport { websocket } from \"./websocket.js\";\nimport { state } from \"./state.js\";\nimport { events } from \"./events.js\";\n\n/**\n * Voice backend interface - common methods between LiveKit and WebSocket backends\n */\ninterface VoiceBackendInterface {\n startVoiceSession(options?: VoiceSessionOptions): Promise<void>;\n stopVoiceSession(): Promise<string>;\n requestEditText(originalText: string): Promise<string>;\n requestCommand(commands: CommandDefinition[]): Promise<CommandResult | null>;\n disconnect(): Promise<void>;\n}\n\n/**\n * Get the active voice backend (always websocket now)\n */\nfunction getBackend(): VoiceBackendInterface {\n // Always use websocket backend (livekit is legacy)\n return websocket;\n}\n\n/**\n * SpeechOS Core SDK\n *\n * Provides two API layers:\n * 1. Low-level API: Granular control over LiveKit connection lifecycle\n * 2. High-level API: One-shot methods for common voice tasks\n */\nclass SpeechOSCore {\n private initialized = false;\n\n /**\n * Initialize the SDK with configuration\n * @param config - Configuration options including apiKey\n */\n init(config: SpeechOSCoreConfig): void {\n setConfig(config);\n this.initialized = true;\n\n const currentConfig = getConfig();\n if (currentConfig.debug) {\n console.log(\"[SpeechOS] Initialized with config:\", {\n host: currentConfig.host,\n debug: currentConfig.debug,\n });\n }\n }\n\n /**\n * Check if the SDK is initialized\n */\n isInitialized(): boolean {\n return this.initialized;\n }\n\n // ============================================\n // Low-level API (granular control)\n // ============================================\n\n /**\n * Connect to LiveKit (fetches token, establishes connection)\n * Call this before other low-level methods\n */\n async connect(): Promise<void> {\n this.ensureInitialized();\n await livekit.connect();\n }\n\n /**\n * Wait until the agent is ready to receive audio\n * Resolves when the agent subscribes to our audio track\n */\n async waitUntilReady(): Promise<void> {\n return livekit.waitUntilReady();\n }\n\n /**\n * Enable microphone (user is now being recorded)\n */\n async enableMicrophone(): Promise<void> {\n await livekit.enableMicrophone();\n state.setRecordingState(\"recording\");\n }\n\n /**\n * Stop recording and get the transcript\n * @returns The transcribed text\n */\n async stopAndGetTranscript(): Promise<string> {\n state.setRecordingState(\"processing\");\n try {\n const transcript = await livekit.stopAndGetTranscript();\n state.completeRecording();\n return transcript;\n } catch (error) {\n state.setError(\n error instanceof Error ? error.message : \"Transcription failed\"\n );\n throw error;\n }\n }\n\n /**\n * Stop recording and get edited text\n * @param originalText - The original text to edit based on voice instructions\n * @returns The edited text\n */\n async stopAndEdit(originalText: string): Promise<string> {\n state.setRecordingState(\"processing\");\n try {\n const editedText = await livekit.stopAndEdit(originalText);\n state.completeRecording();\n return editedText;\n } catch (error) {\n state.setError(\n error instanceof Error ? error.message : \"Edit request failed\"\n );\n throw error;\n }\n }\n\n /**\n * Disconnect from LiveKit\n */\n async disconnect(): Promise<void> {\n await livekit.disconnect();\n state.completeRecording();\n }\n\n // ============================================\n // High-level API (convenience methods)\n // ============================================\n\n /**\n * One-shot dictation: connect, wait for agent, record, and get transcript\n * Automatically handles the full voice session lifecycle\n *\n * @returns The transcribed text\n */\n async dictate(): Promise<string> {\n this.ensureInitialized();\n\n state.setActiveAction(\"dictate\");\n state.startRecording();\n\n try {\n const backend = getBackend();\n\n // Start voice session with action=dictate\n await backend.startVoiceSession({\n action: \"dictate\",\n onMicReady: () => {\n // Transition to recording state as soon as mic is capturing\n state.setRecordingState(\"recording\");\n },\n });\n\n // User is now being recorded...\n // The caller should call stopDictation() when done\n // Or they can just await this if they want to handle it themselves\n\n // For this high-level API, we return immediately after setup\n // The UI should handle when to stop\n return new Promise<string>((resolve, reject) => {\n // Store resolvers for stopDictation to use\n this._dictateResolve = resolve;\n this._dictateReject = reject;\n });\n } catch (error) {\n state.setError(\n error instanceof Error ? error.message : \"Failed to start dictation\"\n );\n await this.cleanup();\n throw error;\n }\n }\n\n private _dictateResolve?: (transcript: string) => void;\n private _dictateReject?: (error: Error) => void;\n\n /**\n * Stop dictation and get the transcript\n * Call this after dictate() when user stops speaking\n */\n async stopDictation(): Promise<string> {\n state.setRecordingState(\"processing\");\n\n try {\n const backend = getBackend();\n const transcript = await backend.stopVoiceSession();\n\n state.completeRecording();\n\n // Resolve the dictate promise if it exists\n if (this._dictateResolve) {\n this._dictateResolve(transcript);\n this._dictateResolve = undefined;\n this._dictateReject = undefined;\n }\n\n return transcript;\n } catch (error) {\n const err =\n error instanceof Error ? error : new Error(\"Transcription failed\");\n state.setError(err.message);\n\n // Reject the dictate promise if it exists\n if (this._dictateReject) {\n this._dictateReject(err);\n this._dictateResolve = undefined;\n this._dictateReject = undefined;\n }\n\n throw err;\n } finally {\n await this.cleanup();\n }\n }\n\n /**\n * One-shot edit: connect, wait for agent, record voice instructions, apply to text\n * Automatically handles the full voice session lifecycle\n *\n * @param originalText - The text to edit\n * @returns The edited text\n */\n async edit(originalText: string): Promise<string> {\n this.ensureInitialized();\n\n state.setActiveAction(\"edit\");\n state.startRecording();\n this._editOriginalText = originalText;\n\n try {\n const backend = getBackend();\n\n // Start voice session with action=edit and inputText\n await backend.startVoiceSession({\n action: \"edit\",\n inputText: originalText,\n onMicReady: () => {\n // Transition to recording state as soon as mic is capturing\n state.setRecordingState(\"recording\");\n },\n });\n\n // Return a promise that will be resolved when stopEdit is called\n return new Promise<string>((resolve, reject) => {\n this._editResolve = resolve;\n this._editReject = reject;\n });\n } catch (error) {\n state.setError(\n error instanceof Error ? error.message : \"Failed to start edit\"\n );\n await this.cleanup();\n throw error;\n }\n }\n\n private _editOriginalText?: string;\n private _editResolve?: (editedText: string) => void;\n private _editReject?: (error: Error) => void;\n\n /**\n * Stop edit recording and get the edited text\n * Call this after edit() when user stops speaking\n */\n async stopEdit(): Promise<string> {\n state.setRecordingState(\"processing\");\n\n try {\n const backend = getBackend();\n const originalText = this._editOriginalText || \"\";\n const editedText = await backend.requestEditText(originalText);\n\n state.completeRecording();\n\n // Resolve the edit promise if it exists\n if (this._editResolve) {\n this._editResolve(editedText);\n this._editResolve = undefined;\n this._editReject = undefined;\n }\n\n return editedText;\n } catch (error) {\n const err =\n error instanceof Error ? error : new Error(\"Edit request failed\");\n state.setError(err.message);\n\n // Reject the edit promise if it exists\n if (this._editReject) {\n this._editReject(err);\n this._editResolve = undefined;\n this._editReject = undefined;\n }\n\n throw err;\n } finally {\n this._editOriginalText = undefined;\n await this.cleanup();\n }\n }\n\n /**\n * One-shot command: connect, wait for agent, record voice, match against commands\n * Automatically handles the full voice session lifecycle\n *\n * @param commands - Array of command definitions to match against\n * @returns The matched command result or null if no match\n */\n async command(commands: CommandDefinition[]): Promise<CommandResult | null> {\n this.ensureInitialized();\n\n state.setActiveAction(\"command\");\n state.startRecording();\n this._commandCommands = commands;\n\n try {\n const backend = getBackend();\n\n // Start voice session with action=command and command definitions\n await backend.startVoiceSession({\n action: \"command\",\n commands: commands,\n onMicReady: () => {\n // Transition to recording state as soon as mic is capturing\n state.setRecordingState(\"recording\");\n },\n });\n\n // Return a promise that will be resolved when stopCommand is called\n return new Promise<CommandResult | null>((resolve, reject) => {\n this._commandResolve = resolve;\n this._commandReject = reject;\n });\n } catch (error) {\n state.setError(\n error instanceof Error ? error.message : \"Failed to start command\"\n );\n await this.cleanup();\n throw error;\n }\n }\n\n private _commandCommands?: CommandDefinition[];\n private _commandResolve?: (result: CommandResult | null) => void;\n private _commandReject?: (error: Error) => void;\n\n /**\n * Stop command recording and get the matched command\n * Call this after command() when user stops speaking\n */\n async stopCommand(): Promise<CommandResult | null> {\n state.setRecordingState(\"processing\");\n\n try {\n const backend = getBackend();\n const commands = this._commandCommands || [];\n const result = await backend.requestCommand(commands);\n\n state.completeRecording();\n\n // Resolve the command promise if it exists\n if (this._commandResolve) {\n this._commandResolve(result);\n this._commandResolve = undefined;\n this._commandReject = undefined;\n }\n\n return result;\n } catch (error) {\n const err =\n error instanceof Error ? error : new Error(\"Command request failed\");\n state.setError(err.message);\n\n // Reject the command promise if it exists\n if (this._commandReject) {\n this._commandReject(err);\n this._commandResolve = undefined;\n this._commandReject = undefined;\n }\n\n throw err;\n } finally {\n this._commandCommands = undefined;\n await this.cleanup();\n }\n }\n\n /**\n * Cancel the current operation\n */\n async cancel(): Promise<void> {\n const err = new Error(\"Operation cancelled\");\n\n if (this._dictateReject) {\n this._dictateReject(err);\n this._dictateResolve = undefined;\n this._dictateReject = undefined;\n }\n\n if (this._editReject) {\n this._editReject(err);\n this._editResolve = undefined;\n this._editReject = undefined;\n }\n\n if (this._commandReject) {\n this._commandReject(err);\n this._commandResolve = undefined;\n this._commandReject = undefined;\n }\n\n this._editOriginalText = undefined;\n this._commandCommands = undefined;\n\n await this.cleanup();\n state.cancelRecording();\n }\n\n // ============================================\n // State and Events access\n // ============================================\n\n /**\n * Access the state manager for subscribing to state changes\n */\n get state(): typeof state {\n return state;\n }\n\n /**\n * Access the event emitter for listening to events\n */\n get events(): typeof events {\n return events;\n }\n\n /**\n * Get the current config\n */\n getConfig(): SpeechOSCoreConfig {\n return getConfig();\n }\n\n // ============================================\n // Private helpers\n // ============================================\n\n private ensureInitialized(): void {\n if (!this.initialized) {\n throw new Error(\n \"SpeechOS not initialized. Call speechOS.init({ apiKey: ... }) first.\"\n );\n }\n }\n\n private async cleanup(): Promise<void> {\n try {\n const backend = getBackend();\n await backend.disconnect();\n } catch (error) {\n // Ignore disconnect errors during cleanup\n const config = getConfig();\n if (config.debug) {\n console.warn(\"[SpeechOS] Cleanup disconnect error:\", error);\n }\n }\n }\n\n /**\n * Reset the SDK (useful for testing)\n */\n reset(): void {\n this.initialized = false;\n this._dictateResolve = undefined;\n this._dictateReject = undefined;\n this._editResolve = undefined;\n this._editReject = undefined;\n this._editOriginalText = undefined;\n this._commandResolve = undefined;\n this._commandReject = undefined;\n this._commandCommands = undefined;\n resetConfig();\n state.reset();\n events.clear();\n }\n}\n\n// Export singleton instance\nexport const speechOS: SpeechOSCore = new SpeechOSCore();\n","/**\n * Backend abstraction for voice sessions.\n *\n * Provides a unified interface for voice backends.\n * Currently always uses WebSocket backend.\n */\n\nimport type { CommandDefinition, CommandResult, VoiceSessionOptions } from './types.js';\nimport { livekit } from './livekit.js';\nimport { websocket } from './websocket.js';\n\n/**\n * Voice backend interface - common methods between backends\n */\nexport interface VoiceBackend {\n startVoiceSession(options?: VoiceSessionOptions): Promise<void>;\n stopVoiceSession(): Promise<string>;\n requestEditText(originalText: string): Promise<string>;\n requestCommand(commands: CommandDefinition[]): Promise<CommandResult | null>;\n disconnect(): Promise<void>;\n isConnected(): boolean;\n\n /** Get the last input text (transcript) from a command result */\n getLastInputText?(): string | undefined;\n\n // LiveKit-specific methods (available on both, but may be no-ops on websocket)\n prefetchToken?(): Promise<unknown>;\n startAutoRefresh?(): void;\n stopAutoRefresh?(): void;\n invalidateTokenCache?(): void;\n}\n\n/**\n * LiveKit backend adapter - wraps the livekit module to match the VoiceBackend interface\n * @internal Legacy backend, kept for potential future use\n */\nconst livekitBackend: VoiceBackend = {\n startVoiceSession: (options) => livekit.startVoiceSession(options),\n stopVoiceSession: () => livekit.stopVoiceSession(),\n requestEditText: (text) => livekit.requestEditText(text),\n requestCommand: (commands) => livekit.requestCommand(commands),\n disconnect: () => livekit.disconnect(),\n isConnected: () => livekit.isConnected(),\n prefetchToken: () => livekit.prefetchToken(),\n startAutoRefresh: () => livekit.startAutoRefresh(),\n stopAutoRefresh: () => livekit.stopAutoRefresh(),\n invalidateTokenCache: () => livekit.invalidateTokenCache(),\n};\n\n/**\n * WebSocket backend adapter - wraps the websocket module to match the VoiceBackend interface\n */\nconst websocketBackend: VoiceBackend = {\n startVoiceSession: (options) => websocket.startVoiceSession(options),\n stopVoiceSession: () => websocket.stopVoiceSession(),\n requestEditText: (text) => websocket.requestEditText(text),\n requestCommand: (commands) => websocket.requestCommand(commands),\n disconnect: () => websocket.disconnect(),\n isConnected: () => websocket.isConnected(),\n getLastInputText: () => websocket.getLastInputText(),\n // No-op methods for LiveKit-specific features\n prefetchToken: () => Promise.resolve({}),\n startAutoRefresh: () => {},\n stopAutoRefresh: () => {},\n invalidateTokenCache: () => {},\n};\n\n/**\n * Get the active voice backend.\n * Always returns WebSocket backend (LiveKit is legacy).\n *\n * @returns The websocket backend\n */\nexport function getBackend(): VoiceBackend {\n return websocketBackend;\n}\n\n/**\n * Check if the current backend is LiveKit.\n * @deprecated Always returns false - LiveKit is legacy\n */\nexport function isLiveKitBackend(): boolean {\n return false;\n}\n\n/**\n * Check if the current backend is WebSocket.\n * @deprecated Always returns true - WebSocket is the only backend\n */\nexport function isWebSocketBackend(): boolean {\n return true;\n}\n\n// Keep livekitBackend reference to prevent unused variable warning\nvoid livekitBackend;\n","/**\n * @speechos/core\n *\n * Headless core SDK for SpeechOS - state management, events, and backend integration.\n * No DOM dependencies - can be used in any JavaScript environment.\n */\n\n// Main SDK class\nexport { speechOS } from \"./speechos.js\";\n\n// Core modules\nexport { events, SpeechOSEventEmitter } from \"./events.js\";\nexport { state, createStateManager } from \"./state.js\";\nexport {\n getConfig,\n setConfig,\n resetConfig,\n updateUserId,\n validateConfig,\n DEFAULT_HOST,\n} from \"./config.js\";\nexport { livekit, Deferred } from \"./livekit.js\";\nexport { websocket } from \"./websocket.js\";\nexport { getBackend } from \"./backend.js\";\nexport type { VoiceBackend } from \"./backend.js\";\n\n// Types\nexport type {\n SpeechOSCoreConfig,\n SpeechOSState,\n SpeechOSAction,\n SpeechOSEventMap,\n StateChangeCallback,\n UnsubscribeFn,\n RecordingState,\n LiveKitTokenResponse,\n ServerErrorMessage,\n ErrorSource,\n UserVocabularyData,\n CommandArgument,\n CommandDefinition,\n CommandResult,\n SessionSettings,\n VoiceSessionOptions,\n WebSocketLike,\n WebSocketFactory,\n} from \"./types.js\";\n\n// Version\nexport const VERSION = \"0.1.0\";\n"],"mappings":"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"}