# ElevenLabs

> Text-to-speech with ElevenLabsTTSService (multi-context WebSocket), ElevenLabsHttpTTSService (HTTP streaming) and ElevenLabsDialogueTTSService (Text to Dialogue), all with word timestamps.

Source: https://polvallverdu.github.io/pipedog/docs/services/tts/elevenlabs/



## Overview [#overview]

ElevenLabs turns text into speech with its Flash, Turbo, Multilingual and v3 models. pipedog
ports all three of Pipecat's ElevenLabs text-to-speech services:

* **`ElevenLabsTTSService`**: streams over ElevenLabs' multi-context WebSocket API
  (`multi-stream-input`), with word-level timestamps and per-turn audio contexts.
* **`ElevenLabsHttpTTSService`**: posts each request to the `stream/with-timestamps` HTTP
  endpoint and streams the audio and alignment back.
* **`ElevenLabsDialogueTTSService`**: streams over ElevenLabs' multi-context Text to Dialogue
  WebSocket, which serves the Eleven v3 and v4 dialogue models.

<Cards>
  <Card title="Upstream page" href="https://docs.pipecat.ai/api-reference/server/services/tts/elevenlabs">
    Pipecat's documentation for the same services.
  </Card>

  <Card title="Source" href="https://github.com/polvallverdu/pipedog/tree/main/packages/elevenlabs/src">
    `packages/elevenlabs/src/tts.ts`, `dialogue/tts.ts` and `tts_base.ts`.
  </Card>

  <Card title="ElevenLabs docs" href="https://elevenlabs.io/docs/api-reference/text-to-speech">
    Voices, models and voice settings.
  </Card>
</Cards>

## Installation [#installation]

```bash
bun add @pipedog/elevenlabs
```

```ts
import { ElevenLabsDialogueTTSService } from "@pipedog/elevenlabs/dialogue/tts";
import { ElevenLabsHttpTTSService, ElevenLabsTTSService } from "@pipedog/elevenlabs/tts";
```

## Prerequisites [#prerequisites]

* An ElevenLabs account and an API key from the
  [ElevenLabs dashboard](https://elevenlabs.io/app/settings/api-keys).
* A voice id from the ElevenLabs voice library. There is no default voice.
* Pass the key as `apiKey`. The examples read it from `ELEVENLABS_API_KEY`.

## ElevenLabsTTSService [#elevenlabsttsservice]

### Configuration [#configuration]

| Option | Type | Required | Description |
|---|---|---|---|
| `url` | `string \| undefined` | no | WebSocket URL for ElevenLabs TTS API. Defaults to "wss://api.elevenlabs.io". |
| `autoMode` | `boolean \| null \| undefined` | no | Whether to enable ElevenLabs' auto mode, which reduces latency by disabling server-side chunk scheduling and buffering. Recommended when sending complete sentences or phrases. When null (default), auto mode is enabled for `SENTENCE` aggregation and disabled for `TOKEN` aggregation — because token streaming relies on the server-side chunk scheduler to accumulate enough text for natural-sounding synthesis. |
| `enableSsmlParsing` | `boolean \| null \| undefined` | no | Whether to parse SSML tags in text. |
| `settings` | `ElevenLabsTTSSettings \| null \| undefined` | no | Runtime-updatable settings. Its given fields override the defaults (model "eleven_flash_v2_5"). |
| `name` | `string \| null \| undefined` | no | Custom name; defaults to `ClassName#count`. |
| `taskManager` | `BaseTaskManager \| null \| undefined` | no | Task manager for handling tasks. |
| `enableDirectMode` | `boolean \| undefined` | no | Process frames immediately instead of using internal queues. |
| `metrics` | `FrameProcessorMetrics \| null \| undefined` | no | Optional metrics collector for this processor. |
| `reconnectBackoffMinWait` | `number \| undefined` | no | Minimum time, in seconds, to wait between reconnection attempts. |
| `reconnectBackoffMaxWait` | `number \| undefined` | no | Maximum time, in seconds, to wait between reconnection attempts. |
| `reconnectOnError` | `boolean \| undefined` | no | Whether to automatically reconnect on connection errors. |
| `wsCloseTimeout` | `number \| undefined` | no | Maximum time, in seconds, to wait for the peer to acknowledge the websocket closing handshake before dropping the connection. Applied to connections opened through `_websocketConnect()`. Increase it for peers that need longer to complete a graceful close. |
| `wsSendTimeout` | `number \| null \| undefined` | no | Maximum time, in seconds, a single send may wait on a peer that has stopped reading before the connection is dropped and the send throws `ConnectionClosedError`. Applied to connections opened through `_websocketConnect()`. Pass `null` to wait indefinitely. Defaults to `WS_SEND_TIMEOUT`. |
| `textAggregationMode` | `TextAggregationMode \| null \| undefined` | no | How to aggregate incoming text before synthesis. `TextAggregationMode.SENTENCE` (default) buffers until sentence boundaries, `TextAggregationMode.TOKEN` streams tokens directly for lower latency. |
| `pushTextFrames` | `boolean \| undefined` | no | Whether to push TextFrames and LLMFullResponseEndFrames. |
| `pushStopFrames` | `boolean \| undefined` | no | Whether to automatically push TTSStoppedFrames. |
| `pushStartFrame` | `boolean \| undefined` | no | Whether to automatically create audio contexts and push TTSStartedFrames. When true, the base class handles `createAudioContext` and yields `TTSStartedFrame` before each synthesis call, so `runTts` implementations do not need to. |
| `stopFrameTimeoutS` | `number \| undefined` | no | Idle time before pushing TTSStoppedFrame when pushStopFrames is true. |
| `pushSilenceAfterStop` | `boolean \| undefined` | no | Whether to push silence audio after TTSStoppedFrame. |
| `silenceTimeS` | `number \| undefined` | no | Duration of silence to push when pushSilenceAfterStop is true. |
| `pauseFrameProcessing` | `boolean \| undefined` | no | Whether to hold incoming frames after a turn until its audio contexts end. Needed by services whose audio doesn't carry its request's context ID. |
| `maxConsecutiveZeroAudioContexts` | `number \| undefined` | no | How many consecutive TTS contexts may complete without producing any audio before the service is reported unable to do its job. Catches a provider that accepts requests and stays silent — an unknown voice ID, say — which no error ever surfaces. Every silent context reports an error the service can carry on from, whatever the limit is; on reaching the limit the service reports a permanent error instead, stops being given work, and the pipeline worker applies its `ProcessorUnusablePolicy`. Set to 0 to report silent contexts without ever writing the service off. |
| `appendTrailingSpace` | `boolean \| undefined` | no | Whether to append a trailing space to text before sending to TTS. This helps prevent some TTS services from vocalizing trailing punctuation (e.g., "dot"). Only applied in sentence aggregation mode; when streaming tokens, the incoming text's own whitespace is preserved. |
| `sampleRate` | `number \| null \| undefined` | no | Output sample rate for generated audio. |
| `skipTextTypes` | `string[] \| null \| undefined` | no | List of text types that should not be spoken. |
| `textTransforms` | `[string, TextTransform][] \| null \| undefined` | no | A list of TextTransforms to transform text just before sending it to TTS. Each TextTransform takes the aggregated text and its type, and returns the transformed text. To register, provide a list of tuples of `[textType \| "*", transformFunction]`. |
| `transportDestination` | `string \| null \| undefined` | no | Destination for generated audio frames. |
| `reuseContextIdWithinTurn` | `boolean \| undefined` | no | Whether the service should reuse context IDs within the same turn. |
| `audioCache` | `TTSAudioCacheStore \| null \| undefined` | no | Where to keep synthesized audio for reuse (e.g. a shared `TTSAudioCache`). Text whose audio is in the cache is served from it, with no request to the provider. Lines get there through `warmAudioCache()`, and also from live synthesis when `cacheSynthesizedAudio` allows it. Only services that yield their audio from `runTts` (HTTP and streaming-RPC services, see `supportsAudioCache`) use it, and only in sentence aggregation mode. Defaults to null (no cache). pipedog only. |
| `cacheSynthesizedAudio` | `boolean \| ((text: string) => boolean) \| undefined` | no | Whether audio synthesized live is stored in `audioCache`, or a function that decides per text. Defaults to false: only warmed lines are stored, since a generated reply can repeat what the user said. pipedog only. |
| `audioCacheNamespace` | `string \| undefined` | no | Extra part of the cache key, for constructor options that change the audio but are not in `settings`. pipedog only. |
| `apiKey` | `string` | yes | ElevenLabs API key for authentication. |
| `enableLogging` | `boolean \| null \| undefined` | no | Whether to enable ElevenLabs server-side logging. |

### Settings [#settings]

Runtime-updatable settings, passed as `settings: new ElevenLabsTTSService.Settings({...})`. Any
setting you leave out keeps the default below. `null` means the field is not sent, so ElevenLabs
uses its own default.

| Setting                  | Type                              | Default               | Description                                                        |
| ------------------------ | --------------------------------- | --------------------- | ------------------------------------------------------------------ |
| `model`                  | `string`                          | `"eleven_flash_v2_5"` | ElevenLabs model id.                                               |
| `voice`                  | `string \| null`                  | `null`                | Voice id. Set one: there is no default.                            |
| `language`               | `Language \| null`                | `null`                | Synthesis language. Sent only to models that take a language code. |
| `stability`              | `number \| null`                  | `null`                | Voice stability, 0.0 to 1.0.                                       |
| `similarityBoost`        | `number \| null`                  | `null`                | Similarity boost, 0.0 to 1.0.                                      |
| `style`                  | `number \| null`                  | `null`                | Style exaggeration, 0.0 to 1.0.                                    |
| `useSpeakerBoost`        | `boolean \| null`                 | `null`                | Speaker boost.                                                     |
| `speed`                  | `number \| null`                  | `null`                | Speaking speed, 0.7 to 1.2.                                        |
| `applyTextNormalization` | `"auto" \| "on" \| "off" \| null` | `null`                | Text normalization mode.                                           |

### Usage [#usage]

```ts title="tts.ts"
import { TTSUpdateSettingsFrame } from "@pipedog/core/frames/frames";
import { TextAggregationMode } from "@pipedog/core/services/tts_service";
import { Language } from "@pipedog/core/transcriptions/language";
import { ElevenLabsHttpTTSService, ElevenLabsTTSService } from "@pipedog/elevenlabs/tts";

const tts = new ElevenLabsTTSService({
  apiKey: process.env.ELEVENLABS_API_KEY ?? "",
  settings: new ElevenLabsTTSService.Settings({
    voice: "your-voice-id",
    model: "eleven_flash_v2_5",
    language: Language.EN,
    stability: 0.5,
    similarityBoost: 0.75,
  }),
});
```

Token streaming:

```ts
// Stream LLM tokens instead of whole sentences. autoMode then defaults to false.
const streaming = new ElevenLabsTTSService({
  apiKey: process.env.ELEVENLABS_API_KEY ?? "",
  textAggregationMode: TextAggregationMode.TOKEN,
  settings: new ElevenLabsTTSService.Settings({ voice: "your-voice-id" }),
});
```

Change settings mid-call with a `TTSUpdateSettingsFrame`:

```ts
// Queue it on the worker, or push it from a processor upstream of the service.
// A voice-character change (stability, speed, ...) starts a new audio context;
// a voice, model or language change reconnects.
const update = new TTSUpdateSettingsFrame({
  delta: new ElevenLabsTTSService.Settings({ speed: 1.1 }),
});

```

### Event handlers [#event-handlers]

Inherited from `TTSService`:

| Event                 | Description                                     |
| --------------------- | ----------------------------------------------- |
| `on_connected`        | Connected to ElevenLabs.                        |
| `on_disconnected`     | Disconnected from ElevenLabs.                   |
| `on_connection_error` | The connection failed.                          |
| `on_tts_request`      | A text chunk is about to be sent for synthesis. |

### Notes [#notes]

* The service does not push the LLM's text frames itself. It builds `TTSTextFrame`s from
  ElevenLabs' word alignment, so the context only records words the user actually heard.
* Text is aggregated into sentences by default. `autoMode` left `null` is `true` in sentence
  mode and `false` in token mode, where ElevenLabs' chunk scheduler needs to gather enough text.
* Changing `voice`, `model` or `language` reconnects, since they are part of the WebSocket URL.
  Changing `stability`, `similarityBoost`, `style`, `useSpeakerBoost` or `speed` closes the
  current audio context, so the next one opens with the new voice settings.
* `language` is checked against the model's language list. A model that takes no language code,
  or doesn't cover the language, gets none, and this is logged.
* The output format is chosen from the sample rate: 8000, 16000, 22050, 24000, 32000, 44100 or
  48000 Hz. Any other rate falls back to `pcm_24000` with a warning.
* `pronunciationTransformIpa()` renders IPA as SSML `<phoneme>` tags. Only `eleven_flash_v2`
  and `eleven_turbo_v2` read them, and this service also needs `enableSsmlParsing: true`.
  Otherwise the transform is skipped.
* Other settings changes (such as `applyTextNormalization`) are not applied mid-call and are
  logged as unhandled, unless a URL change reconnects at the same time.

## ElevenLabsHttpTTSService [#elevenlabshttpttsservice]

### Configuration [#configuration-1]

| Option | Type | Required | Description |
|---|---|---|---|
| `apiKey` | `string` | yes | ElevenLabs API key for authentication. |
| `fetch` | `((input: RequestInfo \| URL, init?: RequestInit) => Promise<Response>) \| null \| undefined` | no | `fetch` used for HTTP requests. Defaults to the global `fetch`. DIVERGENCE: replaces upstream's required `aiohttp_session`. |
| `baseUrl` | `string \| undefined` | no | Base URL for ElevenLabs HTTP API. Defaults to "https://api.elevenlabs.io". |
| `enableLogging` | `boolean \| null \| undefined` | no | Whether to enable ElevenLabs server-side logging. Set to false for zero retention mode (enterprise only). |
| `settings` | `ElevenLabsHttpTTSSettings \| null \| undefined` | no | Runtime-updatable settings. Its given fields override the defaults (model "eleven_flash_v2_5"). |
| `name` | `string \| null \| undefined` | no | Custom name; defaults to `ClassName#count`. |
| `taskManager` | `BaseTaskManager \| null \| undefined` | no | Task manager for handling tasks. |
| `enableDirectMode` | `boolean \| undefined` | no | Process frames immediately instead of using internal queues. |
| `metrics` | `FrameProcessorMetrics \| null \| undefined` | no | Optional metrics collector for this processor. |
| `textAggregationMode` | `TextAggregationMode \| null \| undefined` | no | How to aggregate incoming text before synthesis. `TextAggregationMode.SENTENCE` (default) buffers until sentence boundaries, `TextAggregationMode.TOKEN` streams tokens directly for lower latency. |
| `pushTextFrames` | `boolean \| undefined` | no | Whether to push TextFrames and LLMFullResponseEndFrames. |
| `pushStopFrames` | `boolean \| undefined` | no | Whether to automatically push TTSStoppedFrames. |
| `pushStartFrame` | `boolean \| undefined` | no | Whether to automatically create audio contexts and push TTSStartedFrames. When true, the base class handles `createAudioContext` and yields `TTSStartedFrame` before each synthesis call, so `runTts` implementations do not need to. |
| `stopFrameTimeoutS` | `number \| undefined` | no | Idle time before pushing TTSStoppedFrame when pushStopFrames is true. |
| `pushSilenceAfterStop` | `boolean \| undefined` | no | Whether to push silence audio after TTSStoppedFrame. |
| `silenceTimeS` | `number \| undefined` | no | Duration of silence to push when pushSilenceAfterStop is true. |
| `pauseFrameProcessing` | `boolean \| undefined` | no | Whether to hold incoming frames after a turn until its audio contexts end. Needed by services whose audio doesn't carry its request's context ID. |
| `maxConsecutiveZeroAudioContexts` | `number \| undefined` | no | How many consecutive TTS contexts may complete without producing any audio before the service is reported unable to do its job. Catches a provider that accepts requests and stays silent — an unknown voice ID, say — which no error ever surfaces. Every silent context reports an error the service can carry on from, whatever the limit is; on reaching the limit the service reports a permanent error instead, stops being given work, and the pipeline worker applies its `ProcessorUnusablePolicy`. Set to 0 to report silent contexts without ever writing the service off. |
| `appendTrailingSpace` | `boolean \| undefined` | no | Whether to append a trailing space to text before sending to TTS. This helps prevent some TTS services from vocalizing trailing punctuation (e.g., "dot"). Only applied in sentence aggregation mode; when streaming tokens, the incoming text's own whitespace is preserved. |
| `sampleRate` | `number \| null \| undefined` | no | Output sample rate for generated audio. |
| `skipTextTypes` | `string[] \| null \| undefined` | no | List of text types that should not be spoken. |
| `textTransforms` | `[string, TextTransform][] \| null \| undefined` | no | A list of TextTransforms to transform text just before sending it to TTS. Each TextTransform takes the aggregated text and its type, and returns the transformed text. To register, provide a list of tuples of `[textType \| "*", transformFunction]`. |
| `transportDestination` | `string \| null \| undefined` | no | Destination for generated audio frames. |
| `reuseContextIdWithinTurn` | `boolean \| undefined` | no | Whether the service should reuse context IDs within the same turn. |
| `audioCache` | `TTSAudioCacheStore \| null \| undefined` | no | Where to keep synthesized audio for reuse (e.g. a shared `TTSAudioCache`). Text whose audio is in the cache is served from it, with no request to the provider. Lines get there through `warmAudioCache()`, and also from live synthesis when `cacheSynthesizedAudio` allows it. Only services that yield their audio from `runTts` (HTTP and streaming-RPC services, see `supportsAudioCache`) use it, and only in sentence aggregation mode. Defaults to null (no cache). pipedog only. |
| `cacheSynthesizedAudio` | `boolean \| ((text: string) => boolean) \| undefined` | no | Whether audio synthesized live is stored in `audioCache`, or a function that decides per text. Defaults to false: only warmed lines are stored, since a generated reply can repeat what the user said. pipedog only. |
| `audioCacheNamespace` | `string \| undefined` | no | Extra part of the cache key, for constructor options that change the audio but are not in `settings`. pipedog only. |

### Settings [#settings-1]

| Setting                    | Type                              | Default               | Description                             |
| -------------------------- | --------------------------------- | --------------------- | --------------------------------------- |
| `model`                    | `string`                          | `"eleven_flash_v2_5"` | ElevenLabs model id.                    |
| `voice`                    | `string \| null`                  | `null`                | Voice id. Set one: there is no default. |
| `language`                 | `Language \| null`                | `null`                | Synthesis language.                     |
| `optimizeStreamingLatency` | `number \| null`                  | `null`                | Latency optimization level, 0 to 4.     |
| `stability`                | `number \| null`                  | `null`                | Voice stability, 0.0 to 1.0.            |
| `similarityBoost`          | `number \| null`                  | `null`                | Similarity boost, 0.0 to 1.0.           |
| `style`                    | `number \| null`                  | `null`                | Style exaggeration, 0.0 to 1.0.         |
| `useSpeakerBoost`          | `boolean \| null`                 | `null`                | Speaker boost.                          |
| `speed`                    | `number \| null`                  | `null`                | Speaking speed, 0.25 to 4.0.            |
| `applyTextNormalization`   | `"auto" \| "on" \| "off" \| null` | `null`                | Text normalization mode.                |

### Usage [#usage-1]

```ts title="http.ts"
const httpTts = new ElevenLabsHttpTTSService({
  apiKey: process.env.ELEVENLABS_API_KEY ?? "",
  settings: new ElevenLabsHttpTTSService.Settings({
    voice: "your-voice-id",
    optimizeStreamingLatency: 2,
    speed: 1.1,
  }),
});
```

### Notes [#notes-1]

* Word timestamps come from the `with-timestamps` endpoint, so this service also emits
  `TTSTextFrame`s aligned with playback.
* Earlier text in the turn is sent as `previous_text`, for continuity between requests.
* `enableLogging: false` turns on ElevenLabs' zero retention mode (enterprise only).
* IPA pronunciations (`pronunciationTransformIpa()`) work with `eleven_flash_v2` and
  `eleven_turbo_v2` only.

## ElevenLabsDialogueTTSService [#elevenlabsdialoguettsservice]

### Configuration [#configuration-2]

| Option | Type | Required | Description |
|---|---|---|---|
| `url` | `string \| undefined` | no | WebSocket URL for the ElevenLabs API. Defaults to "wss://api.elevenlabs.io". |
| `seed` | `number \| null \| undefined` | no | Seed for reproducible generation. |
| `settings` | `ElevenLabsDialogueTTSSettings \| null \| undefined` | no | Runtime-updatable settings. Its given fields override the defaults (model "eleven_v4_turbo"). |
| `name` | `string \| null \| undefined` | no | Custom name; defaults to `ClassName#count`. |
| `taskManager` | `BaseTaskManager \| null \| undefined` | no | Task manager for handling tasks. |
| `enableDirectMode` | `boolean \| undefined` | no | Process frames immediately instead of using internal queues. |
| `metrics` | `FrameProcessorMetrics \| null \| undefined` | no | Optional metrics collector for this processor. |
| `reconnectBackoffMinWait` | `number \| undefined` | no | Minimum time, in seconds, to wait between reconnection attempts. |
| `reconnectBackoffMaxWait` | `number \| undefined` | no | Maximum time, in seconds, to wait between reconnection attempts. |
| `reconnectOnError` | `boolean \| undefined` | no | Whether to automatically reconnect on connection errors. |
| `wsCloseTimeout` | `number \| undefined` | no | Maximum time, in seconds, to wait for the peer to acknowledge the websocket closing handshake before dropping the connection. Applied to connections opened through `_websocketConnect()`. Increase it for peers that need longer to complete a graceful close. |
| `wsSendTimeout` | `number \| null \| undefined` | no | Maximum time, in seconds, a single send may wait on a peer that has stopped reading before the connection is dropped and the send throws `ConnectionClosedError`. Applied to connections opened through `_websocketConnect()`. Pass `null` to wait indefinitely. Defaults to `WS_SEND_TIMEOUT`. |
| `textAggregationMode` | `TextAggregationMode \| null \| undefined` | no | How to aggregate incoming text before synthesis. `TextAggregationMode.SENTENCE` (default) buffers until sentence boundaries, `TextAggregationMode.TOKEN` streams tokens directly for lower latency. |
| `pushTextFrames` | `boolean \| undefined` | no | Whether to push TextFrames and LLMFullResponseEndFrames. |
| `pushStopFrames` | `boolean \| undefined` | no | Whether to automatically push TTSStoppedFrames. |
| `pushStartFrame` | `boolean \| undefined` | no | Whether to automatically create audio contexts and push TTSStartedFrames. When true, the base class handles `createAudioContext` and yields `TTSStartedFrame` before each synthesis call, so `runTts` implementations do not need to. |
| `stopFrameTimeoutS` | `number \| undefined` | no | Idle time before pushing TTSStoppedFrame when pushStopFrames is true. |
| `pushSilenceAfterStop` | `boolean \| undefined` | no | Whether to push silence audio after TTSStoppedFrame. |
| `silenceTimeS` | `number \| undefined` | no | Duration of silence to push when pushSilenceAfterStop is true. |
| `pauseFrameProcessing` | `boolean \| undefined` | no | Whether to hold incoming frames after a turn until its audio contexts end. Needed by services whose audio doesn't carry its request's context ID. |
| `maxConsecutiveZeroAudioContexts` | `number \| undefined` | no | How many consecutive TTS contexts may complete without producing any audio before the service is reported unable to do its job. Catches a provider that accepts requests and stays silent — an unknown voice ID, say — which no error ever surfaces. Every silent context reports an error the service can carry on from, whatever the limit is; on reaching the limit the service reports a permanent error instead, stops being given work, and the pipeline worker applies its `ProcessorUnusablePolicy`. Set to 0 to report silent contexts without ever writing the service off. |
| `appendTrailingSpace` | `boolean \| undefined` | no | Whether to append a trailing space to text before sending to TTS. This helps prevent some TTS services from vocalizing trailing punctuation (e.g., "dot"). Only applied in sentence aggregation mode; when streaming tokens, the incoming text's own whitespace is preserved. |
| `sampleRate` | `number \| null \| undefined` | no | Output sample rate for generated audio. |
| `skipTextTypes` | `string[] \| null \| undefined` | no | List of text types that should not be spoken. |
| `textTransforms` | `[string, TextTransform][] \| null \| undefined` | no | A list of TextTransforms to transform text just before sending it to TTS. Each TextTransform takes the aggregated text and its type, and returns the transformed text. To register, provide a list of tuples of `[textType \| "*", transformFunction]`. |
| `transportDestination` | `string \| null \| undefined` | no | Destination for generated audio frames. |
| `reuseContextIdWithinTurn` | `boolean \| undefined` | no | Whether the service should reuse context IDs within the same turn. |
| `audioCache` | `TTSAudioCacheStore \| null \| undefined` | no | Where to keep synthesized audio for reuse (e.g. a shared `TTSAudioCache`). Text whose audio is in the cache is served from it, with no request to the provider. Lines get there through `warmAudioCache()`, and also from live synthesis when `cacheSynthesizedAudio` allows it. Only services that yield their audio from `runTts` (HTTP and streaming-RPC services, see `supportsAudioCache`) use it, and only in sentence aggregation mode. Defaults to null (no cache). pipedog only. |
| `cacheSynthesizedAudio` | `boolean \| ((text: string) => boolean) \| undefined` | no | Whether audio synthesized live is stored in `audioCache`, or a function that decides per text. Defaults to false: only warmed lines are stored, since a generated reply can repeat what the user said. pipedog only. |
| `audioCacheNamespace` | `string \| undefined` | no | Extra part of the cache key, for constructor options that change the audio but are not in `settings`. pipedog only. |
| `apiKey` | `string` | yes | ElevenLabs API key for authentication. |
| `enableLogging` | `boolean \| null \| undefined` | no | Whether to enable ElevenLabs server-side logging. |

### Settings [#settings-2]

Runtime-updatable settings, passed as `settings: new ElevenLabsDialogueTTSService.Settings({...})`.

| Setting     | Type               | Default             | Description                                                                 |
| ----------- | ------------------ | ------------------- | --------------------------------------------------------------------------- |
| `model`     | `string`           | `"eleven_v4_turbo"` | An `eleven_v3` or `eleven_v4` model. Any other model logs a warning.        |
| `voice`     | `string \| null`   | `null`              | Voice id. Registered per context, so a change only opens a new context.     |
| `language`  | `Language \| null` | `null`              | Synthesis language, sent as `language_code` where the model supports it.    |
| `stability` | `number \| null`   | `null`              | Voice stability, 0.0 to 1.0. The only voice setting Text to Dialogue reads. |

A `model` or `language` change reconnects the WebSocket. A `voice` or `stability` change closes the
current audio context, and the next one registers the new values.

### Usage [#usage-2]

```ts title="dialogue.ts"
import { ElevenLabsDialogueTTSService } from "@pipedog/elevenlabs/dialogue/tts";

// Eleven v3 and v4 dialogue models. Needs a voice id.
const dialogue = new ElevenLabsDialogueTTSService({
  apiKey: process.env.ELEVENLABS_API_KEY ?? "",
  settings: new ElevenLabsDialogueTTSService.Settings({
    voice: "your-voice-id",
    model: "eleven_v4_turbo",
    stability: 0.5,
  }),
});
```

### Notes [#notes-2]

* Text is always aggregated by sentence, and a trailing space is added to each input, because the
  server joins inputs verbatim. A `textAggregationMode` other than `SENTENCE` is ignored with a
  warning.
* Eleven v3 and v4 perform inline audio tags such as `[laughs]`. They come back as spoken
  characters in the alignment, so they reach the LLM context unless a text transform removes them.
* A keepalive context is registered when the socket opens, because the server drops a connection
  that is idle for 20 seconds and rejects a keepalive that names no registered context. Set a
  `voice` so it can be registered.
* `pronunciationTransformIpa()` renders pronunciations as IPA between slashes, which Eleven v3
  reads.
* `seed` makes generation reproducible.

## Runtime notes [#runtime-notes]

* `ElevenLabsTTSService` uses the global `WebSocket` and sends the key as an `xi-api-key` header
  on the handshake. That works on Bun, Node and Deno, and on Cloudflare Workers, where the socket
  is opened through `fetch`. Upstream sets a 16 MB message size limit; the standard `WebSocket`
  has none.
* `ElevenLabsDialogueTTSService` uses the same WebSocket and `xi-api-key` header as
  `ElevenLabsTTSService`, so it runs wherever that does.
* `ElevenLabsHttpTTSService` uses `fetch`. Upstream's required `aiohttp_session` is replaced by
  an optional `fetch` option (the global `fetch` by default).
* Not ported: the deprecated `PronunciationDictionaryLocator`, the `InputParams` classes and the
  `voiceId`, `model`, `params`, `pronunciationDictionaryLocators` and `aggregateSentences`
  constructor options. Use `settings` and `textAggregationMode`.

| Runtime            | Works | What it needs |
| ------------------ | ----- | ------------- |
| Bun                | ✅ yes | Nothing.      |
| Node.js            | ✅ yes | Nothing.      |
| Deno               | ✅ yes | Nothing.      |
| Cloudflare Workers | ✅ yes | Nothing.      |

Generated from `pipecat.runtimes` and `pipecat.compat` in `packages/elevenlabs/package.json`: ✅ means the compat smoke test passes on that runtime.
