# Deepgram

> Text-to-speech with DeepgramTTSService (Aura over WebSocket), DeepgramHttpTTSService (Aura over HTTP) and DeepgramFluxTTSService (Flux).

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



## Overview [#overview]

pipedog ports three of Pipecat's Deepgram text-to-speech services:

* **`DeepgramTTSService`**: Deepgram Aura over a WebSocket (`/v1/speak`). Streams audio as text
  arrives and clears Deepgram's buffer on interruption.
* **`DeepgramHttpTTSService`**: Deepgram Aura over HTTP. One `POST` per sentence, with the audio
  streamed back in the response body.
* **`DeepgramFluxTTSService`**: Deepgram Flux over a WebSocket (`/v2/speak`). Streams LLM tokens
  as they arrive and keeps acoustic state across turns on one connection.

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

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

  <Card title="Deepgram docs" href="https://developers.deepgram.com/docs/tts-models">
    Aura and Flux voices and voice controls.
  </Card>
</Cards>

## Installation [#installation]

```bash
bun add @pipedog/deepgram
```

```ts
import { DeepgramHttpTTSService, DeepgramTTSService } from "@pipedog/deepgram/tts";
import { DeepgramFluxTTSService } from "@pipedog/deepgram/flux/tts";
```

## Prerequisites [#prerequisites]

* A Deepgram account and an API key from the [Deepgram console](https://console.deepgram.com).
  Flux needs a key with Flux TTS access.
* Pass the key as `apiKey`. The examples read it from `DEEPGRAM_API_KEY`.

## DeepgramTTSService [#deepgramttsservice]

### Configuration [#configuration]

| Option | Type | Required | Description |
|---|---|---|---|
| `apiKey` | `string` | yes | Deepgram API key for authentication. |
| `baseUrl` | `string \| undefined` | no | WebSocket base URL for Deepgram API. Defaults to "wss://api.deepgram.com". |
| `encoding` | `string \| undefined` | no | Audio encoding format. Defaults to "linear16". Must be one of SUPPORTED_ENCODINGS. |
| `mipOptOut` | `boolean \| null \| undefined` | no | Opt out of the Deepgram Model Improvement Program. See https://dpgr.am/deepgram-mip for pricing impacts before setting to true. |
| `settings` | `DeepgramTTSSettings \| null \| undefined` | no | Runtime-updatable settings. |
| `webSocketFactory` | `((uri: string, options: { headers?: Record<string, string>; protocols?: string[]; }) => WebSocket \| Promise<WebSocket>) \| null \| undefined` | no | Creates the underlying `WebSocket`, to open it some other way (through a proxy, say). Defaults to `websocketConnect`'s, which sends the header on every runtime, Cloudflare Workers included. Not in upstream. |
| `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. |

### Settings [#settings]

Runtime-updatable settings, passed as `settings: new DeepgramTTSService.Settings({...})`. Any
setting you leave out keeps the default below. `DeepgramHttpTTSService` uses the same settings
class.

| Setting    | Type               | Default              | Description                                                                                                              |
| ---------- | ------------------ | -------------------- | ------------------------------------------------------------------------------------------------------------------------ |
| `voice`    | `string`           | `"aura-2-helena-en"` | Aura voice. Sent to Deepgram as the `model` query parameter.                                                             |
| `model`    | `string \| null`   | `null`               | Kept in sync with `voice` when the voice changes, for metrics.                                                           |
| `language` | `Language \| null` | `null`               | Used to split text into sentences. Not sent to Deepgram.                                                                 |
| `speed`    | `number \| null`   | `null`               | Speech-rate multiplier, from 0.7 to 1.5. `null` leaves Aura at its default rate. Aura-2 English and Spanish voices only. |

### Usage [#usage]

```ts title="tts.ts"
import { TTSUpdateSettingsFrame } from "@pipedog/core/frames/frames";
import { TextAggregationMode } from "@pipedog/core/services/tts_service";
import { DeepgramFluxTTSService } from "@pipedog/deepgram/flux/tts";
import { DeepgramHttpTTSService, DeepgramTTSService } from "@pipedog/deepgram/tts";

const tts = new DeepgramTTSService({
  apiKey: process.env.DEEPGRAM_API_KEY ?? "",
  settings: new DeepgramTTSService.Settings({
    voice: "aura-2-helena-en",
    speed: 1.1,
  }),
});
```

Change settings mid-call with a `TTSUpdateSettingsFrame`. Any change closes the WebSocket and
reconnects with the new settings:

```ts
// Queue it on the worker, or push it from a processor upstream of the service.
// Any change reconnects the WebSocket with the new settings.
const update = new TTSUpdateSettingsFrame({
  delta: new DeepgramTTSService.Settings({ voice: "aura-2-thalia-en" }),
});
```

### Event handlers [#event-handlers]

| Event                 | Description                                                              |
| --------------------- | ------------------------------------------------------------------------ |
| `on_connected`        | The WebSocket to Deepgram is open.                                       |
| `on_disconnected`     | The WebSocket to Deepgram closed.                                        |
| `on_connection_error` | Connecting failed. Receives the error message.                           |
| `on_tts_request`      | A TTS request is about to be made. Receives the context ID and the text. |

### Notes [#notes]

* `encoding` must be `"linear16"`, `"mulaw"` or `"alaw"`; anything else throws in the
  constructor. The received bytes go into `TTSAudioRawFrame`s as they are, so only `"linear16"`
  plays correctly in a pipedog pipeline.
* Text is sent with `Speak` messages. The service sends `Flush` when the LLM response ends, and
  emits `TTSStoppedFrame` when Deepgram answers with `Flushed`.
* On interruption it sends `Clear`, so Deepgram drops its text buffer and stops sending audio.
* `sampleRate` defaults to the pipeline's output rate.
* Aura-2 reads inline pronunciations. `formatPronunciation(word, ipa)` renders one as Deepgram's
  inline object, and returns `null` when the IPA is empty or too long for the word (over 128
  characters, or over 10 times the word's length with a floor of 15).

## DeepgramHttpTTSService [#deepgramhttpttsservice]

### Configuration [#configuration-1]

| Option | Type | Required | Description |
|---|---|---|---|
| `apiKey` | `string` | yes | Deepgram API key for authentication. |
| `fetch` | `{ (input: RequestInfo \| URL, init?: RequestInit): Promise<Response>; (input: string \| URL \| Request, init?: RequestInit): Promise<Response>; } \| null \| undefined` | no | `fetch` used for HTTP requests. Defaults to the global `fetch`. DIVERGENCE: upstream requires a shared `aiohttp_session`. |
| `baseUrl` | `string \| undefined` | no | Custom base URL for Deepgram API. Defaults to "https://api.deepgram.com". |
| `encoding` | `string \| undefined` | no | Audio encoding format. Defaults to "linear16". |
| `mipOptOut` | `boolean \| null \| undefined` | no | Opt out of the Deepgram Model Improvement Program. See https://dpgr.am/deepgram-mip for pricing impacts before setting to true. |
| `settings` | `DeepgramTTSSettings \| null \| undefined` | no | Runtime-updatable settings. |
| `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]

Same settings and defaults as [`DeepgramTTSService`](#settings).

### Usage [#usage-1]

```ts title="http.ts"
const httpTts = new DeepgramHttpTTSService({
  apiKey: process.env.DEEPGRAM_API_KEY ?? "",
  settings: new DeepgramHttpTTSService.Settings({ voice: "aura-2-helena-en" }),
});
```

### Notes [#notes-1]

* Each request is a `POST` to `{baseUrl}/v1/speak` with `container=none`. A status other than 200
  becomes an `ErrorFrame` with the response body.
* The response body is read as a stream and cut into sample-aligned `TTSAudioRawFrame`s, so audio
  starts before the whole response has arrived.
* Upstream requires an `aiohttp_session`. Here `fetch` is optional and defaults to the global
  `fetch`.

## DeepgramFluxTTSService [#deepgramfluxttsservice]

### Configuration [#configuration-2]

| Option | Type | Required | Description |
|---|---|---|---|
| `apiKey` | `string` | yes | Deepgram API key for authentication. |
| `url` | `string \| undefined` | no | WebSocket URL for the Flux TTS API. Defaults to "wss://api.deepgram.com/v2/speak". |
| `settings` | `DeepgramFluxTTSSettings \| null \| undefined` | no | Runtime-updatable settings. |
| `webSocketFactory` | `((uri: string, options: { headers?: Record<string, string>; protocols?: string[]; }) => WebSocket \| Promise<WebSocket>) \| null \| undefined` | no | Creates the underlying `WebSocket`, to open it some other way (through a proxy, say). Defaults to `websocketConnect`'s, which sends the header on every runtime, Cloudflare Workers included. Not in upstream. |
| `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`. |
| `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. |
| `mipOptOut` | `boolean \| null \| undefined` | no | Opt out of the Deepgram Model Improvement Program. See https://dpgr.am/deepgram-mip for pricing impacts before setting to true. |
| `tag` | `string[] \| null \| undefined` | no | Tags to label requests for identification during usage reporting. |
| `textAggregationMode` | `TextAggregationMode \| undefined` | no | How to aggregate incoming text before synthesis. Defaults to `TextAggregationMode.TOKEN`, streaming LLM tokens straight to Flux for the lowest latency. |

### Settings [#settings-2]

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

| Setting        | Type                              | Default             | Description                                                                                  |
| -------------- | --------------------------------- | ------------------- | -------------------------------------------------------------------------------------------- |
| `voice`        | `string`                          | `"flux-heather-en"` | Flux voice, `flux-{voice}-{language}`. Sent as the `model` query parameter.                  |
| `model`        | `string \| null`                  | synced to `voice`   | Always set to `voice`, for metrics.                                                          |
| `language`     | `Language \| null`                | `null`              | Used to split text into sentences. Not sent to Deepgram.                                     |
| `speed`        | `number \| null`                  | `null`              | Speech-rate multiplier, 0.5 to 1.5 in steps of 0.05. `null` leaves Flux at its default rate. |
| `expressivity` | `-2 \| -1 \| 0 \| 1 \| 2 \| null` | `null`              | Expressive range, calm to animated. `null` leaves Flux at its default range.                 |

### Usage [#usage-2]

```ts title="flux.ts"
const fluxTts = new DeepgramFluxTTSService({
  apiKey: process.env.DEEPGRAM_API_KEY ?? "",
  settings: new DeepgramFluxTTSService.Settings({
    voice: "flux-alexis-en",
    speed: 1.05,
    expressivity: 1,
  }),
});

fluxTts.addEventHandler("on_connection_error", async (_service, error: string) => {
  console.error("Flux connection error:", error);
});
```

Flux streams LLM tokens by default (`TextAggregationMode.TOKEN`). To aggregate text into
sentences first:

```ts
// Aggregate text into sentences instead of streaming LLM tokens.
const fluxSentences = new DeepgramFluxTTSService({
  apiKey: process.env.DEEPGRAM_API_KEY ?? "",
  textAggregationMode: TextAggregationMode.SENTENCE,
});
```

A `speed` change is applied to the open connection with a Flux `Configure` message, so the
cross-turn acoustic state is kept. Any other change (`voice`, `expressivity`, `language`)
reconnects:

```ts
// A speed change is sent on the open connection with a Configure message.
const fluxSpeed = new TTSUpdateSettingsFrame({
  delta: new DeepgramFluxTTSService.Settings({ speed: 0.9 }),
});
```

### Event handlers [#event-handlers-1]

| Event                 | Description                                                                           |
| --------------------- | ------------------------------------------------------------------------------------- |
| `on_connected`        | The WebSocket to Flux is open.                                                        |
| `on_disconnected`     | The WebSocket to Flux closed.                                                         |
| `on_connection_error` | Connecting failed, or the receive loop reported an error. Receives the error message. |
| `on_tts_request`      | A TTS request is about to be made. Receives the context ID and the text.              |

### Notes [#notes-2]

* Audio is always requested as `linear16`. Supported sample rates are 8000, 16000, 24000, 32000,
  44100 and 48000 Hz. Another rate only logs a warning on connect.
* On interruption the service sends `Interrupt`, which ends the active turn without closing the
  connection. `Flush` ends a turn when the LLM response is done.
* Clearing `speed` (setting it to `null`) sends `Configure` with `speed: 1.0`, Flux's default.
* Disconnecting closes the socket without a `Close` message: in Flux, `Close` asks the server to
  finish the active turn's audio first.
* `formatPronunciation` is left at the base implementation, so words are spoken as written. Flux
  rejects inline pronunciations on a connection whose speed is not 1.0.
* `tag` adds one `tag` query parameter per entry, for Deepgram usage reports.

## Runtime notes [#runtime-notes]

| 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/deepgram/package.json`: ✅ means the compat smoke test passes on that runtime.

`DeepgramTTSService` and `DeepgramFluxTTSService` use the global `WebSocket` and send the key as an
`Authorization: Token <key>` header on the handshake. That works on Bun, Node, Deno and Cloudflare
Workers (where the socket is opened through `fetch`). Pass `webSocketFactory` to open the socket
some other way, through a proxy for example. A standard `WebSocket` does not expose the handshake's
response headers, so the `dg-*` headers upstream logs on connect are not available.

`DeepgramHttpTTSService` only needs `fetch`, available on all four runtimes.

Query values use JS number formatting, so `speed: 1.0` is sent as `speed=1`. The deprecated
`voice` constructor option is not ported; use `settings.voice`.

Not ported yet: `DeepgramSageMakerTTSService` and `DeepgramFluxSageMakerTTSService` (AWS
SageMaker endpoints).
