8.5 KiB
8.5 KiB
Tasks: voice-elevenlabs
Change ID: voice-elevenlabs
Package: @suaveplan/voice-elevenlabs
Target path: packages/audio/voice-elevenlabs
Prerequisite packages on Verdaccio: @suaveplan/voice, @suaveplan/third-party-elevenlabs, @suaveplan/error, @suaveplan/types, @suaveplan/streams
Supersedes the 2026-04-24 fused-HTTP design. The prior
packages/audio/voice-elevenlabsimplementation MUST be deleted before this rebuild starts.
0. Pre-rebuild teardown
- Confirm
git statusshows the priorpackages/audio/voice-elevenlabscontent is committed or accounted for; if uncommitted scratch work exists, stash it explicitly with the user. rm -rf packages/audio/voice-elevenlabs— delete the entire prior implementation.- Confirm no other package imports from
@suaveplan/voice-elevenlabs(grep -rln '@suaveplan/voice-elevenlabs' packages/); the only references should be inside the to-be-deleted folder itself.
1. Scaffold
- Create
packages/audio/voice-elevenlabs/withsrc/,tests/,src/__benchmarks__/. - Write
package.jsonat version0.1.0,"type": "module","sideEffects": false, full subpathexportsmap per feature module, deps@suaveplan/voice,@suaveplan/third-party-elevenlabs,@suaveplan/error,@suaveplan/types,@suaveplan/streams,zod,tslib(catalogs only). - Write
tsconfig.lib.jsonextending../../../tsconfig.base.json. - Write
vite.config.tswith multi-entry build for every feature module + factory. - Write
vitest.config.tsusingcreateTestConfig()from@suaveplan/testing/config, coverage thresholds 100/100/100/100. - Add
lint,lint:fix,format,check,typecheck,test,test:coverage,build,docs:checkscripts.
2. Error remapping module
src/errors/remap.ts—mapElevenLabsError(err: unknown, context: { operation: VoiceOperation; voiceId?: string }): VoiceErrorcovering everyElevenLabsApiErrorsubclass per the proposal §Error remapping table. Preserves the original error viacause.src/errors/remap.schemas.ts— Zod forVoiceOperation = 'tts' | 'tts-stream' | 'stt' | 'voices' | 'sound-generation' | 'audio-isolation'.src/errors/remap.mdsrc/errors/remap.test.ts— everyElevenLabsApiErrorsubclass produces the expectedVoiceErrorcode, preservescause, and threads throughcontext.voiceIdwhere the table specifies.
3. Engine implementations
3.1 tts
src/tts/tts.ts—ElevenLabsTtsEngineimplementsVoiceSynthesizer.synthesize(req): mapsSynthesisRequest→ ElevenLabs TTS body viamapSynthesisRequest(); callstts.synthesize(req.voiceId, body); mapsBinaryResponse→SynthesisResult(audio bytes, format echoed from request,charactersUsedfrommetadata.characterCost).synthesizeStream(req): maps request, callstts.synthesizeStream(req.voiceId, body), returnsresponse.bodydirectly.- All errors flow through
mapElevenLabsError.
src/tts/tts.schemas.ts— Zod forElevenLabsTtsAdapterOptions,mapSynthesisRequestinput/output.src/tts/tts.mdsrc/tts/tts.test.ts— happy-path synthesize, happy-path stream, voice-not-found, validation error, rate-limited withretryAfterMs, abort,causechain preserved.
3.2 stt
src/stt/stt.ts—ElevenLabsSttEngineimplementsTranscriptionEngine.transcribe(req): mapsTranscriptionRequest→ ElevenLabs STT multipart body (audio:Uint8Array | ReadableStream | stringpassed through to third-partyMultipartFile); callsstt.transcribe(body); maps response toTranscriptionResult(words, derived speakers, entities split from word-stream).
src/stt/stt.schemas.tssrc/stt/stt.mdsrc/stt/stt.test.ts— transcribe from bytes / stream / URL, with diarization, with entity detection, error remapping.
3.3 voices
src/voices/voices.ts—ElevenLabsVoiceRepositoryimplementsVoiceRepository.list(opts): callsvoices.list({ ...opts }); maps each ElevenLabs voice DTO →Voice;nextPageToken/hasMorederived from response.get(id): callsvoices.get(id); remapsTHIRD_PARTY_ELEVENLABS.REQUEST.NOT_FOUND→null(perVoiceRepository.getcontract); other errors flow throughmapElevenLabsErrorwithvoiceIdcontext.
src/voices/voices.schemas.tssrc/voices/voices.mdsrc/voices/voices.test.ts
3.4 sound-effects
src/sound-effects/sound-effects.ts—ElevenLabsSoundEffectEngineimplementsSoundEffectEngine.generate(req): mapsSoundEffectRequest→ ElevenLabs sound-generation body; callssoundGeneration.generate(body); maps response toSoundEffectResult.
src/sound-effects/sound-effects.schemas.tssrc/sound-effects/sound-effects.mdsrc/sound-effects/sound-effects.test.ts
3.5 voice-isolation
src/voice-isolation/voice-isolation.ts—ElevenLabsVoiceIsolationEngineimplementsVoiceIsolationEngine.isolate(req): mapsIsolationRequest→ ElevenLabs isolation multipart body; callsaudioIsolation.isolate(body); maps response toIsolationResult.
src/voice-isolation/voice-isolation.schemas.tssrc/voice-isolation/voice-isolation.mdsrc/voice-isolation/voice-isolation.test.ts
4. Composite factory
src/factory/factory.ts—createElevenLabsVoice(options)returns{ client, tts, stt, voices, effects, isolation }.- Accepts either an existing
ElevenLabsClientorElevenLabsClientOptionsto construct one fresh. - Wires every engine to share the same client instance.
- Accepts either an existing
src/factory/factory.schemas.ts— Zod forCreateElevenLabsVoiceOptions.src/factory/factory.mdsrc/factory/factory.test.ts— factory returns five engines + client; engines share client; both option shapes (existing client vs construct fresh) work.
5. Index
src/index.ts— barrel re-export of all five engines + factory + adapter-options types +mapElevenLabsError(for advanced callers).
6. Property-based tests (tests/property.test.ts)
AudioFormatround-trip — every@suaveplan/voiceAudioFormatvalue passes through to ElevenLabsoutput_formatand back without modification.SynthesisRequestround-trip — arbitrary valid request maps to ElevenLabs body and remaps to equivalentSynthesisResultshape (subject to documented field mappings).- Error remapping totality — every
ElevenLabsApiErrorsubclass produces aVoiceErrorfor every documented operation; no unmapped paths.
7. Benchmarks (src/__benchmarks__/voice-elevenlabs.bench.ts)
ElevenLabsTtsEngine.synthesizeround-trip with mockedfetchImpl(no network).ElevenLabsSttEngine.transcriberound-trip with mockedfetchImpl.
8. Pre-publish gate
bunx turbo lint typecheck test --filter=@suaveplan/voice-elevenlabs— green.bunx turbo test:coverage --filter=@suaveplan/voice-elevenlabs— 100/100/100/100.bun run docs:check --filter=@suaveplan/voice-elevenlabs— ≥ 90%.- Banned-pattern scan in
src/— zero hits. README.mdexists at package root.- Subpath exports count matches feature module count.
- No bare versions in
package.json. bunx turbo test --filter=...@suaveplan/voice-elevenlabs— dependents pass (currently none).
9. Archive
- Fold spec deltas into
openspec/specs/voice-elevenlabs/spec.md. - Move
openspec/changes/voice-elevenlabs/→openspec/archive/2026-05-02-voice-elevenlabs/. - Publish to Verdaccio:
bun scripts/publish.ts --package @suaveplan/voice-elevenlabs. - Update
STATUS.md.
Audit Note (2026-05-02)
Status: DEFERRED.
State at audit:
- The package directory
packages/audio/voice-elevenlabs/does not exist on genesis. The teardown step (Section 0) is a no-op for the audit. - Upstream
@suaveplan/voiceis shipped (103 tests / 100% coverage / archived 2026-05-02). - Upstream
@suaveplan/third-party-elevenlabsis also deferred (typecheck green after this audit's edits, but missingsrc/index.ts, factory implementation, property tests, benchmarks, README) —voice-elevenlabscannot ship until that prerequisite lands.
Resuming work:
- Finish
third-party-elevenlabs(see its audit note) so the third-party client surface is stable. - Then execute Sections 1–9 of this tasks file to scaffold
packages/audio/voice-elevenlabs/and wire the five engines + factory.