Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
1bfd426
Updating typing annotations
rodrigobr-msft Jun 16, 2026
c8a4dfa
Another commit
rodrigobr-msft Jun 17, 2026
05f00e7
Separating route hubs
rodrigobr-msft Jun 17, 2026
4290f74
Adding file consent routes
rodrigobr-msft Jun 17, 2026
7537bc2
Adding refactored configuration routes
rodrigobr-msft Jun 17, 2026
50867ec
Refactor Team routes
rodrigobr-msft Jun 17, 2026
130b7ab
Cleaned up TeamsAgentExtension
rodrigobr-msft Jun 17, 2026
f3d1b31
Removing duplicate logic
rodrigobr-msft Jun 17, 2026
c0ed7b0
Cleaning up extension and adding new route handler protocol for Handoff
rodrigobr-msft Jun 18, 2026
b04bcaa
Addressing merge conflicts
rodrigobr-msft Jun 18, 2026
d1a4371
Further cleanup and formatting
rodrigobr-msft Jun 18, 2026
22a280e
Fixing teams models imports
rodrigobr-msft Jun 18, 2026
7a37f95
Adding direct decorator support for route hooks without positional ar…
rodrigobr-msft Jun 18, 2026
5758dac
Adding missing route hooks
rodrigobr-msft Jun 22, 2026
a11e18a
Adding _StateContra for proper route handler definitions
rodrigobr-msft Jun 22, 2026
9ff8559
Revisions to TeamsInfo file
rodrigobr-msft Jun 22, 2026
c166ca9
Adding targeted activity functionality to TeamsTurnContext
rodrigobr-msft Jun 22, 2026
dc0e763
Modification to test sample
rodrigobr-msft Jun 22, 2026
d1f9c5d
Adding back in ActivityHandler extension and fixing TeamsInfo tests
rodrigobr-msft Jun 22, 2026
97b8aed
Authorization.connection_manager
rodrigobr-msft Jun 22, 2026
1981797
renaming teams to msteams
rodrigobr-msft Jun 23, 2026
5fc9e37
Renaming teams to msteams
rodrigobr-msft Jun 23, 2026
d933753
Resolving merge conflicts
rodrigobr-msft Jun 23, 2026
f663e4c
Another commit
rodrigobr-msft Jun 23, 2026
ec17f2c
Merge branch 'main' of https://github.com/microsoft/Agents-for-python…
rodrigobr-msft Jun 23, 2026
6349c6c
Revising ApiClient setting
rodrigobr-msft Jun 23, 2026
0495df0
Adding TeamsActivity static helper
rodrigobr-msft Jun 23, 2026
8642094
Graph service client creation
rodrigobr-msft Jun 23, 2026
6bbcdc5
client getters
rodrigobr-msft Jun 24, 2026
5be4b3f
Adding TeamsActivity connection
rodrigobr-msft Jun 24, 2026
2390f12
Bug fixes
rodrigobr-msft Jun 24, 2026
33e2bf3
Fixing unit tests
rodrigobr-msft Jun 24, 2026
d87e2b2
Adding routing integration tests
rodrigobr-msft Jun 24, 2026
f575e59
Updating package README and minor bug
rodrigobr-msft Jun 24, 2026
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
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@
ProductInfo,
Thing,
StreamInfo,
ActivityTreatment,
ActivityTreatmentTypes,
)
from .error import Error
from .error_response import ErrorResponse
Expand Down Expand Up @@ -199,5 +201,7 @@
"load_configuration_from_env",
"ChannelAdapterProtocol",
"TurnContextProtocol",
"ActivityTreatment",
"ActivityTreatmentTypes",
"TokenOrSignInResourceResponse",
]
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License.

from .activity_treatment import ActivityTreatment, ActivityTreatmentTypes
from .mention import Mention
from .entity import Entity
from .entity_types import EntityTypes
Expand Down Expand Up @@ -35,4 +36,6 @@
"Place",
"ProductInfo",
"Thing",
"ActivityTreatment",
"ActivityTreatmentTypes",
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License.

from enum import Enum
from typing import Literal

from .entity import Entity
from .entity_types import EntityTypes


class ActivityTreatmentTypes(str, Enum):
"""Well-known enumeration of activity treatment types."""

TARGETED = "targeted"


class ActivityTreatment(Entity):
"""Activity treatment information (entity type: "activity_treatment").

:param treatment: The type of treatment
:type treatment: ~microsoft_agents.activity.ActivityTreatmentTypes
:param type: Type of this entity (RFC 3987 IRI)
:type type: str
"""

type: Literal[EntityTypes.ACTIVITY_TREATMENT] = EntityTypes.ACTIVITY_TREATMENT
treatment: ActivityTreatmentTypes
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
class EntityTypes(str, Enum):
"""Well-known enumeration of entity types."""

ACTIVITY_TREATMENT = "activityTreatment"
GEO_COORDINATES = "GeoCoordinates"
MENTION = "mention"
PLACE = "Place"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,9 @@

class RouteHandler(Protocol[StateT]):
def __call__(self, context: TurnContext, state: StateT, /) -> Awaitable[None]: ...


class HandoffHandler(Protocol[StateT]):
def __call__(
self, context: TurnContext, state: StateT, handoff_data: str
) -> Awaitable[None]: ...
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,11 @@
from .typing_indicator import TypingIndicator
from .telemetry import spans

from ._type_defs import RouteHandler, RouteSelector
from ._type_defs import (
RouteHandler,
HandoffHandler,
RouteSelector,
)
from ._routes import _RouteList, _Route, RouteRank, _agentic_selector
from .proactive import Proactive, ProactiveOptions

Expand All @@ -69,7 +73,7 @@ class AgentApplication(Agent, Generic[StateT]):

_options: ApplicationOptions
_adapter: Optional[ChannelServiceAdapter] = None
_auth: Optional[Authorization] = None
_auth: Authorization
_proactive: Optional[Proactive] = None
_internal_before_turn: list[Callable[[TurnContext, StateT], Awaitable[bool]]]
_internal_after_turn: list[Callable[[TurnContext, StateT], Awaitable[bool]]]
Expand Down Expand Up @@ -608,11 +612,11 @@ def __call(func: RouteHandler[StateT]) -> RouteHandler[StateT]:
@overload
def handoff(
self,
func: Callable[[TurnContext, StateT, str], Awaitable[None]],
func: HandoffHandler[StateT],
*,
auth_handlers: Optional[list[str]] = None,
**kwargs,
) -> Callable[[TurnContext, StateT, str], Awaitable[None]]: ...
) -> HandoffHandler[StateT]: ...

@overload
def handoff(
Expand All @@ -621,21 +625,21 @@ def handoff(
auth_handlers: Optional[list[str]] = None,
**kwargs: Any,
) -> Callable[
[Callable[[TurnContext, StateT, str], Awaitable[None]]],
Callable[[TurnContext, StateT, str], Awaitable[None]],
[HandoffHandler[StateT]],
HandoffHandler[StateT],
]: ...

def handoff(
self,
func: Optional[Callable[[TurnContext, StateT, str], Awaitable[None]]] = None,
func: Optional[HandoffHandler[StateT]] = None,
*,
auth_handlers: Optional[list[str]] = None,
**kwargs,
) -> (
Callable[[TurnContext, StateT, str], Awaitable[None]]
HandoffHandler[StateT]
| Callable[
[Callable[[TurnContext, StateT, str], Awaitable[None]]],
Callable[[TurnContext, StateT, str], Awaitable[None]],
[HandoffHandler[StateT]],
HandoffHandler[StateT],
]
):
"""
Expand All @@ -649,7 +653,7 @@ async def on_handoff(context: TurnContext, state: TurnState, continuation: str):
print(continuation)

:param func: Optional handler to register directly without using decorator syntax.
:type func: Optional[Callable[[TurnContext, StateT, str], Awaitable[None]]]
:type func: Optional[HandoffHandler[StateT]]
:param auth_handlers: Optional list of authorization handler IDs for the route.
:type auth_handlers: Optional[list[str]]
:param kwargs: Additional route configuration passed to :meth:`microsoft_agents.hosting.core.AgentApplication.add_route`.
Expand All @@ -662,8 +666,8 @@ def __selector(context: TurnContext) -> bool:
)

def __call(
func: Callable[[TurnContext, StateT, str], Awaitable[None]],
) -> Callable[[TurnContext, StateT, str], Awaitable[None]]:
func: HandoffHandler[StateT],
) -> HandoffHandler[StateT]:
async def __handler(context: TurnContext, state: StateT):
if (
isinstance(context.activity.value, dict)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from typing import Callable, Optional

from microsoft_agents.hosting.core.app.oauth import AuthHandler
from microsoft_agents.hosting.core.authorization import Connections
from microsoft_agents.hosting.core.storage import Storage
Comment on lines 12 to 14

# from .auth import AuthOptions
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,8 @@ def __init__(

:param storage: The storage system to use for state management.
:type storage: :class:`microsoft_agents.hosting.core.storage.Storage`
:param connection_manager: The connection manager for OAuth providers.
:type connection_manager: :class:`microsoft_agents.hosting.core.authorization.Connections`
:param connections: The connection manager for OAuth providers.
:type connections: :class:`microsoft_agents.hosting.core.authorization.Connections`
:param auth_handlers: Configuration for OAuth providers.
:type auth_handlers: dict[str, :class:`microsoft_agents.hosting.core.app.oauth.auth_handler.AuthHandler`], Optional
:raises ValueError: When storage is None or no auth handlers provided.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ class TurnContext(TurnContextProtocol):
# Same constant as in the BF Adapter, duplicating here to avoid circular dependency
_INVOKE_RESPONSE_KEY = "TurnContext.InvokeResponse"

_activity: Activity

def __init__(
self,
adapter_or_context,
Expand All @@ -43,7 +45,7 @@ def __init__(
self._identity = adapter_or_context.identity
else:
self.adapter = adapter_or_context
self._activity = request
self._activity = request # exception thrown if None further down
self.responses: list[Activity] = []
self._services: dict = {}
self._on_send_activities: Callable[
Expand All @@ -60,7 +62,7 @@ def __init__(

if self.adapter is None:
raise TypeError("TurnContext must be instantiated with an adapter.")
if self.activity is None:
if self._activity is None:
raise TypeError(
"TurnContext must be instantiated with a request parameter of type Activity."
)
Expand All @@ -83,7 +85,7 @@ def copy_to(self, context: "TurnContext") -> None:
"""
for attribute in [
"adapter",
"activity",
"_activity",
"_responded",
"_services",
"_on_send_activities",
Expand Down Expand Up @@ -187,7 +189,7 @@ async def send_activity(
activity_or_text: Activity | str,
speak: str | None = None,
input_hint: str | None = None,
) -> ResourceResponse | None:
) -> ResourceResponse:
"""
Sends a single activity or message to the user.
:param activity_or_text:
Expand All @@ -203,7 +205,7 @@ async def send_activity(
activity_or_text.speak = speak

result = await self.send_activities([activity_or_text])
return result[0] if result else None
return result[0] if result else ResourceResponse()

async def send_activities(
self, activities: list[Activity]
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
"""
Microsoft Agents Hosting Teams package.

Provides Teams-specific activity handlers, extensions, and utilities for building
Microsoft Teams bots and agents using the AgentApplication or ActivityHandler models.
"""

from .teams_agent_extension import TeamsAgentExtension
from .channel import Channel
from .config import Config
from .file_consent import FileConsent
from .meeting import Meeting
from .message import Message
from .message_extension import MessageExtension
from .task_module import TaskModule
from .team import Team

from .teams_activity import TeamsActivity
from .teams_turn_context import TeamsTurnContext

__all__ = [
"TeamsAgentExtension",
"Channel",
"Config",
"FileConsent",
"Meeting",
"Message",
"MessageExtension",
"TaskModule",
"Team",
"TeamsActivity",
"TeamsTurnContext",
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License.

from typing import Any

from kiota_abstractions.request_information import RequestInformation
from kiota_abstractions.authentication import AuthenticationProvider

from msgraph import GraphServiceClient, GraphRequestAdapter

from microsoft_agents.hosting.core import (
AgentApplication,
TurnContext,
)


class _SDKAuthenticationProvider(AuthenticationProvider):

def __init__(self, app: AgentApplication, context: TurnContext, handler_name: str):
self._app = app
self._context = context
self._handler_name = handler_name

async def authenticate_request(
self,
request: RequestInformation,
additional_authentication_context: dict[str, Any] | None = None,
) -> None:
"""Authenticates the application request

Args:
request (RequestInformation): The request to authenticate
additional_authentication_context (dict):
"""
if additional_authentication_context is None:
additional_authentication_context = {}

token = await self._app.auth.get_token(self._context, self._handler_name)
if token:
request.headers["Authorization"] = f"Bearer {token}"


def _create_graph_service_client(
app: AgentApplication,
context: TurnContext,
handler_name: str | None = None,
) -> GraphServiceClient:
return GraphServiceClient(
request_adapter=GraphRequestAdapter(
_SDKAuthenticationProvider(app, context, handler_name)
)
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License.

from microsoft_teams.common import ClientOptions
from microsoft_teams.api import ApiClient

from microsoft_agents.hosting.core import (
Connections,
TurnContext,
)

_TEAMS_API_CLIENT_KEY = "TeamsApiClient"


def _get_teams_api_client(context: TurnContext) -> ApiClient:
"""
Get the cached Teams API client from the context.

:param context: The turn context.
:return: The cached Teams API client.
:raises ValueError: If the Teams API client is not found.
"""
api_client = context.turn_state.get(_TEAMS_API_CLIENT_KEY)
if isinstance(api_client, ApiClient):
return api_client
raise ValueError("Unable to retrieve Teams API client.")


def _set_teams_api_client(
context: TurnContext, connection_manager: Connections
) -> None:
"""
Set the Teams API client in the context if it is not already set.

:param context: The turn context.
:param connection_manager: The connection manager.
"""

if _TEAMS_API_CLIENT_KEY in context.turn_state:
return

headers = {
"Accept": "application/json",
"Content-Type": "application/json",
}

options: ClientOptions

if context.identity:
provider = connection_manager.get_token_provider(
context.identity, context.activity.service_url
)

async def token_factory() -> str:
return await provider.get_access_token(
"https://api.botframework.com",
["https://api.botframework.com/.default"],
)

options = ClientOptions(
base_url=context.activity.service_url, headers=headers, token=token_factory
)
else:
options = ClientOptions(base_url=context.activity.service_url, headers=headers)

api_client = ApiClient(
context.activity.service_url,
options,
)

context.turn_state[_TEAMS_API_CLIENT_KEY] = api_client
Loading
Loading