From c85bc1d9f6e48f58acffc2e869e43cd0dfade851 Mon Sep 17 00:00:00 2001 From: Chris Mullins Date: Tue, 28 Oct 2025 11:22:10 -0700 Subject: [PATCH 1/2] Update parameter type hints and documentation across multiple files for clarity and consistency. (#207) * Update parameter type hints and documentation across multiple files for clarity and consistency. Fixes all 43 errors identified in issue https://github.com/microsoft/Agents-for-python/issues/204 * Update libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/channel_service_adapter.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Chris Mullins Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../activity/channel_account.py | 2 +- .../activity/conversation_account.py | 2 +- .../activity/teams/teams_channel_account.py | 2 +- .../authentication/msal/msal_auth.py | 2 +- .../hosting/core/activity_handler.py | 69 ++++-- .../hosting/core/app/agent_application.py | 221 ++++++++++-------- .../hosting/core/channel_adapter.py | 39 ++-- .../hosting/core/channel_service_adapter.py | 15 +- 8 files changed, 204 insertions(+), 148 deletions(-) diff --git a/libraries/microsoft-agents-activity/microsoft_agents/activity/channel_account.py b/libraries/microsoft-agents-activity/microsoft_agents/activity/channel_account.py index 2759e376..c00e5ad7 100644 --- a/libraries/microsoft-agents-activity/microsoft_agents/activity/channel_account.py +++ b/libraries/microsoft-agents-activity/microsoft_agents/activity/channel_account.py @@ -12,7 +12,7 @@ class ChannelAccount(AgentsModel): """Channel account information needed to route a message. :param id: Channel id for the user or agent on this channel (Example: - joe@smith.com, or @joesmith or 123456) + ``joe@smith.com``, or ``@joesmith`` or ``123456``) :type id: str :param name: Display friendly name :type name: str diff --git a/libraries/microsoft-agents-activity/microsoft_agents/activity/conversation_account.py b/libraries/microsoft-agents-activity/microsoft_agents/activity/conversation_account.py index a3032d9d..80a8bfa2 100644 --- a/libraries/microsoft-agents-activity/microsoft_agents/activity/conversation_account.py +++ b/libraries/microsoft-agents-activity/microsoft_agents/activity/conversation_account.py @@ -16,7 +16,7 @@ class ConversationAccount(AgentsModel): channels that distinguish between conversation types :type conversation_type: str :param id: Channel id for the user or agent on this channel (Example: - joe@smith.com, or @joesmith or 123456) + ``joe@smith.com``, or ``@joesmith`` or ``123456``) :type id: str :param name: Display friendly name :type name: str diff --git a/libraries/microsoft-agents-activity/microsoft_agents/activity/teams/teams_channel_account.py b/libraries/microsoft-agents-activity/microsoft_agents/activity/teams/teams_channel_account.py index b2294998..da810e1b 100644 --- a/libraries/microsoft-agents-activity/microsoft_agents/activity/teams/teams_channel_account.py +++ b/libraries/microsoft-agents-activity/microsoft_agents/activity/teams/teams_channel_account.py @@ -9,7 +9,7 @@ class TeamsChannelAccount(AgentsModel): """Teams channel account detailing user Azure Active Directory details. - :param id: Channel id for the user or bot on this channel (Example: joe@smith.com, or @joesmith or 123456) + :param id: Channel id for the user or bot on this channel (Example: ``joe@smith.com``, or ``@joesmith`` or ``123456``) :type id: str :param name: Display friendly name :type name: str diff --git a/libraries/microsoft-agents-authentication-msal/microsoft_agents/authentication/msal/msal_auth.py b/libraries/microsoft-agents-authentication-msal/microsoft_agents/authentication/msal/msal_auth.py index f9486cc4..ebaf3ffb 100644 --- a/libraries/microsoft-agents-authentication-msal/microsoft_agents/authentication/msal/msal_auth.py +++ b/libraries/microsoft-agents-authentication-msal/microsoft_agents/authentication/msal/msal_auth.py @@ -58,7 +58,7 @@ def __init__(self, msal_configuration: AgentAuthConfiguration): :param msal_configuration: The MSAL authentication configuration. Assumed to not be mutated after being passed in. - :type msal_configuration: AgentAuthConfiguration + :type msal_configuration: :class:`microsoft_agents.hosting.core.authorization.agent_auth_configuration.AgentAuthConfiguration` """ self._msal_configuration = msal_configuration diff --git a/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/activity_handler.py b/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/activity_handler.py index a7ab04ad..0b584f31 100644 --- a/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/activity_handler.py +++ b/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/activity_handler.py @@ -3,6 +3,7 @@ from __future__ import annotations from http import HTTPStatus +from typing import Awaitable from pydantic import BaseModel from microsoft_agents.activity import TurnContextProtocol @@ -24,9 +25,9 @@ class ActivityHandler(Agent): """ Handles activities and should be subclassed. - .. remarks:: + .. note:: Derive from this class to handle particular activity types. - Yon can add pre and post processing of activities by calling the base class + You can add pre and post processing of activities by calling the base class in the derived class. """ @@ -34,15 +35,16 @@ async def on_turn( self, turn_context: TurnContextProtocol ): # pylint: disable=arguments-differ """ - Called by the adapter (for example, :class:`ChannelAdapter`) at runtime + Called by the adapter (for example, :class:`microsoft_agents.hosting.core.channel_adapter.ChannelAdapter`) at runtime in order to process an inbound :class:`microsoft_agents.activity.Activity`. :param turn_context: The context object for this turn :type turn_context: :class:`microsoft_agents.activity.TurnContextProtocol` :returns: A task that represents the work queued to execute + :rtype: Awaitable[None] - .. remarks:: + .. note:: It calls other methods in this class based on the type of the activity to process, which allows a derived class to provide type-specific logic in a controlled way. In a derived class, override this method to add logic that applies to all activity types. @@ -108,6 +110,7 @@ async def on_message_activity( # pylint: disable=unused-argument :param turn_context: The context object for this turn :type turn_context: :class:`microsoft_agents.activity.TurnContextProtocol` :returns: A task that represents the work queued to execute + :rtype: Awaitable[None] """ return @@ -121,6 +124,7 @@ async def on_message_update_activity( # pylint: disable=unused-argument :param turn_context: The context object for this turn :type turn_context: :class:`microsoft_agents.activity.TurnContextProtocol` :returns: A task that represents the work queued to execute + :rtype: Awaitable[None] """ return @@ -134,6 +138,7 @@ async def on_message_delete_activity( # pylint: disable=unused-argument :param turn_context: The context object for this turn :type turn_context: :class:`microsoft_agents.activity.TurnContextProtocol` :returns: A task that represents the work queued to execute + :rtype: Awaitable[None] """ return @@ -145,17 +150,18 @@ async def on_conversation_update_activity(self, turn_context: TurnContextProtoco :param turn_context: The context object for this turn :type turn_context: :class:`microsoft_agents.activity.TurnContextProtocol` :returns: A task that represents the work queued to execute + :rtype: Awaitable[None] - .. remarks:: + .. note:: When the :meth:`on_turn()` method receives a conversation update activity, it calls this method. Also - If the conversation update activity indicates that members other than the agent joined the conversation, - it calls the :meth:`on_members_added_activity()` method. + it calls the :meth:`on_members_added_activity()` method. - If the conversation update activity indicates that members other than the agent left the conversation, - it calls the :meth:`on_members_removed_activity()` method. + it calls the :meth:`on_members_removed_activity()` method. - In a derived class, override this method to add logic that applies to all conversation update activities. - Add logic to apply before the member added or removed logic before the call to this base class method. + Add logic to apply before the member added or removed logic before the call to this base class method. """ # TODO: confirm behavior of added and removed at the same time as C# doesn't support it if turn_context.activity.members_added: @@ -175,13 +181,14 @@ async def on_members_added_activity( the conversation. You can add your agent's welcome logic. :param members_added: A list of all the members added to the conversation, as described by the - conversation update activity + conversation update activity :type members_added: list[:class:`microsoft_agents.activity.ChannelAccount`] :param turn_context: The context object for this turn :type turn_context: :class:`microsoft_agents.activity.TurnContextProtocol` :returns: A task that represents the work queued to execute + :rtype: Awaitable[None] - .. remarks:: + .. note:: When the :meth:`on_conversation_update_activity()` method receives a conversation update activity that indicates one or more users other than the agent are joining the conversation, it calls this method. @@ -196,13 +203,14 @@ async def on_members_removed_activity( the conversation. You can add your agent's good-bye logic. :param members_removed: A list of all the members removed from the conversation, as described by the - conversation update activity + conversation update activity :type members_removed: list[:class:`microsoft_agents.activity.ChannelAccount`] :param turn_context: The context object for this turn :type turn_context: :class:`microsoft_agents.activity.TurnContextProtocol` :returns: A task that represents the work queued to execute + :rtype: Awaitable[None] - .. remarks:: + .. note:: When the :meth:`on_conversation_update_activity()` method receives a conversation update activity that indicates one or more users other than the agent are leaving the conversation, it calls this method. @@ -219,8 +227,9 @@ async def on_message_reaction_activity(self, turn_context: TurnContextProtocol): :type turn_context: :class:`microsoft_agents.activity.TurnContextProtocol` :returns: A task that represents the work queued to execute + :rtype: Awaitable[None] - .. remarks:: + .. note:: Message reactions correspond to the user adding a 'like' or 'sad' etc. (often an emoji) to a previously sent activity. @@ -231,9 +240,9 @@ async def on_message_reaction_activity(self, turn_context: TurnContextProtocol): method. - If the message reaction indicates that reactions were added to a message, it calls - :meth:`on_reaction_added()`. + :meth:`on_reactions_added()`. - If the message reaction indicates that reactions were removed from a message, it calls - :meth:`on_reaction_removed()`. + :meth:`on_reactions_removed()`. In a derived class, override this method to add logic that applies to all message reaction activities. Add logic to apply before the reactions added or removed logic before the call to the this base class @@ -264,8 +273,9 @@ async def on_reactions_added( # pylint: disable=unused-argument :param turn_context: The context object for this turn :type turn_context: :class:`microsoft_agents.activity.TurnContextProtocol` :returns: A task that represents the work queued to execute + :rtype: Awaitable[None] - .. remarks:: + .. note:: Message reactions correspond to the user adding a 'like' or 'sad' etc. (often an emoji) to a previously sent message on the conversation. Message reactions are supported by only a few channels. @@ -290,8 +300,9 @@ async def on_reactions_removed( # pylint: disable=unused-argument :type turn_context: :class:`microsoft_agents.activity.TurnContextProtocol` :returns: A task that represents the work queued to execute + :rtype: Awaitable[None] - .. remarks:: + .. note:: Message reactions correspond to the user adding a 'like' or 'sad' etc. (often an emoji) to a previously sent message on the conversation. Message reactions are supported by only a few channels. The activity that the message is in reaction to is identified by the activity's reply to Id property. @@ -308,8 +319,9 @@ async def on_event_activity(self, turn_context: TurnContextProtocol): :param turn_context: The context object for this turn :type turn_context: :class:`microsoft_agents.activity.TurnContextProtocol` :returns: A task that represents the work queued to execute + :rtype: Awaitable[None] - .. remarks:: + .. note:: When the :meth:`on_turn()` method receives an event activity, it calls this method. If the activity name is `tokens/response`, it calls :meth:`on_token_response_event()`; otherwise, it calls :meth:`on_event()`. @@ -338,8 +350,9 @@ async def on_token_response_event( # pylint: disable=unused-argument :param turn_context: The context object for this turn :type turn_context: :class:`microsoft_agents.activity.TurnContextProtocol` :returns: A task that represents the work queued to execute + :rtype: Awaitable[None] - .. remarks:: + .. note:: When the :meth:`on_event()` method receives an event with an activity name of `tokens/response`, it calls this method. If your agent uses an `oauth_prompt`, forward the incoming activity to the current dialog. @@ -352,11 +365,13 @@ async def on_event( # pylint: disable=unused-argument """ Invoked when an event other than `tokens/response` is received when the base behavior of :meth:`on_event_activity()` is used. + :param turn_context: The context object for this turn :type turn_context: :class:`microsoft_agents.activity.TurnContextProtocol` :returns: A task that represents the work queued to execute + :rtype: Awaitable[None] - .. remarks:: + .. note:: When the :meth:`on_event_activity()` is used method receives an event with an activity name other than `tokens/response`, it calls this method. This method could optionally be overridden if the agent is meant to handle miscellaneous events. @@ -368,9 +383,11 @@ async def on_end_of_conversation_activity( # pylint: disable=unused-argument ): """ Invoked when a conversation end activity is received from the channel. + :param turn_context: The context object for this turn :type turn_context: :class:`microsoft_agents.activity.TurnContextProtocol` :returns: A task that represents the work queued to execute + :rtype: Awaitable[None] """ return @@ -384,6 +401,7 @@ async def on_typing_activity( # pylint: disable=unused-argument :param turn_context: The context object for this turn :type turn_context: :class:`microsoft_agents.activity.TurnContextProtocol` :returns: A task that represents the work queued to execute + :rtype: Awaitable[None] """ return @@ -397,6 +415,7 @@ async def on_installation_update( # pylint: disable=unused-argument :param turn_context: The context object for this turn :type turn_context: :class:`microsoft_agents.activity.TurnContextProtocol` :returns: A task that represents the work queued to execute + :rtype: Awaitable[None] """ if turn_context.activity.action in ("add", "add-upgrade"): return await self.on_installation_update_add(turn_context) @@ -414,6 +433,7 @@ async def on_installation_update_add( # pylint: disable=unused-argument :param turn_context: The context object for this turn :type turn_context: :class:`microsoft_agents.activity.TurnContextProtocol` :returns: A task that represents the work queued to execute + :rtype: Awaitable[None] """ return @@ -427,6 +447,7 @@ async def on_installation_update_remove( # pylint: disable=unused-argument :param turn_context: The context object for this turn :type turn_context: :class:`microsoft_agents.activity.TurnContextProtocol` :returns: A task that represents the work queued to execute + :rtype: Awaitable[None] """ return @@ -440,10 +461,10 @@ async def on_unrecognized_activity_type( # pylint: disable=unused-argument :param turn_context: The context object for this turn :type turn_context: :class:`microsoft_agents.activity.TurnContextProtocol` - :returns: A task that represents the work queued to execute + :rtype: Awaitable[None] - .. remarks:: + .. note:: When the :meth:`on_turn()` method receives an activity that is not a message, conversation update, message reaction, or event activity, it calls this method. """ @@ -494,8 +515,8 @@ async def on_sign_in_invoke( # pylint: disable=unused-argument :param turn_context: The context object for this turn :type turn_context: :class:`microsoft_agents.activity.TurnContextProtocol` - :returns: A task that represents the work queued to execute + :rtype: Awaitable[None] """ raise _InvokeResponseException(HTTPStatus.NOT_IMPLEMENTED) @@ -512,7 +533,7 @@ async def on_adaptive_card_invoke( :type turn_context: :class:`microsoft_agents.activity.TurnContextProtocol` :param invoke_value: A string-typed object from the incoming activity's value. :type invoke_value: :class:`microsoft_agents.activity.AdaptiveCardInvokeValue` - :return: The HealthCheckResponse object + :returns: The HealthCheckResponse object :rtype: :class:`microsoft_agents.activity.AdaptiveCardInvokeResponse` """ raise _InvokeResponseException(HTTPStatus.NOT_IMPLEMENTED) diff --git a/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/app/agent_application.py b/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/app/agent_application.py index ac777846..e0cb6de4 100644 --- a/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/app/agent_application.py +++ b/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/app/agent_application.py @@ -236,7 +236,7 @@ def add_route( Routes are ordered by: is_agentic, is_invoke, rank (lower is higher priority), in that order. :param selector: A function that takes a TurnContext and returns a boolean indicating whether the route should be selected. - :type selector: :class:`microsoft_agents.hosting.core.app._type_defs.RouteSelector` + :type selector: Callable[[:class:`microsoft_agents.hosting.core.turn_context.TurnContext`], bool] :param handler: A function that takes a TurnContext and a TurnState and returns an Awaitable. :type handler: :class:`microsoft_agents.hosting.core.app._type_defs.RouteHandler`[StateT] :param is_invoke: Whether the route is for an invoke activity, defaults to False @@ -245,7 +245,7 @@ def add_route( the selector will include a new check for `context.activity.is_agentic_request()`. :type is_agentic: bool, Optional :param rank: The rank of the route, defaults to RouteRank.DEFAULT - :type rank: :class:`microsoft_agents.hosting.core.app._routes.RouteRank`, Optional + :type rank: :class:`microsoft_agents.hosting.core.app._routes.route_rank.RouteRank`, Optional :param auth_handlers: A list of authentication handler IDs to use for this route, defaults to None :type auth_handlers: Optional[list[str]], Optional :raises ApplicationError: If the selector or handler are not valid. @@ -272,19 +272,21 @@ def activity( **kwargs, ) -> Callable[[RouteHandler[StateT]], RouteHandler[StateT]]: """ - Registers a new activity event listener. This method can be used as either - a decorator or a method. - - ```python - # Use this method as a decorator - @app.activity("event") - async def on_event(context: TurnContext, state: TurnState): - print("hello world!") - return True - ``` - - #### Args: - - `type`: The type of the activity + Register a new activity event listener as either a decorator or a method. + + Example: + .. code-block:: python + + @app.activity("event") + async def on_event(context: TurnContext, state: TurnState): + print("hello world!") + return True + + :param activity_type: Activity type or collection of types that should trigger the handler. + :type activity_type: Union[str, microsoft_agents.activity.ActivityTypes, list[Union[str, microsoft_agents.activity.ActivityTypes]]] + :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:`add_route`. """ def __selector(context: TurnContext): @@ -307,18 +309,21 @@ def message( **kwargs, ) -> Callable[[RouteHandler[StateT]], RouteHandler[StateT]]: """ - Registers a new message activity event listener. This method can be used as either - a decorator or a method. - - ```python - # Use this method as a decorator - @app.message("hi") - async def on_hi_message(context: TurnContext, state: TurnState): - print("hello!") - return True - - #### Args: - - `select`: a string or regex pattern + Register a new message activity event listener as either a decorator or a method. + + Example: + .. code-block:: python + + @app.message("hi") + async def on_hi_message(context: TurnContext, state: TurnState): + print("hello!") + return True + + :param select: Literal text, compiled regex, or list of either used to match the incoming message. + :type select: Union[str, Pattern[str], list[Union[str, Pattern[str]]]] + :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:`add_route`. """ def __selector(context: TurnContext): @@ -349,20 +354,21 @@ def conversation_update( **kwargs, ) -> Callable[[RouteHandler[StateT]], RouteHandler[StateT]]: """ - Registers a new message activity event listener. This method can be used as either - a decorator or a method. + Register a handler for conversation update activities as either a decorator or a method. - ```python - # Use this method as a decorator - @app.conversation_update("channelCreated") - async def on_channel_created(context: TurnContext, state: TurnState): - print("a new channel was created!") - return True + Example: + .. code-block:: python - ``` + @app.conversation_update("channelCreated") + async def on_channel_created(context: TurnContext, state: TurnState): + print("a new channel was created!") + return True - #### Args: - - `type`: a string or regex pattern + :param type: Conversation update category that must match the incoming activity. + :type type: microsoft_agents.activity.ConversationUpdateTypes + :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:`add_route`. """ def __selector(context: TurnContext): @@ -402,19 +408,21 @@ def message_reaction( **kwargs, ) -> Callable[[RouteHandler[StateT]], RouteHandler[StateT]]: """ - Registers a new message activity event listener. This method can be used as either - a decorator or a method. - - ```python - # Use this method as a decorator - @app.message_reaction("reactionsAdded") - async def on_reactions_added(context: TurnContext, state: TurnState): - print("reactions was added!") - return True - ``` - - #### Args: - - `type`: a string or regex pattern + Register a handler for message reaction activities as either a decorator or a method. + + Example: + .. code-block:: python + + @app.message_reaction("reactionsAdded") + async def on_reactions_added(context: TurnContext, state: TurnState): + print("reaction was added!") + return True + + :param type: Reaction category that must match the incoming activity. + :type type: microsoft_agents.activity.MessageReactionTypes + :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:`add_route`. """ def __selector(context: TurnContext): @@ -450,19 +458,21 @@ def message_update( **kwargs, ) -> Callable[[RouteHandler[StateT]], RouteHandler[StateT]]: """ - Registers a new message activity event listener. This method can be used as either - a decorator or a method. - - ```python - # Use this method as a decorator - @app.message_update("editMessage") - async def on_edit_message(context: TurnContext, state: TurnState): - print("message was edited!") - return True - ``` - - #### Args: - - `type`: a string or regex pattern + Register a handler for message update activities as either a decorator or a method. + + Example: + .. code-block:: python + + @app.message_update("editMessage") + async def on_edit_message(context: TurnContext, state: TurnState): + print("message was edited!") + return True + + :param type: Message update category that must match the incoming activity. + :type type: microsoft_agents.activity.MessageUpdateTypes + :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:`add_route`. """ def __selector(context: TurnContext): @@ -510,15 +520,18 @@ def handoff( Callable[[TurnContext, StateT, str], Awaitable[None]], ]: """ - Registers a handler to handoff conversations from one copilot to another. - ```python - # Use this method as a decorator - @app.handoff - async def on_handoff( - context: TurnContext, state: TurnState, continuation: str - ): - print(query) - ``` + Register a handler to hand off conversations from one copilot to another. + + Example: + .. code-block:: python + + @app.handoff + async def on_handoff(context: TurnContext, state: TurnState, continuation: str): + print(continuation) + + :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:`add_route`. """ def __selector(context: TurnContext) -> bool: @@ -555,15 +568,18 @@ def on_sign_in_success( self, func: Callable[[TurnContext, StateT, Optional[str]], Awaitable[None]] ) -> Callable[[TurnContext, StateT, Optional[str]], Awaitable[None]]: """ - Registers a new event listener that will be executed when a user successfully signs in. - - ```python - # Use this method as a decorator - @app.on_sign_in_success - async def sign_in_success(context: TurnContext, state: TurnState): - print("hello world!") - return True - ``` + Register a callback that executes when a user successfully signs in. + + Example: + .. code-block:: python + + @app.on_sign_in_success + async def sign_in_success(context: TurnContext, state: TurnState, connection_id: str | None): + print("sign-in succeeded") + + :param func: Callable that handles the sign-in success event. + :type func: Callable[[TurnContext, StateT, Optional[str]], Awaitable[None]] + :raises ApplicationError: If authorization services are not configured. """ if self._auth: @@ -588,15 +604,18 @@ def on_sign_in_failure( self, func: Callable[[TurnContext, StateT, Optional[str]], Awaitable[None]] ) -> Callable[[TurnContext, StateT, Optional[str]], Awaitable[None]]: """ - Registers a new event listener that will be executed when a user fails to sign in. - - ```python - # Use this method as a decorator - @app.on_sign_in_failure - async def sign_in_failure(context: TurnContext, state: TurnState): - print("hello world!") - return True - ``` + Register a callback that executes when a user fails to sign in. + + Example: + .. code-block:: python + + @app.on_sign_in_failure + async def sign_in_failure(context: TurnContext, state: TurnState, connection_id: str | None): + print("sign-in failed") + + :param func: Callable that handles the sign-in failure event. + :type func: Callable[[TurnContext, StateT, Optional[str]], Awaitable[None]] + :raises ApplicationError: If authorization services are not configured. """ if self._auth: @@ -621,15 +640,17 @@ def error( self, func: Callable[[TurnContext, Exception], Awaitable[None]] ) -> Callable[[TurnContext, Exception], Awaitable[None]]: """ - Registers an error handler that will be called anytime - the app throws an Exception - - ```python - # Use this method as a decorator - @app.error - async def on_error(context: TurnContext, err: Exception): - print(err.message) - ``` + Register an error handler that is invoked whenever the application raises an exception. + + Example: + .. code-block:: python + + @app.error + async def on_error(context: TurnContext, err: Exception): + print(err) + + :param func: Callable executed when an uncaught exception occurs during a turn. + :type func: Callable[[TurnContext, Exception], Awaitable[None]] """ logger.debug(f"Registering the error handler {func.__name__} ") diff --git a/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/channel_adapter.py b/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/channel_adapter.py index facf8fbd..2592d958 100644 --- a/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/channel_adapter.py +++ b/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/channel_adapter.py @@ -42,8 +42,9 @@ async def send_activities( :param context: The context object for the turn. :type context: :class:`microsoft_agents.hosting.core.turn_context.TurnContext` :param activities: The activities to send. - :type activities: list[Activity] - :return: + :type activities: list[microsoft_agents.activity.Activity] + :return: Channel responses produced by the adapter. + :rtype: list[microsoft_agents.activity.ResourceResponse] """ raise NotImplementedError() @@ -56,7 +57,8 @@ async def update_activity(self, context: TurnContext, activity: Activity): :type context: :class:`microsoft_agents.hosting.core.turn_context.TurnContext` :param activity: New replacement activity. :type activity: :class:`microsoft_agents.activity.Activity` - :return: + :return: None + :rtype: None """ raise NotImplementedError() @@ -71,7 +73,8 @@ async def delete_activity( :type context: :class:`microsoft_agents.hosting.core.turn_context.TurnContext` :param reference: Conversation reference for the activity to delete. :type reference: :class:`microsoft_agents.activity.ConversationReference` - :return: + :return: None + :rtype: None """ raise NotImplementedError() @@ -80,7 +83,9 @@ def use(self, middleware): Registers a middleware handler with the adapter. :param middleware: The middleware to register. - :return: + :type middleware: object + :return: The current adapter instance to support fluent calls. + :rtype: ChannelAdapter """ self.middleware_set.use(middleware) return self @@ -97,16 +102,14 @@ async def continue_conversation( to the user. :param agent_id: The application ID of the agent. This parameter is ignored in - single tenant the Adapters (Console, Test, etc) but is critical to the ChannelAdapter - which is multi-tenant aware. + single-tenant adapters (Console, Test, etc.) but is required for multi-tenant adapters. + :type agent_id: str :param reference: A reference to the conversation to continue. :type reference: :class:`microsoft_agents.activity.ConversationReference` :param callback: The method to call for the resulting agent turn. :type callback: Callable[[microsoft_agents.hosting.core.turn_context.TurnContext], Awaitable] - :param claims_identity: A :class:`microsoft_agents.hosting.core.ClaimsIdentity` for the conversation. - :type claims_identity: :class:`microsoft_agents.hosting.core.ClaimsIdentity` - :param audience:A value signifying the recipient of the proactive message. - :type audience: str + :return: Result produced by the adapter pipeline. + :rtype: typing.Any """ context = TurnContext(self, reference.get_continuation_activity()) return await self.run_pipeline(context, callback) @@ -123,7 +126,7 @@ async def continue_conversation_with_claims( Most channels require a user to initiate a conversation with an agent before the agent can send activities to the user. - :param claims_identity: A :class:`microsoft_agents.hosting.core.ClaimsIdentity` for the conversation. + :param claims_identity: A :class:`microsoft_agents.hosting.core.authorization.ClaimsIdentity` for the conversation. :type claims_identity: :class:`microsoft_agents.hosting.core.authorization.ClaimsIdentity` :param continuation_activity: The activity to send. :type continuation_activity: :class:`microsoft_agents.activity.Activity` @@ -131,6 +134,8 @@ async def continue_conversation_with_claims( :type callback: Callable[[microsoft_agents.hosting.core.turn_context.TurnContext], Awaitable] :param audience: A value signifying the recipient of the proactive message. :type audience: str + :return: Result produced by the adapter pipeline. + :rtype: typing.Any """ raise NotImplementedError() @@ -154,16 +159,17 @@ async def create_conversation( :type service_url: str :param audience: A value signifying the recipient of the proactive message. :type audience: str - :param conversation_parameters: The information to use to create the conversation + :param conversation_parameters: The information to use to create the conversation. :type conversation_parameters: :class:`microsoft_agents.activity.ConversationParameters` :param callback: The method to call for the resulting agent turn. :type callback: Callable[[microsoft_agents.hosting.core.turn_context.TurnContext], Awaitable] - :raises: Exception - Not implemented or when the implementation fails. + :raises Exception: Not implemented or when the implementation fails. :return: A task representing the work queued to execute. + :rtype: typing.Any - .. remarks:: + .. note:: To start a conversation, your agent must know its account information and the user's account information on that channel. Most channels only support initiating a direct message (non-group) conversation. @@ -225,7 +231,8 @@ async def run_pipeline( :type context: :class:`microsoft_agents.hosting.core.turn_context.TurnContext` :param callback: A callback method to run at the end of the pipeline. :type callback: Callable[[TurnContext], Awaitable] - :return: + :return: Result produced by the middleware pipeline. + :rtype: typing.Any """ if context is None: raise TypeError(context.__class__.__name__) diff --git a/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/channel_service_adapter.py b/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/channel_service_adapter.py index 7e1cf796..2b4e6969 100644 --- a/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/channel_service_adapter.py +++ b/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/channel_service_adapter.py @@ -358,13 +358,13 @@ async def process_activity( :return: A task that represents the work queued to execute. :rtype: Optional[:class:`microsoft_agents.activity.InvokeResponse`] - .. remarks:: + .. note:: This class processes an activity received by the agents web server. This includes any messages sent from a user and is the method that drives what's often referred to as the agent *reactive messaging* flow. Call this method to reactively send a message to a conversation. - If the task completes successfully, then an :class:`InvokeResponse` is returned; - otherwise, `null` is returned. + If the task completes successfully, then an :class:`microsoft_agents.activity.InvokeResponse` is returned; + otherwise, `None` is returned. """ scopes: list[str] = None outgoing_audience: str = None @@ -493,7 +493,14 @@ def _create_turn_context( return context - def _process_turn_results(self, context: TurnContext) -> InvokeResponse: + def _process_turn_results(self, context: TurnContext) -> Optional[InvokeResponse]: + """Process the results of a turn and return the appropriate response. + + :param context: The turn context + :type context: :class:`microsoft_agents.hosting.core.turn_context.TurnContext` + :return: The invoke response, if applicable + :rtype: Optional[:class:`microsoft_agents.activity.InvokeResponse`] + """ # Handle ExpectedReplies scenarios where all activities have been # buffered and sent back at once in an invoke response. if context.activity.delivery_mode == DeliveryModes.expect_replies: From 2521e1c10471aad9c2632f77d9bdd146bc1e275a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Axel=20Su=C3=A1rez?= Date: Fri, 31 Oct 2025 00:44:22 -0700 Subject: [PATCH 2/2] Proactive sample WIP (#213) * Proactive sample WIP * Proactive echo in teams WIP * Small fixes for proactive with experimental sample --- .../activity/channel_account.py | 12 +- .../activity/conversation_account.py | 8 +- .../core/authorization/claims_identity.py | 6 +- .../hosting/core/channel_service_adapter.py | 5 +- test_samples/app_style/README.md | 44 +++ .../app_style/echo_proactive_agent.py | 285 ++++++++++++++++++ test_samples/app_style/emtpy_agent.py | 27 +- test_samples/app_style/env.TEMPLATE | 12 +- 8 files changed, 384 insertions(+), 15 deletions(-) create mode 100644 test_samples/app_style/README.md create mode 100644 test_samples/app_style/echo_proactive_agent.py diff --git a/libraries/microsoft-agents-activity/microsoft_agents/activity/channel_account.py b/libraries/microsoft-agents-activity/microsoft_agents/activity/channel_account.py index c00e5ad7..cb0b7055 100644 --- a/libraries/microsoft-agents-activity/microsoft_agents/activity/channel_account.py +++ b/libraries/microsoft-agents-activity/microsoft_agents/activity/channel_account.py @@ -1,7 +1,7 @@ # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. -from typing import Any +from typing import Any, Optional from pydantic import ConfigDict from .agents_model import AgentsModel @@ -27,11 +27,11 @@ class ChannelAccount(AgentsModel): id: NonEmptyString = None name: str = None - aad_object_id: NonEmptyString = None - role: NonEmptyString = None - agentic_user_id: NonEmptyString = None - agentic_app_id: NonEmptyString = None - tenant_id: NonEmptyString = None + aad_object_id: Optional[NonEmptyString] = None + role: Optional[NonEmptyString] = None + agentic_user_id: Optional[NonEmptyString] = None + agentic_app_id: Optional[NonEmptyString] = None + tenant_id: Optional[NonEmptyString] = None @property def properties(self) -> dict[str, Any]: diff --git a/libraries/microsoft-agents-activity/microsoft_agents/activity/conversation_account.py b/libraries/microsoft-agents-activity/microsoft_agents/activity/conversation_account.py index 80a8bfa2..93813146 100644 --- a/libraries/microsoft-agents-activity/microsoft_agents/activity/conversation_account.py +++ b/libraries/microsoft-agents-activity/microsoft_agents/activity/conversation_account.py @@ -31,11 +31,11 @@ class ConversationAccount(AgentsModel): :type properties: object """ - is_group: bool = None + is_group: Optional[bool] = None conversation_type: NonEmptyString = None id: NonEmptyString - name: NonEmptyString = None - aad_object_id: NonEmptyString = None - role: NonEmptyString = None + name: Optional[NonEmptyString] = None + aad_object_id: Optional[NonEmptyString] = None + role: Optional[NonEmptyString] = None tenant_id: Optional[NonEmptyString] = None properties: object = None diff --git a/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/authorization/claims_identity.py b/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/authorization/claims_identity.py index af30b409..5cd8f089 100644 --- a/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/authorization/claims_identity.py +++ b/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/authorization/claims_identity.py @@ -78,4 +78,8 @@ def get_token_audience(self) -> str: :return: The token audience. """ - return f"app://{self.get_outgoing_app_id()}" + return ( + f"app://{self.get_outgoing_app_id()}" + if self.is_agent_claim() + else AuthenticationConstants.AGENTS_SDK_SCOPE + ) diff --git a/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/channel_service_adapter.py b/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/channel_service_adapter.py index 2b4e6969..60cf0e79 100644 --- a/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/channel_service_adapter.py +++ b/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/channel_service_adapter.py @@ -232,7 +232,10 @@ async def continue_conversation_with_claims( :type audience: Optional[str] """ return await self.process_proactive( - claims_identity, continuation_activity, audience, callback + claims_identity, + continuation_activity, + audience or claims_identity.get_token_audience(), + callback, ) async def create_conversation( # pylint: disable=arguments-differ diff --git a/test_samples/app_style/README.md b/test_samples/app_style/README.md new file mode 100644 index 00000000..4c79ffb0 --- /dev/null +++ b/test_samples/app_style/README.md @@ -0,0 +1,44 @@ +# App-style samples + +This folder contains end-to-end samples that resemble production “app-style” experiences built on the Microsoft 365 Agents Python SDK. The new proactive messaging sample shows how to start a Microsoft Teams conversation or send a proactive message to an existing one. + +## Proactive messaging sample + +`proactive_messaging_agent.py` hosts two HTTP endpoints: + +- `POST /api/createconversation` – creates a new 1:1 Teams conversation with a user and optionally sends an initial message. +- `POST /api/sendmessage` – sends another proactive message to an existing conversation id. + +### Prerequisites + +1. Python 3.10 or later. +2. Install the Agents Python SDK packages (for example by running `pip install -e libraries/microsoft-agents-*`). +3. A published Copilot Studio agent configured for Teams with application (client) ID, client secret, and tenant ID. + +### Configure environment variables + +1. Copy `env.TEMPLATE` to `.env` if you have not already. +2. Populate the connection settings used to acquire tokens: + - `CONNECTIONS__SERVICE_CONNECTION__SETTINGS__CLIENTID` + - `CONNECTIONS__SERVICE_CONNECTION__SETTINGS__CLIENTSECRET` + - `CONNECTIONS__SERVICE_CONNECTION__SETTINGS__TENANTID` +3. Add the proactive messaging settings from the template (bot id, agent id, tenant id, service URL, etc.). Optionally set `PROACTIVEMESSAGING__USERAADOBJECTID` to provide a default recipient. +4. Leave `TOKENVALIDATION__ENABLED=false` for local testing. Set it to `true` and supply a valid bearer token when calling the APIs if you need auth checks. + +### Run the sample + +```pwsh +python proactive_messaging_agent.py +``` + +The server listens on `http://localhost:5199` by default. Use the following helper commands to exercise the endpoints (replace the sample values with your own IDs): + +```pwsh +# Create a new conversation (returns conversationId) +Invoke-RestMethod -Method POST -Uri "http://localhost:5199/api/createconversation" -ContentType "application/json" -Body (@{ Message = "Hello from proactive sample"; UserAadObjectId = "00000000-0000-0000-0000-000000000123" } | ConvertTo-Json) + +# Send another proactive message +Invoke-RestMethod -Method POST -Uri "http://localhost:5199/api/sendmessage" -ContentType "application/json" -Body (@{ ConversationId = ""; Message = "Second proactive ping" } | ConvertTo-Json) +``` + +When `TOKENVALIDATION__ENABLED` is `true`, add an `Authorization: Bearer ` header to each call. The proactive endpoints will respond with JSON payloads describing success or validation errors. diff --git a/test_samples/app_style/echo_proactive_agent.py b/test_samples/app_style/echo_proactive_agent.py new file mode 100644 index 00000000..ed91ded4 --- /dev/null +++ b/test_samples/app_style/echo_proactive_agent.py @@ -0,0 +1,285 @@ +"""Echo skill sample that mirrors the Copilot Studio EchoSkill agent.""" + +from __future__ import annotations + +import json +import logging +from dataclasses import dataclass +from os import environ, path +from typing import Any, Dict, Optional + +from aiohttp import web +from dotenv import load_dotenv + +from microsoft_agents.activity import ( + load_configuration_from_env, + Activity, + ConversationReference, + EndOfConversationCodes, +) +from microsoft_agents.authentication.msal import MsalConnectionManager +from microsoft_agents.hosting.aiohttp import CloudAdapter, start_agent_process +from microsoft_agents.hosting.core import ( + AgentApplication, + Authorization, + MemoryStorage, + MessageFactory, + TurnContext, + TurnState, +) +from microsoft_agents.hosting.core.authorization import ClaimsIdentity +from microsoft_agents.hosting.core.storage import StoreItem + + +@dataclass +class SendActivityRequest: + """Request payload used to resume conversations proactively.""" + + conversation_id: str + message: str + + @classmethod + def from_dict(cls, payload: Dict[str, Any]) -> "SendActivityRequest": + conversation_id = payload.get("conversationId") or payload.get( + "conversation_id" + ) + if not conversation_id: + raise ValueError("conversationId is required.") + + message = payload.get("message") + if not message: + raise ValueError("message is required.") + + return cls(conversation_id=conversation_id, message=message) + + +@dataclass +class ConversationReferenceRecord(StoreItem): + """Persistent envelope for a conversation reference and associated identity.""" + + claims: dict[str, str] + is_authenticated: bool + authentication_type: Optional[str] + reference: ConversationReference + + @staticmethod + def get_key(conversation_id: str) -> str: + return f"conversationreferences/{conversation_id}" + + @property + def key(self) -> str: + return self.get_key(self.reference.conversation.id) + + @classmethod + def from_context(cls, context: TurnContext) -> "ConversationReferenceRecord": + identity = context.identity or ClaimsIdentity({}, False) + reference = context.activity.get_conversation_reference() + return cls( + claims=dict(identity.claims), + is_authenticated=identity.is_authenticated, + authentication_type=identity.authentication_type, + reference=reference, + ) + + def to_identity(self) -> ClaimsIdentity: + return ClaimsIdentity( + claims=dict(self.claims), + is_authenticated=self.is_authenticated, + authentication_type=self.authentication_type, + ) + + def store_item_to_json(self) -> Dict[str, Any]: + return { + "claims": dict(self.claims), + "is_authenticated": self.is_authenticated, + "authentication_type": self.authentication_type, + "reference": self.reference.model_dump(mode="json"), + } + + @staticmethod + def from_json_to_store_item( + json_data: Dict[str, Any], + ) -> "ConversationReferenceRecord": + reference_payload = json_data.get("reference") + if not reference_payload: + raise ValueError("Conversation reference payload is missing.") + + reference = ConversationReference.model_validate( + reference_payload, strict=False + ) + return ConversationReferenceRecord( + claims=json_data.get("claims", {}), + is_authenticated=json_data.get("is_authenticated", False), + authentication_type=json_data.get("authentication_type"), + reference=reference, + ) + + +load_dotenv(path.join(path.dirname(__file__), ".env")) +agents_sdk_config = load_configuration_from_env(environ) + +storage = MemoryStorage() +connection_manager = MsalConnectionManager(**agents_sdk_config) +adapter = CloudAdapter(connection_manager=connection_manager) +authorization = Authorization(storage, connection_manager, **agents_sdk_config) +AGENT_APP = AgentApplication[TurnState]( + storage=storage, + adapter=adapter, + authorization=authorization, + **agents_sdk_config.get("AGENTAPPLICATION", {}), +) + + +@AGENT_APP.activity("message") +async def on_message(context: TurnContext, state: TurnState) -> None: + text = context.activity.text or "" + if "end" == text: + await context.send_activity("(EchoSkill) Ending conversation...") + end_activity = Activity.create_end_of_conversation_activity() + end_activity.code = EndOfConversationCodes.completed_successfully + await context.send_activity(end_activity) + await state.conversation.delete(context) + conversation = context.activity.conversation + if conversation and conversation.id: + await state.conversation._storage.delete( + [ConversationReferenceRecord.get_key(conversation.id)] + ) + return + + logging.info( + f"(EchoSkill) ConversationReference to save: {context.activity.get_conversation_reference().model_dump(mode='json', exclude_unset=True, by_alias=True)} with message: {text}" + ) + record = ConversationReferenceRecord.from_context(context) + await state.conversation._storage.write({record.key: record}) + + await context.send_activity(MessageFactory.text(f"(EchoSkill): {text}")) + + +class EchoSkillService: + def __init__( + self, + storage: MemoryStorage, + adapter: CloudAdapter, + ) -> None: + self._storage = storage + self._adapter = adapter + + async def send_activity_to_conversation( + self, conversation_id: str, message: str + ) -> bool: + if not conversation_id: + return False + + key = ConversationReferenceRecord.get_key(conversation_id) + items: Dict[str, ConversationReferenceRecord] = await self._storage.read( + [key], target_cls=ConversationReferenceRecord + ) + record = items.get(key) + if not record: + return False + + continuation_activity = record.reference.get_continuation_activity() + + async def _callback(turn_context: TurnContext) -> None: + await turn_context.send_activity(message) + + await self._adapter.continue_conversation_with_claims( + record.to_identity(), continuation_activity, _callback + ) + return True + + +async def _read_optional_json(request: web.Request) -> Dict[str, Any]: + if request.content_length in (0, None): + return {} + try: + return await request.json() + except json.JSONDecodeError: + return {} + + +def create_app() -> web.Application: + """Create and configure the aiohttp application hosting the sample.""" + + load_dotenv(path.join(path.dirname(__file__), ".env")) + + echo_service = EchoSkillService(storage, adapter) + global SERVICE_INSTANCE + SERVICE_INSTANCE = echo_service + + app = web.Application() + app["adapter"] = adapter + app["agent_app"] = AGENT_APP + app["echo_service"] = echo_service + agent_config = connection_manager.get_default_connection_configuration() + if not agent_config: + raise ValueError("SERVICE_CONNECTION settings are missing.") + app["agent_configuration"] = agent_config + + app.router.add_get("/", _handle_root) + app.router.add_post("/api/messages", _agent_entry_point) + app.router.add_post("/api/sendactivity", _handle_send_activity) + + return app + + +async def _handle_root(request: web.Request) -> web.Response: + return web.json_response({"status": "ready", "sample": "echo-skill"}) + + +async def _agent_entry_point(request: web.Request) -> web.Response: + agent_app: AgentApplication = request.app["agent_app"] + adapter: CloudAdapter = request.app["adapter"] + response = await start_agent_process(request, agent_app, adapter) + return response or web.Response(status=202) + + +async def _handle_send_activity(request: web.Request) -> web.Response: + service: EchoSkillService = request.app["echo_service"] + payload = await _read_optional_json(request) + + try: + send_request = SendActivityRequest.from_dict(payload) + except ValueError as exc: + return web.json_response( + { + "status": "Error", + "error": {"code": "Validation", "message": str(exc)}, + }, + status=400, + ) + + success = await service.send_activity_to_conversation( + send_request.conversation_id, send_request.message + ) + + if not success: + return web.json_response( + { + "status": "Error", + "error": { + "code": "NotFound", + "message": "Conversation reference not found.", + }, + }, + status=404, + ) + + return web.json_response( + {"status": "Delivered", "conversationId": send_request.conversation_id}, + status=202, + ) + + +def main() -> None: + logging.basicConfig(level=logging.INFO) + app = create_app() + + host = environ.get("HOST", "localhost") + port = int(environ.get("PORT", "3978")) + + web.run_app(app, host=host, port=port) + + +if __name__ == "__main__": + main() diff --git a/test_samples/app_style/emtpy_agent.py b/test_samples/app_style/emtpy_agent.py index dad36347..c68083c9 100644 --- a/test_samples/app_style/emtpy_agent.py +++ b/test_samples/app_style/emtpy_agent.py @@ -1,6 +1,13 @@ # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. +import logging +from os import environ, path +from dotenv import load_dotenv +from microsoft_agents.activity import load_configuration_from_env +from microsoft_agents.authentication.msal import ( + MsalConnectionManager, +) from microsoft_agents.hosting.core import ( AgentApplication, TurnState, @@ -8,10 +15,23 @@ MemoryStorage, ) from microsoft_agents.hosting.aiohttp import CloudAdapter +from microsoft_agents.hosting.core.app.oauth.authorization import Authorization from shared import start_server -AGENT_APP = AgentApplication[TurnState](storage=MemoryStorage(), adapter=CloudAdapter()) +logging.basicConfig(level=logging.INFO) +load_dotenv(path.join(path.dirname(__file__), ".env")) + +agents_sdk_config = load_configuration_from_env(environ) + +STORAGE = MemoryStorage() +CONNECTION_MANAGER = MsalConnectionManager(**agents_sdk_config) +ADAPTER = CloudAdapter(connection_manager=CONNECTION_MANAGER) +AUTHORIZATION = Authorization(STORAGE, CONNECTION_MANAGER, **agents_sdk_config) + +AGENT_APP = AgentApplication[TurnState]( + storage=STORAGE, adapter=ADAPTER, authorization=AUTHORIZATION, **agents_sdk_config +) async def _help(context: TurnContext, _state: TurnState): @@ -33,6 +53,9 @@ async def on_message(context: TurnContext, _): if __name__ == "__main__": try: - start_server(AGENT_APP, None) + start_server( + agent_application=AGENT_APP, + auth_configuration=CONNECTION_MANAGER.get_default_connection_configuration(), + ) except Exception as error: raise error diff --git a/test_samples/app_style/env.TEMPLATE b/test_samples/app_style/env.TEMPLATE index f9adb9ac..89af6af9 100644 --- a/test_samples/app_style/env.TEMPLATE +++ b/test_samples/app_style/env.TEMPLATE @@ -16,4 +16,14 @@ AGENTAPPLICATION__USERAUTHORIZATION__HANDLERS__MCS__SETTINGS__OBOCONNECTIONNAME= COPILOTSTUDIOAGENT__ENVIRONMENTID=environment-id COPILOTSTUDIOAGENT__SCHEMANAME=schema-name COPILOTSTUDIOAGENT__TENANTID=tenant-id -COPILOTSTUDIOAGENT__AGENTAPPID=agent-app-id \ No newline at end of file +COPILOTSTUDIOAGENT__AGENTAPPID=agent-app-id + +# Proactive messaging sample settings +PROACTIVEMESSAGING__BOTID=28:teams-app-id +PROACTIVEMESSAGING__AGENTID=teams-app-id +PROACTIVEMESSAGING__TENANTID=tenant-id +PROACTIVEMESSAGING__SCOPE=https://api.botframework.com/.default +PROACTIVEMESSAGING__CHANNELID=msteams +PROACTIVEMESSAGING__SERVICEURL=https://smba.trafficmanager.net/teams/ +# Optional default user (if not supplied per request) +PROACTIVEMESSAGING__USERAADOBJECTID=