Skip to main content
A core, platform-keyed registry turns one portable MessagePresentation into a native payload for each channel. Built-in channels and pip-installed plugins register through the same seam — register_presentation_renderer — and unknown channels degrade gracefully to text.

Quick Start

1

Render for a known channel

2

Degrade unknown channels to text

3

Register a renderer from a plugin

Any pip-installed channel plugin plugs a native renderer into the core registry — no wrapper edits required.

How It Works

render_for(platform, presentation) resolves the platform’s renderer through the core registry and returns its native payload; unregistered channels fall back to fallback_text. The registry lives in core (praisonaiagents.bots); the wrapper (praisonai_bot) only resolves through it and registers the four built-ins at import time.

The PresentationRendererProtocol

Every renderer implements PresentationRendererProtocol — two @staticmethod methods. The protocol is @runtime_checkable, so isinstance(cls, PresentationRendererProtocol) works. Each renderer runs adapt_presentation against its own get_limits() before mapping blocks, so button overflow, unsupported selects/web-apps, and label truncation are applied uniformly. Both import paths are valid:
register_presentation_renderer and get_presentation_renderer are also exported from the top-level praisonaiagents.bots package — no need to reach into the presentation submodule.

Validation & Precedence

Registration fails loudly on misuse, and lookups are case-insensitive. The wrapper registers each built-in only if the core slot is empty, so a plugin that registers first for "telegram" wins — the built-in never clobbers it.
If the installed praisonaiagents predates the seam, the wrapper silently falls back to its local built-in map — existing wrapper code keeps working on older cores.

Built-in Renderers

The wrapper registers all four through the core seam at import time.

Graceful Degradation

fallback_text(presentation) keeps interactive content readable for channels without a renderer — nothing is silently dropped.
Text/context/divider blocks become lines, buttons and select options become • Label bullets, and URL buttons inline as • Label: URL.

Add a Channel

Write a class with the two static methods, then register it through the public core seamregister_presentation_renderer.
render_for("mychannel", presentation) — and render_for("MyChannel", ...), render_for("MYCHANNEL", ...) — now all resolve the same renderer.

Best Practices

Call register_presentation_renderer(platform, MyRenderer) from praisonaiagents.bots. That is the only registration visible to the resolution path everywhere else — a plugin renderer becomes indistinguishable from a built-in.
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.
render_for(platform, presentation) resolves the renderer and falls back to text for unknown platforms, so adapters stay channel-agnostic.
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.
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.

Bot Presentations

The portable presentation model and per-channel limits

Bot Platform Plugins

Register a channel adapter and its native renderer as a pip-installable plugin

Message Presentation

Buttons, menus, and web-app links on agent replies

WhatsApp Bot

Native interactive rendering on WhatsApp