Python: add agent-framework-hosting-teams channel (microsoft-teams-apps SDK)#5642
Conversation
eb01b3b to
597ba83
Compare
There was a problem hiding this comment.
Automated Code Review
Reviewers: 3 | Confidence: 72%
✗ Security Reliability
The main security concern is a path traversal vulnerability in
_resolve_checkpoint_storagein_host.py:request.session.isolation_keyis used directly in aPathjoin (self._checkpoint_location / request.session.isolation_key) without sanitization. SinceFileCheckpointStorage.__init__callsmkdir(parents=True, exist_ok=True)on the resulting path, a maliciousisolation_keycontaining../sequences could create directories and write checkpoint files outside the intended storage root. TheFileCheckpointStorage._validate_file_pathmethod only guardscheckpoint_idwithin the storage path — it cannot protect when the storage path itself is attacker-influenced. The risk is elevated whenskip_auth=Trueis used or a channel derivesisolation_keyfrom unvalidated user input.
✓ Test Coverage
The test_channel.py test suite for hosting-teams is well-structured and covers the main paths (message dispatch, outbound transforms, citations, streaming, feedback, identity, lifecycle startup). However, there are notable gaps: the async stream_transform_hook code path is untested (only sync hooks are tested), the _on_shutdown lifecycle handler logic is untested, and there is no test verifying behavior when activity.text is None (the
or "fallback). The new hosting package has good test coverage overall for the host wiring, session management, delivery routing, and workflow targets. However, theapply_run_hookhelper function—which contains meaningful branching logic (sync vs async hook returns)—is exported in__all__but has no test coverage in either test file. The host tests never pass arun_hooktoAgentFrameworkHost, so that code path is untested.
✗ Design Approach
The new hosting types and tests look directionally fine, but the workspace lockfile change is solving package-resolution problems by narrowing the entire Python support matrix from the repo’s declared 3.10+ baseline to 3.12+. That is a design-level regression because it affects every consumer of the frozen workspace, not just the new hosting packages, and it also strips existing per-package version guards from unrelated dependencies like github-copilot. The lockfile regeneration appears to have narrowed the supported install surface without updating the corresponding package metadata. In particular, it drops the only Python 3.10/3.11 artifacts for
hyperlight-sandbox-backend-wasmeven thoughagent-framework-hyperlightstill advertises>=3.10and depends on that wheel on supported Linux/Windows targets, which makes the repo’s frozen install path inconsistent with the declared support matrix. The lockfile changes appear to silently narrow Python compatibility from the repo’s declared>=3.10support to a 3.11+/3.12+ solution for key scientific dependencies. That is a design-level problem because it changes the supported runtime matrix through generated lock state only, without updating package metadata or docs. The lockfile regeneration appears to have been done against a narrower interpreter matrix than the repo actually supports. This lockfile regeneration appears to have been done from a 3.12+ environment and committed as if it were a workspace-wide update. That is a design-level mismatch: the checked-in lockfile no longer represents the full supported interpreter matrix.
Flagged Issues
- Path traversal in
_host.py_resolve_checkpoint_storage:request.session.isolation_keyis used unsanitized inself._checkpoint_location / request.session.isolation_key. TheFileCheckpointStorageconstructor callsmkdir(parents=True, exist_ok=True)on whatever path it receives, so anisolation_keycontaining../can escape the intended storage directory. Sanitize or validate that the resolved path stays withinself._checkpoint_locationbefore constructing the storage.
Automated review by eavanvalkenburg's agents
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>
…eams-apps SDK
New ``agent-framework-hosting-teams`` package that exposes the
``TeamsChannel`` for ``agent-framework-hosting``. Layered on top of the
official microsoft/teams.py SDK (PyPI: ``microsoft-teams-apps``)
instead of speaking the Bot Framework Activity Protocol over raw
HTTP. Teams traffic still flows through Azure Bot Service -- this
change is to the *programming model*, not the transport.
Compared to the channel-neutral
``agent-framework-hosting-activity-protocol`` (PR-5a), this channel
gives users typed activity models, the SDK's streaming primitive,
adaptive cards, and citation entities, all wired as first-class
features through the host.
Highlights
----------
- Host integration:
- Custom ``HttpServerAdapter`` (``_StarletteCaptureAdapter``) that
captures the SDK's route registration into Starlette ``Route``
instances, so the host mounts the messaging webhook without
handing the SDK its own server.
- The synchronous half of ``App.initialize`` is invoked inside
``contribute()`` so the host has the routes ready before
serving; the async half (plugin ``on_init``) runs from the
channel's startup hook, idempotently.
- Defaults:
- Mount path: ``/teams/messages`` (matches Bot Framework
convention).
- Channel name: ``"teams"`` and isolation key prefix ``teams:``
via ``teams_isolation_key()``, distinct from the
``activity:`` prefix used by hosting-activity-protocol so the
two channels never collide on the same host.
- Inbound:
- ``on_message`` builds a ``ChannelRequest`` with the inbound
text, the ``ConversationAccount`` id as session isolation key,
and the inbound ``MessageActivity`` exposed as
``protocol_request`` to user ``ChannelRunHook`` callables.
- Optional ``on_message_submit_feedback`` registration when the
user supplies a ``feedback_handler`` -- the channel translates
the typed invoke into a ``TeamsFeedbackContext`` and supports
sync or async callables.
- Outbound:
- Default reply: ``ctx.send(result.text)``.
- Optional ``outbound_transform`` returns a
``TeamsOutboundPayload`` with one of:
- plain text,
- ``AdaptiveCard`` (sent verbatim via the SDK),
- text + ``citations`` (rendered as a
``CitationEntity`` containing one ``Claim`` per
``TeamsCitation``, positions are 1-based).
- Streaming:
- ``streaming=True`` calls ``run_stream`` on the host, emits each
update's text deltas through the SDK's ``HttpStream``, then
closes the stream and runs ``deliver_response`` with the
accumulated text so cross-channel delivery still works.
- Optional ``stream_transform_hook`` lets callers drop or rewrite
individual ``AgentResponseUpdate`` instances per-channel.
Auth
----
``client_id`` / ``client_secret`` / ``tenant_id`` (or a custom
``token`` callable) are passed through to the SDK as-is. ``skip_auth``
disables JWT validation on inbound activities for local Bot Framework
Emulator development.
Test coverage
-------------
30 unit tests covering: isolation key + helpers, citation entity
shape, route adapter capture and Starlette translation, channel
construction, end-to-end inbound dispatch (including run-hook
invocation, transform variants, citation wiring), streaming with
transform-hook drop, sync + async feedback handlers, identity
extraction, and lifecycle hook idempotency. Coverage 92%; pyright +
mypy clean.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
597ba83 to
c2d74be
Compare
Motivation and Context
Implements the higher-level Teams channel described in SPEC-002 §7 + §"Teams-native fast follow" (merged via #5549). Built on the
microsoft-teams-appsSDK so users get Teams-native affordances out of the box on top of the same Bot Service transport as PR-5a.TeamsChannel(this PR)ActivityProtocolChannel(PR-5a)Description
Adds
agent-framework-hosting-teams(python/packages/hosting-teams/):TeamsChannel— built onmicrosoft-teams-apps. Default mount/teams/messages. Surfaces:ctx.stream.transform_outboundhook to emit Adaptive Cards.on_message_submit_feedbackcallback for Teams feedback events.HttpServerAdapter— captures the SDK's route registration synchronously insideChannel.contribute()so the host can mount a stable Starlette route (instead of letting the SDK spin up its own server).ActivityContext+HttpStream(no live Bot Service required).Note: this still uses Azure Bot Service as the transport (Teams does not currently expose a direct webhook outside Bot Service). The win over PR-5a is the developer-facing surface, not the hop count.
Stack
PR-5b of 9. Depends on #PR-2 (
feat/hosting-core). Independent of PR-5a — sample apps in PR-8 useActivityProtocolChannelfrom PR-5a; a follow-up sample for the Teams-SDK channel will be added separately.Contribution Checklist