Repository navigation
feat(telegram): native replies, replied-to context, mention_on_reply (#228) - #299
Conversation
…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
|
Warning Review limit reachedYou'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. View limit detailsLimit details: You’ve used the included review currently available. Review configuration: ⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Advanced Run ID: 📒 Files selected for processing (6)
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. Comment |
…itional callers keep streaming fields
|
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). |
Summary
Telegram now supports the core reply features:
message.reply_to, which is the parsed parent (one level deep, since the Bot API never nests further). Albums already takereply_tofrom 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_replyorTELEGRAM_MENTION_ON_REPLY=true, off by default). When it is on, a reply to one of the bot's own messages setsis_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.TelegramAdapter.reply(thread_id, message_id, message)implements the [4.41/C6] Thread.reply, Thread.mark_as_read, post_ephemeral options #200 hook, sothread.reply(...)works on Telegram.post_messagegains a keyword-onlyreply_to_message_id._build_reply_parametersdecodes the target once, before any rendering or download. A malformed id or one from another chat raisesValidationErrorbefore any API call, and no attachment is fetched.{"message_id", "allow_sending_without_reply": true}is sent withsendMessage,sendRichMessage,sendDocumentand thesend_attachmentURL 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 aValidationErrorthat mentionsreply_parameters, the adapter falls back tosendMessage, which keeps the reply.[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.eddcd7e4): N/A.download_filealready returnsbytes. Only the regression test is ported.Upstream commits mapped
0f24cc30feat(chat): preserve replied-to message context (#802), chat@4.38.0, Telegram halfparse_telegram_messagereply_to=;TelegramMessage.reply_to_message26a06ca5feat(telegram): treat a reply to the bot as a mention (#834), chat@4.39.0TelegramAdapterConfig.mention_on_reply,__init__,is_bot_mentioned. The lazy pollinggetMeretry was already ported in #227 (_ensure_bot_identity()on every loop iteration).d5ebec12feat(telegram): implement native message replies (#833), chat@4.39.0reply,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_ideddcd7e4fix(telegram): return portable file data (#828), chat@4.39.0bytes); test onlyTests ported
From
adapter-telegram/src/index.test.ts:tests/test_telegram_webhook.py: "parses replied-to message context"; all 6 webhook cases ofdescribe("mentionOnReply"); "downloads file bytes without the Node Buffer global" (assertstype(await attachment.fetch_data()) is bytes); "recovers bot identity while polling after a failed startup getMe". This test already existed from [4.41/TG3] Telegram polling & media: await handlers before advancing offset, media groups, multi-file uploads #227. It is extended, as upstream does, so the polled update is a group reply to the bot withmention_on_replyon, and theon_mentionhandler must receiveis_mention=True.tests/test_telegram_api.pyTestReply: rich text, plain string, document upload, URL attachment without an inline keyboard, buffer attachment, rich fallback onreply_parameters, plainpost_messageunthreaded, foreign-chat target, malformed bare ids"7abc","7.9"," 7".sendMediaGroupis not on main yet ([4.41/TG3] Telegram polling & media: await handlers before advancing offset, media groups, multi-file uploads #227 split it to [4.41/TG3b] Telegram outbound media groups: multi-file and multi-attachment sendMediaGroup #278). [4.41/TG3b] Telegram outbound media groups: multi-file and multi-attachment sendMediaGroup #278 must passreply_parametersto its media-group senders and port this test.Python-specific:
mention_on_reply=Falsewins over env"true". Only the exact string"true"opts in ("TRUE"and"1"do not)."+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 bydecode_composite_message_id.fetch_datais never awaited.Thread.reply→ adapter hook test: onesendMessagecarryingreply_parameters,SentMessage.reply_to is target, and the followingthread.post(...)has noreply_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,orinstead ofis not Nonefor the config, a case-insensitive env read, droppingreply_parametersfrom 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, andreply()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-telegramis not fidelity-mapped (#78), soscripts/fidelity_target.jsonis 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_parametersis left out of JSON bodies when unset, which matchesJSON.stringifydroppingundefined. The id-pattern change restores upstream's ASCII-digit, end-anchored meaning.docs/UPSTREAM_SYNC.mdhas a new "Telegram replies" section and notes thateddcd7e4is N/A.Consumer impact
thread.reply,mention_on_reply).mention_on_replyis keyword-only, so callers that pass the existingTelegramAdapterConfigfields positionally are unaffected.message.reply_tois now set on Telegram replies. It is serialized asreplyTo([4.41/C2a] Core mentions & message model: tri-state is_mention, mention regex, Author.email/is_system, Message.reply_to #192).edit_message,delete_message,add_reaction,remove_reactionandreplynow 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.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
post_messageuploads were not checked for omittingreply_parametersis closed. The new parametrizedtest_leaves_plain_post_message_uploads_unthreadedcovers the URL attachment, bytes attachment andFileUploadpaths. With theis not Noneguards insend_attachmentand_append_reply_parameterschanged toif True:, all 3 cases fail; with the guards restored, they pass.origin/main. Conflicts intelegram/types.py(the [4.41/TG2] Telegram streaming: native draft streaming opt-in, streaming hardening, link-preserving truncation #226 streaming fields) anddocs/UPSTREAM_SYNC.mdwere resolved by keeping both sides.fidelity_target.jsondid not conflict and is unchanged:missing 104 -> 104 (+0).mention_on_replybeforenative_streamingandstreaming_edit_interval_ms(from main), which shifted positional callers. Fixed: the field is nowfield(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 testtest_mention_on_reply_does_not_shift_existing_positional_fieldsfailed before the fix.Closes #228
Part of #184