3.6 KiB
voices
ElevenLabs implementation of @suaveplan/voice's VoiceRepository
interface. Lists voices via GET /v1/voices and looks them up via
GET /v1/voices/{voice_id}. 404 on lookup is normalised to null per
the contract.
(no submodules)
Purpose
ElevenLabs returns voice records with a labels bag (accent,
description, age, gender, use_case) and a settings bag
(stability, similarity_boost, style, use_speaker_boost) that
look nothing like the Voice shape. This adapter maps the
upstream record onto the package schema, normalises gender / age
strings, copies labels into a metadata bag for round-tripping, and
honours the VoiceRepository.get() contract that "not found" must
resolve to null rather than throw.
Features
list(options?)— paged listing withcategory,languageCode,search,pageSize, andpageTokenfilters.get(voiceId)— returns the mapped voice ornullon 404.- Maps unknown / non-canonical category strings to
"premade". - Tolerates missing
gender/agefields and only emits values that match the canonical enums; unknown values are dropped silently. - Pulls language code from
fine_tuning.languageinto a single-elementlanguageCodesarray. - Settings mapping converts each ElevenLabs snake_case field into the camelCase equivalent, omitting unspecified entries.
- Description falls back to
labels.descriptionwhen the top-level field is absent.
Usage: Basic Example
import { createElevenLabsAdapters } from "@suaveplan/third-party-elevenlabs";
const { voices } = createElevenLabsAdapters({ apiKey: KEY });
const page = await voices.list({ category: "premade" });
for (const v of page.voices) console.log(v.id, v.name);
const adam = await voices.get("21m00Tcm4TlvDq8ikWAM");
if (adam === null) throw new Error("voice missing");
Usage: Advanced Example
import { createElevenLabsAdapters } from "@suaveplan/third-party-elevenlabs";
const { voices } = createElevenLabsAdapters({ apiKey: KEY });
let pageToken: string | undefined;
const all: Voice[] = [];
do {
const page = await voices.list({
category: "professional",
languageCode: "en",
search: "narrator",
pageSize: 50,
...(pageToken !== undefined ? { pageToken } : {}),
});
all.push(...page.voices);
pageToken = page.hasMore ? page.nextPageToken : undefined;
} while (pageToken !== undefined);
console.log(`${all.length} voices retrieved`);
API Reference
class ElevenLabsVoiceRepository implements VoiceRepositoryproviderName: "elevenlabs"list(options?: ListVoicesOptions): Promise<VoiceList>— returns{ voices, hasMore, nextPageToken? }.hasMoredefaults tofalsewhen the upstream omits it.get(voiceId: string): Promise<Voice | null>—nullon 404, throws on any other error.
Throws: ProviderNotConfiguredError, ProviderRateLimitedError, and
generic VoiceError-shaped failures.
Implementation Notes
- Age detection uses substring matching on the ElevenLabs label
(
"young adult"→"young","middle aged"→"middle","old man"→"old") because ElevenLabs has no canonical enum. - Gender is matched exactly against
male/female/neutral; other values (e.g. ElevenLabs's occasional"non-binary") are dropped — the raw label remains accessible viametadata. voice_idisencodeURIComponent-escaped before being interpolated into the URL so that workspace IDs with spaces work correctly.- A 404 on
getis the only non-2xx response that's swallowed; every other error (401 / 422 / 429 / 5xx) throws.