14 KiB
14 KiB
Tasks: third-party-elevenlabs
Change ID: third-party-elevenlabs
Package: @suaveplan/third-party-elevenlabs
Target path: packages/audio/third-party-elevenlabs
Wave: 1 (voice surface)
Prerequisite packages on Verdaccio: @suaveplan/error, @suaveplan/types, @suaveplan/streams, @suaveplan/retry, @suaveplan/logger
Wave 1 only. Subsequent waves (
tts-websocket,voice-cloning,voice-design,pronunciation-dictionaries,conversational-ai,music,dubbing,studio,audio-native,history,user,workspace) ship under their own change IDs.
1. Scaffold
- Create
packages/audio/third-party-elevenlabs/withsrc/,tests/,src/__benchmarks__/. - Write
package.jsonat version0.1.0,"type": "module","sideEffects": false, full subpathexportsmap per feature module,dependenciesanddevDependenciesvia catalogs only (no bare versions). - Write
tsconfig.lib.jsonextending../../../tsconfig.base.json. - Write
vite.config.tswith multi-entry build for every feature module. - Write
vitest.config.tsusingcreateTestConfig()from@suaveplan/testing/configwithpool: 'forks', coverage thresholds 100/100/100/100,provider: 'istanbul'. - Add
lint,lint:fix,format,check,typecheck,test,test:coverage,build,docs:checkscripts. - Update
scripts/folder-reorg/assignment.ymlto includeaudio/third-party-elevenlabsand regenerateMANIFEST.mdviabun scripts/folder-reorg/generate-manifest.ts. - Update root
CLAUDE.mdpackage listing.
2. Errors
src/errors/errors.ts— defineELEVENLABS_CODESviadefineCodes('THIRD_PARTY_ELEVENLABS', [...])covering 17 codes from proposal §Errors; exportElevenLabsApiErrorbase + 17 typed subclasses (UnauthorizedError,ForbiddenError,RequestInvalidError,RequestValidationFailedError,RequestNotFoundError,RateLimitExceededError,RateLimitQuotaExhaustedError,ServerTransientError,ServerUnavailableError,NetworkTimeoutError,NetworkAbortedError,NetworkUnreachableError,ResponseMalformedError,ResponseUnsupportedFormatError,StreamInterruptedError,WebSocketConnectionFailedError,WebSocketProtocolError).src/errors/errors.md— full API reference with throw-site examples.src/errors/errors.test.ts— instantiation, code mapping, context preservation, JSON round-trip viaSuaveplanError.toJSON().
3. Shared types
src/types/types.ts—JsonRequest,BinaryRequest,StreamRequest,MultipartRequest,SocketRequest,BinaryResponse,RequestMetadata(requestId,rateLimitRemaining,characterCost),ElevenLabsClientEvents(request-start, request-end, retry-attempt, rate-limit-hit),RetryDecision,MultipartFile(Uint8Array | Blob | ReadableStream | string).src/types/types.mdsrc/types/types.test.ts— Zod schemas for runtime-checked subset.
4. Client
src/client/client.ts—ElevenLabsClientclass.- Constructor: validate options (Zod), default base URL
https://api.elevenlabs.io, default timeout30_000, default retry policy (exponential backoff with jitter, 3 attempts, retryable codes only). fetchJson(req, schema)— JSON in, Zod-validated JSON out, returns{ body, metadata }.fetchBinary(req)— JSON in, binaryUint8Arrayout + headers metadata.fetchStream(req)— JSON or multipart in, returns{ stream, metadata }withReadableStream<Uint8Array>body.fetchMultipart(req, schema)—FormDataupload (string URL → fetched first;ReadableStream→ buffered via@suaveplan/streams.toBytes;Uint8Array/Blob→ direct), Zod-validated JSON out.openSocket(req)—WebSockethandshake withxi-api-keyheader, returnsElevenLabsSocket.- HTTP status mapping per proposal §Errors.
Retry-Afterparsing (seconds + HTTP-date).X-RateLimit-Remaining+X-Character-Costheader surfacing in metadata.- OTel span / counter / histogram per call.
AbortSignalplumbed end-to-end (caller signal merged with internal timeout signal).- Circuit breaker integration (opt-in via options).
- Constructor: validate options (Zod), default base URL
src/client/client.schemas.ts— Zod forElevenLabsClientOptions, request envelopes, metadata.src/client/client.mdsrc/client/client.test.ts— unit tests withfetchImplinjection: 200 happy path, 401, 403, 404, 422, 429 withRetry-After: 5, 429 with HTTP-date, 500 retried then succeeds, 503 retried then fails, timeout, abort,RESPONSE.MALFORMEDon Zod mismatch, multipart withUint8Array, multipart withReadableStream, multipart with URL fetched first, stream returns body unmodified, OTel span emitted, counter incremented, histogram recorded.
5. WebSocket transport
src/socket/socket.ts—ElevenLabsSocketover injectedWebSocketImpl. Handles ping/pong, abnormal close →WebSocketProtocolError, handshake failure →WebSocketConnectionFailedError, send buffering, ready-state transitions.src/socket/socket.schemas.ts— genericSocketFramediscriminated-union schema (typed per-feature in feature modules).src/socket/socket.mdsrc/socket/socket.test.ts— fakeWebSocketimplementation: connect/send/close/error paths, ping/pong, protocol error mapping.
6. Feature modules — Wave 1
Every feature module follows the same shape: class taking ElevenLabsClient, Zod schemas in *.schemas.ts, sibling .md, *.test.ts with mocked fetchImpl, OTel attributes per call.
6.1 tts
src/tts/tts.ts—ElevenLabsTtsclass.synthesize(voiceId, body)→BinaryResponseviaPOST /v1/text-to-speech/{voiceId}synthesizeWithTimestamps(voiceId, body)→JsonResponse<TtsTimestampsBody>viaPOST /v1/text-to-speech/{voiceId}/with-timestampssynthesizeStream(voiceId, body, options?)→StreamResponseviaPOST /v1/text-to-speech/{voiceId}/streamsynthesizeStreamWithTimestamps(voiceId, body, options?)→StreamResponse<JsonLine>viaPOST /v1/text-to-speech/{voiceId}/stream/with-timestamps
src/tts/tts.schemas.ts— full request schema (text,model_id,language_code,voice_settings,previous_text,next_text,seed,pronunciation_dictionary_locators,apply_text_normalization,apply_language_text_normalization,output_format,optimize_streaming_latency).src/tts/tts.mdsrc/tts/tts.test.ts
6.2 stt
src/stt/stt.ts—ElevenLabsSttclass.transcribe(body)→JsonResponse<SttTranscript>viaPOST /v1/speech-to-text(multipart).
src/stt/stt.schemas.ts—model_id,language_code,tag_audio_events,num_speakers,timestamps_granularity,diarize,diarization_threshold,additional_formats,file,cloud_storage_url,webhookrequest shape;language_code,language_probability,text,words[](withstart,end,speaker_id,type),additional_formats[]response.src/stt/stt.mdsrc/stt/stt.test.ts
6.3 voices
src/voices/voices.ts—ElevenLabsVoicesclass.list(params)—GET /v1/voiceswithshow_legacy,category,voice_type,search,next_page_token,page_sizelistShared(params)—GET /v1/shared-voicesget(voiceId, params?)—GET /v1/voices/{id}getSettings(voiceId)—GET /v1/voices/{id}/settingseditSettings(voiceId, body)—POST /v1/voices/{id}/settings/editgetDefaultSettings()—GET /v1/voices/settings/default
src/voices/voices.schemas.tssrc/voices/voices.mdsrc/voices/voices.test.ts
6.4 sound-generation
src/sound-generation/sound-generation.ts—ElevenLabsSoundGenerationclass.generate(body)—POST /v1/sound-generationreturning binary audio.
src/sound-generation/sound-generation.schemas.ts—text,duration_seconds(0.5–30),loop,prompt_influence(0–1),model_id,output_format.src/sound-generation/sound-generation.mdsrc/sound-generation/sound-generation.test.ts
6.5 audio-isolation
src/audio-isolation/audio-isolation.ts—ElevenLabsAudioIsolationclass.isolate(body)—POST /v1/audio-isolation(multipart) returning binary audio.isolateStream(body)—POST /v1/audio-isolation/streamreturning stream.
src/audio-isolation/audio-isolation.schemas.ts—audio(multipart file),file_format.src/audio-isolation/audio-isolation.mdsrc/audio-isolation/audio-isolation.test.ts
6.6 speech-to-speech
src/speech-to-speech/speech-to-speech.ts—ElevenLabsSpeechToSpeechclass.convert(voiceId, body)—POST /v1/speech-to-speech/{voiceId}(multipart) returning binary audio.convertStream(voiceId, body)—POST /v1/speech-to-speech/{voiceId}/streamreturning stream.
src/speech-to-speech/speech-to-speech.schemas.ts—audio,model_id,voice_settings,seed,remove_background_noise,output_format.src/speech-to-speech/speech-to-speech.mdsrc/speech-to-speech/speech-to-speech.test.ts
6.7 models
src/models/models.ts—ElevenLabsModelsclass.list()—GET /v1/modelsreturning model registry.
src/models/models.schemas.ts—model_id,name,can_be_finetuned,can_do_text_to_speech,can_do_voice_conversion,can_use_style,can_use_speaker_boost,serves_pro_voices,token_cost_factor,description,requires_alpha_access,max_characters_request_free_user,max_characters_request_subscribed_user,maximum_text_length_per_request,languages[],model_rates.src/models/models.mdsrc/models/models.test.ts
7. Index + composite factory
src/index.ts— barrel re-export ofElevenLabsClient+ every feature class + every error subclass + every TS type.src/factory/factory.ts—createElevenLabsApi(options)returning{ client, tts, stt, voices, soundGeneration, audioIsolation, speechToSpeech, models }.src/factory/factory.mdsrc/factory/factory.test.ts— smoke test that factory returns working instances.
8. Property-based tests (tests/property.test.ts)
Retry-Afterparser: arbitrary integer seconds →retryAfterMs = seconds * 1000.Retry-Afterparser: HTTP-date (RFC 7231) → positiveretryAfterMsrelative toclock.now().- Multipart builder: any
MultipartFileshape produces aFormDatawhose blob bytes equal the input bytes. - HTTP status → error code mapping is total and disjoint over
[400, 600). OutputFormatround-trip: every documented format string serialises and parses to itself via Zod.
9. Benchmarks (src/__benchmarks__/third-party-elevenlabs.bench.ts)
client.fetchJsonmock-server round-trip.client.fetchStreammock-server round-trip (1 MB body).tts.synthesizemock-server round-trip.
10. Spec deltas
- Verify
openspec/changes/third-party-elevenlabs/specs/third-party-elevenlabs/spec.mdcovers every Wave 1 requirement from the proposal. - Confirm 100% coverage thresholds enforced in
vitest.config.ts.
11. Pre-publish gate
bunx turbo lint typecheck test --filter=@suaveplan/third-party-elevenlabs— greenbunx turbo test:coverage --filter=@suaveplan/third-party-elevenlabs— 100/100/100/100bun run docs:check --filter=@suaveplan/third-party-elevenlabs— ≥ 90%- Banned-pattern scan (
@deprecated,TODO,FIXME,ts-ignore,ts-expect-error,.skip,.only,\bany\b,console.log,biome-ignore) — zero hits insrc/. README.mdexists at package root with install + quick-start + Modules section.- Subpath exports count matches feature module count.
- No bare versions in
package.json. bunx turbo test --filter=...@suaveplan/third-party-elevenlabs— dependents pass (onlyvoice-elevenlabsafter its rebuild).
12. Archive
- Fold spec deltas into
openspec/specs/third-party-elevenlabs/spec.md. - Move
openspec/changes/third-party-elevenlabs/→openspec/archive/2026-05-02-third-party-elevenlabs/. - Publish to Verdaccio:
bun scripts/publish.ts --package @suaveplan/third-party-elevenlabs. - Update
STATUS.md.
Audit Note (2026-05-02)
Status: DEFERRED.
State at audit:
- Package scaffold exists at
packages/audio/third-party-elevenlabs/with all 11 feature directories (tts,stt,voices,sound-generation,audio-isolation,speech-to-speech,models,client,errors,types,factory). - Typecheck fixed in this audit:
src/errors/errors.tsswitched 15ctx.<prop> = ...writes (banned by TS4111 under index-signatureRecord<string, unknown>) to bracket access (ctx["<prop>"] = ...);src/types/types.tsElevenLabsClientEventsgained areadonly [event: string]: unknownindex signature so it satisfies theEventBusLike<TMap extends Record<string, unknown>>constraint. Typecheck is now green. - Build fails: there is no
src/index.tsbarrel —vite builderrors withCould not resolve entry module "src/index.ts". Task 7 ("Index + composite factory") is unchecked. - No
tests/property.test.ts(Section 8) and nosrc/__benchmarks__/(Section 9). - Package
README.mdnot present at root (Section 11). - Spec deltas live under
specs/third-party-elevenlabs/but have not been folded.
Resuming work:
- Author
src/index.tsre-exportingElevenLabsClient, every feature class, every error subclass, every TS type. - Implement
src/factory/factory.ts(exists as folder) andcreateElevenLabsApi(options). - Add
tests/property.test.tscovering Retry-After parsing, multipart builder, status→code mapping totality, OutputFormat round-trip. - Add
src/__benchmarks__/third-party-elevenlabs.bench.ts(mock-server fetchJson / fetchStream / tts.synthesize). - Author root
README.md. - Confirm
bunx turbo lint typecheck test:coverage --filter=@suaveplan/third-party-elevenlabsis 100/100/100/100, fold deltas, archive.