From 5f6be7f31bd7ef53f92d030b060325783f84f169 Mon Sep 17 00:00:00 2001 From: Austin Pickett Date: Mon, 15 Jun 2026 14:35:15 -0400 Subject: [PATCH] fix(teams): package Microsoft Teams SDK as an installable extra (salvage #43945) (#46764) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(teams): package Microsoft Teams SDK as an installable extra The Teams adapter imports the microsoft-teams-apps SDK, but it was never declared as a dependency, so source/local installs hit ImportError and the adapter silently reported the SDK as unavailable. Add a 'teams' extra (microsoft-teams-apps==2.0.13.4 + aiohttp) and document 'uv sync --extra teams'. Per the 2026-05-12 [all] policy, opt-in messaging-platform SDKs are NOT added to [all] (they would break every fresh install on a quarantined release); the teams extra is installed on demand like the other platform backends. Co-authored-by: rio-jeong * chore: map rio-jeong contributor email for attribution (#43945) * feat(teams): lazy-install the Teams SDK on demand (parity with other channels) The teams extra alone left Teams as the only messaging platform that wouldn't auto-install its SDK — every other channel (telegram, discord, slack, matrix, dingtalk, feishu) lazy-installs via tools.lazy_deps on first connect. Bring Teams to parity: - Add 'platform.teams' to LAZY_DEPS (microsoft-teams-apps + aiohttp). - Replace the passive 'check_teams_requirements = check_requirements' alias with a real lazy-installer that calls ensure_and_bind('platform.teams', ...), rebinding all Teams SDK globals on success (mirrors check_slack_requirements). - Call check_teams_requirements() at the top of TeamsAdapter.connect() so enabling Teams installs the SDK on demand. - Keep the passive check_requirements() as the registry check_fn so 'gateway status' probes never trigger a pip install. The 'teams' extra remains for packagers / explicit 'uv sync --extra teams'. Tests: rework the alias test into shortcircuit + lazy-install assertions, and update test_connect_fails_without_sdk to simulate an uninstallable SDK. --------- Co-authored-by: rio-jeong Co-authored-by: Teknium <127238744+teknium1@users.noreply.github.com> --- plugins/platforms/teams/adapter.py | 74 +++++++++++++++++- pyproject.toml | 1 + scripts/release.py | 1 + tests/gateway/test_teams.py | 38 +++++++++- tools/lazy_deps.py | 5 ++ uv.lock | 87 +++++++++++++++++++++- website/docs/user-guide/messaging/teams.md | 9 +++ 7 files changed, 211 insertions(+), 4 deletions(-) diff --git a/plugins/platforms/teams/adapter.py b/plugins/platforms/teams/adapter.py index a7d024419e1..f8175a6a621 100644 --- a/plugins/platforms/teams/adapter.py +++ b/plugins/platforms/teams/adapter.py @@ -617,7 +617,74 @@ async def _standalone_send( # Keep the old name as an alias so existing test imports don't break. -check_teams_requirements = check_requirements +# NOTE: ``check_requirements`` is the PASSIVE probe (used as the registry +# ``check_fn`` and by ``gateway status``) — it must never trigger a pip +# install. ``check_teams_requirements`` is the ACTIVE lazy-installer called +# from ``connect()``; it installs ``platform.teams`` on demand and rebinds the +# SDK globals, mirroring ``check_slack_requirements`` in gateway/platforms/slack.py. +def check_teams_requirements() -> bool: + """Ensure the Teams SDK is importable, lazy-installing it on first use. + + Lazy-installs ``microsoft-teams-apps`` via + ``tools.lazy_deps.ensure("platform.teams")`` if not present, then rebinds + all module-level SDK globals on success. Returns True once the SDK (and + aiohttp) are importable, False if they couldn't be installed/imported. + """ + if TEAMS_SDK_AVAILABLE and AIOHTTP_AVAILABLE: + return True + + def _import() -> dict: + from aiohttp import web as _web + from microsoft_teams.apps import App, ActivityContext + from microsoft_teams.common.http.client import ClientOptions + from microsoft_teams.api import MessageActivity, ConversationReference + from microsoft_teams.api.activities.typing import TypingActivityInput + from microsoft_teams.api.activities.invoke.adaptive_card import ( + AdaptiveCardInvokeActivity, + ) + from microsoft_teams.api.models.adaptive_card import ( + AdaptiveCardActionCardResponse, + AdaptiveCardActionMessageResponse, + ) + from microsoft_teams.api.models.invoke_response import ( + InvokeResponse, + AdaptiveCardInvokeResponse, + ) + from microsoft_teams.apps.http.adapter import ( + HttpMethod, + HttpRequest, + HttpResponse, + HttpRouteHandler, + ) + from microsoft_teams.cards import AdaptiveCard, ExecuteAction, TextBlock + + return { + "web": _web, + "AIOHTTP_AVAILABLE": True, + "App": App, + "ActivityContext": ActivityContext, + "ClientOptions": ClientOptions, + "MessageActivity": MessageActivity, + "ConversationReference": ConversationReference, + "TypingActivityInput": TypingActivityInput, + "AdaptiveCardInvokeActivity": AdaptiveCardInvokeActivity, + "AdaptiveCardActionCardResponse": AdaptiveCardActionCardResponse, + "AdaptiveCardActionMessageResponse": AdaptiveCardActionMessageResponse, + "InvokeResponse": InvokeResponse, + "AdaptiveCardInvokeResponse": AdaptiveCardInvokeResponse, + "HttpMethod": HttpMethod, + "HttpRequest": HttpRequest, + "HttpResponse": HttpResponse, + "HttpRouteHandler": HttpRouteHandler, + "AdaptiveCard": AdaptiveCard, + "ExecuteAction": ExecuteAction, + "TextBlock": TextBlock, + "TEAMS_SDK_AVAILABLE": True, + } + + from tools.lazy_deps import ensure_and_bind + + return ensure_and_bind("platform.teams", _import, globals(), prompt=False) class TeamsAdapter(BasePlatformAdapter): @@ -642,10 +709,13 @@ class TeamsAdapter(BasePlatformAdapter): self._conv_refs: Dict[str, Any] = {} async def connect(self) -> bool: + # Lazy-install the Teams SDK on demand (parity with Slack/Discord/etc.), + # then re-check the module globals it rebinds. + check_teams_requirements() if not TEAMS_SDK_AVAILABLE: self._set_fatal_error( "MISSING_SDK", - "microsoft-teams-apps not installed. Run: pip install microsoft-teams-apps", + "microsoft-teams-apps could not be installed. Run: pip install microsoft-teams-apps", retryable=False, ) return False diff --git a/pyproject.toml b/pyproject.toml index 9520d496107..4a2ab1c6b7b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -179,6 +179,7 @@ mcp = ["mcp==1.26.0", "starlette==1.0.1"] # starlette: CVE-2026-48710 nemo-relay = ["nemo-relay==0.3"] homeassistant = ["aiohttp==3.13.4"] sms = ["aiohttp==3.13.4"] +teams = ["microsoft-teams-apps==2.0.13.4", "aiohttp==3.13.4"] # Computer use — macOS background desktop control via cua-driver (MCP stdio). # The cua-driver binary itself is installed via `hermes tools` post-setup # (curl install script); this extra just pins the MCP client used to talk diff --git a/scripts/release.py b/scripts/release.py index 5058e406cd3..318c8c82d2d 100755 --- a/scripts/release.py +++ b/scripts/release.py @@ -45,6 +45,7 @@ ACP_REGISTRY_MANIFEST = REPO_ROOT / "acp_registry" / "agent.json" # Auto-extracted from noreply emails + manual overrides AUTHOR_MAP = { + "rio.jeong@thebytesize.ai": "rio-jeong", "yehaotian@xuanshudeMac-mini.local": "ArcanePivot", "dbeyer7@gmail.com": "benegessarit", "kenmege@yahoo.com": "Kenmege", diff --git a/tests/gateway/test_teams.py b/tests/gateway/test_teams.py index d4f56104a6a..1ae10593cc6 100644 --- a/tests/gateway/test_teams.py +++ b/tests/gateway/test_teams.py @@ -211,10 +211,39 @@ class TestTeamsRequirements: monkeypatch.setattr(_teams_mod, "AIOHTTP_AVAILABLE", True) assert check_requirements() is True - def test_alias_matches(self, monkeypatch): + def test_check_teams_requirements_shortcircuits_when_present(self, monkeypatch): + # When the SDK + aiohttp are already importable, the active lazy- + # installer returns True immediately without attempting an install. monkeypatch.setattr(_teams_mod, "TEAMS_SDK_AVAILABLE", True) monkeypatch.setattr(_teams_mod, "AIOHTTP_AVAILABLE", True) + called = {"ensure_and_bind": 0} + + def _fake_ensure_and_bind(*_args, **_kwargs): + called["ensure_and_bind"] += 1 + return True + + monkeypatch.setattr( + "tools.lazy_deps.ensure_and_bind", _fake_ensure_and_bind + ) assert check_teams_requirements() is True + assert called["ensure_and_bind"] == 0 + + def test_check_teams_requirements_lazy_installs_when_missing(self, monkeypatch): + # When deps are missing, the active installer delegates to + # ensure_and_bind("platform.teams", ...) — parity with Slack/Discord. + monkeypatch.setattr(_teams_mod, "TEAMS_SDK_AVAILABLE", False) + monkeypatch.setattr(_teams_mod, "AIOHTTP_AVAILABLE", False) + seen = {} + + def _fake_ensure_and_bind(feature, importer, target_globals, **kwargs): + seen["feature"] = feature + return True + + monkeypatch.setattr( + "tools.lazy_deps.ensure_and_bind", _fake_ensure_and_bind + ) + assert check_teams_requirements() is True + assert seen["feature"] == "platform.teams" def test_validate_config_with_env(self, monkeypatch): monkeypatch.setenv("TEAMS_CLIENT_ID", "test-id") @@ -371,6 +400,13 @@ class TestTeamsConnect: @pytest.mark.anyio async def test_connect_fails_without_sdk(self, monkeypatch): monkeypatch.setattr(_teams_mod, "TEAMS_SDK_AVAILABLE", False) + # Simulate the SDK being unavailable AND not installable (offline / + # locked-down env): the lazy-installer can't rebind the globals, so + # TEAMS_SDK_AVAILABLE stays False and connect() must fail. + monkeypatch.setattr( + "tools.lazy_deps.ensure_and_bind", + lambda *_a, **_k: False, + ) adapter = TeamsAdapter(_make_config( client_id="id", client_secret="secret", tenant_id="tenant", )) diff --git a/tools/lazy_deps.py b/tools/lazy_deps.py index e4b0a9a57f0..cb123caaf9f 100644 --- a/tools/lazy_deps.py +++ b/tools/lazy_deps.py @@ -152,6 +152,11 @@ LAZY_DEPS: dict[str, tuple[str, ...]] = { # defusedxml only; aiohttp/httpx are core dependencies of every messaging # adapter and ship via `platform.discord` / `platform.slack` / etc. "platform.wecom_callback": ("defusedxml==0.7.1",), + # Microsoft Teams adapter — microsoft-teams-apps pulls a heavy tree + # (microsoft-teams-api/cards/common, dependency-injector, msal). Lazy- + # installed on demand like every other messaging platform; also exposed + # as the `teams` extra in pyproject for packagers / explicit installs. + "platform.teams": ("microsoft-teams-apps==2.0.13.4", "aiohttp==3.13.4"), # ─── Terminal backends ───────────────────────────────────────────────── "terminal.modal": ("modal==1.3.4",), diff --git a/uv.lock b/uv.lock index 86949511683..385cffe0dd5 100644 --- a/uv.lock +++ b/uv.lock @@ -960,6 +960,28 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/07/6c/aa3f2f849e01cb6a001cd8554a88d4c77c5c1a31c95bdf1cf9301e6d9ef4/defusedxml-0.7.1-py2.py3-none-any.whl", hash = "sha256:a352e7e428770286cc899e2542b6cdaedb2b4953ff269a210103ec58f6198a61", size = 25604, upload-time = "2021-03-08T10:59:24.45Z" }, ] +[[package]] +name = "dependency-injector" +version = "4.49.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f3/be/26bb530d06618fb0bb34244d46b0d0ccc53d0974e680d8653f1b1b313a0e/dependency_injector-4.49.0.tar.gz", hash = "sha256:17a04dbfaa8159f1dc068fc26bc2fa0af9774cdd87f99e3b61bd74c9e7171589", size = 1168930, upload-time = "2026-03-22T21:20:05.524Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5c/5d/cc49fb34e0c03aa56d7583de00e2f8f5aa1b8a878b695e970dcdb751a477/dependency_injector-4.49.0-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:9690192fd5aed07f21dfdfae07696fef12c68bf98e4c0e1af8f8128b255a74a7", size = 1769395, upload-time = "2026-03-22T21:19:14.163Z" }, + { url = "https://files.pythonhosted.org/packages/7f/97/b3b144c96e1f7fff0a7e2e83eb0767bd23b6bacffd0ac8cff397d350e94d/dependency_injector-4.49.0-cp310-abi3-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:f91f2a191bdb17bd3068f32fe65f04128bc162c6237ea554c117b303c22aaabb", size = 1852089, upload-time = "2026-03-22T21:19:16.354Z" }, + { url = "https://files.pythonhosted.org/packages/4c/e7/33061f427bcb56c8936d5db464d757d926bf752a874683fb64b2ee225463/dependency_injector-4.49.0-cp310-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:733c0d88b26be17a48e5741cc3e3956080112e40c07a38ff38e99dfa772f9772", size = 1765608, upload-time = "2026-03-22T21:19:19.217Z" }, + { url = "https://files.pythonhosted.org/packages/1e/4d/2751a6c055de4a200d65af297ecd926d6b6107f66f3849e8122928abf461/dependency_injector-4.49.0-cp310-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:45720b30a2a3df6e5e2320e242f6dd94540ba27c3da57cafdc37fdeec59d5ce3", size = 1746555, upload-time = "2026-03-22T21:19:21.231Z" }, + { url = "https://files.pythonhosted.org/packages/02/6f/f74fee9629528f0879295b9f89a5c751d3ad931eca0c78407f715e5472a6/dependency_injector-4.49.0-cp310-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:3b5d2f1be2dc971db47b1305a83b5a8c24d0eba7fb4cea7845679f9c9f24a0a9", size = 1843223, upload-time = "2026-03-22T21:19:23.356Z" }, + { url = "https://files.pythonhosted.org/packages/c5/f0/45948c7c933f063039a44afb4bd61747a7bafd50693e6ccdc972fac0839c/dependency_injector-4.49.0-cp310-abi3-win32.whl", hash = "sha256:0593c8aaade651a5a88ff8ba1271a8364773e76d3aa2efbeacc3be4969cafd1c", size = 1546172, upload-time = "2026-03-22T21:19:25.392Z" }, + { url = "https://files.pythonhosted.org/packages/e2/b5/1d8e5627137cb9a6812ecaa468eaf39154f6605c5088da4749e5a8579483/dependency_injector-4.49.0-cp310-abi3-win_amd64.whl", hash = "sha256:fa4b587158b0d65a1f9681ca648da3f9bf90f312f68c2f2e73cc58296ec2bf45", size = 1674743, upload-time = "2026-03-22T21:19:27.018Z" }, + { url = "https://files.pythonhosted.org/packages/92/35/ca21ab897fc193dcdbad1f856361e7614b8e2b69f9f9351e9a87a3c58e51/dependency_injector-4.49.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:6c4b49df30f13f5e4361719b21c79445db11869a7a00d80a0486c03fd764ba8f", size = 1744444, upload-time = "2026-03-22T21:19:57.332Z" }, + { url = "https://files.pythonhosted.org/packages/25/44/d108aeee8f2edd3e725ac0e32d16e4339a034a07da9ddaf07f772f425140/dependency_injector-4.49.0-pp311-pypy311_pp73-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:4b0c637ba230e390631da13bb80c955a9f85487f78c9772c0f6a3b50bfbff3a6", size = 1822320, upload-time = "2026-03-22T21:19:59.334Z" }, + { url = "https://files.pythonhosted.org/packages/35/32/6243ef32c384dda156b053c3df5c8b6c3ac42250ec089a09915f015d38a1/dependency_injector-4.49.0-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a5857b2672512654110dd0371fa965b98255e2f0507dd4732a066767b72e23c4", size = 1741215, upload-time = "2026-03-22T21:20:01.523Z" }, + { url = "https://files.pythonhosted.org/packages/b9/52/a1957d4ef87a52c13f2b790c1cc5fae17eb385fbe2e978c7fd8c1ebb4ea9/dependency_injector-4.49.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:8ffa2ac9297446f73bd28ada81aadf4494a52d869159d58923435bcd88b5ef60", size = 1652017, upload-time = "2026-03-22T21:20:03.653Z" }, +] + [[package]] name = "deprecated" version = "1.3.1" @@ -1542,6 +1564,10 @@ slack = [ sms = [ { name = "aiohttp" }, ] +teams = [ + { name = "aiohttp" }, + { name = "microsoft-teams-apps" }, +] termux = [ { name = "agent-client-protocol" }, { name = "honcho-ai" }, @@ -1591,6 +1617,7 @@ requires-dist = [ { name = "aiohttp", marker = "extra == 'messaging'", specifier = "==3.13.4" }, { name = "aiohttp", marker = "extra == 'slack'", specifier = "==3.13.4" }, { name = "aiohttp", marker = "extra == 'sms'", specifier = "==3.13.4" }, + { name = "aiohttp", marker = "extra == 'teams'", specifier = "==3.13.4" }, { name = "aiohttp-socks", marker = "extra == 'matrix'", specifier = "==0.11.0" }, { name = "aiosqlite", marker = "extra == 'matrix'", specifier = "==0.22.1" }, { name = "alibabacloud-dingtalk", marker = "extra == 'dingtalk'", specifier = "==2.2.42" }, @@ -1649,6 +1676,7 @@ requires-dist = [ { name = "mcp", marker = "extra == 'computer-use'", specifier = "==1.26.0" }, { name = "mcp", marker = "extra == 'dev'", specifier = "==1.26.0" }, { name = "mcp", marker = "extra == 'mcp'", specifier = "==1.26.0" }, + { name = "microsoft-teams-apps", marker = "extra == 'teams'", specifier = "==2.0.13.4" }, { name = "mistralai", marker = "extra == 'mistral'", specifier = "==2.4.8" }, { name = "modal", marker = "extra == 'modal'", specifier = "==1.3.4" }, { name = "nemo-relay", marker = "extra == 'nemo-relay'", specifier = "==0.3" }, @@ -1697,7 +1725,7 @@ requires-dist = [ { name = "websockets", specifier = "==15.0.1" }, { name = "youtube-transcript-api", marker = "extra == 'youtube'", specifier = "==1.2.4" }, ] -provides-extras = ["anthropic", "exa", "firecrawl", "parallel-web", "fal", "edge-tts", "modal", "daytona", "hindsight", "dev", "messaging", "cron", "slack", "matrix", "wecom", "cli", "tts-premium", "voice", "pty", "honcho", "vision", "mcp", "nemo-relay", "homeassistant", "sms", "computer-use", "acp", "mistral", "bedrock", "azure-identity", "termux", "termux-all", "dingtalk", "feishu", "google", "youtube", "web", "all"] +provides-extras = ["anthropic", "exa", "firecrawl", "parallel-web", "fal", "edge-tts", "modal", "daytona", "hindsight", "dev", "messaging", "cron", "slack", "matrix", "wecom", "cli", "tts-premium", "voice", "pty", "honcho", "vision", "mcp", "nemo-relay", "homeassistant", "sms", "teams", "computer-use", "acp", "mistral", "bedrock", "azure-identity", "termux", "termux-all", "dingtalk", "feishu", "google", "youtube", "web", "all"] [[package]] name = "hf-xet" @@ -2176,6 +2204,63 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979, upload-time = "2022-08-14T12:40:09.779Z" }, ] +[[package]] +name = "microsoft-teams-api" +version = "2.0.13.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "microsoft-teams-cards" }, + { name = "microsoft-teams-common" }, + { name = "pydantic" }, + { name = "pyjwt", extra = ["crypto"] }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e1/7f/dc1995f72a8d23e723b168db20bac67b819ef2fa734bc23f63bc8086c41b/microsoft_teams_api-2.0.13.4.tar.gz", hash = "sha256:d16f88ae90f65bcce83ede9ecc57773f7b1a19cbecde63be624b586b59e34fc9", size = 51779, upload-time = "2026-06-08T19:24:02.661Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/27/15/e1a1369a22c265b52da3ac4b3ee67b5c02911300db045894868bd7be932f/microsoft_teams_api-2.0.13.4-py3-none-any.whl", hash = "sha256:be52ef7765ea5851e0982de1ff6b1192869c85fc74e890ae20029bd99064b532", size = 149825, upload-time = "2026-06-08T19:24:13.202Z" }, +] + +[[package]] +name = "microsoft-teams-apps" +version = "2.0.13.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cryptography" }, + { name = "dependency-injector" }, + { name = "fastapi" }, + { name = "microsoft-teams-api" }, + { name = "microsoft-teams-common" }, + { name = "msal" }, + { name = "pydantic-settings" }, + { name = "pyjwt", extra = ["crypto"] }, + { name = "python-dotenv" }, + { name = "uvicorn" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/61/0a/733f05f8decee2da6e53ee38757e742520ae56363bc2d006b309cdbf9cfe/microsoft_teams_apps-2.0.13.4.tar.gz", hash = "sha256:d0b12e5e82024cffd3739b329b098b98a08803753eb5484bf96dbb6ce1237e04", size = 91366, upload-time = "2026-06-08T19:24:04.591Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3a/d4/3c4205258642035d160c09f598a302260776dcb6d5bdf659eea7c6066d5e/microsoft_teams_apps-2.0.13.4-py3-none-any.whl", hash = "sha256:db16f714ec658b592929c6386a29792e90bb73840732f8ae65a198cda1fea96c", size = 71406, upload-time = "2026-06-08T19:24:15.034Z" }, +] + +[[package]] +name = "microsoft-teams-cards" +version = "2.0.13.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ef/7f/cce9633f635d9e1b2318ce2146a804a14a46c9e34e855c3784beb8ab39b3/microsoft_teams_cards-2.0.13.4.tar.gz", hash = "sha256:de54956a2afbbcf187f2531459967515b4f4743fa784bd0f454eaff1ac675c90", size = 28108, upload-time = "2026-06-08T19:24:07.841Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/20/09/95cad44d4417e33df11a15c82ca1bde442c1f1f77396f936f18896f116c1/microsoft_teams_cards-2.0.13.4-py3-none-any.whl", hash = "sha256:b8b887466c8144675ff5704064daf05ec3ebdf4d322658ab9a25bfc1373d7909", size = 29617, upload-time = "2026-06-08T19:24:17.373Z" }, +] + +[[package]] +name = "microsoft-teams-common" +version = "2.0.13.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "httpx" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/6c/f1/a32821cfdde6c0d33a1e4022492a2211af670a81ec1fab727c49cddd4f7a/microsoft_teams_common-2.0.13.4.tar.gz", hash = "sha256:ed3175316f77f083a500da0a84ddf53ac31c6de008a252f0cfd86bdb70120bf3", size = 11122, upload-time = "2026-06-08T19:24:09.694Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cb/04/859b3d7fadd1d61ab581f79afb6125c16c60cecf2a2e6bbb2ebbcfd34f80/microsoft_teams_common-2.0.13.4-py3-none-any.whl", hash = "sha256:19524ec75587d797d07c5a78e9b72921b6d58f33d39512ca2d33468160fd0d82", size = 16588, upload-time = "2026-06-08T19:24:18.325Z" }, +] + [[package]] name = "mistralai" version = "2.4.8" diff --git a/website/docs/user-guide/messaging/teams.md b/website/docs/user-guide/messaging/teams.md index ae30d4a5856..bc59ca342ed 100644 --- a/website/docs/user-guide/messaging/teams.md +++ b/website/docs/user-guide/messaging/teams.md @@ -24,6 +24,15 @@ Teams delivers @mentions as regular messages with `BotName` tags, which --- +For source or local installs, include the Teams extra so the bundled adapter can +import the Microsoft Teams SDK: + +```bash +uv sync --extra teams +# or, for editable installs: +uv pip install -e ".[teams]" +``` + ## Step 1: Install the Teams CLI The `@microsoft/teams.cli` automates bot registration — no Azure portal needed.