Skip to content

Telegram Rich Messages (Bot API 10.1+) — edit_message stream destroys rich formatting #46009

Description

@vyunolbek

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

  1. First chunkadapter.send()_send_rich_text()sendRichMessage ✅ (rich, but only ~18 chars — just the cursor + prefix)
  2. Intermediate chunksedit_message(finalize=False)editMessageText without parse mode → replaces the entire message with plain text ❌ (destroys all ## headings, | tables, etc.)
  3. Final chunkedit_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

  1. Enable Telegram gateway
  2. Send any message with rich Markdown (## heading, | table |, `code`)
  3. 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

Metadata

Metadata

Assignees

No one assigned

    Labels

    P2Medium — degraded but workaround existscomp/gatewayGateway runner, session dispatch, deliveryplatform/telegramTelegram bot adaptertype/bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions