Python: add agent-framework-hosting-telegram channel#5643
Python: add agent-framework-hosting-telegram channel#5643eavanvalkenburg wants to merge 3 commits intomicrosoft:feature/python-hostingfrom
Conversation
There was a problem hiding this comment.
Automated Code Review
Reviewers: 4 | Confidence: 83%
✓ Correctness
No actionable issues found in this dimension.
✓ Security Reliability
The Telegram channel and hosting package introduce a non-constant-time comparison for the webhook secret token (line 326 of _channel.py), which could theoretically enable timing-based attacks against the authentication mechanism. Additionally, the shutdown sequence in
_on_shutdowndoes not cancel or await in-flight_update_tasksbefore closing the shared HTTP client, creating a race condition that can produce errors during graceful shutdown.
✓ Test Coverage
The Telegram channel tests cover parsing helpers, webhook dispatch, push, and command handling, but have significant gaps around the streaming path (
_stream_to_chat) which is the constructor's default behavior (stream=True), callback query handling, and image delivery in_reply_with_result. The streaming code is ~150 lines of complex async coordination (edit workers, typing workers, rate limiting, error recovery) with zero test coverage. Additionally, the webhook dispatch test assertion only checks that the agent was invoked, not what input it received or what response was sent to the user. The test suite is comprehensive and well-structured, covering host wiring, invocation, session management, workflow targeting, checkpointing, and delivery routing with meaningful assertions throughout. Two notable coverage gaps exist: (1) no test exercises the streaming path (run_stream) with an agent target (only workflow targets are tested for streaming), and (2) no test verifies error-propagation behavior when the underlying target'srun()raises an exception. Both gaps are acknowledged in the code (pragma: no cover comment at line 68) and are reasonable additions to consider.
✗ Design Approach
Those are approach issues rather than implementation nits, so I would request changes. One design issue stands out: the new hosting tests rely on
tests._workflow_fixturesas iftestswere a unique package, but this repo runs allpackages/**/teststrees in one pytest session and already has another top-leveltestspackage inpackages/core. That makes the new approach import-order dependent rather than package-local, so these tests are fragile and can resolve to the wrong package or fail once multiple packages exposetests/__init__.py.
Automated review by eavanvalkenburg's agents
f8c881a to
7958ec5
Compare
New ``agent-framework-hosting`` package implementing ADR 0026 / SPEC-002:
the channel-neutral host that lets a single ``Agent`` (or ``Workflow``)
fan out across multiple wire protocols ("channels") behind one Starlette
ASGI app.
Surface (re-exported from ``agent_framework_hosting``):
- ``AgentFrameworkHost`` — wraps a hostable target, mounts channels onto
an ASGI app, owns per-isolation-key ``AgentSession`` reuse, threads
request context (``response_id`` / ``previous_response_id``) into
context providers via an ``ExitStack`` of ``bind_request_context``
calls, and exposes an opt-in Hypercorn ``serve()`` helper (extra
``[serve]``).
- ``Channel`` protocol + ``ChannelContribution`` — the surface a channel
package implements (routes, lifespans, identity hooks, …).
- ``ChannelRequest`` / ``ChannelSession`` / ``ChannelIdentity`` /
``ChannelPush`` / ``ChannelCommand[Context]`` / ``ChannelRunHook`` /
``ChannelStreamTransformHook`` / ``DeliveryReport`` /
``HostedRunResult`` / ``ResponseTarget`` / ``ResponseTargetKind`` /
``apply_run_hook`` — channel-side dataclasses + helpers.
- ``IsolationKeys`` + ``ISOLATION_HEADER_USER`` / ``..._CHAT`` +
``get/set/reset_current_isolation_keys`` — the host's ASGI middleware
reads the ``x-agent-{user,chat}-isolation-key`` headers off each
inbound request and exposes them to the agent stack via a
``ContextVar`` so storage-side providers (e.g.
``FoundryHostedAgentHistoryProvider``) can apply per-tenant
partitioning without channels having to forward anything.
Includes 45 unit tests covering the host, channel contributions,
isolation contextvar, and shared types. Registers the package in
``python/pyproject.toml`` ``[tool.uv.sources]`` and adds the matching
pyright ``executionEnvironments`` entry for tests.
Hypercorn is an optional dependency (``[serve]`` extra); the soft import
in ``serve()`` is annotated for pyright since it isn't on the default
install.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
New ``agent-framework-hosting-telegram`` package implementing the Telegram Bot API channel for the Hosting framework. Mounts a webhook endpoint (``POST /telegram/webhook``) and an in-process polling loop onto an ``AgentFrameworkHost`` and translates Telegram ``Update`` payloads to/from the channel-neutral ``ChannelRequest`` / ``HostedRunResult`` plumbing. Surface (re-exported from ``agent_framework_hosting_telegram``): - ``TelegramChannel`` -- concrete ``Channel`` implementation. Owns the webhook route + an optional ``getUpdates`` long-polling lifespan, parses Telegram ``Update``s into ``ChannelRequest`` (text, photo, document, voice, callback_query, …), runs the optional ``ChannelRunHook``, calls back into the ``ChannelContext`` to invoke the agent target, and posts the response back via ``sendMessage`` / ``sendChatAction`` / ``answerCallbackQuery`` on the Telegram Bot API. Honours ``DeliveryReport.include_originating`` so cross-channel pushes can target the originating Telegram chat without double-acking. - Native fields the channel doesn't lift onto ``ChannelRequest`` (e.g. ``chat.type``, ``message.message_id``, ``callback_query.data``) are attached to ``ChannelRequest.attributes`` so a ``ChannelRunHook`` can pick them up via the standard ``protocol_request=`` kwarg. - 13 unit tests covering route wiring, ``Update`` parsing across the common content shapes, hook composition, and originating vs non-originating delivery branches. Registers the package in ``python/pyproject.toml`` ``[tool.uv.sources]`` and adds the matching pyright ``executionEnvironments`` entry. Stacks on PR-2 (Hosting core); independent of PR-3 / PR-4. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
7958ec5 to
3aea8f0
Compare
…in shutdown - Replace per-update task fan-out with per-chat asyncio.Queue + worker. Telegram only guarantees update ordering up to getUpdates; the previous code spawned one task per update, which broke ordering for adjacent updates in the same chat. Updates are now serialised per chat_id (so /start then "what's the weather" can't race) while different chats still process in parallel. - Webhook handler now acks (200) immediately and runs the agent in the per-chat worker. Telegram redelivers any update the webhook doesn't 200 within ~60 seconds, so a streamed agent reply that runs longer than that previously triggered a retry storm and duplicate replies. - _on_shutdown now drains everything: poll task → per-chat workers → webhook-spawned dispatcher tasks (the new ack-before-run path), then deletes the webhook + closes the HTTP client. Previously webhook tasks were not tracked at all, so an in-flight agent invocation could leak past app shutdown. - _enqueue_update extracts chat_id from message / edited_message / callback_query; updates with no resolvable chat fall back to a one-shot dispatcher task that's still tracked in _update_tasks for shutdown. - Webhook handler now also returns 400 on malformed JSON / non-object payloads instead of crashing the request. 4 new tests cover per-chat serial ordering, parallel-across-chats isolation, ack-before-run latency, and shutdown drain. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Motivation and Context
Implements the Telegram channel described in SPEC-002 §7 (merged via #5549). Demonstrates a non-Microsoft channel built on the same
Channelprotocol — a useful reference for community channels.Description
Adds
agent-framework-hosting-telegram(python/packages/hosting-telegram/):TelegramChannel— mounts a webhook endpoint, decodes TelegramUpdatepayloads intoChannelRequest, supports text replies + simple multicast (response_targets).chat_id(the basis forlocal_telegramand the cross-channellocal_identity_linksample in PR-8).Stack
PR-6 of 9. Depends on #PR-2 (
feat/hosting-core).Contribution Checklist