MessagePresentation into a native payload for each channel, and degrades gracefully to text when a channel has no renderer.
Quick Start
How It Works
render_for(platform, presentation) resolves the platform’s renderer from the registry and returns its native payload; unregistered channels fall back to fallback_text.
| Function | Returns | Purpose |
|---|---|---|
get_renderer(platform) | type or None | Look up the registered renderer class |
render_for(platform, presentation) | Dict[str, Any] | Render natively, else fall back to text |
fallback_text(presentation) | Dict[str, Any] | Flatten to a readable {"text": ...} payload |
The PresentationRenderer Protocol
Every renderer implements thePresentationRenderer Protocol — two static methods.
| Method | Returns | Purpose |
|---|---|---|
get_limits() | PresentationLimits | Channel capability caps used by adapt_presentation() |
render(presentation) | Dict[str, Any] | Native, platform-specific payload |
adapt_presentation against its own get_limits() before mapping blocks, so button overflow, unsupported selects/web-apps, and label truncation are applied uniformly.
Built-in Renderers
| Platform | Renderer | Payload shape |
|---|---|---|
telegram | TelegramPresentationRenderer | {"text", "reply_markup": {"inline_keyboard": ...}} |
slack | SlackPresentationRenderer | {"blocks": [...]} (Block Kit) |
discord | DiscordPresentationRenderer | {"content", "components": [...]} |
whatsapp | WhatsAppPresentationRenderer | {"text", "interactive": {"type": "button" | "list", ...}} |
Graceful Degradation
fallback_text(presentation) keeps interactive content readable for channels without a renderer — nothing is silently dropped.
• Label bullets, and URL buttons inline as • Label: URL.
Add a Channel
Write a class with the two static methods, then register it into_RENDERERS.
render_for("mychannel", presentation) now resolves your renderer.
Best Practices
Always adapt before mapping blocks
Always adapt before mapping blocks
Run
adapt_presentation(presentation, get_limits()) first so button overflow, label caps, and unsupported selects/web-apps degrade uniformly before you build the native payload.Use render_for, not the class directly, in adapters
Use render_for, not the class directly, in adapters
render_for(platform, presentation) resolves the renderer and falls back to text for unknown platforms, so adapters stay channel-agnostic.Never drop interactive content
Never drop interactive content
When a channel can’t render a widget, surface it as text (labels, URLs) the way
fallback_text does — a silently dropped button is worse than a text link.Return the id shape your callback layer expects
Return the id shape your callback layer expects
Reply/list-row ids are how taps route back to your handler. Derive stable ids (command, callback value, or URL) and keep them within the channel’s id cap.
Related
Bot Presentations
The portable presentation model and per-channel limits
Message Presentation
Buttons, menus, and web-app links on agent replies
WhatsApp Bot
Native interactive rendering on WhatsApp
Channel Capabilities
Live-edit, reactions, typing, and text limits per channel

