import { VowelRoute, VowelActionDefinition, VowelVoiceConfig, VowelClientConfig, ActionHandler } from '../types'; import { RouterAdapter } from '../types/types'; import { ToolResult, VoiceSessionState, SessionManager } from '../managers'; import { FloatingCursorManager } from '../managers/FloatingCursorManager'; import { ActionNotification } from './action-notifier'; /** * Main Vowel client class * * @example * ```ts * // vowel.client.ts * import { Vowel } from '@/lib/vowel'; * import { tanstackRouterAdapter } from '@/lib/vowel/adapters'; * import { router } from './router'; * * export const vowel = new Vowel({ * apiKey: 'vkey_public_xxx', * router: tanstackRouterAdapter(router), * routes: [ * { path: '/products', description: 'Browse products' }, * { path: '/cart', description: 'View cart' } * ] * }); * * // Register custom actions * vowel.registerAction('searchProducts', { * description: 'Search for products', * parameters: { * query: { type: 'string', description: 'Search query' } * } * }, async (params) => { * await searchProducts(params.query); * }); * ``` */ export declare class Vowel { private config; private toolManager; private stateManager; private audioManager; private sessionManager; private typingSoundManager?; private floatingCursorManager?; private borderGlowManager?; private actionPillManager?; private actionNotifier; private _routes; private navigationAdapter?; private automationAdapter?; private transcriptEventListeners; private legacyRouter?; private darkModeManager?; private context; private webMCPRegistrations; constructor(config: VowelClientConfig); /** * Initialize WebMCP tool discovery * Discovers WebMCP tools registered in the browser and registers them as Vowel actions */ private initializeWebMCPDiscovery; /** * Discover and re-register WebMCP tools * Use this to manually re-discover WebMCP tools after page changes * * @returns Promise that resolves when discovery is complete * * @example * ```ts * // Re-discover WebMCP tools * await vowel.rediscoverWebMCPTools(); * ``` */ rediscoverWebMCPTools(): Promise; /** * Expose a Vowel action as a WebMCP tool * * @param name - Action name * @param definition - Action definition * @param handler - Action handler */ private exposeActionAsWebMCP; /** * Clean up all WebMCP tool registrations */ private cleanupWebMCPTools; /** * Register built-in actions based on available adapters */ private registerBuiltInActions; /** * Get app ID */ get appId(): string | undefined; /** * Get token issuer identifier alias */ get apiKey(): string | undefined; /** * Get router adapter (legacy) * @deprecated Use navigationAdapter property instead */ get router(): RouterAdapter | undefined; /** * Get configured routes */ get routes(): VowelRoute[]; /** * Get session manager (for internal use by action handlers) */ get session(): SessionManager; /** * Get voice configuration */ get voiceConfig(): VowelVoiceConfig | undefined; /** * Get system instruction override */ get systemInstructionOverride(): string | undefined; /** * Get current session state */ get state(): VoiceSessionState; /** * Check if user is currently speaking (client-side VAD) */ isUserSpeaking(): boolean; /** * Check if AI is currently thinking/processing */ isAIThinking(): boolean; /** * Check if AI is currently speaking */ isAISpeaking(): boolean; /** * Get floating cursor manager (if enabled) */ get floatingCursor(): FloatingCursorManager | undefined; /** * Register a custom action that the AI can perform * * ⚠️ CRITICAL: Actions MUST be registered BEFORE calling startSession()! * Actions registered after the session starts will have NO EFFECT. * Tool definitions are sent to the server during session initialization. * * @param name - Action name (will be used as tool name) * @param definition - Action definition with parameters * @param handler - Function to execute when action is called * * @example * ```ts * // ✅ CORRECT - Register before starting session * vowel.registerAction('addToCart', { * description: 'Add product to shopping cart', * parameters: { * productId: { * type: 'string', * description: 'Product ID' * }, * quantity: { * type: 'number', * description: 'Quantity', * optional: true * } * } * }, async ({ productId, quantity = 1 }) => { * await addToCart(productId, quantity); * }); * * // Then start the session * await vowel.startSession(); * * // ❌ WRONG - Too late! * await vowel.startSession(); * vowel.registerAction('addToCart', ...); // Won't work! * ``` */ registerAction(name: string, definition: VowelActionDefinition, handler: ActionHandler): void; /** * Register multiple actions at once * * @example * ```ts * vowel.registerActions({ * searchProducts: { * definition: { ... }, * handler: async (params) => { ... } * }, * addToCart: { * definition: { ... }, * handler: async (params) => { ... } * } * }); * ``` */ registerActions(actions: Record): void; /** * Unregister an action */ unregisterAction(name: string): void; /** * Get all registered actions as a record (for configuration) */ getActionsConfig(): Record; onActionNotification(listener: (notification: ActionNotification) => void): () => void; /** * Execute a registered action */ executeAction(name: string, params: any): Promise; /** * Check if an action is registered */ hasAction(name: string): boolean; /** * Add routes (useful for dynamic route registration) */ addRoutes(routes: VowelRoute[]): void; /** * Set routes (replaces existing) */ setRoutes(routes: VowelRoute[]): void; /** * Get current path */ getCurrentPath(): string; /** * Navigate to a path */ navigate(path: string): Promise; /** * Subscribe to state changes */ onStateChange(listener: (state: VoiceSessionState) => void): () => void; /** * @internal * Subscribe to transcript events for caption system (unofficial dev tool) * * @param listener - Callback function to receive transcript events * @returns Unsubscribe function */ onTranscriptEvent(listener: (event: { type: 'delta' | 'done'; text: string; role: 'user' | 'assistant'; responseId?: string; itemId?: string; }) => void): () => void; /** * Get client configuration (read-only access) * @internal */ getConfig(): VowelClientConfig; /** * Clear transcript history */ clearTranscripts(): void; /** * Clear the current error state */ clearError(): void; /** * Get current dark mode state */ getDarkMode(): boolean; /** * Set dark mode state */ setDarkMode(isDark: boolean): void; /** * Toggle dark mode */ toggleDarkMode(): void; /** * Export conversation state for later restoration * * @param options - Export options * @param options.maxTurns - Maximum conversation turns to include (default: all) * @returns Serializable state object * * @example * ```ts * // Save state to localStorage * const state = vowel.exportState({ maxTurns: 20 }); * localStorage.setItem('vowel-conversation', JSON.stringify(state)); * ``` */ exportState(options?: { maxTurns?: number; }): VoiceSessionState; /** * Import conversation state from a previous session * * @param savedState - Previously exported state * * @example * ```ts * // Restore state from localStorage * const saved = localStorage.getItem('vowel-conversation'); * if (saved) { * vowel.importState(JSON.parse(saved)); * } * ``` */ importState(savedState: Partial): void; /** * Start a voice session * Content window should be opened before calling this (in click handler for Shopify) * Navigation is handled via BroadcastChannel, not WindowProxy * * ⚠️ CRITICAL for iOS Safari: This method MUST be called directly from a user gesture * handler (click, touchstart, touchend). iOS Safari requires AudioContext creation and * getUserMedia() calls to happen within the same user gesture event handler. * * ✅ Correct usage: * ```ts * button.addEventListener('click', async () => { * await vowel.startSession(); // Called directly from click handler * }); * ``` * * ❌ Incorrect usage (will fail on iOS): * ```ts * button.addEventListener('click', () => { * setTimeout(() => { * vowel.startSession(); // Too late - outside gesture handler * }, 100); * }); * ``` * * @param options - Session start options * @param options.restoreState - Previously exported state to restore conversation context * * @example * ```ts * // Start fresh session * await vowel.startSession(); * * // Start with restored context * const saved = localStorage.getItem('vowel-conversation'); * if (saved) { * await vowel.startSession({ restoreState: JSON.parse(saved) }); * } * ``` */ startSession(options?: { restoreState?: Partial; }): Promise; /** * Send a notification to the AI about an app event * This allows programmatic triggering of AI responses * * @param eventType - Type of event (e.g., 'notification', 'resource_issue', 'session_start') * @param message - Human-readable message describing the event * @param data - Optional additional data/context * * @example * ```ts * // Notify AI about a game event * vowel.notify('resource_issue', 'Low on wood', { wood: 5, required: 20 }); * * // Notify about session start * vowel.notify('session_start', 'Voice control is now active'); * ``` */ notify(eventType: string, message: string, data?: Record): Promise; /** * Perform cleanup of audio, VAD, and UI resources * Called both on manual stopSession() and on unexpected WebSocket close * @private */ private performCleanup; /** * Stop the current session */ stopSession(): Promise; /** * Pause the current session (mute microphone, keep connection alive) * This is useful for temporarily stopping audio input without disconnecting * * @example * ```ts * // Pause during a phone call * await vowel.pauseSession(); * * // Resume after the call * await vowel.resumeSession(); * ``` */ pauseSession(): Promise; /** * Resume a paused session (unmute microphone) * * @example * ```ts * await vowel.resumeSession(); * ``` */ resumeSession(): Promise; /** * Toggle session on/off */ toggleSession(): Promise; /** * Get available microphone devices * * @param requirePermission - If true, request getUserMedia first to get device labels. * On iOS Safari, this MUST be called from a user gesture handler. * @returns Promise resolving to array of MediaDeviceInfo for available audio input devices * * @example * ```ts * const devices = await vowel.getAvailableMicrophones(); * console.log('Available microphones:', devices.map(d => d.label)); * ``` */ getAvailableMicrophones(requirePermission?: boolean): Promise; /** * Check if microphone permission has been granted * @returns Promise resolving to true if permission granted, false otherwise */ hasMicrophonePermission(): Promise; /** * Request microphone permission * MUST be called from a user gesture handler on iOS Safari * @returns Promise resolving to true if permission granted, false otherwise */ requestMicrophonePermission(): Promise; /** * Get the currently active microphone device * * @returns MediaDeviceInfo for current device, or null if not available * * @example * ```ts * const currentMic = vowel.getCurrentMicrophone(); * if (currentMic) { * console.log('Current microphone:', currentMic.label); * } * ``` */ getCurrentMicrophone(): MediaDeviceInfo | null; /** * Set microphone device preference * This will apply the device on the next microphone setup (next connection) * * @param deviceId - The device ID to use * * @example * ```ts * const devices = await vowel.getAvailableMicrophones(); * const usbMic = devices.find(d => d.label.includes('USB')); * if (usbMic) { * vowel.setMicrophoneDevice(usbMic.deviceId); * } * ``` */ setMicrophoneDevice(deviceId: string): void; /** * Switch microphone device during an active session * This will reinitialize the microphone stream with the new device * * @param deviceId - The device ID to switch to * * @example * ```ts * const devices = await vowel.getAvailableMicrophones(); * const newMic = devices[1]; * await vowel.switchMicrophoneDevice(newMic.deviceId); * ``` */ switchMicrophoneDevice(deviceId: string): Promise; /** * Notify the AI about an app event * This allows programmatic triggering of AI voice responses without user speech input * * @param eventDetails - Description of the event that occurred * @param context - Optional context object to provide additional information * * @example * ```ts * // Simple notification * await vowel.notifyEvent('Order placed successfully!'); * * // Notification with context * await vowel.notifyEvent('New message received', { * from: 'John Doe', * preview: 'Hey, are you available?', * timestamp: new Date().toISOString() * }); * * // Timer expiry notification * await vowel.notifyEvent('Your 5-minute timer has expired'); * * // Shopping cart update * await vowel.notifyEvent('Item added to cart', { * productName: 'Wireless Headphones', * price: 79.99, * cartTotal: 3 * }); * ``` */ notifyEvent(eventDetails: string, context?: Record): Promise; /** * Send text to the AI for processing * Lower-level method for custom text-based interactions * * @param text - Text to send to the AI * * @example * ```ts * // Ask a question programmatically * await vowel.sendText('What are the current promotions?'); * * // Provide context to the AI * await vowel.sendText('The user is looking at product ID 12345'); * ``` */ sendText(text: string): Promise; /** * Send an image to the AI for processing * Enables multimodal interactions where the AI can see and respond to images * * @param imageUrl - URL or data URI of the image to send * * @example * ```ts * // Send an image URL * await vowel.sendImage('https://example.com/product.jpg'); * * // Send a data URI (e.g., from canvas or file upload) * await vowel.sendImage('data:image/png;base64,iVBORw0KGgoAAAANS...'); * * // Combined with text context * await vowel.sendText('What do you see in this image?'); * await vowel.sendImage(imageUrl); * ``` */ sendImage(imageUrl: string): Promise; /** * Update the dynamic context that gets appended to system prompt. * Context is stringified, wrapped in `` tags and sent via session.update. * * When context changes, a session.update event is automatically sent * to update the system prompt so the AI knows about the current context. * * @param context - Context object to append to system prompt. Use null to clear. * * @example * ```ts * // Update context with current page info * vowel.updateContext({ page: 'product', productId: 'iphone-15-pro' }); * * // Update with multiple details * vowel.updateContext({ * page: 'checkout', * cartTotal: 199.99, * itemCount: 2 * }); * * // Clear context * vowel.updateContext(null); * ``` */ updateContext(context: Record | null): void; /** * Get current context value * * @returns Current context object (null if no context set) * * @example * ```ts * const currentContext = vowel.getContext(); * console.log('Current context:', currentContext); * ``` */ getContext(): Record | null; /** * Handle Session messages from Gemini Live */ private handleMessage; } export type { ActionHandler }; export type VowelConfig = VowelClientConfig; //# sourceMappingURL=VowelClient.d.ts.map