Async
Text-to-speech with Async: AsyncAITTSService over WebSocket and AsyncAIHttpTTSService over HTTP streaming.
Overview
AsyncAITTSService: keeps one WebSocket open and sends each sentence as a forced transcript on its own context, closing the context when its audio has played or the bot is interrupted.AsyncAIHttpTTSService: one HTTP request per sentence. The audio is collected and pushed as one frame.
Upstream page
Pipecat's documentation for the same service.
Source
packages/asyncai/src/tts.ts.
Async docs
The WebSocket API reference.
Installation
bun add @pipedog/asyncaiimport { AsyncAIHttpTTSService, AsyncAITTSService } from "@pipedog/asyncai/tts";Prerequisites
- An Async account, an API key and the UUID of a voice.
- Pass the key as
apiKey. The examples read it fromASYNC_API_KEY.
AsyncAITTSService
Connects to wss://api.async.com/text_to_speech/websocket/ws, with the key and API version as query parameters.
Configuration
Prop
Type
Settings
Runtime-updatable settings, passed as settings: new AsyncAITTSService.Settings({...})
(AsyncAITTSSettings). Any setting you leave out keeps the default below. A change to model, voice or language opens a new session, because they are sent once, in the init message.
| Setting | Type | Default | Description |
|---|---|---|---|
model | string | "async_flash_v1.5" | Speech model. |
voice | string | null | null | Voice UUID. |
language | Language | null | null | Language, sent as a two-letter code. |
Usage
import { AsyncAIHttpTTSService, AsyncAITTSService } from "@pipedog/asyncai/tts";
import { TTSUpdateSettingsFrame } from "@pipedog/core/frames/frames";
const tts = new AsyncAITTSService({
apiKey: process.env.ASYNC_API_KEY ?? "",
settings: new AsyncAITTSService.Settings({
model: "async_flash_v1.5",
voice: "your-voice-uuid",
language: "en",
}),
});Change settings mid-call with a TTSUpdateSettingsFrame:
// Opens a new session, since the voice is part of the init message.
const update = new TTSUpdateSettingsFrame({
delta: new AsyncAITTSService.Settings({ voice: "another-voice-uuid" }),
});Event handlers
| Event | Description |
|---|---|
on_connected | The WebSocket connected. |
on_disconnected | The WebSocket closed. |
on_connection_error | The connection failed. |
AsyncAIHttpTTSService
Posts each sentence to https://api.async.com/text_to_speech/streaming.
Configuration
Prop
Type
It takes the same settings as the WebSocket service.
Usage
const httpTts = new AsyncAIHttpTTSService({
apiKey: process.env.ASYNC_API_KEY ?? "",
settings: new AsyncAIHttpTTSService.Settings({ voice: "your-voice-uuid" }),
});Notes
- Audio is raw
pcm_s16leby default (encodingandcontainerchange that) at the pipeline's sample rate. - The WebSocket sends a blank transcript every 10 seconds to keep the connection open.
Runtime notes
- The WebSocket opens through the core
websocketConnect. Async takes the key in the URL, so no header is needed. The HTTP service uses the globalfetch; passfetchto use another one (upstream takes an aiohttp session). Both work on Bun, Node, Deno and Cloudflare Workers. - The deprecated
params(InputParams),voiceId,modelandaggregateSentencesoptions are not ported. UsesettingsandtextAggregationMode. - Tested against local servers, as upstream's tests do for the settings updates, not against Async itself.
| 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/asyncai/package.json: ✅ means the compat smoke test passes on that runtime.
pipedog is an unofficial community TypeScript port of Pipecat. It is not affiliated with or endorsed by Daily or the Pipecat project.
Portions adapted from the Pipecat documentation, Copyright (c) 2024–2025 Daily, BSD-2-Clause (see apps/docs/LICENSE-pipecat-docs).