fix(teams): package Microsoft Teams SDK as an installable extra (salvage #43945) (#46764)

* 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 <rio.jeong@thebytesize.ai>

* 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 <rio.jeong@thebytesize.ai>
Co-authored-by: Teknium <127238744+teknium1@users.noreply.github.com>
This commit is contained in:
Austin Pickett 2026-06-15 14:35:15 -04:00 committed by GitHub
parent 0bbf325a8f
commit 5f6be7f31b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 211 additions and 4 deletions

View file

@ -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

View file

@ -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

View file

@ -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",

View file

@ -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",
))

View file

@ -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",),

87
uv.lock generated
View file

@ -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"

View file

@ -24,6 +24,15 @@ Teams delivers @mentions as regular messages with `<at>BotName</at>` 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.