@juspay/neurolink
Version:
The pipe layer of an AI nervous system: one interface connecting provider neurons to your application, across three inference types — generate, stream and decide. `decide` returns typed, calibrated judgements from a non-generative model (~400ms, ~$0.00002
134 lines (133 loc) • 6.14 kB
TypeScript
/**
* ElevenLabs Text-to-Speech Handler
*
* Implementation of TTS using ElevenLabs API.
*
* @module voice/providers/ElevenLabsTTS
*/
import type { TTSHandler, TTSOptions, TTSResult, TTSVoice } from "../../types/index.js";
/**
* ElevenLabs Text-to-Speech Handler
*
* Supports high-quality multilingual TTS with voice cloning.
*
* @see https://elevenlabs.io/docs/api-reference
*/
export declare class ElevenLabsTTS implements TTSHandler {
private readonly apiKey;
private readonly baseUrl;
/** `ELEVENLABS_VOICE_ID`, else Rachel; a per-request `voice` still wins. */
private readonly defaultVoiceId;
/** `ELEVENLABS_MODEL`, else `eleven_multilingual_v2`; a per-request `model` still wins. */
private readonly defaultModel;
private voicesCache;
private static readonly CACHE_TTL_MS;
/**
* Maximum text length (5000 characters)
*/
readonly maxTextLength = 5000;
constructor(apiKey?: string);
/** A trimmed, non-empty environment value, else `undefined`. */
private static envValue;
isConfigured(): boolean;
getVoices(languageCode?: string): Promise<TTSVoice[]>;
synthesize(text: string, options?: TTSOptions): Promise<TTSResult>;
/**
* Issue the synthesis request, retrying on 429 / 5xx / network failure /
* per-attempt timeout up to `retries` more times. A `Retry-After` header
* sets the pause before the next attempt (capped); otherwise exponential
* backoff from 500 ms. Any other failure — 4xx, or a body that is not audio
* — is thrown on the first attempt.
*
* Total wall-clock is bounded by `timeoutMs × (1 + retries)` plus the pauses
* between attempts; `BaseProvider`'s outer synthesis timeout is raised to at
* least that figure (see `resolveTTSHandlerBudgetMs`).
*
* `signal` is the caller's cancellation: once it fires, the attempt in
* flight is aborted and no further attempt is made. Without it, a caller
* that had already given up (its own timeout, an aborted request) left the
* loop to pause and issue a second, billable request nobody consumed.
*/
private fetchWithRetry;
/**
* A backoff pause that ends as soon as `signal` fires, so a cancelled
* caller never waits it out. The timer is owned here and cleared on the
* way out: racing a bare `delay()` against the signal settled the promise
* at the abort but left the backoff timer armed for up to `RETRY_AFTER_CAP_MS`,
* holding the event loop open after the caller had already been answered.
*/
private static pause;
/** The non-retriable error a caller-cancelled synthesis surfaces as. */
private static cancelledError;
/**
* One attempt, end to end: the per-attempt timer covers the response
* headers AND the audio body, so a stalled or truncated body is a timed-out
* attempt the retry loop can resend rather than a hang that only the outer
* provider budget ends. The caller's `signal` aborts the same controller,
* and an abort it caused is reported as a cancellation, not a timeout.
*/
private fetchOnce;
/** Map an abort from the per-attempt timer to a retriable timeout error. */
private static asAttemptError;
private static isRetriableStatus;
/**
* 429 / 5xx, a per-attempt timeout or broken body (already a retriable
* `TTSError`), or a transport failure. Any other throw — a `TypeError` for
* an invalid header value or URL, thrown before a request exists — is
* permanent and is not retried; it used to be, on the grounds that it was
* an `Error`.
*/
private static isRetriableFailure;
/**
* A socket-level failure: undici wraps one as `TypeError("fetch failed")`
* with the system error on `cause`, and the codes are checked on both.
*/
private static isTransportError;
private static errorCode;
/** A BCP-47 tag reduced to its ISO 639-1 primary subtag: `en-US` → `en`, `pt_BR` → `pt`. */
private static toIso639Primary;
/** One of `allowed`, else `fallback` with a warning — never an unlisted value in `output_format`. */
private static resolveBitrate;
/** `Retry-After` as delta-seconds or an HTTP-date, in milliseconds. */
private static parseRetryAfter;
/** A positive finite `timeoutMs`, clamped to the largest delay a timer honours; otherwise the default. */
private static resolveTimeoutMs;
private static resolveRetries;
private static normalizeBaseUrl;
/**
* Assemble `voice_settings`. The camelCase options are the documented
* surface; `voiceSettings` passes raw fields through and wins on conflict.
* `speed` comes from the generic `TTSOptions.speed` (0.25–4.0) clamped into
* ElevenLabs' accepted 0.7–1.2 — clamped with a warning rather than
* rejected, because the generic validator has already accepted the value.
*/
private buildVoiceSettings;
/**
* Map gender string to standard type
*/
private mapGender;
/**
* Map TTSAudioFormat to an ElevenLabs `output_format`.
*
* ElevenLabs has no `ogg_*` family: Ogg/Opus is `opus_48000_<kbps>`. Both
* `ogg` and `opus` map there. `wav` maps to raw `pcm_44100` (headerless —
* see `effectiveFormat`). Anything else falls back to `mp3_44100_128`.
*/
private mapFormat;
/**
* Get sample rate from format string
*/
private getSampleRate;
/**
* Map the ElevenLabs `output_format` string back to a canonical
* TTSAudioFormat. mapFormat() falls back to mp3_44100_128 for unsupported
* inputs, so this is needed to keep TTSResult.format honest.
*
* NOTE: ElevenLabs `pcm_*` outputs are RAW 16-bit signed-LE PCM samples
* with no RIFF/WAV header. We surface that as `pcm16` (which exists in the
* `TTSAudioFormat` union exactly for this case) — labeling it as `wav`
* would cause consumers writing the buffer to a `.wav` file or feeding it
* to a WAV parser to produce unplayable output (CodeRabbit review).
*/
private effectiveFormat;
}