UNPKG

ai

Version:

AI SDK by Vercel - build apps like ChatGPT, Claude, Gemini, and more with a single interface for any model using the Vercel AI Gateway or go direct to OpenAI, Anthropic, Google, or any other model provider.

82 lines (75 loc) • 2.5 kB
import { generateId } from '@ai-sdk/provider-utils'; import type { RealtimeClientEvent, RealtimeServerEvent, } from '../types/realtime-model'; /** Attempt-local ACK correlation; unresolved commands are never evicted. */ export class RealtimeCommandTracker { private readonly pending = new Map<string, { muted?: boolean }>(); private readonly recent = new Set<string>(); constructor( private readonly sendCommand: (event: RealtimeClientEvent) => Promise<void>, ) {} validateId(id: string): void { if (this.pending.has(id) || this.recent.has(id)) throw new Error( 'Realtime command ID was already used; retry with a fresh eventId', ); if (this.pending.size >= 512) throw new Error('Too many pending realtime commands'); } private complete(id: string): { muted?: boolean } | undefined { const command = this.pending.get(id); if (command == null) return; this.pending.delete(id); this.recent.add(id); if (this.recent.size > 4096) { const oldest = this.recent.values().next().value; if (oldest != null) this.recent.delete(oldest); } return command; } send(event: RealtimeClientEvent): Promise<void> { const muted = event.type === 'input-audio-mute' ? true : event.type === 'input-audio-unmute' ? false : undefined; if ( event.type === 'input-audio-mute' || event.type === 'input-audio-unmute' ) event = { ...event, eventId: event.eventId ?? generateId() }; const id = 'eventId' in event ? event.eventId : undefined; if (id != null) { this.validateId(id); this.pending.set(id, { muted }); } const awaitsAcknowledgement = event.type === 'context-append' || event.type === 'session-update' || muted != null; try { const sent = this.sendCommand(event); void sent.then( () => { if (id != null && !awaitsAcknowledgement) this.complete(id); }, () => { if (id != null) this.complete(id); }, ); return sent; } catch (error) { if (id != null) this.complete(id); throw error; } } receive(event: RealtimeServerEvent): { muted?: boolean } | undefined { if (event.type === 'command-acknowledged' && event.clientEventId != null) return this.complete(event.clientEventId); if (event.type === 'error' && event.clientEventId != null) this.complete(event.clientEventId); } }