Skip to main content

Channel capability matrix

Not every channel supports every feature. Telegram edits messages in place; email cannot. Slack uploads files; WhatsApp does not. The gateway reads each adapter's declared capabilities and degrades gracefully — a streamed reply falls back to a single message where edits are unavailable, and outbound media falls back to text where uploads are unsupported. This page is the authoritative lookup for what each adapter supports today.

Each capability is declared on the adapter itself (the canSendTyping / canEditMessage / canReact / canSendFiles flags and the capabilities manifest), so this table tracks source, not aspiration.

Support matrix

CapabilityTelegramSlackDiscordWhatsAppEmail
Typing indicator✓ (probed)
Streaming draft edits
Message edit (editMessage)
Inbound media
Outbound media
Reactions
Threads / topics✓ (forum topics)✓ (thread_ts)
Reply-to a message
Approval buttons
Slash commands
Voice in (transcribed)
Voice out (TTS)
Voice-out renderingvoice bubblefile + inline playerfile + inline playervoice bubble (ptt)
Webhook mode
Max message length40963000200065536100000

✓ (probed) — Slack's typing indicator uses an unofficial API; the adapter probes it once at runtime and reports the real result thereafter, so canSendTyping reflects what actually works on the workspace.

Voice in requires the adapter to classify an inbound upload as type: 'audio' — the gateway's transcription gate keys on nothing else. All four chat adapters do: Telegram from voice and audio messages, Slack and Discord from the upload's extension or content type, WhatsApp from audioMessage. A .webm upload on Slack or Discord stays a video and is not transcribed.

Voice out is a separate declaration — voiceCaps plus sendVoiceNote, see Voice output caps below — which is why a channel can speak without being able to listen.

Voice output caps

Voice output is declared per adapter as an AdapterVoiceCaps object, not inferred from method names. The gateway reads it through isVoiceOutboundAdapter and transcodes each reply into the first format the target accepts.

PlatformAccepted outbound formats (preferred first)Rendering (kind)Platform flagsSize cap
Telegramopus, ogg, mp3voice_note50 MB
Slackmp3, m4a, wavfile25 MB
Discordmp3, ogg, wavfile25 MB
WhatsAppopus, oggvoice_noteptt: true16 MB
Email
  • voice_note renders as a playable bubble; file renders as an upload the platform gives an inline player. Neither Slack nor Discord has a bot-accessible voice-bubble primitive, so both declare file rather than claiming one.
  • Producing the accepted container needs ffmpeg on the gateway host unless the TTS provider already emits it. Without ffmpeg, a mismatched reply is skipped (gateway.voice_format_unsupported) rather than delivered unplayable.
  • A reply over the size cap is skipped with gateway.voice_too_large; the text reply has already been delivered.

How degradation works

The gateway never assumes a capability. Three behaviors depend directly on this matrix:

  • Streaming draft edits (W3.1). When a chat is streaming-enabled and the adapter reports canEditMessage, the gateway delivers the reply as throttled editMessage updates that grow in place. When the adapter cannot edit (WhatsApp, Email), the reply is delivered as a single final message instead. Streaming defaults on for direct messages and off for group chats; set display.streaming_edits in ~/.ethos/config.yaml to off, dms, or all.
  • Outbound media (W3.2). When a tool produces media and the adapter reports canSendFiles (Telegram, Slack), the gateway maps it to native attachments (sendPhoto / sendDocument on Telegram, files.uploadV2 on Slack). When the adapter cannot send files, the reply degrades to the text summary — no error, no dropped turn.
  • Spoken replies. When a conversation's voice mode says speak and the adapter declares voiceCaps, the gateway synthesizes the reply once, transcodes it to the first accepted format, and calls sendVoiceNote. When the adapter declares no caps, nothing is synthesized and the turn ends with the text reply already delivered — the skip is recorded as a gateway.voice_no_caps event rather than passing silently. See Send and receive voice notes on a channel.

Source

Each adapter declares its capabilities in source. Consult the adapter for the exact runtime behavior:

PlatformAdapter
Telegramextensions/platform-telegram/src/index.ts
Slackextensions/platform-slack/src/adapter.ts
Discordextensions/platform-discord/src/index.ts
WhatsAppextensions/platform-whatsapp/src/index.ts
Emailextensions/platform-email/src/index.ts

The capability contract itself — ChannelCapabilities and the legacy AdapterCapabilities — lives in packages/types/src/platform.ts.

See also