Commit 8703c4d
committed
feat(teams): migrate inbound + auth to microsoft-teams-apps SDK (#93 PR 1/4)
Rewire the Teams adapter's inbound webhook path and JWT authentication to
delegate to the official microsoft-teams-apps Python SDK, mirroring upstream
adapter-teams@chat@4.30.0. Outbound (post/edit/delete/typing), native
streaming, and the Graph reader stay hand-rolled (PRs 2/3 + a separate
decision).
New: src/chat_sdk/adapters/teams/bridge.py
- BridgeHttpAdapter implements the SDK HttpServerAdapter protocol. It captures
the route handler the App registers during app.initialize(), exposes an async
dispatch(request, options) for handle_webhook to call, and owns a per-activity
WebhookOptions map (keyed by activity id, cleared in finally). Framework
agnostic — body/header extraction duck-types across web frameworks; response
shaping returns the {body, status, headers} dict consumers expect. Port of
upstream bridge-adapter.ts.
adapter.py:
- Construct the SDK App from TeamsAdapterConfig via _to_app_options (port of
config.ts toAppOptions): app_id/app_password/app_tenant_id/federated/app_type
-> client_id/client_secret/tenant_id/managed_identity_client_id, with the same
TEAMS_* env fallbacks. Inject the bridge; stamp client User-Agent
"Vercel.ChatSDK".
- initialize() registers SDK handlers (on_message/on_message_reaction/
on_card_action/on_dialog_open/on_dialog_submit/on_conversation_update/
on_install_add/remove), awaits app.initialize(), and overrides
server.on_request with _dispatch_activity.
- handle_webhook now delegates to bridge.dispatch. Inbound JWT validation is
performed by the SDK's TokenValidator (RS256 + audience app_id/api:// variants
+ Bot Framework issuer/JWKS), enforced by default (skip_auth=False). Deleted
the hand-rolled _verify_bot_framework_token / _jwks_client / openid-config
fetch / BOT_FRAMEWORK_OPENID_CONFIG_URL and the now-unused request/response
helpers (body/header extraction + response shaping live in the bridge).
- _handle_teams_error (port of errors.ts handleTeamsError) now maps both the
plain dicts the hand-rolled Graph/outbound path raises and the SDK's typed
exceptions (status on inner_http_error.status_code / status_code / retry_after)
onto AuthenticationError / AdapterPermissionError / AdapterRateLimitError /
NetworkError.
Divergence (SDK-forced, documented in docs/UPSTREAM_SYNC.md): the SDK's default
on_request runs strict per-activity validation + a live token fetch before any
handler. We keep the SDK as the auth + transport layer but route the
already-authenticated lenient CoreActivity ourselves so minimal serverless
payloads and the existing dict-based handler logic keep working unchanged.
Tests: add tests/test_teams_bridge.py (protocol conformance, dispatch happy/
error paths, WebhookOptions lifecycle, body/header extraction). Rework the
JWT-bypass fixtures to force the SDK's own skip_auth instead of patching the
deleted method; add TestSdkInboundAuth asserting the SDK rejects unauthenticated
/ bad-token / unconfigured requests. Add _to_app_options and SDK-exception
error-mapping tests. Point the shared body-extraction test at
BridgeHttpAdapter._read_body.
Public Adapter contract and create_teams_adapter(TeamsAdapterConfig(...))
unchanged. Gauntlet green: ruff/format/audit clean, pyrefly 0 errors,
4793 passed / 3 skipped.1 parent fd3f0d5 commit 8703c4d
9 files changed
Lines changed: 1173 additions & 302 deletions
File tree
- docs
- src/chat_sdk/adapters/teams
- tests
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
610 | 610 | | |
611 | 611 | | |
612 | 612 | | |
| 613 | + | |
| 614 | + | |
613 | 615 | | |
614 | 616 | | |
615 | 617 | | |
| |||
0 commit comments