fix(telegram): measure the rich-message cap in UTF-16 code units by Lesereingrape · Pull Request #6174 · bytedance/deer-flow · GitHub
Skip to content

fix(telegram): measure the rich-message cap in UTF-16 code units - #6174

Merged
WillemJiang merged 1 commit into
bytedance:mainfrom
Lesereingrape:fix/telegram-rich-message-utf16-limit
Oct 2, 2026
Merged

WillemJiang merged 1 commit into
bytedance:mainfrom
Lesereingrape:fix/telegram-rich-message-utf16-limit

Conversation

@Lesereingrape

Copy link
Copy Markdown
Contributor

What

_can_send_rich compared len(text) — a Python code-point count — against TELEGRAM_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 invariant test_stream_update_clips_without_measuring_the_whole_reply pins for stream previews.

Checks (run on f840e843, Python 3.12)

$ pytest tests/test_telegram_message_length.py -q
1 failed, 15 passed   # before the production change: `assert True is False` for the emoji-heavy reply
16 passed             # after

$ pytest tests -q -k telegram
92 passed, 6 skipped

$ ruff check app/channels/telegram.py tests/test_telegram_message_length.py
All checks passed!

$ ruff format --check app/channels/telegram.py tests/test_telegram_message_length.py
2 files already formatted

Diff is 2 files, +32 −1: one guard in _can_send_rich and 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.

`_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.
@github-actions github-actions Bot added area:backend Gateway / runtime / core backend under backend/ risk:medium Medium risk: regular code changes size/S PR changes 20-100 lines labels Oct 2, 2026

@willem-bd willem-bd left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@WillemJiang WillemJiang added this to the 2.2.0 milestone Oct 2, 2026
@WillemJiang
WillemJiang merged commit 23b366a into bytedance:main Oct 2, 2026
17 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:backend Gateway / runtime / core backend under backend/ risk:medium Medium risk: regular code changes size/S PR changes 20-100 lines

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants