Skip to main content

okbot_render/
lib.rs

1//! Renders the agent's Markdown into each chat platform's dialect and splits long replies.
2//!
3//! Agents answer in CommonMark. Each platform shows a different subset: Zalo has no links, code
4//! blocks or tables; Google Chat and Slack use `*bold*` and `<url|text>`; and every platform caps
5//! message length. [`MarkdownRenderer`] parses the answer once, writes it in the channel's
6//! [`MarkdownDialect`](okbot_core::MarkdownDialect) and packs the result into messages that fit,
7//! never splitting a code block without re-opening its fence.
8//!
9//! ```
10//! use okbot_core::{ChannelCaps, LengthUnit, MarkdownDialect, Renderer};
11//! use okbot_render::MarkdownRenderer;
12//!
13//! let caps = ChannelCaps {
14//!     max_message_len: 2000,
15//!     length_unit: LengthUnit::Utf16,
16//!     dialect: MarkdownDialect::ZaloMd,
17//!     ..ChannelCaps::basic()
18//! };
19//! let parts = MarkdownRenderer.render("See [docs](https://example.com) for **details**.", &caps);
20//! assert_eq!(parts[0].text, "See docs (https://example.com) for **details**.");
21//! ```
22
23mod emit;
24mod ir;
25mod split;
26
27use okbot_core::{ChannelCaps, RenderedMessage, Renderer};
28
29/// The default [`Renderer`]: CommonMark in, platform dialect out.
30#[derive(Debug, Clone, Copy, Default)]
31pub struct MarkdownRenderer;
32
33impl Renderer for MarkdownRenderer {
34    fn render(&self, markdown: &str, caps: &ChannelCaps) -> Vec<RenderedMessage> {
35        let blocks = ir::parse(markdown);
36        let chunks = emit::emit(&blocks, caps.dialect);
37        split::pack(chunks, caps.max_message_len, caps.length_unit)
38            .into_iter()
39            .map(|text| RenderedMessage { text })
40            .collect()
41    }
42}