@polyweave/elevenlabs (1.0.9)
Installation
@polyweave:registry=https://hub.kl1.tenere.ai/api/packages/Polyweave/npm/npm install @polyweave/elevenlabs@1.0.9"@polyweave/elevenlabs": "1.0.9"About this package
@polyweave/elevenlabs
ElevenLabs multi-modal audio adapter for Polyweave — TTS (Text-to-Speech), STT (Speech-to-Text), and voice management via HTTP and WebSocket streaming. Built on the Polyweave frame duplex protocol.
Synopsis
import { createElevenLabsAdapter } from '@polyweave/elevenlabs';
const adapter = createElevenLabsAdapter({
apiKey: process.env.ELEVENLABS_API_KEY,
region: 'us'
});
adapter.source.pipe(consumerSink);
// Text-to-Speech (WebSocket streaming, default)
adapter.sink.write({
type: 'TEXT',
id: 'msg-1',
streamId: 'stream-1',
content: { text: 'Hello, how can I help you today?' }
});
// Speech-to-Text (HTTP batch)
adapter.sink.write({
type: 'PARAM',
content: { key: 'task', value: 'stt' }
});
adapter.sink.write({
type: 'AUDIO',
id: 'audio-1',
streamId: 'stream-1',
content: { data: audioBuffer }
});
// Realtime streaming STT
adapter.sink.write({
type: 'PARAM',
content: { key: 'streaming_stt', value: true }
});
adapter.sink.write({
type: 'AUDIO',
id: 'audio-2',
streamId: 'stream-2',
content: { data: pcmChunk }
});
// ... more AUDIO chunks ...
adapter.sink.write({
type: 'END',
streamId: 'stream-2'
});
// Voice management
adapter.sink.write({
type: 'PARAM',
content: { key: 'task', value: 'list_voices' }
});
adapter.sink.write({
type: 'TEXT',
id: 'voices-1',
streamId: 'voices-1',
content: { text: '' }
});
Install
npm install @polyweave/elevenlabs
Why
ElevenLabs offers TTS, STT, and voice cloning — but with three different API surfaces (HTTP REST, WebSocket streaming TTS, WebSocket realtime STT). This adapter unifies them behind a single frame-duplex interface: you send TEXT or AUDIO frames, configure behavior with PARAM frames, and receive AUDIO, TEXT, RECEIPT, and END frames in response. Streaming WebSocket sessions (LLM→TTS, microphone→STT) are managed automatically — the adapter creates sessions per streamId, queues audio before the WebSocket handshake completes, and emits partial transcripts in real time.
API
Primary export
createElevenLabsAdapter(config?)
createElevenLabsAdapter(config?: {
apiKey?: string,
region?: 'default' | 'us' | 'eu' | 'india',
baseURL?: string,
wsBaseURL?: string,
timeout?: number,
name?: string
}): IFrameDuplex
Creates the ElevenLabs frame-duplex adapter. Returns immediately; the adapter enters a buffered state until an API key is provided (via config or PARAM frame).
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
config.apiKey |
string |
no | process.env.ELEVENLABS_API_KEY |
ElevenLabs API key |
config.region |
string |
no | 'default' |
API region: 'default', 'us', 'eu', 'india' |
config.baseURL |
string |
no | Auto from region | Override HTTP base URL |
config.wsBaseURL |
string |
no | Auto from region | Override WebSocket base URL |
config.timeout |
number |
no | 30000 |
Request timeout in milliseconds |
config.name |
string |
no | null |
Adapter name (reflected in topology reports) |
Return value: IFrameDuplex with { source, sink, setReady, ready, apiKey, name }.
duplex.source— push-stream source that emits response frames (AUDIO,TEXT,RECEIPT,ERROR,END)duplex.sink— push-stream sink that acceptsTEXT,AUDIO,PARAM,START,END,TOPOLOGY_SCAN,TOPOLOGY_REPORT,TOPOLOGY_COMPLETE,TEST_INJECT,TEST_SNAPSHOTframesduplex.setReady(apiKey)— provide API key after construction; flushes the internal frame bufferduplex.ready— boolean getter;trueonce an API key is setduplex.apiKey— the currently configured API key
Frame protocol — Inputs (sink.write):
| Frame type | Purpose |
|---|---|
TEXT |
TTS input (text to synthesize; defaults to TTS unless task is set to 'stt' or voice operations). content.text or content (fallback). If streaming_tts param is truthy, uses WebSocket streaming; otherwise HTTP batch. |
AUDIO |
STT input (audio to transcribe; defaults to STT unless task is set to 'tts' or voice ops). content.data (Buffer) or content (fallback). If streaming_stt param is truthy, uses realtime WebSocket; otherwise HTTP multipart. |
PARAM |
Sets configuration. { key, value }. Supported keys: apiKey, task, stt_model, language_code, include_timestamps, include_language_detection, commit_strategy, vad_silence_threshold_secs, vad_threshold, min_speech_duration_ms, min_silence_duration_ms, audio_format, voice_id, model_id, stability, similarity_boost, style, use_speaker_boost, speed, output_format, chunk_length_schedule, auto_mode, streaming_stt, streaming_tts, voice_operation, voice_name, voice_description, receipt_mode, receipt_interval |
END |
Terminates a streaming session (STT or TTS). Routed by streamId to the active session. |
START |
Pass-through (no-op). |
TOPOLOGY_SCAN |
Returns a TOPOLOGY_REPORT describing the adapter's receives/generates and state. |
TOPOLOGY_REPORT / TOPOLOGY_COMPLETE |
Pass-through to source. |
TEST_INJECT |
Injects an array of test frames from content.frames. |
TEST_SNAPSHOT |
Returns a TEST_SNAPSHOT frame with the adapter's paused state. |
Frame protocol — Outputs (source emits):
| Frame type | Emitted when |
|---|---|
AUDIO |
TTS audio chunk received (HTTP streaming or WebSocket). Each chunk includes metadata.streaming, format, chunkIndex, ttft. |
TEXT |
STT transcript received. metadata.partial for realtime partials, metadata.committed for final utterances. HTTP STT emits a single TEXT frame. Voice operations emit result JSON as TEXT frames with metadata.type (voice_list, voice_info, voice_cloned, voice_deleted). |
RECEIPT |
Final receipt frame after operation completes. Contains characters, audioSeconds, totalCost (estimated), totalTime, ttft. |
ERROR |
On API errors, network failures, WebSocket errors, or misrouted frames. Includes content.code (ErrorCode.PROVIDER_ERROR or mapped code) and metadata.statusCode. |
END |
Sent after every completed operation or error. metadata.error: true for error endings. |
TOPOLOGY_REPORT |
Response to TOPOLOGY_SCAN. |
TOPOLOGY_COMPLETE |
Pass-through. |
TEST_SNAPSHOT |
Response to TEST_SNAPSHOT. |
Task routing (task param):
task value |
Frame type | Handler | Description |
|---|---|---|---|
'tts', 't2a', 'text_to_speech' |
TEXT |
handleTtsWebSocket / handleTtsHttp |
Text-to-Speech |
'stt', 'a2t', 'speech_to_text', 'transcribe' |
AUDIO |
handleSttRealtime / handleSttHttp |
Speech-to-Text |
'voice_clone', 'clone' |
TEXT or AUDIO |
handleVoices |
Clone a voice (audio from AUDIO frame) |
'voice_design', 'design_voice' |
TEXT |
handleVoices |
Design a voice |
'voices', 'list_voices' |
TEXT |
handleVoices |
List all voices |
| (empty/unset) | TEXT |
handleTtsWebSocket / handleTtsHttp |
Default: TTS |
| (empty/unset) | AUDIO |
handleSttRealtime / handleSttHttp |
Default: STT |
Error conditions:
- If
frame.typeis neitherTEXTnorAUDIOand no voice task is set: throwsError('Unsupported frame type: ...')— caught by sink and emitted asERROR+END - If
TEXTcontent has no text:ERROR('No text provided for TTS')+END - If
AUDIOcontent has no data:ERROR('No audio data provided for STT')+END - HTTP 400+ responses from ElevenLabs:
ERRORframe with message fromdetail.messageand mappedstatusCode - WebSocket connection errors:
ERRORwitherr.message - WebSocket close before connection:
ERROR('WebSocket closed before connection: {code} {reason}') - JSON parse failure on WebSocket message:
ERROR('Failed to parse WebSocket message: ...') - Invalid audio data format in STT:
ERROR('Invalid audio data format') - Voice operations with missing
voice_id:ERROR('voice_id required for get/delete operation') - Voice clone with missing audio data:
ERROR('Audio data required for voice cloning') PARAMwith key'apiKey'triggerssetReady(value)and is not stored in params
Internal exports
getModelCost(model) (./costs.js)
getModelCost(model: string): { perCharacter?: number, perMinute?: number } | null
Returns the pricing config for a TTS/STT model, or null if unknown.
| Model | Cost |
|---|---|
tts-multilingual-v2 |
$0.00003/character |
tts-turbo-v2 |
$0.00002/character |
stt-v1 |
$0.006/minute |
calculateCost(model, units)
calculateCost(model: string, units?: number): number
Calculates cost for the given model and units (characters for TTS, minutes for STT). Returns 0 for unknown models.
mapElevenLabsError(error, frame) (./utils/errorMapper.js)
Maps ElevenLabs API error types to canonical ErrorCode values and returns an error frame. Handles auth_error, quota_exceeded, rate_limited, input_error, chunk_size_exceeded, resource_exhausted, queue_overflow, session_time_limit_exceeded, unaccepted_terms, transcriber_error, and mapped HTTP status codes.
isRetryable(error)
Returns true if the error type (rate_limited, resource_exhausted, queue_overflow, commit_throttled) or HTTP status (429, 502, 503) indicates a retryable error.
createAdapterBase(config, options?) (./base.js)
Base factory for creating new adapters. Buffers frames, manages ready state, and provides _translateFrameToProvider/_sendToProvider/_readFromProvider hooks. Not used directly by the ElevenLabs adapter (which has its own implementation) but exported for adapter builders.
Tests
npm test
Tests cover adapter creation, API key configuration (config, env var, PARAM frame), frame buffering, task routing (TTS, STT, voice operations), parameter accumulation, topology scan/report, test inject/snapshot, ready state toggling, and error emission on invalid frames.
Requirements
Node.js 22 or later. ESM only. Depends on @polyweave/core, @polyweave/errors, and @polyweave/push-websocket.
Dependencies
Dependencies
| ID | Version |
|---|---|
| @polyweave/push-websocket | * |
| ws | ^8.19.0 |
Development Dependencies
| ID | Version |
|---|---|
| @rigor/core | * |
Peer Dependencies
| ID | Version |
|---|---|
| @polyweave/core | * |
| @polyweave/errors | * |