Skip to content

feat(telegram): Bot API 10.1 rich messages (opt-in) - #44829

Merged
teknium1 merged 2 commits into
mainfrom
hermes/hermes-632ec726
Jun 12, 2026
Merged

feat(telegram): Bot API 10.1 rich messages (opt-in)#44829
teknium1 merged 2 commits into
mainfrom
hermes/hermes-632ec726

Conversation

@teknium1

Copy link
Copy Markdown
Contributor

Summary

Telegram replies can now render native tables, task lists, headings, math, and collapsible details via Bot API 10.1 Rich Messages (sendRichMessage / sendRichMessageDraft) — shipped opt-in (default off) while the day-old endpoint is validated live.

Salvages #44780 by @ITheEqualizer (cherry-picked, authorship preserved) onto current main, plus three follow-up fixes.

Changes

  • gateway/platforms/telegram.py: rich fast-path in send() / send_draft() via raw Bot.do_api_request (PTB 22.6 has no typed method), conservative permanent-vs-transient fallback classifier, routing parity with the legacy path (contributor's work).
  • Follow-up 1 — reply anchoring: use reply_parameters per the sendRichMessage spec instead of the undocumented reply_to_message_id scalar (unknown params are silently ignored → reply anchor would quietly drop).
  • Follow-up 2 — final-send capability latch: an endpoint-missing failure (old PTB/server) latches rich off so later replies skip the doomed extra roundtrip; per-message BadRequests do NOT latch (mirrors the existing draft latch).
  • Follow-up 3 — opt-in default: rich_messages defaults to false; prompt hint left unchanged (still steers away from tables) until the default flips on. Docs/example config updated to match.
  • Tests: contributor's 21 + reply_parameters shape, send-latch, and BadRequest-non-latch cases → 23 in test_telegram_rich_messages.py.

Enabling

gateway:
  platforms:
    telegram:
      extra:
        rich_messages: true

Validation

Suite Result
test_telegram_rich_messages.py 23 passed
telegram_format / thread_fallback / topic_mode / reply_mode / dm_topics 101 / 46 / 44 / 40 / 36 passed
stream_consumer / stream_consumer_draft 93 / 12 passed
test_prompt_builder.py 134 passed

Endpoint shape verified against the live core.telegram.org/bots/api spec (InputRichMessage{markdown}, reply_parameters, draft = private-chat-only). Live bot validation pending — that's the gate for flipping the default on in a follow-up.

Closes #44780.

Infographic

Telegram Rich Messages infographic

AliZakaee and others added 2 commits June 12, 2026 03:05
Introduce opportunistic support for Telegram Bot API 10.1 rich messages by sending raw agent Markdown via sendRichMessage and streaming previews via sendRichMessageDraft. Implements a rich-path fast‑path in gateway/platforms/telegram.py (RICH_MESSAGE_MAX_BYTES=32768, feature gate platforms.telegram.extra.rich_messages, bot capability checks, routing/thread handling, and conservative fallback rules: permanent/capability errors fall back to the legacy MarkdownV2 path, transient/network errors are surfaced without legacy-resend). Also add a latch for draft capability failures (_rich_draft_disabled) and preserve legacy chunking and draft behavior when needed. Update agent prompt hints (telegram encourages rich Markdown/tables), add CLI config example option, update English and Chinese docs to describe rich messages and fallbacks, and add/adjust tests for rich send and draft behavior.
…default

- Use reply_parameters per the sendRichMessage spec instead of the
  undocumented reply_to_message_id scalar (silently ignored -> reply
  anchor quietly dropped).
- Latch rich sends off after an endpoint-capability failure (old PTB /
  server without sendRichMessage) so every later reply doesn't pay a
  doomed extra roundtrip; per-message BadRequests do NOT latch.
- Default rich_messages to OFF (opt-in) while the day-old Bot API 10.1
  endpoint is validated live; revert the prompt-hint table guidance
  until the default flips on.
- Tests: reply_parameters shape, send-latch behavior, BadRequest
  non-latch; rich tests opt in explicitly via extra.
@github-actions

Copy link
Copy Markdown
Contributor

🔎 Lint report: hermes/hermes-632ec726 vs origin/main

ruff

Total: 0 on HEAD, 0 on base (➖ 0)

🆕 New issues: none

✅ Fixed issues: none

Unchanged: 0 pre-existing issues carried over.

ty (type checker)

Total: 10866 on HEAD, 10861 on base (🆕 +5)

🆕 New issues (3):

Rule Count
unresolved-import 2
unresolved-attribute 1
First entries
gateway/platforms/telegram.py:1168: [unresolved-attribute] unresolved-attribute: Attribute `do_api_request` is not defined on `None` in union `Unknown | None`
tests/gateway/test_telegram_rich_messages.py:16: [unresolved-import] unresolved-import: Cannot resolve imported module `pytest`
tests/gateway/test_telegram_rich_messages.py:21: [unresolved-import] unresolved-import: Cannot resolve imported module `telegram.error`

✅ Fixed issues: none

Unchanged: 5697 pre-existing issues carried over.

Diagnostics are surfaced as warnings — this check never fails the build.

@alt-glitch alt-glitch added type/feature New feature or request comp/gateway Gateway runner, session dispatch, delivery platform/telegram Telegram bot adapter P3 Low — cosmetic, nice to have labels Jun 12, 2026
@AIalliAI

Copy link
Copy Markdown
Contributor

Nice salvage — the three follow-ups address most of what came up in my verification pass on #44780 (details there: live-spec check of the endpoint shapes, negative control, routing-mirror audit). reply_parameters is the spec-clean spelling (FWIW the legacy scalar does work today — the official server's Client.cpp parses replies via a generic reply_to_message_id fallback for every send method — but it's an undocumented contract, so this is strictly better), and the send-side capability latch with BadRequest-non-latch is the right asymmetry.

One bug from that review carried over, plus three small notes:

Pool timeouts are still non-retryable on the rich path. Legacy send()'s outer handler returns retryable=(is_connect_timeout or is_pool_timeout or not is_timeout) — with an explicit comment that an httpx pool timeout means the request was never sent and must not be silently dropped. _try_send_rich's transient branch computes retryable=(is_connect_timeout or not is_timeout), so a pool-exhausted rich send returns retryable=False and the message is silently lost instead of retried. Fix is one line: include self._looks_like_pool_timeout(exc) in the disjunction. (A TimedOut("Pool timeout: ...") test case would pin it.)

"Telegram exposes no rich-edit method" is no longer true. 10.1 added a rich_message parameter to editMessageText (it's in the changelog). Keeping edit-based (group) streaming on MarkdownV2 is a fine scope cut — suggest the comment say "deferred" rather than "no method".

extra.disable_link_previews doesn't apply on the rich pathsendRichMessage exposes no link_preview_options param, so users with that flag set will see previews return whenever the rich path engages. Worth a sentence in the new docs section.

Body nit: consider adding Fixes #44428 — that's the open feature request this implements. As written, merging closes #44780 but leaves #44428 open. Also, test_rich_gate_tolerates_missing_enabled_attr's docstring still says getattr(default=True) from the opt-out version — the gate now defaults False.

We've been running the #44780 variant on our fork since this morning; happy to flip our copy to this opt-in version once it lands.

@AliZakaee

Copy link
Copy Markdown
Contributor

There are more changes and fixes that have been committed to the original branch that need to be picked up.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/gateway Gateway runner, session dispatch, delivery P3 Low — cosmetic, nice to have platform/telegram Telegram bot adapter type/feature New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants