Skip to content

Python: add agent-framework-hosting-teams channel (microsoft-teams-apps SDK)#5642

Open
eavanvalkenburg wants to merge 2 commits intomicrosoft:feature/python-hostingfrom
eavanvalkenburg:feat/hosting-channel-teams
Open

Python: add agent-framework-hosting-teams channel (microsoft-teams-apps SDK)#5642
eavanvalkenburg wants to merge 2 commits intomicrosoft:feature/python-hostingfrom
eavanvalkenburg:feat/hosting-channel-teams

Conversation

@eavanvalkenburg
Copy link
Copy Markdown
Member

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-apps SDK so users get Teams-native affordances out of the box on top of the same Bot Service transport as PR-5a.

Audience Use channel Why
Teams-only deployments that want rich features (Adaptive Cards, streaming, Citations, feedback) TeamsChannel (this PR) First-class Teams SDK; per-message hooks.
Multi-client Activity Protocol (Direct Line, Web Chat, Slack-via-Bot-Service, …) ActivityProtocolChannel (PR-5a) Lower-level; no Teams-specific affordances.

Description

Adds agent-framework-hosting-teams (python/packages/hosting-teams/):

  • TeamsChannel — built on microsoft-teams-apps. Default mount /teams/messages. Surfaces:
    • Text in/out + streaming via ctx.stream.
    • Outbound transform_outbound hook to emit Adaptive Cards.
    • Inbound on_message_submit_feedback callback for Teams feedback events.
    • Citations entity passthrough.
  • HttpServerAdapter — captures the SDK's route registration synchronously inside Channel.contribute() so the host can mount a stable Starlette route (instead of letting the SDK spin up its own server).
  • Tests use in-process fakes for 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 use ActivityProtocolChannel from PR-5a; a follow-up sample for the Teams-SDK channel will be added separately.

Contribution Checklist

  • The code builds clean without any errors or warnings
  • The PR follows the Contribution Guidelines
  • All unit tests pass, and I have added new tests where possible
  • Is this a breaking change? No — new package.

@moonbox3 moonbox3 added documentation Improvements or additions to documentation python labels May 5, 2026
@eavanvalkenburg eavanvalkenburg force-pushed the feat/hosting-channel-teams branch from eb01b3b to 597ba83 Compare May 5, 2026 09:00
Copy link
Copy Markdown

@github-actions github-actions Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Automated Code Review

Reviewers: 3 | Confidence: 72%

✗ Security Reliability

The main security concern is a path traversal vulnerability in _resolve_checkpoint_storage in _host.py: request.session.isolation_key is used directly in a Path join (self._checkpoint_location / request.session.isolation_key) without sanitization. Since FileCheckpointStorage.__init__ calls mkdir(parents=True, exist_ok=True) on the resulting path, a malicious isolation_key containing ../ sequences could create directories and write checkpoint files outside the intended storage root. The FileCheckpointStorage._validate_file_path method only guards checkpoint_id within the storage path — it cannot protect when the storage path itself is attacker-influenced. The risk is elevated when skip_auth=True is used or a channel derives isolation_key from 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, the apply_run_hook helper 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 a run_hook to AgentFrameworkHost, 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-wasm even though agent-framework-hyperlight still advertises >=3.10 and 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.10 support 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_key is used unsanitized in self._checkpoint_location / request.session.isolation_key. The FileCheckpointStorage constructor calls mkdir(parents=True, exist_ok=True) on whatever path it receives, so an isolation_key containing ../ can escape the intended storage directory. Sanitize or validate that the resolved path stays within self._checkpoint_location before constructing the storage.

Automated review by eavanvalkenburg's agents

eavanvalkenburg and others added 2 commits May 5, 2026 11:08
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>
@eavanvalkenburg eavanvalkenburg force-pushed the feat/hosting-channel-teams branch from 597ba83 to c2d74be Compare May 5, 2026 09:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation python

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants