Initial Commit
This commit is contained in:
154
openspec/archive/2026-05-22-voice-elevenlabs/tasks.md
Normal file
154
openspec/archive/2026-05-22-voice-elevenlabs/tasks.md
Normal file
@@ -0,0 +1,154 @@
|
||||
# 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
|
||||
|
||||
- [x] 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.
|
||||
- [x] `rm -rf packages/audio/voice-elevenlabs` — delete the entire prior implementation.
|
||||
- [x] 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
|
||||
|
||||
- [x] Create `packages/audio/voice-elevenlabs/` with `src/`, `tests/`, `src/__benchmarks__/`.
|
||||
- [x] 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).
|
||||
- [x] Write `tsconfig.lib.json` extending `../../../tsconfig.base.json`.
|
||||
- [x] Write `vite.config.ts` with multi-entry build for every feature module + factory.
|
||||
- [x] Write `vitest.config.ts` using `createTestConfig()` from `@suaveplan/testing/config`, coverage thresholds 100/100/100/100.
|
||||
- [x] Add `lint`, `lint:fix`, `format`, `check`, `typecheck`, `test`, `test:coverage`, `build`, `docs:check` scripts.
|
||||
|
||||
---
|
||||
|
||||
## 2. Error remapping module
|
||||
|
||||
- [x] `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`.
|
||||
- [x] `src/errors/remap.schemas.ts` — Zod for `VoiceOperation = 'tts' | 'tts-stream' | 'stt' | 'voices' | 'sound-generation' | 'audio-isolation'`.
|
||||
- [x] `src/errors/remap.md`
|
||||
- [x] `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`
|
||||
|
||||
- [x] `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`.
|
||||
- [x] `src/tts/tts.schemas.ts` — Zod for `ElevenLabsTtsAdapterOptions`, `mapSynthesisRequest` input/output.
|
||||
- [x] `src/tts/tts.md`
|
||||
- [x] `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`
|
||||
|
||||
- [x] `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).
|
||||
- [x] `src/stt/stt.schemas.ts`
|
||||
- [x] `src/stt/stt.md`
|
||||
- [x] `src/stt/stt.test.ts` — transcribe from bytes / stream / URL, with diarization, with entity detection, error remapping.
|
||||
|
||||
### 3.3 `voices`
|
||||
|
||||
- [x] `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.
|
||||
- [x] `src/voices/voices.schemas.ts`
|
||||
- [x] `src/voices/voices.md`
|
||||
- [x] `src/voices/voices.test.ts`
|
||||
|
||||
### 3.4 `sound-effects`
|
||||
|
||||
- [x] `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`.
|
||||
- [x] `src/sound-effects/sound-effects.schemas.ts`
|
||||
- [x] `src/sound-effects/sound-effects.md`
|
||||
- [x] `src/sound-effects/sound-effects.test.ts`
|
||||
|
||||
### 3.5 `voice-isolation`
|
||||
|
||||
- [x] `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`.
|
||||
- [x] `src/voice-isolation/voice-isolation.schemas.ts`
|
||||
- [x] `src/voice-isolation/voice-isolation.md`
|
||||
- [x] `src/voice-isolation/voice-isolation.test.ts`
|
||||
|
||||
---
|
||||
|
||||
## 4. Composite factory
|
||||
|
||||
- [x] `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.
|
||||
- [x] `src/factory/factory.schemas.ts` — Zod for `CreateElevenLabsVoiceOptions`.
|
||||
- [x] `src/factory/factory.md`
|
||||
- [x] `src/factory/factory.test.ts` — factory returns five engines + client; engines share client; both option shapes (existing client vs construct fresh) work.
|
||||
|
||||
---
|
||||
|
||||
## 5. Index
|
||||
|
||||
- [x] `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`)
|
||||
|
||||
- [x] `AudioFormat` round-trip — every `@suaveplan/voice` `AudioFormat` value passes through to ElevenLabs `output_format` and back without modification.
|
||||
- [x] `SynthesisRequest` round-trip — arbitrary valid request maps to ElevenLabs body and remaps to equivalent `SynthesisResult` shape (subject to documented field mappings).
|
||||
- [x] Error remapping totality — every `ElevenLabsApiError` subclass produces a `VoiceError` for every documented operation; no unmapped paths.
|
||||
|
||||
---
|
||||
|
||||
## 7. Benchmarks (`src/__benchmarks__/voice-elevenlabs.bench.ts`)
|
||||
|
||||
- [x] `ElevenLabsTtsEngine.synthesize` round-trip with mocked `fetchImpl` (no network).
|
||||
- [x] `ElevenLabsSttEngine.transcribe` round-trip with mocked `fetchImpl`.
|
||||
|
||||
---
|
||||
|
||||
## 8. Pre-publish gate
|
||||
|
||||
- [x] `bunx turbo lint typecheck test --filter=@suaveplan/voice-elevenlabs` — green.
|
||||
- [x] `bunx turbo test:coverage --filter=@suaveplan/voice-elevenlabs` — 100/100/100/100.
|
||||
- [x] `bun run docs:check --filter=@suaveplan/voice-elevenlabs` — ≥ 90%.
|
||||
- [x] Banned-pattern scan in `src/` — zero hits.
|
||||
- [x] `README.md` exists at package root.
|
||||
- [x] Subpath exports count matches feature module count.
|
||||
- [x] No bare versions in `package.json`.
|
||||
- [x] `bunx turbo test --filter=...@suaveplan/voice-elevenlabs` — dependents pass (currently none).
|
||||
|
||||
---
|
||||
|
||||
## 9. Archive
|
||||
|
||||
- [x] Fold spec deltas into `openspec/specs/voice-elevenlabs/spec.md`.
|
||||
- [x] Move `openspec/changes/voice-elevenlabs/` → `openspec/archive/2026-05-02-voice-elevenlabs/`.
|
||||
- [x] Publish to Verdaccio: `bun scripts/publish.ts --package @suaveplan/voice-elevenlabs`.
|
||||
- [x] 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.
|
||||
Reference in New Issue
Block a user