Skip to content

feat(telegram): native replies, replied-to context, mention_on_reply (#228) - #299

Merged
patrick-chinchill merged 5 commits into
mainfrom
sync/4.41-tg4
Oct 1, 2026
Merged

patrick-chinchill merged 5 commits into
mainfrom
sync/4.41-tg4

Conversation

@patrick-chinchill

@patrick-chinchill patrick-chinchill commented Oct 1, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

Telegram now supports the core reply features:

  • Replied-to context. An inbound message that replies to another one carries message.reply_to, which is the parsed parent (one level deep, since the Bot API never nests further). Albums already take reply_to from their first part that has one ([4.41/TG3] Telegram polling & media: await handlers before advancing offset, media groups, multi-file uploads #227).
  • mention_on_reply (TelegramAdapterConfig.mention_on_reply or TELEGRAM_MENTION_ON_REPLY=true, off by default). When it is on, a reply to one of the bot's own messages sets is_mention=True. This also covers a reply that carries only a photo or document, because the check runs before the empty-text guard. Two cases do not count: a forum topic's implicit reply to its creation message (reply_to_message.message_id == message_thread_id) and the bot's own echoed replies.
  • Native replies. TelegramAdapter.reply(thread_id, message_id, message) implements the [4.41/C6] Thread.reply, Thread.mark_as_read, post_ephemeral options #200 hook, so thread.reply(...) works on Telegram. post_message gains a keyword-only reply_to_message_id. _build_reply_parameters decodes the target once, before any rendering or download. A malformed id or one from another chat raises ValidationError before any API call, and no attachment is fetched. {"message_id", "allow_sending_without_reply": true} is sent with sendMessage, sendRichMessage, sendDocument and the send_attachment URL and upload paths. JSON bodies carry it as a dict and leave it out when there is no target. Multipart bodies carry it as a JSON string. If the rich endpoint rejects the request with a ValidationError that mentions reply_parameters, the adapter falls back to sendMessage, which keeps the reply.
  • Message-id parsing. Bare ids must fully match [0-9]+, and composite ids must fully match ([^:]+):([0-9]+) (re.fullmatch), which is what upstream's /^\d+$/ and /^([^:]+):(\d+)$/ mean. Before this change Python accepted " 7", "+7", "1_0", non-ASCII digits and a trailing newline.
  • Portable file data (eddcd7e4): N/A. download_file already returns bytes. Only the regression test is ported.

Upstream commits mapped

Upstream Where
0f24cc30 feat(chat): preserve replied-to message context (#802), chat@4.38.0, Telegram half parse_telegram_message reply_to=; TelegramMessage.reply_to_message
26a06ca5 feat(telegram): treat a reply to the bot as a mention (#834), chat@4.39.0 TelegramAdapterConfig.mention_on_reply, __init__, is_bot_mentioned. The lazy polling getMe retry was already ported in #227 (_ensure_bot_identity() on every loop iteration).
d5ebec12 feat(telegram): implement native message replies (#833), chat@4.39.0 reply, post_message(reply_to_message_id=), _build_reply_parameters, _append_reply_parameters, send_regular_message / send_document / _create_telegram_document_form_data / send_attachment, can_fallback_from_rich_message, decode_composite_message_id
eddcd7e4 fix(telegram): return portable file data (#828), chat@4.39.0 N/A in substance (Python returns bytes); test only

Tests ported

From adapter-telegram/src/index.test.ts:

Python-specific:

  • An explicit mention_on_reply=False wins over env "true". Only the exact string "true" opts in ("TRUE" and "1" do not).
  • A photo-only reply to the bot counts as a mention.
  • "+7", "1_0", "7", "7\n" and "123:7\n" are rejected as reply targets. Composite ids with a trailing newline or non-ASCII digits are rejected by decode_composite_message_id.
  • The foreign-chat target test also asserts that the attachment's fetch_data is never awaited.
  • An end-to-end Thread.reply → adapter hook test: one sendMessage carrying reply_parameters, SentMessage.reply_to is target, and the following thread.post(...) has no reply_parameters.

Mutation check: each of 18 mutations makes at least one new test fail. The mutations were: dropping reply_to, disabling the reply check, moving it after the empty-text guard, dropping the forum guard, dropping the self guard, or instead of is not None for the config, a case-insensitive env read, dropping reply_parameters from each of the 5 send paths, dropping the rich fallback, non-full bare match, $-anchored composite match, allow_sending_without_reply=False, dropping the per-iteration polling identity retry, and reply() ignoring its target.

Fidelity

TS_ROOT=…/vercel-chat-4.41.1 verify_test_fidelity.py --report-target: Delta vs committed report (HEAD): missing 104 -> 104 (+0). adapter-telegram is not fidelity-mapped (#78), so scripts/fidelity_target.json is unchanged. Strict at the 4.31.0 pin: "All TS tests have Python equivalents."

Divergences

None. Payload keys stay snake_case (they are the Bot API wire names). reply_parameters is left out of JSON bodies when unset, which matches JSON.stringify dropping undefined. The id-pattern change restores upstream's ASCII-digit, end-anchored meaning. docs/UPSTREAM_SYNC.md has a new "Telegram replies" section and notes that eddcd7e4 is N/A.

Consumer impact

  • Low, Telegram only. New features are opt-in (thread.reply, mention_on_reply). mention_on_reply is keyword-only, so callers that pass the existing TelegramAdapterConfig fields positionally are unaffected.
  • message.reply_to is now set on Telegram replies. It is serialized as replyTo ([4.41/C2a] Core mentions & message model: tri-state is_mention, mention regex, Author.email/is_system, Message.reply_to #192).
  • Stricter message ids: edit_message, delete_message, add_reaction, remove_reaction and reply now reject bare ids with whitespace, a sign or underscores, non-ASCII digits, and trailing newlines (ValidationError("Invalid Telegram message ID: …")). Ids produced by the adapter are unaffected. This is in the CHANGELOG.
  • None for Slack, Teams or other adapters.

Validation

ruff check, ruff format --check, audit_test_quality (0 hard failures), verify_test_fidelity.py --check-docs, strict fidelity at 4.31.0, pytest (7598 passed, 24 skipped, after merging main), and pyrefly (0 errors).

Merge gate

  • Independent review (1 finding): fixed. The test gap where plain post_message uploads were not checked for omitting reply_parameters is closed. The new parametrized test_leaves_plain_post_message_uploads_unthreaded covers the URL attachment, bytes attachment and FileUpload paths. With the is not None guards in send_attachment and _append_reply_parameters changed to if True:, all 3 cases fail; with the guards restored, they pass.
  • Merged origin/main. Conflicts in telegram/types.py (the [4.41/TG2] Telegram streaming: native draft streaming opt-in, streaming hardening, link-preserving truncation #226 streaming fields) and docs/UPSTREAM_SYNC.md were resolved by keeping both sides. fidelity_target.json did not conflict and is unchanged: missing 104 -> 104 (+0).
  • gpt-6-astra, 2 rounds.
    • Round 1 (on 9033655), [P2]: the merge had placed mention_on_reply before native_streaming and streaming_edit_interval_ms (from main), which shifted positional callers. Fixed: the field is now field(default=None, kw_only=True) and comes after them, following the GChat [4.41/G1] Google Chat: bind webhook JWT verification to configured identity #222 precedent. The new test test_mention_on_reply_does_not_shift_existing_positional_fields failed before the fix.
    • Round 2 (on 77766fd): PASS, no actionable findings.
  • Bots: CodeRabbit was rate-limited and posted no findings. There are no inline review comments.
  • CI: all green on 77766fd: Lint & Type Check, test (3.12), test (3.13), CodeQL.

Closes #228
Part of #184

…228)

Ports the Telegram halves of vercel/chat 0f24cc30 (#802), 26a06ca5 (#834),
d5ebec12 (#833) and eddcd7e4 (#828, N/A: download_file already returns bytes).

- parse_telegram_message sets Message.reply_to from reply_to_message
- TelegramAdapterConfig.mention_on_reply / TELEGRAM_MENTION_ON_REPLY: a reply to
  the bot counts as a mention (checked before the empty-text guard)
- TelegramAdapter.reply() and post_message(reply_to_message_id=...) thread
  reply_parameters through text, rich, document and attachment sends
- rich sends rejected over reply_parameters fall back to sendMessage
- bare/composite message ids use ASCII-digit fullmatch, as upstream

Closes #228
@coderabbitai

coderabbitai Bot commented Oct 1, 2026 •

Copy link
Copy Markdown

Warning

Review limit reached

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Next included review available in 44 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used the included review currently available.

Learn how review limits work.

Review configuration:

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Advanced

Run ID: d82c9b8b-6478-40d7-9bab-54bc248eff66

📥 Commits

Reviewing files that changed from the base of the PR and between 0d79d22 and 2ba76a3.

📒 Files selected for processing (6)
  • CHANGELOG.md
  • docs/UPSTREAM_SYNC.md
  • src/chat_sdk/adapters/telegram/adapter.py
  • src/chat_sdk/adapters/telegram/types.py
  • tests/test_telegram_api.py
  • tests/test_telegram_webhook.py
  • Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Autopilot is currently an internal CodeRabbit preview.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@patrick-chinchill
patrick-chinchill marked this pull request as ready for review October 1, 2026 07:37
@patrick-chinchill

Copy link
Copy Markdown
Collaborator Author

Merge gate: CI green (test 3.12, test 3.13, Lint & Type Check, CodeQL/Analyze python+actions); local Codex review (gpt-6-astra, xhigh, --base origin/main) on 77766fd: "No actionable regressions found against the supplied merge base. All 874 Telegram and thread/history tests passed, along with targeted lint checks and project type checking."; 3 astra rounds (one P2 — positional-arg shift from mention_on_reply — fixed by making it keyword-only). Final HEAD 2ba76a3 is a conflict-free merge of origin/main (#207/#208, Slack-only; no overlapping src/tests/scripts files), local full validation green (7650 passed, strict fidelity 4.31.0 complete, pyrefly 0 errors, target fidelity delta +0). Bots: CodeRabbit rate-limited, no open review threads. Merging with --admin (Protect Main requires a code-owner approval).

@patrick-chinchill
patrick-chinchill merged commit 1936db0 into main Oct 1, 2026
7 checks passed
@patrick-chinchill
patrick-chinchill deleted the sync/4.41-tg4 branch October 1, 2026 09:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[4.41/TG4] Telegram replies: replied-to context, reply-to-bot as mention, native replies, portable file data

1 participant