Skip to content

Python: Add Telegram channel for agent-framework-hosting#6698

Open
eavanvalkenburg wants to merge 6 commits into
microsoft:mainfrom
eavanvalkenburg:eavan/python-hosting-telegram
Open

Python: Add Telegram channel for agent-framework-hosting#6698
eavanvalkenburg wants to merge 6 commits into
microsoft:mainfrom
eavanvalkenburg:eavan/python-hosting-telegram

Conversation

@eavanvalkenburg

Copy link
Copy Markdown
Member

Motivation & Context

Adds the Telegram Bot channel for the agent-framework-hosting stack, enabling developers to expose an agent on Telegram with streaming progressive edits, per-chat session isolation, and multi-modal support (photos, documents, voice). Also adds the local_telegram sample showing how to combine TelegramChannel with ResponsesChannel on the same host.

This is part of the channel-by-channel split of the hosting work tracked in #6265.

Description & Review Guide

Major changes:

  • agent-framework-hosting-telegram package — new TelegramChannel supporting:

    • Both long-polling (start_polling) and webhook (/telegram route) transports
    • Streaming to Telegram via progressive editMessageText edits with a background rate-limit worker (default 0.4 s throttle, well under Telegram's per-chat edit ceiling)
    • sendChatAction("typing") keepalive every 4 s so the typing bubble stays visible during long streams
    • Interim edits sent as plain text; final edit re-applies configured parse_mode (partial Markdown mid-stream is invalid and Telegram rejects it with 400)
    • Multi-modal inbound: text, photos, documents, voice, captions parsed into AF Content items
    • Multi-modal outbound: image Content items (uri + image/* media_type) sent as sendPhoto
    • Per-chat serial worker queue so messages for the same chat are processed in order
    • run_hook for per-channel customisation (e.g. persona temperature) and access to parsed request
  • local_telegram sample — demonstrates:

    • TelegramChannel + ResponsesChannel on the same AgentFrameworkHost
    • Per-chat history via FileHistoryProvider keyed to Telegram chat ID
    • ResponsesChannel run_hook honouring previous_response_id for cross-channel session resumption
    • TelegramChannel run_hook bumping temperature for a chattier Telegram persona
    • /new command to reset chat session

Impact: New alpha package with no changes to existing packages. Adds agent-framework-hosting-telegram to the workspace and lockfile.

Reviewer focus: streaming edit worker correctness (rate-limit, wake/done event interaction), parse_mode stripping on interim edits vs restoration on final edit, per-chat serial queue design.

Related Issue

Fixes #6588
Refs #6265

Contribution Checklist

  • I have read the CONTRIBUTING guidelines.
  • My changes include tests.
  • My changes include documentation updates (README + sample).
  • This is not a breaking change.

- Add agent-framework-hosting-telegram package with TelegramChannel
  supporting polling and webhook transports, streaming edits with
  Telegram Bot API rate limiting, per-chat serial workers, and
  multi-modal inbound/outbound (text, photo, document, voice)
- Add local_telegram sample demonstrating multi-channel hosting with
  a TelegramChannel alongside ResponsesChannel, using per-chat
  FileHistoryProvider and a run_hook for Telegram persona temperature
- Fix test layout: move tests to tests/hosting_telegram/ (no __init__.py)
- Remove old [tool.mypy] section and mypy poe task; source type-checking
  is handled by pyright via shared_tasks
- Update uv.lock, pyproject.toml workspace sources, and PACKAGE_STATUS.md

Fixes microsoft#6588
Refs microsoft#6265

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings June 24, 2026 07:21
@github-actions

github-actions Bot commented Jun 24, 2026

Copy link
Copy Markdown
Contributor

Python Test Coverage

Python Test Coverage Report •
FileStmtsMissCoverMissing
packages/hosting-telegram/agent_framework_hosting_telegram
   _channel.py37310771%115, 234, 283, 285, 288–289, 303–305, 332–335, 347, 349–350, 377–380, 384–402, 415–421, 476, 505–507, 509–510, 532–533, 592–595, 597, 599–600, 604–605, 607, 616, 622–625, 629, 650, 658, 719–731, 734–735, 741–742, 746–750, 764–765, 796, 802–803, 806, 847–848, 861, 869–872
TOTAL42521508388% 

Python Unit Test Overview

Tests Skipped Failures Errors Time
8329 37 💤 0 ❌ 0 🔥 2m 12s ⏱️

@github-actions github-actions Bot 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.

Automated Code Review

Reviewers: 5 | Confidence: 60%

✓ Correctness

The streaming edit worker has a deadlock bug: when the agent streams more than 4096 characters, accumulated will never equal last_sent (which is truncated to 4096), causing the while-loop termination condition to remain False forever. After worker_done is set and the final truncated snapshot has already been sent, the worker hits await wake.wait() with no one to wake it, hanging await edit_task in the finally block indefinitely.

✓ Security Reliability

The primary security concern is the use of Python's != operator for webhook secret token comparison instead of hmac.compare_digest(), which is vulnerable to timing side-channel attacks. An attacker who discovers the webhook URL could exploit response-time differences to brute-force the secret token character by character. Additionally, per-chat worker tasks and queues grow without bound over the lifetime of the process since they are never evicted after a chat goes idle, which is a reliability concern for long-running deployments with many unique chats.

✓ Test Coverage

The test suite covers parsing helpers, webhook auth, per-chat ordering, ack-before-run, and shutdown draining well. However, the streaming path (_stream_to_chat) — which is ~140 lines of complex async coordination (edit worker, rate-limiting, typing keepalive, parse_mode stripping/restoration, final edit fallback) and the PR rationale's explicit reviewer focus — has zero test coverage. The callback_query handler and photo-output path are also untested.

✓ Failure Modes

The edit_worker in the streaming path has a deadlock when the initial placeholder message fails to send. If message_id stays None (network error, rate limit, bot blocked), the worker loops indefinitely at await wake.wait() after the stream finishes because the only wake-seters have already fired and the worker's continue path doesn't check the termination condition. This permanently stalls the per-chat worker.

✗ Design Approach

The new Telegram channel mostly follows the host’s channel contract, but there is one design bug in the streaming path: streamed replies can silently lose outbound images. Because this package defaults Telegram requests to streaming, the current approach misses a core advertised capability for a common response shape.

Flagged Issues

  • In python/packages/hosting-telegram/agent_framework_hosting_telegram/_channel.py:721-744, the streaming finalization path only edits text and only falls back to _reply_with_result when no text accumulated. Since image sending exists only in _reply_with_result at lines 746-768, any streamed response that emits text deltas and also includes final image Content will drop the image entirely.

Automated review by eavanvalkenburg's agents

Comment thread python/packages/hosting-telegram/agent_framework_hosting_telegram/_channel.py Outdated
Comment thread python/packages/hosting-telegram/agent_framework_hosting_telegram/_channel.py Outdated
Comment thread python/packages/hosting-telegram/agent_framework_hosting_telegram/_channel.py Outdated
@github-actions

Copy link
Copy Markdown
Contributor

Flagged issue

In python/packages/hosting-telegram/agent_framework_hosting_telegram/_channel.py:721-744, the streaming finalization path only edits text and only falls back to _reply_with_result when no text accumulated. Since image sending exists only in _reply_with_result at lines 746-768, any streamed response that emits text deltas and also includes final image Content will drop the image entirely.


Source: automated DevFlow PR review

Copilot AI 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.

Pull request overview

Adds a new Python hosting channel package (agent-framework-hosting-telegram) plus a local_telegram sample that hosts the same agent over both Telegram and the OpenAI Responses-shaped endpoint, including streaming via progressive Telegram message edits and per-chat session isolation.

Changes:

  • Introduces TelegramChannel with webhook/polling transports, per-chat serial processing, and streaming via throttled editMessageText.
  • Adds unit tests for Telegram update parsing, webhook handling, shutdown behavior, and per-chat ordering.
  • Adds a local_telegram sample (plus workspace/package status wiring) demonstrating multi-channel hosting and history persistence.

Reviewed changes

Copilot reviewed 12 out of 13 changed files in this pull request and generated 17 comments.

Show a summary per file
File Description
python/samples/04-hosting/af-hosting/local_telegram/README.md Documents how to run and use the new multi-channel Telegram + Responses sample.
python/samples/04-hosting/af-hosting/local_telegram/pyproject.toml Sample dependencies and uv source wiring for running locally.
python/samples/04-hosting/af-hosting/local_telegram/call_server.py Local OpenAI SDK client for exercising the sample’s /responses endpoint.
python/samples/04-hosting/af-hosting/local_telegram/app.py Sample host wiring: agent + ResponsesChannel + TelegramChannel + hooks + commands.
python/pyproject.toml Adds agent-framework-hosting-telegram to the Python workspace.
python/packages/hosting-telegram/tests/hosting_telegram/test_channel.py Unit tests for parsing, webhook dispatch, ordering, and shutdown semantics.
python/packages/hosting-telegram/README.md Package-level usage and session/isolation key explanation.
python/packages/hosting-telegram/pyproject.toml New package metadata, deps, and tooling config (ruff/pyright/pytest/etc.).
python/packages/hosting-telegram/LICENSE MIT license for the new package.
python/packages/hosting-telegram/agent_framework_hosting_telegram/_channel.py Core TelegramChannel implementation (polling/webhook, streaming edits, media handling).
python/packages/hosting-telegram/agent_framework_hosting_telegram/init.py Public exports for TelegramChannel and telegram_isolation_key.
python/PACKAGE_STATUS.md Marks the new package as alpha.

Comment thread python/samples/04-hosting/af-hosting/local_telegram/pyproject.toml
Comment thread python/samples/04-hosting/af-hosting/local_telegram/pyproject.toml Outdated
Comment thread python/samples/04-hosting/af-hosting/local_telegram/README.md Outdated
Comment thread python/samples/04-hosting/af-hosting/local_telegram/README.md Outdated
Comment thread python/samples/04-hosting/af-hosting/local_telegram/README.md Outdated
Comment thread python/packages/hosting-telegram/agent_framework_hosting_telegram/_channel.py Outdated
Comment thread python/packages/hosting-telegram/agent_framework_hosting_telegram/_channel.py Outdated
Comment thread python/packages/hosting-telegram/agent_framework_hosting_telegram/_channel.py Outdated
Comment thread python/packages/hosting-telegram/agent_framework_hosting_telegram/_channel.py Outdated
- Fix webhook secret validation to use constant-time compare_digest
- Harden webhook update parsing: require integer chat IDs and guard slash-only commands
- Fix streaming edge cases in TelegramChannel:
  - prevent edit worker deadlocks when text exceeds 4096 chars
  - prevent deadlock when placeholder send fails (message_id stays None)
  - enforce edit throttling with minimum interval sleep
  - honor send_typing_action=False in streaming mode
  - always forward final multimodal output (e.g. images), while avoiding duplicate text sends
- Expand Telegram tests for slash-only command handling, non-int chat IDs, and streaming behavior (long text, final images, typing toggle)
- Fix sample/docs feedback:
  - rename sample package to agent-framework-hosting-sample-local-telegram
  - switch sample uv.sources from feature branch to main
  - align docs/tool names with lookup_weather
  - fix broken links and server run instructions in README/call_server.py
  - align local_telegram app docstrings with reasoning hook behavior and strip model in responses_hook

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@moonbox3 moonbox3 added documentation Usage: [Issues, PRs], Target: documentation in the code base and learn docs python Usage: [Issues, PRs], Target: Python labels Jun 24, 2026
eavanvalkenburg and others added 2 commits June 24, 2026 11:22
…dal support

- Remove stale PR reference from module docstring
- Add Google-style docstring to TelegramChannel.__init__ documenting all keyword args
- Fix _stream_to_chat to iterate update.contents instead of using
  getattr(update, 'text', None); text chunks are extracted from Content
  items with type='text', non-text content in updates is correctly
  ignored (images etc. are forwarded via the final response)
- Update _FakeStreamUpdate test helper to use contents list matching the
  real AgentResponseUpdate API; add from_text/from_image class methods
- Update _FakeResponseStream to accept _FakeStreamUpdate objects directly
- Add test verifying multimodal stream updates don't corrupt text accumulator

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…channel sample

local_telegram is now a focused Telegram-only sample:
- Removes ResponsesChannel and all responses_hook code
- Removes call_server.py (no HTTP endpoint to call)
- Uses a deterministic lookup_weather tool (hash-based, not random)
- Single run_hook that strips model and raises reasoning effort
- Drops agent-framework-hosting-responses dependency

New local_multi_channel sample shows running both channels at once:
- ResponsesChannel + TelegramChannel sharing a FileHistoryProvider
- Cross-channel session resumption via previous_response_id
- call_server.py moved here (the Responses endpoint lives here now)
- Demonstrates the multi-channel coordination story

Update README table to list both samples with clear descriptions.

Also delete personal_assistant/.venv which was not tracked but caused
pyright to crawl the entire installed venv (thousands of files),
making sample pyright checks hang indefinitely.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@eavanvalkenburg eavanvalkenburg marked this pull request as ready for review June 24, 2026 10:43
@eavanvalkenburg eavanvalkenburg mentioned this pull request Jun 24, 2026
9 tasks
@eavanvalkenburg eavanvalkenburg self-assigned this Jun 24, 2026

@github-actions github-actions Bot 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.

Automated Code Review

Reviewers: 4 | Confidence: 62%

✓ Correctness

The TelegramChannel implementation is solid overall — per-chat serial queuing, webhook ack-before-run, streaming edit worker, and shutdown draining are all correctly implemented. The previously-resolved review comments (chat_id validation, slash-only command handling, typing action toggle, etc.) have all been addressed. One asymetry exists between the streaming and non-streaming paths: the streaming path truncates text to Telegram's 4096-char limit via _TELEGRAM_MAX_TEXT_LEN, but the non-streaming _send/_reply_with_result path does not, causing silent message delivery failure for long agent responses.

✓ Security Reliability

The Telegram channel implementation is well-structured with good defensive practices. Previously identified security issues (chat_id integer validation, command parsing IndexError on bare /) have been resolved. The code uses timing-safe hmac.compare_digest for secret verification, validates webhook payload structure, and wraps all update processing in _safe_process_update to contain failures. The main remaining concern is that per-chat worker tasks are never reaped when a chat goes idle, representing an unbounded resource growth for long-running bots, though this is acceptable for an alpha-stage package.

✓ Test Coverage

The test suite covers core webhook handling, per-chat ordering, shutdown, and basic streaming. However, there are notable gaps: the _handle_callback_query code path (inline-button clicks) has zero test coverage, and the parse_mode streaming behavior (interim plain text, final with parse_mode, retry on 400) — a key feature called out in the PR description — is untested.

✓ Design Approach

I found one blocking issue in the streaming edit path and one sample-design issue repeated in the Telegram docs. The channel can suppress the final fallback text even when the last editMessageText never succeeded, which loses replies under real Telegram API failures. Separately, the samples recommend multi-worker Hypercorn while defaulting Telegram to polling mode, which starts multiple independent getUpdates consumers for the same bot token.


Automated review by eavanvalkenburg's agents

Comment thread python/packages/hosting-telegram/agent_framework_hosting_telegram/_channel.py Outdated
eavanvalkenburg and others added 2 commits June 24, 2026 16:34
- only mark final edit as sent after a confirmed 2xx edit response
- fall back to sendMessage when final edit returns a non-success status
- add regression test covering failed final edit fallback behavior

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- assert await_args is not None before reading kwargs in streaming fallback test
- resolves test-typing failures across mypy/pyright/ty/zuban for hosting-telegram

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
```

For production, configure `webhook_url="https://…"` and the channel will
register the webhook on startup and receive updates over HTTPS.

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.

nit: how will customers configure Telegram to talk to the host?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Usage: [Issues, PRs], Target: documentation in the code base and learn docs python Usage: [Issues, PRs], Target: Python

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Hosting: Telegram channel and Telegram samples

4 participants