Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@

A comprehensive SDK for building Microsoft Teams applications, bots, and AI agents using Python. This SDK provides a high-level framework with built-in Microsoft Graph integration, OAuth handling, and extensible plugin architecture.

## Agent 365 Support

Agent 365 support is being developed on this integration branch.

<a href="https://microsoft.github.io/teams-sdk" target="_blank">
<img src="https://img.shields.io/badge/📖 Getting Started-blue?style=for-the-badge" />
</a>
Expand Down
18 changes: 18 additions & 0 deletions examples/agent365/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# agent365

Demonstrates passing `AgenticIdentity` directly to Teams API surfaces.

## Proactive API Send

`src/main.py` sends through the normal API client, but supplies `agentic_identity` to the activity create operation. The API layer uses the agentic token provider to put the right Agent ID token in the request header.

```bash
export CLIENT_ID=<agent-identity-blueprint-app-id>
export CLIENT_SECRET=<agent-identity-blueprint-secret>
export TENANT_ID=<tenant-id>

uv run --project examples/agent365 python src/main.py \
<conversation-id> \
<agentic-app-id> \
<agentic-user-id>
```
13 changes: 13 additions & 0 deletions examples/agent365/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
[project]
name = "agent365"
version = "0.1.0"
description = "Agent 365 token example"
readme = "README.md"
requires-python = ">=3.11,<4.0"
dependencies = [
"dotenv>=0.9.9",
"microsoft-teams-apps",
]

[tool.uv.sources]
microsoft-teams-apps = { workspace = true }
39 changes: 39 additions & 0 deletions examples/agent365/src/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
"""
Copyright (c) Microsoft Corporation. All rights reserved.
Licensed under the MIT License.
"""

import argparse
import asyncio
import logging

from microsoft_teams.api import MessageActivityInput
from microsoft_teams.apps import App

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)


async def main():
parser = argparse.ArgumentParser(description="Send a proactive message using API-level AgenticIdentity")
parser.add_argument("conversation_id", help="The Teams conversation ID to send messages to")
parser.add_argument("agentic_app_id", help="The concrete agent identity app/client ID")
parser.add_argument("agentic_user_id", help="The agent user object ID")
args = parser.parse_args()

app = App()
await app.initialize()

agentic_identity = app.get_agentic_identity(args.agentic_app_id, args.agentic_user_id)
activity = MessageActivityInput(text="Hello from an API-level AgenticIdentity.")

result = await app.api.conversations.activities(args.conversation_id).create(
activity,
agentic_identity=agentic_identity,
)

logger.info("Sent activity as agentic identity. Activity ID: %s", result.id)


if __name__ == "__main__":
asyncio.run(main())
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ class CloudEnvironment:
"""The default multi-tenant login tenant (e.g. "botframework.com")."""
bot_scope: str
"""The Bot Framework OAuth scope (e.g. "https://api.botframework.com/.default")."""
agentic_bot_scope: str
"""The Teams Bot API scope for Agent ID user-token calls."""
token_service_url: str
"""The Bot Framework token service base URL (e.g. "https://token.botframework.com")."""
openid_metadata_url: str
Expand All @@ -35,6 +37,7 @@ class CloudEnvironment:
login_endpoint="https://login.microsoftonline.com",
login_tenant="botframework.com",
bot_scope="https://api.botframework.com/.default",
agentic_bot_scope="https://botapi.skype.com/.default",
token_service_url="https://token.botframework.com",
openid_metadata_url="https://login.botframework.com/v1/.well-known/openidconfiguration",
token_issuer="https://api.botframework.com",
Expand All @@ -46,6 +49,7 @@ class CloudEnvironment:
login_endpoint="https://login.microsoftonline.us",
login_tenant="MicrosoftServices.onmicrosoft.us",
bot_scope="https://api.botframework.us/.default",
agentic_bot_scope="https://botapi.skype.com/.default",
token_service_url="https://tokengcch.botframework.azure.us",
openid_metadata_url="https://login.botframework.azure.us/v1/.well-known/openidconfiguration",
token_issuer="https://api.botframework.us",
Expand All @@ -57,6 +61,7 @@ class CloudEnvironment:
login_endpoint="https://login.microsoftonline.us",
login_tenant="MicrosoftServices.onmicrosoft.us",
bot_scope="https://api.botframework.us/.default",
agentic_bot_scope="https://botapi.skype.com/.default",
token_service_url="https://apiDoD.botframework.azure.us",
openid_metadata_url="https://login.botframework.azure.us/v1/.well-known/openidconfiguration",
token_issuer="https://api.botframework.us",
Expand All @@ -68,6 +73,7 @@ class CloudEnvironment:
login_endpoint="https://login.partner.microsoftonline.cn",
login_tenant="microsoftservices.partner.onmschina.cn",
bot_scope="https://api.botframework.azure.cn/.default",
agentic_bot_scope="https://botapi.skype.com/.default",
token_service_url="https://token.botframework.azure.cn",
openid_metadata_url="https://login.botframework.azure.cn/v1/.well-known/openidconfiguration",
token_issuer="https://api.botframework.azure.cn",
Expand Down
9 changes: 6 additions & 3 deletions packages/api/src/microsoft_teams/api/auth/credentials.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

from typing import Awaitable, Callable, Literal, Optional, Union

from ..models import CustomBaseModel
from ..models import AgenticIdentity, CustomBaseModel


class ClientCredentials(CustomBaseModel):
Expand Down Expand Up @@ -36,8 +36,11 @@ class TokenCredentials(CustomBaseModel):
"""
The tenant ID.
"""
# (scope: string | string[], tenantId?: string) => string | Promise<string>
token: Callable[[Union[str, list[str]], Optional[str]], Union[str, Awaitable[str]]]
# (scope: string | string[], tenantId?: string, agenticIdentity?: AgenticIdentity) => string | Promise<string>
token: Callable[
[Union[str, list[str]], Optional[str], Optional[AgenticIdentity]],
Union[str, Awaitable[str]],
]
"""
The token function.
"""
Expand Down
2 changes: 2 additions & 0 deletions packages/api/src/microsoft_teams/api/clients/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from . import bot, conversation, meeting, reaction, team, user
from .api_client import ApiClient
from .api_client_settings import ApiClientSettings, merge_api_client_settings
from .base_client import AuthProvider
from .bot import * # noqa: F403
from .conversation import * # noqa: F403
from .meeting import * # noqa: F403
Expand All @@ -17,6 +18,7 @@
__all__: list[str] = [
"ApiClient",
"ApiClientSettings",
"AuthProvider",
"merge_api_client_settings",
]
__all__.extend(bot.__all__)
Expand Down
14 changes: 11 additions & 3 deletions packages/api/src/microsoft_teams/api/clients/api_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
from microsoft_teams.common import ClientOptions

from .api_client_settings import ApiClientSettings
from .base_client import BaseClient
from .base_client import AuthProvider, BaseClient
from .bot import BotClient
from .conversation import ConversationClient
from .meeting import MeetingClient
Expand All @@ -32,6 +32,8 @@ def __init__(
options: Optional[Union[HttpClient, ClientOptions]] = None,
api_client_settings: Optional[ApiClientSettings] = None,
cloud: Optional[CloudEnvironment] = None,
*,
auth_provider: Optional[AuthProvider] = None,
) -> None:
"""Initialize the unified Teams API client.

Expand All @@ -41,13 +43,19 @@ def __init__(
api_client_settings: Optional API client settings.
cloud: Optional cloud environment for sovereign cloud support.
"""
super().__init__(options, api_client_settings)
super().__init__(options, api_client_settings, auth_provider=auth_provider, cloud=cloud)
self.service_url = service_url.rstrip("/")

# Initialize all client types
self.bots = BotClient(self._http, self._api_client_settings, cloud=cloud)
self.users = UserClient(self._http, self._api_client_settings)
self.conversations = ConversationClient(self.service_url, self._http, self._api_client_settings)
self.conversations = ConversationClient(
self.service_url,
self._http,
self._api_client_settings,
auth_provider=auth_provider,
cloud=cloud,
)
self.teams = TeamClient(self.service_url, self._http, self._api_client_settings)
self.meetings = MeetingClient(self.service_url, self._http, self._api_client_settings)
self._reactions: Optional[ReactionClient] = None
Expand Down
25 changes: 24 additions & 1 deletion packages/api/src/microsoft_teams/api/clients/base_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,32 @@
Licensed under the MIT License.
"""

from typing import Optional, Union
from typing import Awaitable, Optional, Protocol, Union

from microsoft_teams.api.auth.cloud_environment import PUBLIC, CloudEnvironment
from microsoft_teams.common.http import Client, ClientOptions
from microsoft_teams.common.http.client_token import StringLike

from ..models.agentic_identity import AgenticIdentity
from .api_client_settings import ApiClientSettings, merge_api_client_settings


class AuthProvider(Protocol):
def token(
self, scope: str, tenant_id: str | None = None, agentic_identity: AgenticIdentity | None = None
) -> str | StringLike | None | Awaitable[str | StringLike | None]: ...


class BaseClient:
"""Base client"""

def __init__(
self,
options: Optional[Union[Client, ClientOptions]] = None,
api_client_settings: Optional[ApiClientSettings] = None,
*,
auth_provider: Optional[AuthProvider] = None,
cloud: Optional[CloudEnvironment] = None,
) -> None:
"""Initialize the BaseClient.

Expand All @@ -32,6 +44,8 @@ def __init__(
self._http = Client(options)

self._api_client_settings = merge_api_client_settings(api_client_settings)
self._auth_provider = auth_provider
self._cloud = cloud or PUBLIC

@property
def http(self) -> Client:
Expand All @@ -42,3 +56,12 @@ def http(self) -> Client:
def http(self, value: Client) -> None:
"""Set the HTTP client instance."""
self._http = value

def _get_agentic_token(self, identity: AgenticIdentity | None):
if identity is None:
return None
if self._auth_provider is None:
raise ValueError("agentic_identity requires a Teams auth provider")
auth_provider = self._auth_provider

return lambda: auth_provider.token(self._cloud.agentic_bot_scope, identity.tenant_id, identity)
Loading
Loading