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

ITheEqualizer 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.

@ITheEqualizer

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.

@teknium1 teknium1 merged commit 652dd9c into main Jun 12, 2026
29 checks passed
@teknium1 teknium1 deleted the hermes/hermes-632ec726 branch June 12, 2026 18:47
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