Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
7cc6b69
feat(tts): Text-to-Speech HTTP client and WebSocket session
bgotthold-aai Oct 22, 2026
a80abf1
fix(tts): own the stream() body reader from the headers on
aleks-mitov Oct 25, 2026
0d3a214
fix(tts): keep the caller's abort on the stream() body and detach fro…
aleks-mitov Oct 25, 2026
ebf05e2
fix(tts): carry each session connection's outcome and report the real…
aleks-mitov Oct 25, 2026
7543d0b
fix(tts): bind audio() to its request when called and keep its marker…
aleks-mitov Oct 25, 2026
7cbcb55
fix(tts): reconnect a token session with a new token and mint tokens …
aleks-mitov Oct 25, 2026
d961376
fix(tts): isolate session listeners and close the socket on an unread…
aleks-mitov Oct 25, 2026
e1ee30c
fix(tts): report a refused session upgrade's status and settle every …
aleks-mitov Oct 25, 2026
f175b79
fix(tts): share one close() per session connection and wait while the…
aleks-mitov Oct 25, 2026
7c0c034
fix(tts): refuse sends after close() and while the socket closes, and…
aleks-mitov Oct 25, 2026
85fe8aa
fix(tts): drop audio of cancelled requests still in transit and retur…
aleks-mitov Oct 25, 2026
6ae3ef7
fix(tts): give up a stream() body that stalls with a read timeout
aleks-mitov Oct 25, 2026
481c823
fix(tts): send the SDK User-Agent on the Node session handshake
aleks-mitov Oct 25, 2026
b3a3167
fix(tts): put the token service's FastAPI detail in the error message…
aleks-mitov Oct 25, 2026
b84d6c0
fix(tts): report X-TTS-Unknown-Params, read the voice from X-TTS-Voic…
aleks-mitov Oct 25, 2026
a897938
fix(tts): refuse stream() before sending on a runtime without Readabl…
aleks-mitov Oct 25, 2026
5e5062e
fix(tts): export TextToSpeechErrorFields from the package root
aleks-mitov Oct 25, 2026
d4c75c0
test(tts): cover carried-over session behaviour; the fake API's Termi…
aleks-mitov Oct 25, 2026
4f3dafa
docs(tts): stop sending after a barge-in, explain keepAlive(), mentio…
aleks-mitov Oct 25, 2026
0d80cd7
refactor(tts): name the HTTP request and response types TextToSpeech*
aleks-mitov Oct 25, 2026
5ab14b6
perf(tts): decode session audio with Buffer where it exists; README l…
aleks-mitov Oct 25, 2026
34771a3
perf(tts): send session text while the socket opens instead of after …
aleks-mitov Oct 25, 2026
75236da
perf(tts): turn Nagle's algorithm off before the session's upgrade re…
aleks-mitov Oct 25, 2026
e053c42
docs(tts): measured numbers for HTTP keep-alive and token minting
aleks-mitov Oct 25, 2026
d5d8978
fix(tts): release the connection of a stream() result dropped unread
aleks-mitov Oct 25, 2026
2cc017e
fix(tts): say why a session refuses a send, and document the error codes
bgotthold-aai Oct 26, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Changelog

## [4.42.0]

- Add Text-to-Speech: `client.tts.synthesize(request)` returns one text's complete audio (`TextToSpeechResponse`: `audio` plus the response headers as `requestId`, `region`, `model`, `voiceId`, `encoding`, `sampleRate`, `billedCharacters`, `audioDurationMs`, ...), `client.tts.stream(request)` returns it as the API generates it (`TextToSpeechStream`, an async iterable over a web `ReadableStream`), and `client.tts.session(params)` opens a WebSocket `TextToSpeechSession` for voice agents: `connect()`, `generate()` / `flush()` / `audio()`, `speak()`, `cancel()`, `keepAlive()`, `close()`, with `on("audio" | "flushDone" | "wordBoundaries" | "cancelled" | "termination" | "error" | "close", ...)`. Requests take the API's fields (`text`, `voice_id`, `model_id`, `language`, `sample_rate`, `encoding`, `container`, `bit_rate`); failures throw `TextToSpeechError` (`status`, `errorCode`, `param`, `requestId`, `retryable`, `retryAfter`, `errors`, `closeCode`, `body`). New client option `ttsBaseUrl` (default `https://tts.us.assemblyai.com`); `client.tts.createTemporaryToken()` mints a token for browser sessions. Text sent while `connect()` is pending is held and sent the moment the socket opens, so it need not wait for `Begin`

## [4.41.7]

- Add `summarization` to the speech understanding feature requests and responses (`SummarizationRequest`, `SummarizationResponse`): `summary_type`, `headline_length`, `approximate_chapters` and `effort`. The feature was already accepted by the API but missing from the SDK types, so setting it failed to type check
Expand Down
34 changes: 33 additions & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# AssemblyAI Node.js SDK

Speech-to-text and audio intelligence SDK for Node.js, Deno, and Bun. Supports pre-recorded transcription, real-time streaming, and audio analysis features.
Speech-to-text, text-to-speech and audio intelligence SDK for Node.js, Deno, and Bun. Supports pre-recorded transcription, real-time streaming, text-to-speech (HTTP requests and WebSocket sessions), and audio analysis features.

## Quick start

Expand Down Expand Up @@ -52,6 +52,11 @@ const client = new AssemblyAI({
- `client.llmGateway.chatCompletions(request)` — OpenAI-compatible chat completions
- `client.llmGateway.listModels()` — List models available on the LLM Gateway
- `client.llmGateway.understanding(request)` / `client.llmGateway.validateUnderstanding(request)` — Run or validate a Speech Understanding request
- `client.tts.synthesize({ text, voice_id? })` — Text-to-speech in one request: the complete audio (`TextToSpeechResponse.audio`, a Uint8Array) plus the response header fields
- `client.tts.stream(request, { signal? })` — Text-to-speech audio as it is generated: a `TextToSpeechStream`, an async iterable of Uint8Array chunks with `info` (header fields), `body`, `bytes()` and `cancel()`
- `client.tts.session({ voiceId })` — WebSocket `TextToSpeechSession` that speaks text as it is written (voice agents): `connect({ token? })` (text sent before it resolves is held until the socket opens), `speak(text)` (async iterable), `generate()`/`flush()`/`audio(flushId?)`, `cancel()`, `keepAlive()`, `close()`, `on("begin"|"audio"|"flushDone"|"wordBoundaries"|"cancelled"|"termination"|"error"|"close", fn)`
- `client.tts.createTemporaryToken({ expires_in_seconds })` — Mint a short-lived token, server-side, for a session opened in a browser with `new TextToSpeechSession({ token })`; one token per connection
- `TextToSpeechError` — What the `client.tts` methods throw on an API refusal: `errorCode`, `param`, `retryable`, `retryAfter`, `requestId`, `closeCode`, `body`

## Common patterns

Expand Down Expand Up @@ -425,6 +430,33 @@ const result = await client.llmGateway.understanding({
Failures throw `LlmGatewayError` with `.status`, `.requestId`, and `.errors` (validation detail
strings, when present).

## Text-to-Speech

One text per HTTP request, or a WebSocket session for voice agents. Request fields use the API's names (`voice_id`, `sample_rate`, `encoding`, `container`); session parameters are camelCase (`voiceId`, `sampleRate`, `wordBoundaries`, `inactivityTimeout`, `token`). Host: `ttsBaseUrl` (`https://tts.us.assemblyai.com`; EU: `https://tts.eu.assemblyai.com`).

```typescript
const { audio } = await client.tts.synthesize({
text: "Hello.",
voice_id: "aai-jane",
container: "wav",
});

const speech = await client.tts.stream({
text: "Hello.",
encoding: "pcm_mulaw",
sample_rate: 8000,
});
for await (const chunk of speech) send(chunk); // chunk sizes vary; re-frame to 160 B for 20 ms

const session = client.tts.session({ voiceId: "aai-jane" });
await session.connect();
for await (const chunk of session.speak("Hello.")) play(chunk);
session.cancel(); // barge-in: ends the current audio() iteration at once
await session.close(); // Terminate -> Termination summary; rejects with an unobserved session error
```

Gotchas: `stream()` gives up a body that sends no audio for `readTimeout` ms (30,000 by default, `0` off) while a read waits, with the same retryable `TextToSpeechError` as a cut; `stream()` needs a global `ReadableStream` and, on a runtime without one (React Native without a polyfill), throws a non-retryable `TextToSpeechError` before sending (use `synthesize()`); a session streams raw PCM/G.711 only (no `mp3`, `container` or `bit_rate`); `generate()` takes at most 2,000 characters per frame (`speak()` splits); the API paces text at ~100 chars/s; an `audio` listener switches audio from pull to push; a session the API ended (idle after 120 s) can `connect()` again on the same object, but a session opened with a temporary token needs a new token for each connection: `connect({ token: newToken })` (a used or expired token is refused with 1008 `unauthorized`). In a browser use `new TextToSpeechSession({ token })` (no client, no API key); `client.tts.createTemporaryToken()` mints on the token host of the `ttsBaseUrl` zone (`token.eu`/`token.us`), `tokenBaseUrl` overrides.

## Important gotchas

- **.transcribe() polls until complete** — use .submit() for fire-and-forget
Expand Down
Loading
Loading