Décisions fondatricesADR-0013 · ElevenLabs hors Gateway

ADR-0013 · ElevenLabs hors Gateway


title: ADR-0013 — ElevenLabs reste hors de l'AI Gateway description: ElevenLabs reste hors de l’AI Gateway : horodatage par caractère et voix clonées.

Statut

Proposé · 2026-09-27

Remplace ou amende : Rien. Écrit une exception que le code portait sans la nommer.

Origine : ADR 0003 du dépôt applicatif Orbit, renumérotée 0013 à l’intégration dans cette série (une seule série d’ADR vit désormais ici).

Statut dans Orbit (2026-10) : en vigueur. Décision reprise de l'ancien dépôt Studio ; l'exception vit dans packages/engine (@orbit/engine) de ce dépôt.

Répond au constat G2-23 de l'audit de la phase 3. Source : la page texte-vers-parole de l'AI Gateway (vercel.com/docs/ai-gateway), lue le 2026-09-27.

Contexte

Toute l'IA du studio passe par l'AI Gateway, avec des chaînes de modèle et aucune clé de fournisseur (ADR 0012). Une seule exception : la voix off, qui appelle ElevenLabs directement, avec la seule clé de fournisseur que le studio détient (ELEVENLABS_API_KEY). Rien ne disait pourquoi.

La route de parole de la Gateway (experimental_generateSpeech avec des modèles comme openai/tts-1) ne donne pas ce dont la voix off a besoin :

  1. Pas d'horodatage par caractère. Le studio lit /with-timestamps d'ElevenLabs : l'alignement sert aux sous-titres et à la durée de la voix, qui dimensionne la réserve d'un lipsync.
  2. Pas de voix clonées. Les voix du fondateur et de son équipe sont des voix de la bibliothèque ElevenLabs du compte, et la porte de consentement (E2E-09) lit leur catégorie dans cette bibliothèque.

Décision

ElevenLabs reste un appel direct, dans une frontière stricte :

  • Un seul module, côté serveur. packages/engine/src/generation/providers.ts (liveProviders().voice et voiceCategory), atteint seulement par runGeneration, lui-même atteint par les fonctions serveur de apps/web/src/generation/ (qui importent server-only). La clé n'est lue que par apps/web/src/generation/deps.ts, jamais envoyée au navigateur.
  • Jamais réessayé. ElevenLabs n'a pas de clé d'idempotence : une requête répétée est une seconde facture. MAX_RETRIES.voice vaut 0 et le module ne boucle pas. Une panne rembourse la réserve ; le membre relance lui-même.
  • Un budget. La synthèse a 60 s (PROVIDER_BUDGET_MS.voice), la lecture de la catégorie 10 s (voiceLookup) ; au-delà, l'appel est annulé et la réserve remboursée dans la requête.
  • Une clé sk_. Le module refuse une clé qui ne commence pas par sk_ (l'identifiant de clé collé à la place du secret rend un 400 trompeur).
  • Le prix est la formule du studio, par caractère envoyé (voiceoverQuote) : il n'y a pas de coût de Gateway à lire.

Conséquences

  • Le rapport d'usage de la Gateway ne contient pas la voix : son rapprochement se fait sur le tableau de bord ElevenLabs, contre les lignes generateVoiceover du ledger (runbook rendus-et-generations.md).
  • Si la Gateway offre un jour l'horodatage et les voix du compte, cette ADR est à remplacer et la voix rejoint le constructeur d'options commun.

Vérification

providers.transport.test.ts : une synthèse qui répond 503 fait une seule requête, porte un signal d'annulation, et la recherche de catégorie aussi.