Saltar al contenido principal
  • STT: OpenAI (whisper-1, gpt-4o-transcribe, gpt-4o-mini-transcribe y servidores compatibles) y Deepgram (nova-3).
  • TTS: OpenAI (gpt-4o-mini-tts, tts-1, tts-1-hd) y ElevenLabs.
  • Sin SDKs: HTTPS directo con secureFetch de @botmesh/http (anti-SSRF, timeouts, límites de tamaño).
  • install() es síncrono y nunca toca la red: los adapters se resuelven en el primer uso y un warmup no fatal registra al arrancar una configuración mala (falta la API key, proveedor desconocido).

Inicio rápido

autoTranscribe sin stt, o autoReplyWithVoice sin tts, lanza un VoiceConfigError al instalar el plugin.

Transcribir notas de voz (autoTranscribe)

Un hook before('message') (prioridad 100, así que corre antes que la mayoría) descarga cada nota de voz, la transcribe y deja:
  • ctx.transcript: el texto tal cual lo devolvió el servicio ('' si no se dijo nada).
  • ctx.body: el mismo texto sin la puntuación de los extremos (¡Hola! pasa a Hola), para que los triggers por igualdad, c.ask(), la NLU y los handlers de IA “oigan” el audio.
  • ctx.media: el audio descargado (no se vuelve a descargar).
  • ctx.transcriptError: el error, si la transcripción falló. El mensaje se sigue enrutando (sin transcripción) y se registra un aviso.
  • ctx.type: sigue siendo 'voice', así que los triggers VOICE_NOTE siguen disparando. Con el modo por defecto asText: 'auto' pasa a 'text' solo mientras una conversación de ese usuario espera texto (c.ask(), c.waitText()), para que la nota de voz responda a la pregunta. Si la conversación espera audio (c.waitMedia(['voice'])), un menú (c.choose(), que lee body) o cualquier mensaje (c.waitAny()), la nota llega como 'voice' con su transcripción.
autoTranscribe: false desactiva el hook; ctx.tools.voice.transcribe(ctx) sigue disponible a demanda.

Con el autoresponder de IA

El autoresponder de @botmesh/ai solo contesta mensajes de texto. Con el modo por defecto asText: 'auto', una nota de voz enviada fuera de una conversación sigue siendo 'voice', así que la IA no la contesta. Para que la IA conteste notas de voz, usa asText: true:

Idioma e i18n

La pista de idioma para el servicio de STT es, en este orden:
  1. autoTranscribe.language, si está definida.
  2. El idioma que el i18n resolvió para el remitente (ctx.lang, código base: en-gb pasa a en), solo si viene de una señal real (preferencia guardada, provider o detector), nunca del valor por defecto a secas. Se desactiva con useContextLanguage: false.
  3. Si no, ninguna: el servicio detecta el idioma.
Después de que el hook escribe la transcripción en ctx.body, el core vuelve a resolver el idioma si venía del detector o del valor por defecto, así que una función detect del i18n ve el texto hablado. Un ctx.lang fijado por un hook se respeta.

Responder con voz (autoReplyWithVoice)

Mientras el último mensaje entrante de un chat sea una nota de voz, el texto saliente hacia ese chat se sintetiza y se envía como nota de voz. Es un hook before('send'), así que cubre ctx.reply, los pasos say de los flujos, c.say y el autoresponder de IA. Cuando el usuario vuelve a escribir texto, el chat vuelve a texto. Siempre se quedan en texto:
  • Todo lo que envía ctx.stream(): borradores, ediciones en vivo, trozos y el mensaje final (el payload de envío lleva stream). Una respuesta en streaming debe seguir siendo un único texto editable.
  • Las ediciones en el sitio de ctx.edit.
  • Las respuestas a un canal cuyo capabilities.contentKinds no incluye 'voice'.
Si la síntesis falla, se envía el texto original.

ctx.tools.voice

El plugin inyecta un VoiceClient como ctx.tools.voice (tipado por module augmentation, sin casts): Opciones de transcripción: language, prompt, signal, timeoutMs. Opciones de síntesis: voice, instructions, signal, timeoutMs. Un timeoutMs por llamada solo puede acortar limits.timeoutMs. source también puede ser un Uint8Array.

Adapters

Todos los adapters incluidos aceptan además timeoutMs (solo puede acortar limits.timeoutMs), maxResponseBytes y, para servidores propios en una red de confianza, allowHttp / allowPrivateHosts (por ejemplo un servidor compatible con Whisper en http://127.0.0.1:8000/v1). También puedes pasar un adapter propio (un modelo local, otro servicio):

Formatos de audio

  • Entrada: las notas de voz de WhatsApp y Telegram llegan en OGG/Opus, que Whisper, gpt-4o-*-transcribe y Deepgram aceptan directamente. Si el canal no informa el mimetype, se detecta por los primeros bytes (OGG, WAV, MP3, MP4/M4A, WebM, FLAC, AMR) y, si no, se asume OGG.
  • Salida en WhatsApp: una nota de voz debe ser OGG/Opus. @botmesh/baileys transcodifica kind: 'voice' a OGG/Opus por su cuenta (con el binario de ffmpeg que encuentre: FFMPEG_PATH, el paquete opcional ffmpeg-static o ffmpeg en el PATH), así que cualquier formato vale. Para Cloud API y Telegram pide OGG/Opus al TTS: es el valor por defecto de OpenAI (format: 'opus'); en ElevenLabs usa outputFormat: 'opus_48000_64' si tu plan lo ofrece.
  • Este paquete no transcodifica nada (no depende de ffmpeg).

Límites

La duración se comprueba antes de pagar la transcripción cuando se conoce (un durationSec explícito o un archivo OGG). Para otros contenedores no se estima. Los límites se validan al enchufar el plugin (o al crear el VoiceClient): timeoutMs debe estar entre 1 y 2147483647 ms (el rango de setTimeout) y los demás topes deben ser positivos (Infinity desactiva un tope). Cualquier otro valor lanza un VoiceConfigError que nombra el campo.

Errores

Todos extienden VoiceError y llevan un code estable: Los mensajes de error y los logs nunca incluyen API keys, audio ni transcripciones. Se conserva el detalle de error del propio servicio (explica un 400 o un 401), pero antes se le quita la API key de la petición y todo lo que tenga forma de clave (sk-… o sk_…, aunque venga enmascarada).

Testing

Los adapters HTTP aceptan fetch y resolver inyectados para probarlos sin red. Más en el README de @botmesh/voice.

Siguientes pasos