Description
Telegram Bot API 10.1 introduced sendRichMessage which supports tables, headings, code blocks, collapsible details, checklists, and deeply nested formatting. The gateway already has _send_rich_text() which calls this API for first-sends — but during streaming, intermediate editMessageText calls destroy the rich formatting because:
Root cause
- First chunk →
adapter.send() → _send_rich_text() → sendRichMessage ✅ (rich, but only ~18 chars — just the cursor + prefix)
- Intermediate chunks →
edit_message(finalize=False) → editMessageText without parse mode → replaces the entire message with plain text ❌ (destroys all ## headings, | tables, etc.)
- Final chunk →
edit_message(finalize=True) → format_message() → MarkdownV2 — but format_message() calls _wrap_markdown_tables() which converts GFM tables to text row groups, and ## Title is downgraded to *Title* (MarkdownV2 bold)
Result: Users see raw ##, |, ** markers instead of rendered rich formatting.
Steps to Reproduce
- Enable Telegram gateway
- Send any message with rich Markdown (
## heading, | table |, `code`)
- Observe message appears as plain text with visible markers
Expected Behavior
- Tables render natively
## Heading renders as actual section heading
- Code blocks render with monospace and syntax highlighting
- All Rich Message features survive streaming
Actual Behavior
Everything rendered as plain text with raw syntax markers visible.
Proposed Fix
In gateway/platforms/telegram.py, edit_message() method, finalize=True path.
There is no editRichMessage API in Bot API 10.1, so the fix sends the final content as a new Rich Message via _send_rich_text() and deletes the old plain-text preview:
# Try Rich Message finalize first
rich_result = await self._send_rich_text(chat_id=chat_id, content=content)
# Clean up the old plain-text preview
try:
await self._bot.delete_message(chat_id=chat_id, message_id=old_id)
except Exception:
pass
return SendResult(success=True, message_id=str(rich_result.message_id))
If _send_rich_text() fails, it falls back to the existing MarkdownV2 edit path.
Additional Changes
tools/send_message_tool.py: Added rich_message parameter ({"html": "..."} or {"markdown": "..."}) to send_message tool schema — lets the agent explicitly send Rich Messages via sendRichMessage API
gateway/platforms/telegram.py: _send_rich_text() and send_draft(sendRichMessageDraft) already exist but edit_message() was bypassing them
Testing
Tested locally on a self-hosted Hermes instance with Bot API 10.1:
- ✅ Tables render correctly
- ✅ Headings (
## H2, ### H3) render as proper headings
- ✅ Code blocks with syntax highlighting
- ✅ Bold, italic, strikethrough, spoilers
- ✅ Collapsible
<details> blocks
- ✅ Fallback to MarkdownV2 works when
sendRichMessage is unavailable
Environment
- Hermes Agent: self-hosted (latest from main)
- Bot API version: 10.1+
- python-telegram-bot: 22.6
Description
Telegram Bot API 10.1 introduced
sendRichMessagewhich supports tables, headings, code blocks, collapsible details, checklists, and deeply nested formatting. The gateway already has_send_rich_text()which calls this API for first-sends — but during streaming, intermediateeditMessageTextcalls destroy the rich formatting because:Root cause
adapter.send()→_send_rich_text()→sendRichMessage✅ (rich, but only ~18 chars — just the cursor + prefix)edit_message(finalize=False)→editMessageTextwithout parse mode → replaces the entire message with plain text ❌ (destroys all##headings,|tables, etc.)edit_message(finalize=True)→format_message()→ MarkdownV2 — butformat_message()calls_wrap_markdown_tables()which converts GFM tables to text row groups, and## Titleis downgraded to*Title*(MarkdownV2 bold)Result: Users see raw
##,|,**markers instead of rendered rich formatting.Steps to Reproduce
## heading,| table |,`code`)Expected Behavior
## Headingrenders as actual section headingActual Behavior
Everything rendered as plain text with raw syntax markers visible.
Proposed Fix
In
gateway/platforms/telegram.py,edit_message()method,finalize=Truepath.There is no
editRichMessageAPI in Bot API 10.1, so the fix sends the final content as a new Rich Message via_send_rich_text()and deletes the old plain-text preview:If
_send_rich_text()fails, it falls back to the existing MarkdownV2 edit path.Additional Changes
tools/send_message_tool.py: Addedrich_messageparameter ({"html": "..."}or{"markdown": "..."}) tosend_messagetool schema — lets the agent explicitly send Rich Messages viasendRichMessageAPIgateway/platforms/telegram.py:_send_rich_text()andsend_draft(sendRichMessageDraft)already exist butedit_message()was bypassing themTesting
Tested locally on a self-hosted Hermes instance with Bot API 10.1:
## H2,### H3) render as proper headings<details>blockssendRichMessageis unavailableEnvironment