# `tts` ElevenLabs implementation of `@suaveplan/voice`'s `VoiceSynthesizer` interface. Posts to `/v1/text-to-speech/{voice_id}` for buffered synthesis and the `/stream` variant for incremental streaming. _(no submodules)_ ## Purpose Application code that depends on `@suaveplan/voice/VoiceSynthesizer` should be portable across providers. This adapter is the seam between the abstract domain interface and ElevenLabs's REST API: it maps the `SynthesisRequest` shape onto the wire body, mirrors voice settings into ElevenLabs's `voice_settings` envelope, validates output formats against the subset ElevenLabs supports, and remaps every ElevenLabs error onto the corresponding `@suaveplan/voice` domain error. ## Features - Implements `synthesize(request)` for buffered audio and `synthesizeStream(request)` for an incremental `ReadableStream`. - Eight ElevenLabs-supported `AudioFormat` values are passed through with explicit mapping; unsupported formats throw `AudioFormatUnsupportedError` client-side before the request is sent. - Voice settings (stability, similarity_boost, style, use_speaker_boost) are mapped from the camelCase names to the wire snake_case names with all four optional. - Latency mode 0–4 is forwarded as `optimize_streaming_latency`. - Pronunciation dictionary IDs are wrapped into the `pronunciation_dictionary_locators` envelope ElevenLabs expects. - Errors are remapped: 401 → `ProviderNotConfiguredError`, 429 → `ProviderRateLimitedError` (with `retryAfterMs`), `STREAM.INTERRUPTED` → `TtsStreamInterruptedError`, everything else → `TtsSynthesisFailedError`. ## Usage: Basic Example ```ts import { createElevenLabsAdapters, } from "@suaveplan/third-party-elevenlabs"; const { synthesizer } = createElevenLabsAdapters({ apiKey: process.env.ELEVENLABS_KEY!, }); const result = await synthesizer.synthesize({ text: "hello world", voiceId: "21m00Tcm4TlvDq8ikWAM", }); await Bun.write("hello.mp3", result.audio); ``` ## Usage: Advanced Example ```ts import { ElevenLabsClient, ElevenLabsVoiceSynthesizer, } from "@suaveplan/third-party-elevenlabs"; const client = new ElevenLabsClient({ apiKey: KEY }); const synth = new ElevenLabsVoiceSynthesizer(client); const stream = synth.synthesizeStream({ text: "long passage…", voiceId: "abc", modelId: "eleven_multilingual_v2", languageCode: "en", outputFormat: "pcm_44100", latencyMode: 3, voiceSettings: { stability: 0.4, similarityBoost: 0.6, style: 0.1, speakerBoost: true, }, previousText: "Once upon a time …", nextText: "and they lived happily.", seed: 42, pronunciationDictionaryIds: ["dict-en-1"], }); const reader = stream.getReader(); while (true) { const { done, value } = await reader.read(); if (done) break; sink.write(value); } ``` ## API Reference - `class ElevenLabsVoiceSynthesizer implements VoiceSynthesizer` - `providerName: "elevenlabs"` - `synthesize(request: SynthesisRequest): Promise` — `result.audio` is the raw bytes; `format` is the requested format (defaults to `mp3_44100_128`); `requestId` and `charactersUsed` are populated from response headers when present. - `synthesizeStream(request: SynthesisRequest): ReadableStream` — errors during the stream are surfaced via stream `controller.error`. Throws (sync from `synthesizeStream` if format is unsupported, async otherwise): `AudioFormatUnsupportedError`, `ProviderNotConfiguredError`, `ProviderRateLimitedError`, `TtsSynthesisFailedError`, `TtsStreamInterruptedError`. ## Implementation Notes - The default `outputFormat` for the result is `mp3_44100_128` — this is ElevenLabs's server-side default and matches the bytes you'll actually receive when no format is requested. - `voice_id` is `encodeURIComponent`-escaped because some workspace IDs contain spaces. - Format mapping is a static record rather than a passthrough so that adding a new format requires an explicit code change, not a silent wire-passthrough that ElevenLabs might or might not accept. - The streaming path constructs the `ReadableStream` itself instead of returning the raw response body so that error remapping can happen inside `controller.error` rather than as a synchronous throw.