Cartesia
Streaming speech-to-text with CartesiaSTTService (Ink) and CartesiaTurnsSTTService (Ink-2, with server-side turn detection and eager end of turn).
Overview
Cartesia streams transcripts over a WebSocket. pipedog ports both of Pipecat's Cartesia speech-to-text services:
CartesiaSTTService: Cartesia's live STT API (/stt/websocket), with interim and final transcripts, runtime model and language switching, and keyterms on ink-2 models.CartesiaTurnsSTTService: Cartesia's turn-based Streaming ASR v2 API (/stt/turns/websocket, Ink-2). The server decides when the user's turn starts and ends, with optional eager end-of-turn predictions.
Upstream page
Pipecat's documentation for the same services.
Source
packages/cartesia/src/stt.ts and turns/stt.ts.
Cartesia docs
STT models, keyterms and turn detection.
Installation
bun add @pipedog/cartesiaimport { CartesiaSTTService } from "@pipedog/cartesia/stt";
import { CartesiaTurnsSTTService } from "@pipedog/cartesia/turns/stt";Prerequisites
- A Cartesia account and an API key from the Cartesia playground.
- Pass the key as
apiKey. The examples read it fromCARTESIA_API_KEY.
CartesiaSTTService
Configuration
Prop
Type
Settings
Runtime-updatable settings, passed as settings: new CartesiaSTTService.Settings({...}). Any
setting you leave out keeps the default below.
| Setting | Type | Default | Description |
|---|---|---|---|
model | string | "ink-whisper" | Cartesia STT model. |
language | Language | Language.EN | Transcription language. |
keyterm | string[] | null | null | Terms to bias transcription towards. Only ink-2 and ink-preview models use them. |
Usage
import { CartesiaSTTService } from "@pipedog/cartesia/stt";
import { STTUpdateSettingsFrame } from "@pipedog/core/frames/frames";
import { Language } from "@pipedog/core/transcriptions/language";
const stt = new CartesiaSTTService({
apiKey: process.env.CARTESIA_API_KEY ?? "",
settings: new CartesiaSTTService.Settings({
model: "ink-whisper",
language: Language.EN,
}),
});Keyterms, on an ink-2 model:
// Keyterms only work on ink-2 and ink-preview models.
const withKeyterms = new CartesiaSTTService({
apiKey: process.env.CARTESIA_API_KEY ?? "",
settings: new CartesiaSTTService.Settings({
model: "ink-2",
keyterm: ["pipedog", "Pipecat"],
}),
});Change settings mid-call with an STTUpdateSettingsFrame. The service reconnects with the new
settings:
// Queue it on the worker, or push it from a processor upstream of the service.
const update = new STTUpdateSettingsFrame({
delta: new CartesiaSTTService.Settings({ language: Language.ES }),
});Event handlers
Inherited from STTService:
| Event | Description |
|---|---|
on_connected | Connected to Cartesia. |
on_disconnected | Disconnected from Cartesia. |
on_connection_error | The connection failed. |
Notes
- Emits
InterimTranscriptionFramewhile the user speaks andTranscriptionFramefor final results. AVADUserStoppedSpeakingFramemakes it sendfinalize, which flushes a final result. - Cartesia closes a socket after 3 minutes without messages. The service sends silence as a keepalive (after 120 s idle, checked every 30 s).
- A settings change reconnects. While the user is speaking, the reconnect waits until they stop.
- Keyterms set for a model other than
ink-2orink-previeware ignored with a warning. Cartesia accepts at most 100 keyterms totaling 1200 characters; extra ones are dropped with a warning. baseUrltakes a bare host ("api.cartesia.ai") or a URL with a scheme ("ws://localhost:8000").
CartesiaTurnsSTTService
Configuration
Prop
Type
Settings
| Setting | Type | Default | Description |
|---|---|---|---|
model | string | "ink-2" | Cartesia model. Cannot change at runtime. |
language | Language | null | null | Not used: ink-2 is English-only. Emitted frames carry Language.EN. |
keyterm | string[] | null | null | Terms to bias transcription towards. |
turnStartThreshold | number | null | null (Cartesia: 0.8) | Likelihood above which the server emits turn.start. Range 0.5 to 0.9. |
turnEagerEndThreshold | number | null | null (Cartesia: 0.6) | Likelihood below which the server emits turn.eager_end. Range 0.3 to 0.8. |
turnEndThreshold | number | null | null (Cartesia: 0.3) | Likelihood below which the server emits turn.end. Range 0.05 to 0.5. |
turnEndTimeoutMs | number | null | null (Cartesia: 5600) | Wait after the user stops speaking before turn.end. Range 640 to 11200. |
Keep the thresholds in order: turnStartThreshold > turnEagerEndThreshold > turnEndThreshold.
Changing keyterm or a turn* setting at runtime reconnects. A model or language update is
reported as unhandled.
Usage
import { CartesiaTurnsSTTService } from "@pipedog/cartesia/turns/stt";
const stt = new CartesiaTurnsSTTService({
apiKey: process.env.CARTESIA_API_KEY ?? "",
settings: new CartesiaTurnsSTTService.Settings({
keyterm: ["pipedog"],
turnEndTimeoutMs: 2000,
}),
});
stt.addEventHandler("on_turn_end", async (_service, transcript: string) => {
console.log("end of turn:", transcript);
});With eager end of turn:
// Answer the server's predicted end of turn ahead of the committed one.
const eager = new CartesiaTurnsSTTService({
apiKey: process.env.CARTESIA_API_KEY ?? "",
enableEagerEndOfTurn: true,
settings: new CartesiaTurnsSTTService.Settings({
turnStartThreshold: 0.8,
turnEagerEndThreshold: 0.6,
turnEndThreshold: 0.3,
}),
});
eager.addEventHandler("on_turn_eager_end", async (_service, transcript: string) => {
console.log("predicted end of turn:", transcript);
});
eager.addEventHandler("on_turn_resume", async () => {
console.log("user kept speaking");
});Event handlers
| Event | Description |
|---|---|
on_turn_start | The server detected the start of a turn. |
on_turn_update | A transcript update for the turn in progress. |
on_turn_eager_end | The server predicted the end of the turn. |
on_turn_resume | The user kept speaking after an eager end. |
on_turn_end | The final transcript for the turn. |
on_connected, on_disconnected and on_connection_error are inherited from STTService.
Notes
- The server drives turns:
turn.startbroadcasts aProposedUserStartedSpeakingFrame,turn.updatepushes anInterimTranscriptionFrame, andturn.endpushes aTranscriptionFrameand broadcasts aProposedUserStoppedSpeakingFrame. Transcripts are cumulative per turn. There is nofinalizecommand. - The service recommends
ExternalUserTurnStrategiesto the user aggregator, orEagerUserTurnStrategieswithenableEagerEndOfTurn, so no local VAD or smart-turn model is needed. Passing your ownuserTurnStrategiesoverrides this. See Turn detection. - With
enableEagerEndOfTurn: true,turn.eager_endpushes anEagerTranscriptionFramethat starts a response early.turn.resumepushes aUserTurnResponseCancelFrame. The early response is dropped if the user resumes or the final transcript differs. It is off by default because every prediction costs an inference. shouldInterrupt(defaulttrue) decides whether the start of a turn interrupts the bot.- If audio stops after a
turn.start, a watchdog sends silence so the turn does not hang. The idle threshold ismax(chunkDuration * 2, watchdogMinTimeout), withwatchdogMinTimeoutdefaulting to 0.5 s. - The service waits up to 10 s for Cartesia's
connectedmessage after the handshake.
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/cartesia/package.json: ✅ means the compat smoke test passes on that runtime.
Both services use the global WebSocket and send the key as an X-API-Key header on the
handshake. The Bun, Node and Deno WebSocket support handshake headers, and on Cloudflare
Workers the socket is opened through fetch with an Upgrade header. A standard WebSocket
does not expose the handshake response, so the X-Request-ID header that upstream logs on
connect is always logged as None. The deprecated CartesiaLiveOptions class and the
liveOptions constructor option are not ported; use settings.
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).