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
36 changes: 36 additions & 0 deletions examples/agent365/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# agent365

Demonstrates Agent 365 `AgentUserIdentity` support in reactive and proactive modes.

## Reactive Echo

`src/main.py` mimics the echo example. Incoming messages are handled normally, but when the inbound activity recipient has `role="agenticUser"`, `ctx.send()` and `ctx.reply()` send from that concrete `AgentUserIdentity` using the inbound activity's service URL.

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

## Proactive AgentUserIdentity Send

`src/proactive.py` mimics the proactive messaging example, but sends from a specific AgentUserIdentity. Supply the concrete agent identity app ID and agent user ID.

```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/proactive.py \
<conversation-id> \
<agent-identity-app-id> \
<agent-user-id>
```

## Identity Model

- `CLIENT_ID`: blueprint client/app ID.
- `agent_identity_app_id`: concrete agent identity app/client ID.
- `agent_user_id`: user-shaped account/persona object ID for the agent.
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 }
61 changes: 61 additions & 0 deletions examples/agent365/src/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
"""
Copyright (c) Microsoft Corporation. All rights reserved.
Licensed under the MIT License.
"""

# Agent 365 Reactive Example
# ==========================
# This example echoes messages back from the concrete AgentUserIdentity in the inbound activity.

import asyncio
import logging
import re

from microsoft_teams.api import AdaptiveCardInvokeActivity, MessageActivity
from microsoft_teams.api.activities.typing import TypingActivityInput
from microsoft_teams.api.models.adaptive_card import AdaptiveCardActionMessageResponse
from microsoft_teams.api.models.invoke_response import AdaptiveCardInvokeResponse
from microsoft_teams.apps import ActivityContext, App

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

app = App()


@app.on_message_pattern(re.compile(r"hello|hi|greetings"))
async def handle_greeting(ctx: ActivityContext[MessageActivity]) -> None:
"""Handle greeting messages as the inbound agent user."""
await ctx.reply("Hello! How can I assist you today?")


@app.on_message
async def handle_message(ctx: ActivityContext[MessageActivity]):
"""Echo incoming messages as the inbound agent user."""
logger.info(f"[Agent365 onMessage] Message received: {ctx.activity.text}")
logger.info(f"[Agent365 onMessage] From: {ctx.activity.from_}")
logger.info(f"[Agent365 onMessage] Agent user identity: {ctx.agent_user_identity}")

await ctx.reply(TypingActivityInput())

if "reply" in ctx.activity.text.lower():
await ctx.reply("Hello! How can I assist you today?")
else:
await ctx.send(f"You said '{ctx.activity.text}'")


@app.on_card_action_execute("ack_agent365_card")
async def handle_agent365_card_ack(ctx: ActivityContext[AdaptiveCardInvokeActivity]) -> AdaptiveCardInvokeResponse:
"""Handle the Action.Execute button from the proactive AgentUserIdentity card."""
data = ctx.activity.value.action.data
logger.info(f"[Agent365 card] Acknowledged with data: {data}")
await ctx.send(f"Acknowledged Agent 365 card. Data: {data}")
return AdaptiveCardActionMessageResponse(
status_code=200,
type="application/vnd.microsoft.activity.message",
value="Acknowledged",
)


if __name__ == "__main__":
asyncio.run(app.start())
102 changes: 102 additions & 0 deletions examples/agent365/src/proactive.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
"""
Copyright (c) Microsoft Corporation. All rights reserved.
Licensed under the MIT License.
"""

# Agent 365 Proactive Example
# ===========================
# This example sends proactive messages from a specific AgentUserIdentity.

import argparse
import asyncio
import logging

from microsoft_teams.apps import App
from microsoft_teams.cards import ActionSet, AdaptiveCard, ExecuteAction, SubmitData, TextBlock

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


async def send_proactive_message(
app: App,
conversation_id: str,
agent_identity_app_id: str,
agent_user_id: str,
message: str,
) -> None:
"""Send a proactive message from an AgentUserIdentity."""
agent_user_identity = app.get_agent_user_identity(agent_identity_app_id, agent_user_id)
logger.info(f"Sending proactive message as agent user: {agent_user_identity.id}")
logger.info(f"Message: {message}")
result = await app.send(conversation_id, agent_user_identity, message)

logger.info(f"Message sent successfully. Activity ID: {result.id}")


async def send_proactive_card(
app: App,
conversation_id: str,
agent_identity_app_id: str,
agent_user_id: str,
) -> None:
"""Send a proactive Adaptive Card from an AgentUserIdentity."""
agent_user_identity = app.get_agent_user_identity(agent_identity_app_id, agent_user_id)
card = AdaptiveCard(
schema="http://adaptivecards.io/schemas/adaptive-card.json",
body=[
TextBlock(text="Agent 365 Notification", size="Large", weight="Bolder"),
TextBlock(text="This message was sent proactively from an AgentUserIdentity.", wrap=True),
TextBlock(text=f"Agent user: {agent_user_identity.id}", wrap=True, is_subtle=True),
ActionSet(
actions=[
ExecuteAction(title="Acknowledge")
.with_data(SubmitData("ack_agent365_card", {"agent_user_id": agent_user_identity.id}))
.with_associated_inputs("auto")
]
),
],
)

logger.info(f"Sending proactive card as agent user: {agent_user_identity.id}")

result = await app.send(conversation_id, agent_user_identity, card)

logger.info(f"Card sent successfully. Activity ID: {result.id}")


async def main():
parser = argparse.ArgumentParser(description="Send proactive messages from an Agent 365 AgentUserIdentity")
parser.add_argument("conversation_id", help="The Teams conversation ID to send messages to")
parser.add_argument("agent_identity_app_id", help="The concrete agent identity app/client ID")
parser.add_argument("agent_user_id", help="The agent user object ID")
args = parser.parse_args()

app = App()

logger.info("Initializing app without starting server...")
await app.initialize()
logger.info("App initialized")

await send_proactive_message(
app,
args.conversation_id,
args.agent_identity_app_id,
args.agent_user_id,
"Hello! This is a proactive message sent from an AgentUserIdentity.",
)

await asyncio.sleep(2)

await send_proactive_card(
app,
args.conversation_id,
args.agent_identity_app_id,
args.agent_user_id,
)

logger.info("All proactive AgentUserIdentity messages sent successfully")


if __name__ == "__main__":
asyncio.run(main())
13 changes: 13 additions & 0 deletions packages/api/src/microsoft_teams/api/models/account.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from .custom_base_model import CustomBaseModel

AccountType = Literal["person", "tag", "channel", "team", "bot"]
AccountRole = Literal["agenticUser"]


class Account(CustomBaseModel):
Expand Down Expand Up @@ -44,6 +45,18 @@ class Account(CustomBaseModel):
"""
The name of the account.
"""
role: Optional[AccountRole | str] = None
"""The role of the account in the activity."""
agentic_user_id: Optional[str] = None
"""The Agent ID user-shaped identity object ID."""
agentic_app_id: Optional[str] = None
"""The Agent ID app/client ID for the concrete agent identity."""
agentic_app_blueprint_id: Optional[str] = None
"""The Agent ID blueprint app/client ID."""
callback_uri: Optional[str] = None
"""The callback URI associated with the agent identity."""
tenant_id: Optional[str] = None
"""The tenant ID associated with the account."""


class TeamsChannelAccount(CustomBaseModel):
Expand Down
2 changes: 1 addition & 1 deletion packages/apps/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ dependencies = [
"cryptography>=3.4.0",
"pyjwt[crypto]>=2.12.0",
"dependency-injector>=4.48.1",
"msal>=1.33.0",
"msal>=1.37.0",
"python-dotenv>=1.0.0",
"pydantic-settings>=2.11.0",
]
Expand Down
2 changes: 2 additions & 0 deletions packages/apps/src/microsoft_teams/apps/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import logging

from . import auth, contexts, events, plugins
from .agent_user import AgentUserIdentity
from .app import App
from .auth import * # noqa: F403
from .contexts import * # noqa: F403
Expand All @@ -23,6 +24,7 @@
__all__: list[str] = [
"App",
"AppOptions",
"AgentUserIdentity",
"HttpServer",
"HttpServerAdapter",
"FastAPIAdapter",
Expand Down
4 changes: 2 additions & 2 deletions packages/apps/src/microsoft_teams/apps/activity_sender.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,13 +36,13 @@ def __init__(self, client: Client):
"""
self._client = client

async def send(self, activity: ActivityParams, ref: ConversationReference) -> SentActivity:
async def send(self, ref: ConversationReference, activity: ActivityParams) -> SentActivity:
"""
Send an activity to the Bot Framework.

Args:
activity: The activity to send
ref: The conversation reference
activity: The activity to send

Returns:
The sent activity with id and other server-populated fields
Expand Down
18 changes: 18 additions & 0 deletions packages/apps/src/microsoft_teams/apps/agent_user.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
"""
Copyright (c) Microsoft Corporation. All rights reserved.
Licensed under the MIT License.
"""

from dataclasses import dataclass


@dataclass(frozen=True)
class AgentUserIdentity:
"""Identifies an Agent ID user-shaped identity and its backing agent app."""

id: str
agent_identity_app_id: str
tenant_id: str


__all__ = ["AgentUserIdentity"]
Loading
Loading