ctx.stream(source, opts) envía una respuesta que todavía se está generando
(tokens de un LLM, un informe largo) a medida que se produce, con la mejor
estrategia que admite el canal: borradores nativos en Telegram, ediciones en
vivo en Discord y mensajes del tamaño de una frase con “escribiendo…” en
WhatsApp. El mismo handler funciona en todos los canales.
AsyncIterable de strings o bytes (un
generador async, un Readable de Node), un ReadableStream web o un
Iterable de strings. Los fragmentos en bytes se decodifican como UTF-8.
Estrategias por canal
El provider declaracapabilities.streaming ('draft' | 'edit' | 'none') y
ctx.stream() elige la estrategia a partir de ahí:
- Los borradores solo se intentan en chats privados. Si el transporte rechaza un borrador en tiempo de ejecución, el stream baja a ediciones (si el canal puede editar) o a trozos.
strategyfuerza una opción; una no soportada bajadraft→edit→chunk.'chunk'y'buffer'siempre están disponibles.- Ningún mensaje supera
maxLength(por defectocapabilities.maxTextLength, si no 4096): el texto largo se parte por límites naturales (párrafos, líneas, frases, palabras) y sigue en un mensaje nuevo. En los canales que cuentan bytes (capabilities.maxTextBytes, Instagram) ningún mensaje supera tampoco esos bytes UTF-8.
Opciones
ctx.stream() resuelve con un StreamResult: text, messages,
strategy (la que se usó al final, tras cualquier fallback), cancelled y
stoppedByUser. Rechaza con el error de la fuente (después de enviar lo
producido) o con el primer error de envío.
Cancelación
Un stream se detiene cuando se abortasignal o, en los borradores de
Telegram con canStop: true, cuando el usuario pulsa el botón de parar
(stoppedByUser: true). Se deja de leer y se llama al return() de la
fuente, así que una petición de IA también se aborta. bot.stop() también cancela
todos los ctx.stream() en curso (después del drain, si lo hay), pero no envía
nada más: ni envío final, ni edición, ni borrado, diga lo que diga onCancel.
onCancel: 'flush'(por defecto) envía el texto producido hasta entonces.onCancel: 'discard'no envía nada más: un borrador simplemente caduca y, con la estrategia de edición, se borra el mensaje que se estaba editando (si el canal puede borrar; si no, solo se quita el cursor). Los mensajes ya completados antes de cancelar se quedan.
stopped_message_generation, que traen las pulsaciones del botón de parar.
Límites de envío
Las actualizaciones en vivo se espacian y nunca corren en paralelo. Si el transporte responde con unRateLimitedError, se pausan durante su
retryAfterMs. La edición final (que quita el cursor y muestra el texto
completo) espera a que pase la pausa y reintenta un límite hasta tres veces.
Hooks y el log de mensajes
Todo lo que envíactx.stream() pasa por los hooks
before('send'), así que un hook que censura, traduce o aborta respuestas
también cubre el texto en streaming. El payload lleva una marca stream:
Con @botmesh/ai
El autoresponder de IA transmite su respuesta con la opción stream:
streamaceptatrueo lasStreamOptionsde arriba (sinsignalnisendOptions), másenabled: falsepara apagarlo conservando las opciones. Por defecto está apagado (un mensaje al final).- El “escribiendo…” sigue a
humanizeTypingsalvo que fijestyping. - Un rechazo responde
refusalMessagey no se guarda; una respuesta vacía respondeerrorMessage, igual que un stream que falla. - Con
onCancel: 'discard', una respuesta cancelada tampoco entra en el contexto de IA, porque el usuario nunca la vio completa. - Con
toolsconfiguradas los turnos no se transmiten: el bucle de herramientas necesita la respuesta completa, así que el autoresponder vuelve a un solo mensaje.
ctx.tools.ai.streamReplyInChat() es el gemelo en
streaming de replyInChat(): lee y guarda el historial del chat, aplica
memoria y RAG y entrega la respuesta por partes.
keepPartial: false (combínalo con
onCancel: 'discard'). El timeoutMs del plugin funciona como timeout de
inactividad: si no llega ningún fragmento en ese tiempo, lanza
AITimeoutError.
Con @botmesh/voice y autoReplyWithVoice, la salida en
streaming siempre se queda en texto.
Destinos propios
ctx.stream() es streamText() ligado al chat actual. Para otros destinos
(un widget web, un test) úsalo directamente con un StreamTarget:
resolveStreamStrategy(target, requested) dice qué estrategia tendría un
destino, y findSplitPoint(text, max) dónde se cortaría el texto. Los dos
vienen de @botmesh/core/internal, sin garantía semver.
Testing
MockProvider simula cada modo con su opción streaming:
streaming: 'edit' también expone editMessage (se registra en edits) y
deleteMessage (deleted); streaming: 'draft' añade sendDraft (se
registra en drafts). maxTextLength y maxTextBytes simulan los límites de longitud y
supports activa u oculta cada método por separado.
Siguientes pasos
- Plugin de IA — el autoresponder y
ctx.tools.ai - Mensajes y media — ediciones, “escribiendo…” y soporte por transporte
- Hooks — el pipeline de
send