Skip to content

Commit 83fbc89

Browse files
authored
Merge pull request #1313 from openabdev/feat/telegram-native-tables
feat(telegram): auto-disable table code-block wrapping for native rendering
2 parents 4f28c2d + 2e1684a commit 83fbc89

7 files changed

Lines changed: 51 additions & 10 deletions

File tree

crates/openab-core/src/adapter.rs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -407,7 +407,10 @@ pub trait ChatAdapter: Send + Sync + 'static {
407407
/// lets the platform render the raw Markdown table itself.
408408
/// Default: `false` (keep converting). Overridden by Slack (Block Kit
409409
/// `markdown` blocks / `markdown_text` stream chunks render tables natively).
410-
fn renders_native_tables(&self) -> bool {
410+
/// The `platform` parameter allows shared adapters (e.g. UnifiedGatewayAdapter)
411+
/// to make per-platform decisions.
412+
fn renders_native_tables(&self, platform: &str) -> bool {
413+
let _ = platform;
411414
false
412415
}
413416

@@ -697,7 +700,7 @@ impl AdapterRouter {
697700
// Platforms that render Markdown tables natively (e.g. Slack Block Kit
698701
// `markdown` blocks / `markdown_text` stream chunks) skip the
699702
// table→code/bullets pre-pass so the raw table renders natively.
700-
let table_mode = if adapter.renders_native_tables() {
703+
let table_mode = if adapter.renders_native_tables(&thread_channel.platform) {
701704
TableMode::Off
702705
} else {
703706
self.table_mode
@@ -1794,7 +1797,7 @@ mod tests {
17941797
assert!(!adapter.use_streaming(false));
17951798
// renders_native_tables defaults to false: platforms that don't override
17961799
// it keep the table→code/bullets conversion (e.g. Discord, Gateway).
1797-
assert!(!adapter.renders_native_tables());
1800+
assert!(!adapter.renders_native_tables("discord"));
17981801
}
17991802

18001803
#[test]

crates/openab-core/src/config.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -567,6 +567,11 @@ pub struct GatewayConfig {
567567
/// Show "…" placeholder at streaming start. Default: true. Set false for platforms using drafts.
568568
#[serde(default = "default_true")]
569569
pub streaming_placeholder: bool,
570+
/// Whether the connected gateway renders tables natively (e.g. Telegram Rich Messages).
571+
/// Default: true (matches Telegram default). Set false if Rich Messages is disabled
572+
/// on the gateway daemon to preserve table code-block wrapping.
573+
#[serde(default = "default_true")]
574+
pub telegram_rich_messages: bool,
570575
/// Message dispatch mode. Default: per-message.
571576
#[serde(default)]
572577
pub message_processing_mode: MessageProcessingMode,

crates/openab-core/src/gateway.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -233,6 +233,7 @@ pub struct GatewayAdapter {
233233
platform_name: &'static str,
234234
streaming: bool,
235235
streaming_placeholder: bool,
236+
telegram_rich_messages: bool,
236237
}
237238

238239
impl GatewayAdapter {
@@ -242,13 +243,15 @@ impl GatewayAdapter {
242243
platform_name: &'static str,
243244
streaming: bool,
244245
streaming_placeholder: bool,
246+
telegram_rich_messages: bool,
245247
) -> Self {
246248
Self {
247249
ws_tx,
248250
pending,
249251
platform_name,
250252
streaming,
251253
streaming_placeholder,
254+
telegram_rich_messages,
252255
}
253256
}
254257

@@ -730,6 +733,13 @@ impl ChatAdapter for GatewayAdapter {
730733
fn show_streaming_placeholder(&self) -> bool {
731734
self.streaming_placeholder
732735
}
736+
737+
fn renders_native_tables(&self, _platform: &str) -> bool {
738+
// Telegram renders markdown tables natively via Rich Messages;
739+
// skip the table→code-block pre-pass for that platform only when
740+
// Rich Messages is confirmed enabled.
741+
self.platform_name == "telegram" && self.telegram_rich_messages
742+
}
733743
}
734744

735745
// --- Run the gateway adapter (connects to gateway WS, routes events to AdapterRouter) ---
@@ -748,6 +758,7 @@ pub struct GatewayParams {
748758
pub trusted_bot_ids: Vec<String>,
749759
pub streaming: bool,
750760
pub streaming_placeholder: bool,
761+
pub telegram_rich_messages: bool,
751762
pub stt: crate::config::SttConfig,
752763
}
753764

@@ -782,6 +793,7 @@ pub async fn run_gateway_adapter(
782793
params.streaming
783794
};
784795
let streaming_placeholder = params.streaming_placeholder;
796+
let telegram_rich_messages = params.telegram_rich_messages;
785797
let stt_config = params.stt;
786798

787799
let connect_url = match &params.token {
@@ -832,6 +844,7 @@ pub async fn run_gateway_adapter(
832844
platform,
833845
streaming,
834846
streaming_placeholder,
847+
telegram_rich_messages,
835848
));
836849
let slash_ws_tx = ws_tx.clone(); // for fire-and-forget slash command responses
837850
let mut tasks: tokio::task::JoinSet<()> = tokio::task::JoinSet::new();

crates/openab-core/src/slack.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -544,7 +544,7 @@ impl ChatAdapter for SlackAdapter {
544544
self.streaming && !other_bot_present
545545
}
546546

547-
fn renders_native_tables(&self) -> bool {
547+
fn renders_native_tables(&self, _platform: &str) -> bool {
548548
true
549549
}
550550

@@ -2307,7 +2307,7 @@ mod tests {
23072307
fn slack_renders_native_tables() {
23082308
let ttl = std::time::Duration::from_secs(300);
23092309
let adapter = SlackAdapter::new("xoxb-test".into(), ttl, AllowBots::Mentions, true, crate::multibot_cache::MultibotCache::load("/dev/null".into()), true);
2310-
assert!(adapter.renders_native_tables());
2310+
assert!(adapter.renders_native_tables("slack"));
23112311
}
23122312
}
23132313

docs/telegram.md

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -40,16 +40,24 @@ Set environment variables:
4040

4141
OAB config (`config.toml`):
4242

43-
**Minimal**just pass the API key to the agent:
43+
**Minimal**bot token via env var, API key passed to agent:
4444

4545
```toml
46+
[telegram]
47+
bot_token = "${TELEGRAM_BOT_TOKEN}"
48+
allow_all_users = true
49+
4650
[agent]
4751
env = { KIRO_API_KEY = "${KIRO_API_KEY}" }
4852
```
4953

50-
**Recommended** — with tuned pool, streaming, and native table rendering:
54+
**Recommended** — with access control, tuned pool, and streaming:
5155

5256
```toml
57+
[telegram]
58+
bot_token = "${TELEGRAM_BOT_TOKEN}"
59+
allowed_users = ["176096071"]
60+
5361
[agent]
5462
env = { KIRO_API_KEY = "${KIRO_API_KEY}" }
5563

@@ -59,9 +67,13 @@ session_ttl_hours = 1
5967

6068
[reactions]
6169
tool_display = "compact"
70+
```
71+
72+
Table rendering is automatically disabled for Telegram (tables pass through as native markdown for Rich Messages). To force code-block wrapping, set explicitly:
6273

74+
```toml
6375
[markdown]
64-
tables = "off"
76+
tables = "code"
6577
```
6678

6779
Streaming is enabled by default when Rich Messages are active — replies are streamed live via `sendRichMessageDraft` with rich formatting, then finalized with `sendRichMessage`. If `TELEGRAM_RICH_MESSAGES=false`, streaming is also disabled by default. To override, set `TELEGRAM_STREAMING=true` or `TELEGRAM_STREAMING=false` explicitly.
@@ -356,11 +368,11 @@ Agent replies are rendered with Telegram Markdown: **bold**, `code`, and code bl
356368

357369
With **Rich Messages** enabled (default, requires Bot API 10.1+), headings (`##`) and tables render with full formatting via `sendRichMessage`. Code blocks remain on the legacy path for syntax highlighting and copy-button support. Content exceeding 4096 characters is automatically handled via rich messages (up to 32768 chars).
358370

359-
> **Important:** OAB's default table mode wraps markdown tables in code blocks before they reach the gateway. To allow native Telegram table rendering via Rich Messages, disable this conversion in your `config.toml`:
371+
> **Note:** As of v0.9.0, OAB automatically disables table code-block wrapping for Telegram adapters (both unified and standalone gateway) when Rich Messages are enabled. Tables pass through as raw markdown and render natively. No `[markdown]` config is needed. To override this and force code-block wrapping, add:
360372
>
361373
> ```toml
362374
> [markdown]
363-
> tables = "off"
375+
> tables = "code"
364376
> ```
365377
>
366378
> Rich Messages requires gateway version **v0.6.0-rc.1** or above (`ghcr.io/openabdev/openab-gateway:v0.6.0-rc.1`+).

src/main.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -587,6 +587,7 @@ async fn main() -> anyhow::Result<()> {
587587
trusted_bot_ids: gw_cfg.trusted_bot_ids,
588588
streaming: gw_cfg.streaming,
589589
streaming_placeholder: gw_cfg.streaming_placeholder,
590+
telegram_rich_messages: gw_cfg.telegram_rich_messages,
590591
stt: cfg.stt.clone(),
591592
};
592593
let gw_router = router.clone();

src/unified_adapter.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -217,4 +217,11 @@ impl ChatAdapter for UnifiedGatewayAdapter {
217217
// The draft mechanism handles the "typing" indicator natively.
218218
false
219219
}
220+
221+
fn renders_native_tables(&self, platform: &str) -> bool {
222+
// Telegram Rich Messages render markdown tables natively — skip the
223+
// table→code-block pre-pass so tables display with proper formatting.
224+
// Only applies to Telegram; other platforms in unified mode keep wrapping.
225+
platform == "telegram" && self.gw_state.telegram_rich_messages
226+
}
220227
}

0 commit comments

Comments
 (0)