Bot platform adapters now ship in the
praisonai-bot package. praisonai bot serve still works exactly as documented here; for a standalone install see praisonai-bot Migration.Quick Start
How It Works
| Channel | Native rendering | If unsupported |
|---|---|---|
| Telegram | Inline keyboard | select → button column |
| Slack | Block Kit | web_app action → URL button |
| Discord | Components | web_app action → URL button |
| Reply buttons (≤3) / List messages (≤10 rows) | Buttons >3 or select → list message; URL buttons inlined as text links |
Block Types
| Block | Factory | Use for |
|---|---|---|
| Text | make_text(content) | Markdown body |
| Buttons | make_buttons(items) | Action rows |
| Select | make_select(options) | Dropdown menus |
| Divider | make_divider() | Visual separator |
| Context | make_context(content) | Smaller hint text |
adapt_presentation() first. Buttons over the per-channel cap are dropped lowest-priority first, so high-priority actions like Confirm or Deny survive on Discord and Slack.
Channel Adaptation
adapt_presentation() runs automatically inside every renderer — you can also call it yourself to preview what a channel will receive.
presentation is never mutated — adapt_presentation() always returns a new MessagePresentation.
Channel Limits
max_buttons is per-row capacity; total button cap is max_buttons × max_button_rows.
| Channel | Max buttons (per row) | Max button rows | Total cap | Max button label | Max options | Max option label | Supports select | Supports web_app |
|---|---|---|---|---|---|---|---|---|
| Telegram | 8 | 100 | 800 | 64 | 0 (none) | — | No | Yes |
| Slack | 5 | 1 | 5 | 75 | 100 | 75 | Yes | No |
| Discord | 5 | 5 | 25 | 80 | 25 | 100 | Yes | No |
| 10 (list rows) / 3 (reply buttons) | 1 | 10 (list) / 3 (reply) | 24 (list) / 20 (reply) | 10 | 24 | Yes (native list) | No |
Telegram has no native select menu (
supports_select=False). Any select block is automatically converted to a column of callback buttons by adapt_presentation().WhatsApp uses split caps:
≤3 non-URL tappable buttons render as native reply buttons (title cap 20). More than 3 buttons — or any select block — promotes to a native list message (row title cap 24, up to 10 rows). URL buttons are never tappable widgets; their links are inlined into the message body as Label: URL lines.Renderer Registry
Every channel plugs into a platform-keyed registry so adapters resolve a renderer uniformly and unknown channels degrade to readable text.render_for(platform, presentation) resolves the registered renderer and returns its native payload; channels with no renderer fall back to fallback_text(presentation).
PresentationRenderer Protocol — two static methods, get_limits() and render(). Register a new channel by adding a class with these methods to _RENDERERS.
| Method | Returns | Purpose |
|---|---|---|
get_limits() | PresentationLimits | Channel capability caps used by adapt_presentation() |
render(presentation) | Dict[str, Any] | Native, platform-specific payload |
fallback_text(presentation) flattens a presentation for channels without a registered renderer: text/context/divider blocks become lines, buttons and select options become • Label bullets, and URL buttons inline as • Label: URL so nothing is silently dropped.
See Presentation Renderers for the full registry API, the built-in renderer table, and an “add a channel” recipe.
Native rendering per channel
Call a renderer directly to preview the exact native payload a channel receives.Capability Degradation
When a channel lacks a capability,adapt_presentation() gracefully degrades to the next best option.
select → buttons on Telegram:
web_app → URL button on Slack/Discord:
For long
action_id/value combinations, callback payloads are kept under 64 bytes using a SHA1 hash (16-hex truncation) so distinct options remain distinct after truncation.Approval Prompts
On channels implementingSupportsPresentation, approval prompts render as inline Allow Once, Allow Always, and Deny buttons wired to /approve <approval_id> ... commands — replacing fragile yes/no text classification.
Text-keyword backends (TelegramApproval, SlackApproval, DiscordApproval) remain valid fallbacks for channels without presentation support.
When the underlying bot uses MessagePresentation.approval(...), the button namespace is automatically actor-bound — see Interactive Callback Authorization.
Best Practices
Use factory methods for blocks
Use factory methods for blocks
PresentationBlock.make_text() and make_buttons() are the agent-friendly path — fewer field mistakes than raw dataclass construction.Set high priority on destructive or critical actions
Set high priority on destructive or critical actions
When a channel forces truncation,
adapt_presentation() drops the lowest-priority buttons first. Give Confirm / Deny / Cancel high priority so they always reach the user.Provide plain-text content as fallback
Provide plain-text content as fallback
Always set
MessagePresentation text content so channels without a registered renderer (e.g. Email) still deliver a readable message. WhatsApp now renders interactive presentations natively.Use MessagePresentation.approval for tool gates
Use MessagePresentation.approval for tool gates
The built-in helper wires standard Allow/Deny buttons consistently across Telegram, Slack, and Discord.
Related
Approval Protocol
Tool approval backends
Bot Gateway
Multi-channel gateway server
Bot Platform Capabilities
PresentationLimits governs interactive widgets; PlatformCapabilities governs message length, chunking, and editing.
