Repository navigation
fix(telegram): measure the rich-message cap in UTF-16 code units - #6174
WillemJiang merged 1 commit into
Conversation
`_can_send_rich` checked `len(text)`, a code-point count, against the 32768 rich-message cap, while Telegram measures that cap in UTF-16 code units like every other limit it enforces. An emoji-heavy reply that fits by code points is still over the cap by code units, so the rich send was attempted, rejected by the API, and the reply degraded to chunked plain text. The check now reuses `_first_utf16_chunk`, the bounded scan the plain-text path already uses, so eligibility costs the limit rather than the whole reply.
willem-bd
left a comment
There was a problem hiding this comment.
Reviewed at f89ed7c — clean, no findings. Verified the semantics directly: a sweep against a UTF-16 codec oracle confirms _first_utf16_chunk's over_limit flag is exactly utf16_units(text) > limit, so _can_send_rich now measures in the unit Telegram uses while preserving the old inclusive boundary (text of exactly 32,768 units stays eligible). The dropped 0 < len(text) guard is safe because _has_rich_constructs never matches empty text, so _can_send_rich('') still returns False. Both call sites in send() already fall back to _split_message chunking on refusal, and the rich paths send the text as-is with no second length check, so the guard is the single gate — now consistent with the 4096 paths. Cost is bounded: the scan stops at the limit for over-limit replies and is capped by it for eligible ones, and the regex construct scan already ran over the full text before this change. The two new tests cover both sides (refused at 32,773 units / 16,389 code points, eligible at 25 units) with the codec-backed oracle, and the exact-limit boundary is already pinned by test_first_utf16_chunk_returns_text_that_fits in the same module.

What
_can_send_richcomparedlen(text)— a Python code-point count — againstTELEGRAM_MAX_RICH_MESSAGE_LENGTH, while Telegram measures that cap in UTF-16 code units like every other limit it enforces. The 4096 path in this module already counts code units (_utf16_width,_first_utf16_chunk,_split_message); the rich-message check was the last place that still counted code points.Why it is user-visible
A reply of
EMOJI * 16384 + "**x**"is 16 389 code points but 32 773 code units. It passes the code-point check, so the rich send is attempted anyway, Telegram rejects it, and the answer degrades to chunked plain text: the markdown the reply was built with is lost, and every final reply in that shape pays a wasted API round trip.How it is fixed
The check now reuses
_first_utf16_chunk, the bounded scan the plain-text path already uses, so deciding eligibility costs the limit rather than the whole reply — the same invarianttest_stream_update_clips_without_measuring_the_whole_replypins for stream previews.Checks (run on
f840e843, Python 3.12)Diff is 2 files, +32 −1: one guard in
_can_send_richand two tests appended to the existing length-test module (the refusal case and a still-eligible case, so the fix cannot just disable rich rendering).Agent-assisted contribution, disclosed per
AGENTS.md.