Files
2026-05-27 23:29:53 +01:00

8.5 KiB
Raw Permalink Blame History

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-elevenlabs implementation MUST be deleted before this rebuild starts.


0. Pre-rebuild teardown

  • Confirm git status shows the prior packages/audio/voice-elevenlabs content 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/ with src/, tests/, src/__benchmarks__/.
  • Write package.json at version 0.1.0, "type": "module", "sideEffects": false, full subpath exports map per feature module, deps @suaveplan/voice, @suaveplan/third-party-elevenlabs, @suaveplan/error, @suaveplan/types, @suaveplan/streams, zod, tslib (catalogs only).
  • Write tsconfig.lib.json extending ../../../tsconfig.base.json.
  • Write vite.config.ts with multi-entry build for every feature module + factory.
  • Write vitest.config.ts using createTestConfig() from @suaveplan/testing/config, coverage thresholds 100/100/100/100.
  • Add lint, lint:fix, format, check, typecheck, test, test:coverage, build, docs:check scripts.

2. Error remapping module

  • src/errors/remap.ts — mapElevenLabsError(err: unknown, context: { operation: VoiceOperation; voiceId?: string }): VoiceError covering every ElevenLabsApiError subclass per the proposal §Error remapping table. Preserves the original error via cause.
  • src/errors/remap.schemas.ts — Zod for VoiceOperation = 'tts' | 'tts-stream' | 'stt' | 'voices' | 'sound-generation' | 'audio-isolation'.
  • src/errors/remap.md
  • src/errors/remap.test.ts — every ElevenLabsApiError subclass produces the expected VoiceError code, preserves cause, and threads through context.voiceId where the table specifies.

3. Engine implementations

3.1 tts

  • src/tts/tts.ts — ElevenLabsTtsEngine implements VoiceSynthesizer.
    • synthesize(req): maps SynthesisRequest → ElevenLabs TTS body via mapSynthesisRequest(); calls tts.synthesize(req.voiceId, body); maps BinaryResponse → SynthesisResult (audio bytes, format echoed from request, charactersUsed from metadata.characterCost).
    • synthesizeStream(req): maps request, calls tts.synthesizeStream(req.voiceId, body), returns response.body directly.
    • All errors flow through mapElevenLabsError.
  • src/tts/tts.schemas.ts — Zod for ElevenLabsTtsAdapterOptions, mapSynthesisRequest input/output.
  • src/tts/tts.md
  • src/tts/tts.test.ts — happy-path synthesize, happy-path stream, voice-not-found, validation error, rate-limited with retryAfterMs, abort, cause chain preserved.

3.2 stt

  • src/stt/stt.ts — ElevenLabsSttEngine implements TranscriptionEngine.
    • transcribe(req): maps TranscriptionRequest → ElevenLabs STT multipart body (audio: Uint8Array | ReadableStream | string passed through to third-party MultipartFile); calls stt.transcribe(body); maps response to TranscriptionResult (words, derived speakers, entities split from word-stream).
  • src/stt/stt.schemas.ts
  • src/stt/stt.md
  • src/stt/stt.test.ts — transcribe from bytes / stream / URL, with diarization, with entity detection, error remapping.

3.3 voices

  • src/voices/voices.ts — ElevenLabsVoiceRepository implements VoiceRepository.
    • list(opts): calls voices.list({ ...opts }); maps each ElevenLabs voice DTO → Voice; nextPageToken/hasMore derived from response.
    • get(id): calls voices.get(id); remaps THIRD_PARTY_ELEVENLABS.REQUEST.NOT_FOUND → null (per VoiceRepository.get contract); other errors flow through mapElevenLabsError with voiceId context.
  • src/voices/voices.schemas.ts
  • src/voices/voices.md
  • src/voices/voices.test.ts

3.4 sound-effects

  • src/sound-effects/sound-effects.ts — ElevenLabsSoundEffectEngine implements SoundEffectEngine.
    • generate(req): maps SoundEffectRequest → ElevenLabs sound-generation body; calls soundGeneration.generate(body); maps response to SoundEffectResult.
  • src/sound-effects/sound-effects.schemas.ts
  • src/sound-effects/sound-effects.md
  • src/sound-effects/sound-effects.test.ts

3.5 voice-isolation

  • src/voice-isolation/voice-isolation.ts — ElevenLabsVoiceIsolationEngine implements VoiceIsolationEngine.
    • isolate(req): maps IsolationRequest → ElevenLabs isolation multipart body; calls audioIsolation.isolate(body); maps response to IsolationResult.
  • src/voice-isolation/voice-isolation.schemas.ts
  • src/voice-isolation/voice-isolation.md
  • src/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 ElevenLabsClient or ElevenLabsClientOptions to construct one fresh.
    • Wires every engine to share the same client instance.
  • src/factory/factory.schemas.ts — Zod for CreateElevenLabsVoiceOptions.
  • src/factory/factory.md
  • src/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)

  • AudioFormat round-trip — every @suaveplan/voice AudioFormat value passes through to ElevenLabs output_format and back without modification.
  • SynthesisRequest round-trip — arbitrary valid request maps to ElevenLabs body and remaps to equivalent SynthesisResult shape (subject to documented field mappings).
  • Error remapping totality — every ElevenLabsApiError subclass produces a VoiceError for every documented operation; no unmapped paths.

7. Benchmarks (src/__benchmarks__/voice-elevenlabs.bench.ts)

  • ElevenLabsTtsEngine.synthesize round-trip with mocked fetchImpl (no network).
  • ElevenLabsSttEngine.transcribe round-trip with mocked fetchImpl.

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.md exists 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/voice is shipped (103 tests / 100% coverage / archived 2026-05-02).
  • Upstream @suaveplan/third-party-elevenlabs is also deferred (typecheck green after this audit's edits, but missing src/index.ts, factory implementation, property tests, benchmarks, README) — voice-elevenlabs cannot ship until that prerequisite lands.

Resuming work:

  1. Finish third-party-elevenlabs (see its audit note) so the third-party client surface is stable.
  2. Then execute Sections 1–9 of this tasks file to scaffold packages/audio/voice-elevenlabs/ and wire the five engines + factory.