From 1bfd426b03e24b755e4e83ee8b5d2f8f66d6027a Mon Sep 17 00:00:00 2001 From: Rodrigo Brandao Date: Tue, 16 Jun 2026 08:54:13 -0700 Subject: [PATCH 1/8] Updating typing annotations --- .../microsoft_agents/activity/activity.py | 13 ++++++-- .../activity/channel_adapter_protocol.py | 8 ++--- .../activity/entity/ai_entity.py | 8 ++--- .../activity/teams/conversation_list.py | 5 ++- .../meeting_notification_channel_data.py | 3 +- .../teams/meeting_notification_response.py | 3 +- .../meeting_participants_event_details.py | 3 +- .../activity/teams/message_actions_payload.py | 14 ++++---- .../teams/messaging_extension_action.py | 5 ++- .../teams/messaging_extension_query.py | 6 ++-- .../teams/messaging_extension_result.py | 6 ++-- .../messaging_extension_suggested_action.py | 5 ++- .../activity/teams/o365_connector_card.py | 10 +++--- .../teams/o365_connector_card_action_card.py | 9 +++--- .../o365_connector_card_multichoice_input.py | 4 +-- .../teams/o365_connector_card_open_uri.py | 5 ++- .../teams/o365_connector_card_section.py | 14 ++++---- .../activity/teams/tab_response_cards.py | 3 +- .../activity/teams/tab_suggested_actions.py | 4 +-- .../targeted_meeting_notification_value.py | 7 ++-- .../activity/teams/teams_channel_account.py | 2 +- .../activity/teams/teams_channel_data.py | 5 ++- .../teams/teams_paged_members_result.py | 3 +- .../activity/turn_context_protocol.py | 12 ++++--- .../msal/msal_connection_manager.py | 18 +++++------ .../client/connection_settings.py | 4 +-- .../hosting/core/_oauth/_oauth_flow.py | 2 +- .../hosting/core/app/agent_application.py | 9 +++--- .../hosting/core/app/app_options.py | 4 +-- .../hosting/core/app/input_file.py | 4 +-- .../core/app/state/conversation_state.py | 2 +- .../hosting/core/app/state/state.py | 13 +++----- .../hosting/core/app/state/temp_state.py | 10 +++--- .../hosting/core/app/state/turn_state.py | 6 ++-- .../core/app/streaming/citation_util.py | 6 ++-- .../core/app/streaming/streaming_response.py | 15 ++++----- .../hosting/core/app/typing_indicator.py | 4 +-- .../hosting/core/channel_adapter.py | 14 ++++---- .../hosting/core/channel_service_adapter.py | 4 +-- .../core/client/channels_configuration.py | 12 +++---- .../core/client/configuration_channel_host.py | 4 +-- .../hosting/core/client/http_agent_channel.py | 2 +- .../core/connector/agent_sign_in_base.py | 12 +++---- .../hosting/core/connector/user_token_base.py | 21 ++++++++---- .../core/http/_channel_service_routes.py | 4 +-- .../hosting/core/http/_http_adapter_base.py | 4 +-- .../core/http/_http_request_protocol.py | 6 ++-- .../hosting/core/http/_http_response.py | 4 +-- .../hosting/core/message_factory.py | 32 +++++++++---------- .../hosting/core/state/agent_state.py | 18 +++++------ .../core/state/state_property_accessor.py | 6 ++-- .../hosting/core/storage/memory_storage.py | 2 +- .../hosting/core/storage/storage.py | 19 ++++------- .../core/storage/transcript_file_store.py | 16 +++++----- .../hosting/core/storage/transcript_logger.py | 7 ++-- .../core/storage/transcript_memory_store.py | 4 +-- .../hosting/core/storage/transcript_store.py | 4 +-- .../hosting/core/turn_context.py | 14 +++++--- .../hosting/dialogs/object_path.py | 8 ++--- .../prompts/prompt_validator_context.py | 5 +-- .../hosting/slack/_path_navigator.py | 6 ++-- .../hosting/slack/api/slack_stream.py | 6 ++-- .../hosting/slack/slack_agent_extension.py | 4 +-- .../hosting/teams/teams_activity_handler.py | 10 +++--- .../hosting/teams/teams_agent_extension.py | 4 +-- .../hosting/teams/teams_info.py | 10 +++--- .../storage/blob/blob_storage.py | 6 ++-- .../storage/blob/blob_storage_config.py | 6 ++-- .../storage/cosmos/cosmos_db_storage.py | 11 +++---- .../cosmos/cosmos_db_storage_config.py | 7 ++-- 70 files changed, 269 insertions(+), 277 deletions(-) diff --git a/libraries/microsoft-agents-activity/microsoft_agents/activity/activity.py b/libraries/microsoft-agents-activity/microsoft_agents/activity/activity.py index cad64ed24..d5f960e0a 100644 --- a/libraries/microsoft-agents-activity/microsoft_agents/activity/activity.py +++ b/libraries/microsoft-agents-activity/microsoft_agents/activity/activity.py @@ -497,7 +497,7 @@ def create_message_activity(): """ return Activity(type=ActivityTypes.message) - def create_reply(self, text: str = None, locale: str = None): + def create_reply(self, text: str | None = None, locale: str | None = None): """ Creates a new message activity as a response to this activity. @@ -539,7 +539,11 @@ def create_reply(self, text: str = None, locale: str = None): ) def create_trace( - self, name: str, value: object = None, value_type: str = None, label: str = None + self, + name: str, + value: object = None, + value_type: str | None = None, + label: str | None = None, ): """ Creates a new trace activity based on this activity. @@ -585,7 +589,10 @@ def create_trace( @staticmethod def create_trace_activity( - name: str, value: object = None, value_type: str = None, label: str = None + name: str, + value: object = None, + value_type: str | None = None, + label: str | None = None, ): """ Creates an instance of the :class:`microsoft_agents.activity.Activity` class as a TraceActivity object. diff --git a/libraries/microsoft-agents-activity/microsoft_agents/activity/channel_adapter_protocol.py b/libraries/microsoft-agents-activity/microsoft_agents/activity/channel_adapter_protocol.py index ce07f2481..811d694f1 100644 --- a/libraries/microsoft-agents-activity/microsoft_agents/activity/channel_adapter_protocol.py +++ b/libraries/microsoft-agents-activity/microsoft_agents/activity/channel_adapter_protocol.py @@ -2,7 +2,7 @@ # Licensed under the MIT License. from abc import abstractmethod -from typing import Protocol, List, Callable, Awaitable, Optional +from typing import Protocol, Callable, Awaitable, Optional from .turn_context_protocol import TurnContextProtocol from microsoft_agents.activity import ( @@ -18,8 +18,8 @@ class ChannelAdapterProtocol(Protocol): @abstractmethod async def send_activities( - self, context: TurnContextProtocol, activities: List[Activity] - ) -> List[ResourceResponse]: + self, context: TurnContextProtocol, activities: list[Activity] + ) -> list[ResourceResponse]: pass @abstractmethod @@ -54,7 +54,7 @@ async def continue_conversation_with_claims( claims_identity: dict, continuation_activity: Activity, callback: Callable[[TurnContextProtocol], Awaitable], - audience: str = None, + audience: str | None = None, ): pass diff --git a/libraries/microsoft-agents-activity/microsoft_agents/activity/entity/ai_entity.py b/libraries/microsoft-agents-activity/microsoft_agents/activity/entity/ai_entity.py index 68f1a6bf1..45235092c 100644 --- a/libraries/microsoft-agents-activity/microsoft_agents/activity/entity/ai_entity.py +++ b/libraries/microsoft-agents-activity/microsoft_agents/activity/entity/ai_entity.py @@ -2,7 +2,7 @@ # Licensed under the MIT License. from enum import Enum -from typing import List, Optional, Literal +from typing import Optional, Literal from pydantic import Field from ..agents_model import AgentsModel @@ -79,7 +79,7 @@ class ClientCitationAppearance(AgentsModel, _SchemaMixin): abstract: str = "" encoding_format: Optional[str] = None image: Optional[ClientCitationImage] = None - keywords: Optional[List[str]] = None + keywords: Optional[list[str]] = None usage_info: Optional[SensitivityUsageInfo] = None @@ -107,6 +107,6 @@ class AIEntity(Entity): type: str = "https://schema.org/Message" id: str = "" - additional_type: List[str] = Field(default_factory=lambda: ["AIGeneratedContent"]) - citation: Optional[List[ClientCitation]] = None + additional_type: list[str] = Field(default_factory=lambda: ["AIGeneratedContent"]) + citation: Optional[list[ClientCitation]] = None usage_info: Optional[SensitivityUsageInfo] = None diff --git a/libraries/microsoft-agents-activity/microsoft_agents/activity/teams/conversation_list.py b/libraries/microsoft-agents-activity/microsoft_agents/activity/teams/conversation_list.py index 52ddadf57..f417fa6b9 100644 --- a/libraries/microsoft-agents-activity/microsoft_agents/activity/teams/conversation_list.py +++ b/libraries/microsoft-agents-activity/microsoft_agents/activity/teams/conversation_list.py @@ -2,7 +2,6 @@ # Licensed under the MIT License. from ..agents_model import AgentsModel -from typing import List from .channel_info import ChannelInfo @@ -11,7 +10,7 @@ class ConversationList(AgentsModel): """List of channels under a team. :param conversations: List of ChannelInfo objects. - :type conversations: List[ChannelInfo] + :type conversations: list[ChannelInfo] """ - conversations: List[ChannelInfo] + conversations: list[ChannelInfo] diff --git a/libraries/microsoft-agents-activity/microsoft_agents/activity/teams/meeting_notification_channel_data.py b/libraries/microsoft-agents-activity/microsoft_agents/activity/teams/meeting_notification_channel_data.py index b7aaee69a..1dd3ced65 100644 --- a/libraries/microsoft-agents-activity/microsoft_agents/activity/teams/meeting_notification_channel_data.py +++ b/libraries/microsoft-agents-activity/microsoft_agents/activity/teams/meeting_notification_channel_data.py @@ -2,7 +2,6 @@ # Licensed under the MIT License. from ..agents_model import AgentsModel -from typing import List from .on_behalf_of import OnBehalfOf @@ -13,4 +12,4 @@ class MeetingNotificationChannelData(AgentsModel): :type on_behalf_of_list: list[OnBehalfOf] """ - on_behalf_of_list: List[OnBehalfOf] = None + on_behalf_of_list: list[OnBehalfOf] = None diff --git a/libraries/microsoft-agents-activity/microsoft_agents/activity/teams/meeting_notification_response.py b/libraries/microsoft-agents-activity/microsoft_agents/activity/teams/meeting_notification_response.py index d08dd55e7..3b8b779e4 100644 --- a/libraries/microsoft-agents-activity/microsoft_agents/activity/teams/meeting_notification_response.py +++ b/libraries/microsoft-agents-activity/microsoft_agents/activity/teams/meeting_notification_response.py @@ -2,7 +2,6 @@ # Licensed under the MIT License. from ..agents_model import AgentsModel -from typing import List from .meeting_notification_recipient_failure_info import ( MeetingNotificationRecipientFailureInfo, ) @@ -17,4 +16,4 @@ class MeetingNotificationResponse(AgentsModel): :type recipients_failure_info: list[MeetingNotificationRecipientFailureInfo] """ - recipients_failure_info: List[MeetingNotificationRecipientFailureInfo] = None + recipients_failure_info: list[MeetingNotificationRecipientFailureInfo] = None diff --git a/libraries/microsoft-agents-activity/microsoft_agents/activity/teams/meeting_participants_event_details.py b/libraries/microsoft-agents-activity/microsoft_agents/activity/teams/meeting_participants_event_details.py index 6d55f14fb..e946252a2 100644 --- a/libraries/microsoft-agents-activity/microsoft_agents/activity/teams/meeting_participants_event_details.py +++ b/libraries/microsoft-agents-activity/microsoft_agents/activity/teams/meeting_participants_event_details.py @@ -2,7 +2,6 @@ # Licensed under the MIT License. from ..agents_model import AgentsModel -from typing import List from .teams_meeting_member import TeamsMeetingMember @@ -13,4 +12,4 @@ class MeetingParticipantsEventDetails(AgentsModel): :type members: list[TeamsMeetingMember] """ - members: List[TeamsMeetingMember] = None + members: list[TeamsMeetingMember] = None diff --git a/libraries/microsoft-agents-activity/microsoft_agents/activity/teams/message_actions_payload.py b/libraries/microsoft-agents-activity/microsoft_agents/activity/teams/message_actions_payload.py index e0a07db55..ab45b415c 100644 --- a/libraries/microsoft-agents-activity/microsoft_agents/activity/teams/message_actions_payload.py +++ b/libraries/microsoft-agents-activity/microsoft_agents/activity/teams/message_actions_payload.py @@ -3,7 +3,7 @@ from pydantic import Field from ..agents_model import AgentsModel -from typing import Annotated, List +from typing import Annotated from .message_actions_payload_from import MessageActionsPayloadFrom from .message_actions_payload_body import MessageActionsPayloadBody @@ -44,11 +44,11 @@ class MessageActionsPayload(AgentsModel): :param attachment_layout: How the attachment(s) are displayed in the message. :type attachment_layout: str :param attachments: Attachments in the message - card, image, file, etc. - :type attachments: List[MessageActionsPayloadAttachment] + :type attachments: list[MessageActionsPayloadAttachment] :param mentions: List of entities mentioned in the message. - :type mentions: List[MessageActionsPayloadMention] + :type mentions: list[MessageActionsPayloadMention] :param reactions: Reactions for the message. - :type reactions: List[MessageActionsPayloadReaction] + :type reactions: list[MessageActionsPayloadReaction] """ id: str = None @@ -65,6 +65,6 @@ class MessageActionsPayload(AgentsModel): from_property: MessageActionsPayloadFrom = None body: MessageActionsPayloadBody = None attachment_layout: str = None - attachments: List[MessageActionsPayloadAttachment] = None - mentions: List[MessageActionsPayloadMention] = None - reactions: List[MessageActionsPayloadReaction] = None + attachments: list[MessageActionsPayloadAttachment] = None + mentions: list[MessageActionsPayloadMention] = None + reactions: list[MessageActionsPayloadReaction] = None diff --git a/libraries/microsoft-agents-activity/microsoft_agents/activity/teams/messaging_extension_action.py b/libraries/microsoft-agents-activity/microsoft_agents/activity/teams/messaging_extension_action.py index 2e47c7d36..dbd828601 100644 --- a/libraries/microsoft-agents-activity/microsoft_agents/activity/teams/messaging_extension_action.py +++ b/libraries/microsoft-agents-activity/microsoft_agents/activity/teams/messaging_extension_action.py @@ -2,7 +2,6 @@ # Licensed under the MIT License. from ..agents_model import AgentsModel -from typing import Any, List from .task_module_request_context import TaskModuleRequestContext from .message_actions_payload import MessageActionsPayload @@ -23,7 +22,7 @@ class MessagingExtensionAction(AgentsModel): :param bot_message_preview_action: Bot message preview action taken by user. Possible values include: 'edit', 'send' :type bot_message_preview_action: str :param bot_activity_preview: List of bot activity previews. - :type bot_activity_preview: List[Activity] + :type bot_activity_preview: list[Activity] :param message_payload: Message content sent as part of the command request. :type message_payload: MessageActionsPayload """ @@ -33,5 +32,5 @@ class MessagingExtensionAction(AgentsModel): command_id: str = None command_context: str = None bot_message_preview_action: str = None - bot_activity_preview: List[Activity] = None + bot_activity_preview: list[Activity] = None message_payload: MessageActionsPayload = None diff --git a/libraries/microsoft-agents-activity/microsoft_agents/activity/teams/messaging_extension_query.py b/libraries/microsoft-agents-activity/microsoft_agents/activity/teams/messaging_extension_query.py index c784ec9c8..7ebd6b745 100644 --- a/libraries/microsoft-agents-activity/microsoft_agents/activity/teams/messaging_extension_query.py +++ b/libraries/microsoft-agents-activity/microsoft_agents/activity/teams/messaging_extension_query.py @@ -2,7 +2,7 @@ # Licensed under the MIT License. from ..agents_model import AgentsModel -from typing import List, Optional +from typing import Optional from .messaging_extension_parameter import MessagingExtensionParameter from .messaging_extension_query_options import MessagingExtensionQueryOptions @@ -14,7 +14,7 @@ class MessagingExtensionQuery(AgentsModel): :param command_id: Id of the command assigned by Bot :type command_id: str :param parameters: Parameters for the query - :type parameters: List["MessagingExtensionParameter"] + :type parameters: list["MessagingExtensionParameter"] :param query_options: Query options for the extension :type query_options: Optional["MessagingExtensionQueryOptions"] :param state: State parameter passed back to the bot after authentication/configuration flow @@ -22,6 +22,6 @@ class MessagingExtensionQuery(AgentsModel): """ command_id: str = None - parameters: List[MessagingExtensionParameter] = None + parameters: list[MessagingExtensionParameter] = None query_options: Optional[MessagingExtensionQueryOptions] = None state: str = None diff --git a/libraries/microsoft-agents-activity/microsoft_agents/activity/teams/messaging_extension_result.py b/libraries/microsoft-agents-activity/microsoft_agents/activity/teams/messaging_extension_result.py index 24f4d8568..ab8c32caf 100644 --- a/libraries/microsoft-agents-activity/microsoft_agents/activity/teams/messaging_extension_result.py +++ b/libraries/microsoft-agents-activity/microsoft_agents/activity/teams/messaging_extension_result.py @@ -2,7 +2,7 @@ # Licensed under the MIT License. from ..agents_model import AgentsModel -from typing import List, Optional +from typing import Optional from .messaging_extension_attachment import MessagingExtensionAttachment from .messaging_extension_suggested_action import MessagingExtensionSuggestedAction @@ -17,7 +17,7 @@ class MessagingExtensionResult(AgentsModel): :param type: The type of the result. Possible values include: 'result', 'auth', 'config', 'message', 'botMessagePreview' :type type: str :param attachments: (Only when type is result) Attachments - :type attachments: List["MessagingExtensionAttachment"] + :type attachments: list["MessagingExtensionAttachment"] :param suggested_actions: Suggested actions for the result. :type suggested_actions: Optional["MessagingExtensionSuggestedAction"] :param text: (Only when type is message) Text @@ -28,7 +28,7 @@ class MessagingExtensionResult(AgentsModel): attachment_layout: str = None type: str = None - attachments: List[MessagingExtensionAttachment] = None + attachments: list[MessagingExtensionAttachment] = None suggested_actions: Optional[MessagingExtensionSuggestedAction] = None text: Optional[str] = None activity_preview: Optional["Activity"] = None diff --git a/libraries/microsoft-agents-activity/microsoft_agents/activity/teams/messaging_extension_suggested_action.py b/libraries/microsoft-agents-activity/microsoft_agents/activity/teams/messaging_extension_suggested_action.py index 6829cccc4..fac981fa2 100644 --- a/libraries/microsoft-agents-activity/microsoft_agents/activity/teams/messaging_extension_suggested_action.py +++ b/libraries/microsoft-agents-activity/microsoft_agents/activity/teams/messaging_extension_suggested_action.py @@ -2,7 +2,6 @@ # Licensed under the MIT License. from ..agents_model import AgentsModel -from typing import List from ..card_action import CardAction @@ -11,7 +10,7 @@ class MessagingExtensionSuggestedAction(AgentsModel): """Messaging extension suggested actions. :param actions: List of suggested actions. - :type actions: List["CardAction"] + :type actions: list["CardAction"] """ - actions: List[CardAction] = None + actions: list[CardAction] = None diff --git a/libraries/microsoft-agents-activity/microsoft_agents/activity/teams/o365_connector_card.py b/libraries/microsoft-agents-activity/microsoft_agents/activity/teams/o365_connector_card.py index afa2ffedd..e36e3e9be 100644 --- a/libraries/microsoft-agents-activity/microsoft_agents/activity/teams/o365_connector_card.py +++ b/libraries/microsoft-agents-activity/microsoft_agents/activity/teams/o365_connector_card.py @@ -2,7 +2,7 @@ # Licensed under the MIT License. from ..agents_model import AgentsModel -from typing import List, Optional +from typing import Optional from .o365_connector_card_section import O365ConnectorCardSection from .o365_connector_card_action_base import O365ConnectorCardActionBase @@ -19,14 +19,14 @@ class O365ConnectorCard(AgentsModel): :param theme_color: Theme color for the card :type theme_color: Optional[str] :param sections: Set of sections for the current card - :type sections: Optional[List["O365ConnectorCardSection"]] + :type sections: Optional[list["O365ConnectorCardSection"]] :param potential_action: Set of actions for the current card - :type potential_action: Optional[List["O365ConnectorCardActionBase"]] + :type potential_action: Optional[list["O365ConnectorCardActionBase"]] """ title: str = None text: Optional[str] = None summary: Optional[str] = None theme_color: Optional[str] = None - sections: Optional[List[O365ConnectorCardSection]] = None - potential_action: Optional[List[O365ConnectorCardActionBase]] = None + sections: Optional[list[O365ConnectorCardSection]] = None + potential_action: Optional[list[O365ConnectorCardActionBase]] = None diff --git a/libraries/microsoft-agents-activity/microsoft_agents/activity/teams/o365_connector_card_action_card.py b/libraries/microsoft-agents-activity/microsoft_agents/activity/teams/o365_connector_card_action_card.py index a72528aad..3dc6f045f 100644 --- a/libraries/microsoft-agents-activity/microsoft_agents/activity/teams/o365_connector_card_action_card.py +++ b/libraries/microsoft-agents-activity/microsoft_agents/activity/teams/o365_connector_card_action_card.py @@ -3,7 +3,6 @@ from pydantic import Field from ..agents_model import AgentsModel -from typing import List from .o365_connector_card_input_base import O365ConnectorCardInputBase from .o365_connector_card_action_base import O365ConnectorCardActionBase @@ -18,13 +17,13 @@ class O365ConnectorCardActionCard(AgentsModel): :param id: Action Id :type id: str :param inputs: Set of inputs contained in this ActionCard - :type inputs: List["O365ConnectorCardInputBase"] + :type inputs: list["O365ConnectorCardInputBase"] :param actions: Set of actions contained in this ActionCard - :type actions: List["O365ConnectorCardActionBase"] + :type actions: list["O365ConnectorCardActionBase"] """ type: str = Field(None, alias="@type") name: str = None id: str = Field(None, alias="@id") - inputs: List[O365ConnectorCardInputBase] = None - actions: List[O365ConnectorCardActionBase] = None + inputs: list[O365ConnectorCardInputBase] = None + actions: list[O365ConnectorCardActionBase] = None diff --git a/libraries/microsoft-agents-activity/microsoft_agents/activity/teams/o365_connector_card_multichoice_input.py b/libraries/microsoft-agents-activity/microsoft_agents/activity/teams/o365_connector_card_multichoice_input.py index efb41ead9..bbbf23440 100644 --- a/libraries/microsoft-agents-activity/microsoft_agents/activity/teams/o365_connector_card_multichoice_input.py +++ b/libraries/microsoft-agents-activity/microsoft_agents/activity/teams/o365_connector_card_multichoice_input.py @@ -23,7 +23,7 @@ class O365ConnectorCardMultichoiceInput(AgentsModel): :param value: Default value for this input field :type value: Optional[str] :param choices: Set of choices for this input field. - :type choices: List["O365ConnectorCardMultichoiceInputChoice"] + :type choices: list["O365ConnectorCardMultichoiceInputChoice"] :param style: Choice style. Possible values include: 'compact', 'expanded' :type style: Optional[str] :param is_multi_select: Define if this input field allows multiple selections. Default value is false. @@ -35,6 +35,6 @@ class O365ConnectorCardMultichoiceInput(AgentsModel): is_required: Optional[bool] = None title: Optional[str] = None value: Optional[str] = None - choices: List[O365ConnectorCardMultichoiceInputChoice] = None + choices: list[O365ConnectorCardMultichoiceInputChoice] = None style: Optional[str] = None is_multi_select: Optional[bool] = None diff --git a/libraries/microsoft-agents-activity/microsoft_agents/activity/teams/o365_connector_card_open_uri.py b/libraries/microsoft-agents-activity/microsoft_agents/activity/teams/o365_connector_card_open_uri.py index 7d466a017..bb09af084 100644 --- a/libraries/microsoft-agents-activity/microsoft_agents/activity/teams/o365_connector_card_open_uri.py +++ b/libraries/microsoft-agents-activity/microsoft_agents/activity/teams/o365_connector_card_open_uri.py @@ -3,7 +3,6 @@ from pydantic import Field from ..agents_model import AgentsModel -from typing import List from .o365_connector_card_open_uri_target import O365ConnectorCardOpenUriTarget @@ -17,10 +16,10 @@ class O365ConnectorCardOpenUri(AgentsModel): :param id: Id of the OpenUri action. :type id: str :param targets: List of targets for the OpenUri action. - :type targets: List["O365ConnectorCardOpenUriTarget"] + :type targets: list["O365ConnectorCardOpenUriTarget"] """ type: str = Field(None, alias="@type") name: str = None id: str = Field(None, alias="@id") - targets: List[O365ConnectorCardOpenUriTarget] = None + targets: list[O365ConnectorCardOpenUriTarget] = None diff --git a/libraries/microsoft-agents-activity/microsoft_agents/activity/teams/o365_connector_card_section.py b/libraries/microsoft-agents-activity/microsoft_agents/activity/teams/o365_connector_card_section.py index 6bb0d700b..55496a5e8 100644 --- a/libraries/microsoft-agents-activity/microsoft_agents/activity/teams/o365_connector_card_section.py +++ b/libraries/microsoft-agents-activity/microsoft_agents/activity/teams/o365_connector_card_section.py @@ -2,7 +2,7 @@ # Licensed under the MIT License. from ..agents_model import AgentsModel -from typing import List, Optional +from typing import Optional from .o365_connector_card_fact import O365ConnectorCardFact from .o365_connector_card_image import O365ConnectorCardImage from .o365_connector_card_action_base import O365ConnectorCardActionBase @@ -24,11 +24,11 @@ class O365ConnectorCardSection(AgentsModel): :param activity_text: Activity text. :type activity_text: Optional[str] :param facts: List of facts for the section. - :type facts: Optional[List["O365ConnectorCardFact"]] + :type facts: Optional[list["O365ConnectorCardFact"]] :param images: List of images for the section. - :type images: Optional[List["O365ConnectorCardImage"]] + :type images: Optional[list["O365ConnectorCardImage"]] :param potential_action: List of actions for the section. - :type potential_action: Optional[List["O365ConnectorCardActionBase"]] + :type potential_action: Optional[list["O365ConnectorCardActionBase"]] """ title: Optional[str] = None @@ -37,6 +37,6 @@ class O365ConnectorCardSection(AgentsModel): activity_subtitle: Optional[str] = None activity_image: Optional[str] = None activity_text: Optional[str] = None - facts: Optional[List[O365ConnectorCardFact]] = None - images: Optional[List[O365ConnectorCardImage]] = None - potential_action: Optional[List[O365ConnectorCardActionBase]] = None + facts: Optional[list[O365ConnectorCardFact]] = None + images: Optional[list[O365ConnectorCardImage]] = None + potential_action: Optional[list[O365ConnectorCardActionBase]] = None diff --git a/libraries/microsoft-agents-activity/microsoft_agents/activity/teams/tab_response_cards.py b/libraries/microsoft-agents-activity/microsoft_agents/activity/teams/tab_response_cards.py index e5afbbf11..8cdceb29b 100644 --- a/libraries/microsoft-agents-activity/microsoft_agents/activity/teams/tab_response_cards.py +++ b/libraries/microsoft-agents-activity/microsoft_agents/activity/teams/tab_response_cards.py @@ -2,7 +2,6 @@ # Licensed under the MIT License.from ..agents_model import AgentsModel from ..agents_model import AgentsModel -from typing import List from .tab_response_card import TabResponseCard @@ -14,4 +13,4 @@ class TabResponseCards(AgentsModel): :type cards: list[TabResponseCard] """ - cards: List[TabResponseCard] = None + cards: list[TabResponseCard] = None diff --git a/libraries/microsoft-agents-activity/microsoft_agents/activity/teams/tab_suggested_actions.py b/libraries/microsoft-agents-activity/microsoft_agents/activity/teams/tab_suggested_actions.py index aa285a440..0f45be211 100644 --- a/libraries/microsoft-agents-activity/microsoft_agents/activity/teams/tab_suggested_actions.py +++ b/libraries/microsoft-agents-activity/microsoft_agents/activity/teams/tab_suggested_actions.py @@ -2,8 +2,6 @@ # Licensed under the MIT License. from ..agents_model import AgentsModel -from typing import List - from ..card_action import CardAction @@ -14,4 +12,4 @@ class TabSuggestedActions(AgentsModel): :type actions: list[CardAction] """ - actions: List[CardAction] = None + actions: list[CardAction] = None diff --git a/libraries/microsoft-agents-activity/microsoft_agents/activity/teams/targeted_meeting_notification_value.py b/libraries/microsoft-agents-activity/microsoft_agents/activity/teams/targeted_meeting_notification_value.py index 0ba96d61e..65be92eb1 100644 --- a/libraries/microsoft-agents-activity/microsoft_agents/activity/teams/targeted_meeting_notification_value.py +++ b/libraries/microsoft-agents-activity/microsoft_agents/activity/teams/targeted_meeting_notification_value.py @@ -2,7 +2,6 @@ # Licensed under the MIT License. from ..agents_model import AgentsModel -from typing import List from .surface import Surface @@ -10,10 +9,10 @@ class TargetedMeetingNotificationValue(AgentsModel): """Specifies the value for targeted meeting notifications. :param recipients: List of recipient MRIs for the notification. - :type recipients: List[str] + :type recipients: list[str] :param message: The message content of the notification. :type message: str """ - recipients: List[str] = None - surfaces: List[Surface] = None + recipients: list[str] = None + surfaces: list[Surface] = None 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 da810e1bb..0f06fa120 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 @@ -39,6 +39,6 @@ class TeamsChannelAccount(AgentsModel): user_role: str = None @property - def properties(self) -> dict[str, Any]: + def properties(self) -> dict[str, Any] | None: """Returns the set of properties that are not None.""" return self.model_extra diff --git a/libraries/microsoft-agents-activity/microsoft_agents/activity/teams/teams_channel_data.py b/libraries/microsoft-agents-activity/microsoft_agents/activity/teams/teams_channel_data.py index 14fb5cdc2..16e8dffe6 100644 --- a/libraries/microsoft-agents-activity/microsoft_agents/activity/teams/teams_channel_data.py +++ b/libraries/microsoft-agents-activity/microsoft_agents/activity/teams/teams_channel_data.py @@ -2,7 +2,6 @@ # Licensed under the MIT License. from ..agents_model import AgentsModel -from typing import List from .channel_info import ChannelInfo from .team_info import TeamInfo from .notification_info import NotificationInfo @@ -30,7 +29,7 @@ class TeamsChannelData(AgentsModel): :param settings: Information about the settings in which the message was sent :type settings: TeamsChannelDataSettings :param on_behalf_of: The OnBehalfOf list for user attribution - :type on_behalf_of: List[OnBehalfOf] + :type on_behalf_of: list[OnBehalfOf] """ channel: ChannelInfo = None @@ -40,4 +39,4 @@ class TeamsChannelData(AgentsModel): tenant: TenantInfo = None meeting: TeamsMeetingInfo = None settings: TeamsChannelDataSettings = None - on_behalf_of: List[OnBehalfOf] = None + on_behalf_of: list[OnBehalfOf] = None diff --git a/libraries/microsoft-agents-activity/microsoft_agents/activity/teams/teams_paged_members_result.py b/libraries/microsoft-agents-activity/microsoft_agents/activity/teams/teams_paged_members_result.py index b523376a6..1c4fed5a3 100644 --- a/libraries/microsoft-agents-activity/microsoft_agents/activity/teams/teams_paged_members_result.py +++ b/libraries/microsoft-agents-activity/microsoft_agents/activity/teams/teams_paged_members_result.py @@ -2,7 +2,6 @@ # Licensed under the MIT License. from ..agents_model import AgentsModel -from typing import List from .teams_channel_account import TeamsChannelAccount @@ -16,4 +15,4 @@ class TeamsPagedMembersResult(AgentsModel): """ continuation_token: str = None - members: List[TeamsChannelAccount] = None + members: list[TeamsChannelAccount] = None diff --git a/libraries/microsoft-agents-activity/microsoft_agents/activity/turn_context_protocol.py b/libraries/microsoft-agents-activity/microsoft_agents/activity/turn_context_protocol.py index 945cbc71a..e9e613f00 100644 --- a/libraries/microsoft-agents-activity/microsoft_agents/activity/turn_context_protocol.py +++ b/libraries/microsoft-agents-activity/microsoft_agents/activity/turn_context_protocol.py @@ -3,7 +3,7 @@ from __future__ import annotations -from typing import Protocol, List, Callable, Optional, Generic, TypeVar +from typing import Protocol, Callable, Optional, Generic, TypeVar from abc import abstractmethod from microsoft_agents.activity import ( @@ -35,8 +35,8 @@ async def send_activity( @abstractmethod async def send_activities( - self, activities: List[Activity] - ) -> List[ResourceResponse]: + self, activities: list[Activity] + ) -> list[ResourceResponse]: pass @abstractmethod @@ -63,6 +63,10 @@ def on_delete_activity(self, handler: Callable) -> "TurnContextProtocol": @abstractmethod async def send_trace_activity( - self, name: str, value: object = None, value_type: str = None, label: str = None + self, + name: str, + value: object = None, + value_type: str | None = None, + label: str | None = None, ) -> ResourceResponse: pass diff --git a/libraries/microsoft-agents-authentication-msal/microsoft_agents/authentication/msal/msal_connection_manager.py b/libraries/microsoft-agents-authentication-msal/microsoft_agents/authentication/msal/msal_connection_manager.py index a1424014e..7ee323918 100644 --- a/libraries/microsoft-agents-authentication-msal/microsoft_agents/authentication/msal/msal_connection_manager.py +++ b/libraries/microsoft-agents-authentication-msal/microsoft_agents/authentication/msal/msal_connection_manager.py @@ -2,7 +2,7 @@ # Licensed under the MIT License. import re -from typing import Dict, List, Optional +from typing import Dict, Optional from microsoft_agents.hosting.core import ( AgentAuthConfiguration, AccessTokenProviderBase, @@ -14,27 +14,27 @@ class MsalConnectionManager(Connections): - _connections: Dict[str, MsalAuth] - _connections_map: List[Dict[str, str]] + _connections: dict[str, MsalAuth] + _connections_map: list[dict[str, str]] _service_connection_configuration: AgentAuthConfiguration def __init__( self, - connections_configurations: Optional[Dict[str, AgentAuthConfiguration]] = None, - connections_map: Optional[List[Dict[str, str]]] = None, + connections_configurations: Optional[dict[str, AgentAuthConfiguration]] = None, + connections_map: Optional[list[dict[str, str]]] = None, **kwargs, ): """ Initialize the MSAL connection manager. :arg connections_configurations: A dictionary of connection configurations. - :type connections_configurations: Dict[str, :class:`microsoft_agents.hosting.core.AgentAuthConfiguration`] + :type connections_configurations: dict[str, :class:`microsoft_agents.hosting.core.AgentAuthConfiguration`] :arg connections_map: A list of connection mappings. - :type connections_map: List[Dict[str, str]] + :type connections_map: list[dict[str, str]] :raises ValueError: If no service connection configuration is provided. """ - self._connections: Dict[str, MsalAuth] = {} + self._connections: dict[str, MsalAuth] = {} self._connections_map = connections_map or kwargs.get("CONNECTIONSMAP", {}) self._config_map: dict[str, AgentAuthConfiguration] = {} @@ -46,7 +46,7 @@ def __init__( self._connections[connection_name] = MsalAuth(agent_auth_config) self._config_map[connection_name] = agent_auth_config else: - raw_configurations: Dict[str, Dict] = kwargs.get("CONNECTIONS", {}) + raw_configurations: dict[str, dict] = kwargs.get("CONNECTIONS", {}) for connection_name, connection_settings in raw_configurations.items(): parsed_configuration = AgentAuthConfiguration( **connection_settings.get("SETTINGS", {}) diff --git a/libraries/microsoft-agents-copilotstudio-client/microsoft_agents/copilotstudio/client/connection_settings.py b/libraries/microsoft-agents-copilotstudio-client/microsoft_agents/copilotstudio/client/connection_settings.py index 96909a10e..90bec29fc 100644 --- a/libraries/microsoft-agents-copilotstudio-client/microsoft_agents/copilotstudio/client/connection_settings.py +++ b/libraries/microsoft-agents-copilotstudio-client/microsoft_agents/copilotstudio/client/connection_settings.py @@ -2,7 +2,7 @@ # Licensed under the MIT License. from os import environ -from typing import Dict, Optional, Any +from typing import Optional, Any from .direct_to_engine_connection_settings_protocol import ( DirectToEngineConnectionSettingsProtocol, ) @@ -67,7 +67,7 @@ def populate_from_environment( direct_connect_url: Optional[str] = None, use_experimental_endpoint: Optional[bool] = None, enable_diagnostics: Optional[bool] = None, - ) -> Dict[str, Any]: + ) -> dict[str, Any]: """ Populate connection settings from environment variables. diff --git a/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/_oauth/_oauth_flow.py b/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/_oauth/_oauth_flow.py index b3c0bc110..6100c4c10 100644 --- a/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/_oauth/_oauth_flow.py +++ b/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/_oauth/_oauth_flow.py @@ -107,7 +107,7 @@ def __init__( def flow_state(self) -> _FlowState: return self._flow_state.model_copy() - async def get_user_token(self, magic_code: str = None) -> TokenResponse: + async def get_user_token(self, magic_code: str | None = None) -> TokenResponse: """Get the user token based on the context. Args: 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 f46d699c8..1b76103ba 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 @@ -18,7 +18,6 @@ Optional, Pattern, TypeVar, - Union, cast, ) @@ -289,7 +288,7 @@ def add_route( def activity( self, - activity_type: Union[str, ActivityTypes, list[Union[str, ActivityTypes]]], + activity_type: str | ActivityTypes | list[str | ActivityTypes], *, auth_handlers: Optional[list[str]] = None, **kwargs, @@ -306,7 +305,7 @@ async def on_event(context: TurnContext, state: TurnState): 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]]] + :type activity_type: str | microsoft_agents.activity.ActivityTypes | list[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:`microsoft_agents.hosting.core.AgentApplication.add_route`. @@ -326,7 +325,7 @@ def __call(func: RouteHandler[StateT]) -> RouteHandler[StateT]: def message( self, - select: Union[str, Pattern[str], list[Union[str, Pattern[str]]]], + select: str | Pattern[str] | list[str | Pattern[str]], *, auth_handlers: Optional[list[str]] = None, **kwargs, @@ -343,7 +342,7 @@ async def on_hi_message(context: TurnContext, state: TurnState): 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]]]] + :type select: str | Pattern[str] | list[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:`microsoft_agents.hosting.core.AgentApplication.add_route`. diff --git a/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/app/app_options.py b/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/app/app_options.py index 376548b79..27a2013f7 100644 --- a/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/app/app_options.py +++ b/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/app/app_options.py @@ -7,7 +7,7 @@ from dataclasses import dataclass, field from logging import Logger -from typing import Callable, List, Optional +from typing import Callable, Optional from microsoft_agents.hosting.core.app.oauth import AuthHandler from microsoft_agents.hosting.core.storage import Storage @@ -81,7 +81,7 @@ class ApplicationOptions: will mark the bot's process as idle and shut it down. """ - file_downloaders: List[InputFileDownloader] = field(default_factory=list) + file_downloaders: list[InputFileDownloader] = field(default_factory=list) """ Optional. Array of input file download plugins to use. """ diff --git a/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/app/input_file.py b/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/app/input_file.py index 4e44627c3..1a6589d93 100644 --- a/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/app/input_file.py +++ b/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/app/input_file.py @@ -7,7 +7,7 @@ from abc import ABC, abstractmethod from dataclasses import dataclass -from typing import List, Optional +from typing import Optional from microsoft_agents.hosting.core import TurnContext @@ -38,7 +38,7 @@ class InputFileDownloader(ABC): """ @abstractmethod - async def download_files(self, context: TurnContext) -> List[InputFile]: + async def download_files(self, context: TurnContext) -> list[InputFile]: """ Download any files referenced by the incoming activity for the current turn. diff --git a/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/app/state/conversation_state.py b/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/app/state/conversation_state.py index d7436ab36..4287862e9 100644 --- a/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/app/state/conversation_state.py +++ b/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/app/state/conversation_state.py @@ -33,7 +33,7 @@ def __init__(self, storage: Storage) -> None: super().__init__(storage=storage, context_service_key=self.CONTEXT_SERVICE_KEY) def get_storage_key( - self, turn_context: TurnContext, *, target_cls: Type[StoreItem] = None + self, turn_context: TurnContext, *, target_cls: Type[StoreItem] | None = None ): channel_id = turn_context.activity.channel_id if not channel_id: diff --git a/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/app/state/state.py b/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/app/state/state.py index ce4c70646..0b9987a71 100644 --- a/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/app/state/state.py +++ b/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/app/state/state.py @@ -6,10 +6,9 @@ from __future__ import annotations import logging -import json from abc import ABC, abstractmethod from copy import deepcopy -from typing import Any, Callable, List, Optional, Type, TypeVar, Union, overload +from typing import Any, Callable, Optional, Type, TypeVar, overload from microsoft_agents.hosting.core.state.state_property_accessor import ( StatePropertyAccessor as _StatePropertyAccessor, @@ -32,7 +31,7 @@ def state(_cls: Type[T]) -> Type[T]: ... def state( _cls: Optional[Type[T]] = None, -) -> Union[Callable[[Type[T]], Type[T]], Type[T]]: +) -> Callable[[Type[T]], Type[T]] | Type[T]: """ @state\n class Example(State): @@ -71,7 +70,7 @@ class State(dict[str, StoreItem], ABC): The Storage Key """ - __deleted__: List[str] + __deleted__: list[str] """ Deleted Keys """ @@ -206,9 +205,7 @@ def __init__(self, state: State, name: str) -> None: async def get( self, turn_context: TurnContext, - default_value_or_factory: Optional[ - Union[Any, Callable[[], Optional[Any]]] - ] = None, + default_value_or_factory: Optional[Any | Callable[[], Optional[Any]]] = None, ) -> Optional[Any]: """ Get the property value from the state. @@ -216,7 +213,7 @@ async def get( :param turn_context: The turn context. :type turn_context: :class:`microsoft_agents.hosting.core.turn_context.TurnContext` :param default_value_or_factory: Default value or factory function to use if property doesn't exist. - :type default_value_or_factory: Optional[Union[Any, Callable[[], Optional[Any]]]] + :type default_value_or_factory: Optional[Any | Callable[[], Optional[Any]]] :return: The property value or default value if not found. :rtype: Optional[Any] """ diff --git a/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/app/state/temp_state.py b/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/app/state/temp_state.py index cb954c721..03e22379c 100644 --- a/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/app/state/temp_state.py +++ b/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/app/state/temp_state.py @@ -5,9 +5,7 @@ from __future__ import annotations -from typing import Dict, List, Optional, TypeVar, Callable, Any, Generic - -from microsoft_agents.hosting.core.storage import Storage +from typing import Optional, TypeVar, Callable, Any from microsoft_agents.hosting.core.turn_context import TurnContext from microsoft_agents.hosting.core.app.input_file import InputFile @@ -32,7 +30,7 @@ class TempState(AgentState): def __init__(self): super().__init__(None, context_service_key=self.SCOPE_NAME) - self._state: Dict[str, Any] = {} + self._state: dict[str, Any] = {} @property def name(self) -> str: @@ -40,12 +38,12 @@ def name(self) -> str: return self.SCOPE_NAME @property - def input_files(self) -> List[InputFile]: + def input_files(self) -> list[InputFile]: """Downloaded files included in the Activity""" return self.get_value(self.INPUT_FILES_KEY, lambda: []) @input_files.setter - def input_files(self, value: List[InputFile]) -> None: + def input_files(self, value: list[InputFile]) -> None: self.set_value(self.INPUT_FILES_KEY, value) def clear(self, turn_context: TurnContext) -> None: diff --git a/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/app/state/turn_state.py b/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/app/state/turn_state.py index be1f8339f..0f17366b0 100644 --- a/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/app/state/turn_state.py +++ b/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/app/state/turn_state.py @@ -6,7 +6,7 @@ from __future__ import annotations import logging -from typing import Any, Dict, Optional, Type, TypeVar, cast, Callable, Awaitable +from typing import Any, Optional, Type, TypeVar, Callable import asyncio from microsoft_agents.hosting.core.storage import Storage @@ -41,7 +41,7 @@ def __init__(self, *agent_states: AgentState) -> None: Args: agent_states: Initial list of AgentState objects to manage. """ - self._scopes: Dict[str, AgentState] = {} + self._scopes: dict[str, AgentState] = {} # Add all provided agent states for agent_state in agent_states: @@ -115,7 +115,7 @@ def get_value( name: str, default_value_factory: Optional[Callable[[], T]] = None, *, - target_cls: Type[T] = None, + target_cls: Type[T] | None = None, ) -> T: """ Gets a value from state. diff --git a/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/app/streaming/citation_util.py b/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/app/streaming/citation_util.py index 1ec923dc9..e849b899e 100644 --- a/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/app/streaming/citation_util.py +++ b/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/app/streaming/citation_util.py @@ -2,7 +2,7 @@ # Licensed under the MIT License. import re -from typing import List, Optional +from typing import Optional from microsoft_agents.activity import ClientCitation @@ -45,8 +45,8 @@ def format_citations_response(text: str) -> str: @staticmethod def get_used_citations( - text: str, citations: List[ClientCitation] - ) -> Optional[List[ClientCitation]]: + text: str, citations: list[ClientCitation] + ) -> Optional[list[ClientCitation]]: """ Get the citations used in the text. This will remove any citations that are included in the citations array from the response but not referenced in the text. diff --git a/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/app/streaming/streaming_response.py b/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/app/streaming/streaming_response.py index f5229d3d6..34db4eb24 100644 --- a/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/app/streaming/streaming_response.py +++ b/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/app/streaming/streaming_response.py @@ -4,12 +4,11 @@ import uuid import asyncio import logging -from typing import List, Optional, Callable, Literal, cast +from typing import Optional, Callable, Literal, cast from microsoft_agents.activity import ( Activity, AIEntity, - Entity, EntityTypes, Attachment, Channels, @@ -51,15 +50,15 @@ def __init__(self, context: "TurnContext"): self._sequence_number = 1 self._stream_id: Optional[str] = None self._message = "" - self._queue: List[Callable[[], Activity | None]] = [] + self._queue: list[Callable[[], Activity | None]] = [] self._queue_sync: Optional[asyncio.Task] = None self._chunk_queued = False self._ended = False self._cancelled = False self._is_streaming_channel = False self._interval = 0.1 - self._attachments: Optional[List[Attachment]] = None - self._citations: List[ClientCitation] = [] + self._attachments: Optional[list[Attachment]] = None + self._citations: list[ClientCitation] = [] self._sensitivity_label: Optional[SensitivityUsageInfo] = None self._enable_feedback_loop = False self._feedback_loop_type: Optional[Literal["default", "custom"]] = None @@ -102,7 +101,7 @@ def create_activity(): self._queue_activity(create_activity) def queue_text_chunk( - self, text: str, citations: Optional[List[Citation]] = None + self, text: str, citations: Optional[list[Citation]] = None ) -> None: """ Queues a chunk of partial message text to be sent to the client. @@ -142,7 +141,7 @@ async def end_stream(self) -> None: # Wait for the queue to drain await self.wait_for_queue() - def set_attachments(self, attachments: List[Attachment]) -> None: + def set_attachments(self, attachments: list[Attachment]) -> None: """ Sets the attachments to attach to the final chunk. @@ -160,7 +159,7 @@ def set_sensitivity_label(self, sensitivity_label: SensitivityUsageInfo) -> None """ self._sensitivity_label = sensitivity_label - def set_citations(self, citations: List[Citation]) -> None: + def set_citations(self, citations: list[Citation]) -> None: """ Sets the citations for the full message. diff --git a/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/app/typing_indicator.py b/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/app/typing_indicator.py index cedab4342..a99ddc30e 100644 --- a/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/app/typing_indicator.py +++ b/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/app/typing_indicator.py @@ -8,7 +8,7 @@ import asyncio import logging from dataclasses import dataclass, field -from typing import Dict, Optional +from typing import Optional from microsoft_agents.hosting.core import TurnContext from microsoft_agents.activity import Activity, ActivityTypes, Channels, EntityTypes @@ -42,7 +42,7 @@ class TypingOptions: initial_delay_ms: int = DEFAULT_INITIAL_DELAY_MS interval_ms: int = DEFAULT_INTERVAL_MS - channel_strategies: Dict[str, TypingChannelStrategy] = field(default_factory=dict) + channel_strategies: dict[str, TypingChannelStrategy] = field(default_factory=dict) def __post_init__(self): # Apply default channel overrides (matching .NET's M365Copilot default) 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 2592d9580..d8768d551 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 @@ -3,7 +3,7 @@ from abc import ABC, abstractmethod from collections.abc import Callable -from typing import List, Awaitable +from typing import Awaitable from microsoft_agents.hosting.core.authorization import ClaimsIdentity from microsoft_agents.activity import ChannelAdapterProtocol from microsoft_agents.activity import ( @@ -27,15 +27,15 @@ class ChannelAdapter(ABC, ChannelAdapterProtocol): AGENT_CALLBACK_HANDLER_KEY = "AgentCallbackHandler" CHANNEL_SERVICE_FACTORY_KEY = "ChannelServiceClientFactory" - on_turn_error: Callable[[TurnContext, Exception], Awaitable] = None + on_turn_error: Callable[[TurnContext, Exception], Awaitable] | None = None def __init__(self): self.middleware_set = MiddlewareSet() @abstractmethod async def send_activities( - self, context: TurnContext, activities: List[Activity] - ) -> List[ResourceResponse]: + self, context: TurnContext, activities: list[Activity] + ) -> list[ResourceResponse]: """ Sends a set of activities to the user. An array of responses from the server will be returned. @@ -119,7 +119,7 @@ async def continue_conversation_with_claims( claims_identity: ClaimsIdentity, continuation_activity: Activity, callback: Callable[[TurnContext], Awaitable], - audience: str = None, + audience: str | None = None, ): """ Sends a proactive message to a conversation. Call this method to proactively send a message to a conversation. @@ -221,7 +221,9 @@ async def create_conversation( return await self.run_pipeline(context, callback) async def run_pipeline( - self, context: TurnContext, callback: Callable[[TurnContext], Awaitable] = None + self, + context: TurnContext, + callback: Callable[[TurnContext], Awaitable] | None = None, ): """ Called by the parent class to run the adapters middleware set and calls the passed in `callback()` handler at 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 abd8a6f1f..2be89cf01 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 @@ -227,7 +227,7 @@ async def continue_conversation_with_claims( claims_identity: ClaimsIdentity, continuation_activity: Activity, callback: Callable[[TurnContext], Awaitable], - audience: str = None, + audience: str | None = None, ): """ Continue a conversation with the provided claims identity. @@ -393,7 +393,7 @@ async def process_activity( otherwise, `None` is returned. """ scopes: list[str] = claims_identity.get_token_scope() - outgoing_audience: str = None + outgoing_audience: str | None = None if claims_identity.is_agent_claim(): outgoing_audience = claims_identity.get_token_audience() diff --git a/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/client/channels_configuration.py b/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/client/channels_configuration.py index 935611a94..03547f23d 100644 --- a/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/client/channels_configuration.py +++ b/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/client/channels_configuration.py @@ -10,12 +10,12 @@ class ChannelInfo(ChannelInfoProtocol): def __init__( self, - id: str = None, - app_id: str = None, - resource_url: str = None, - token_provider: str = None, - channel_factory: str = None, - endpoint: str = None, + id: str | None = None, + app_id: str | None = None, + resource_url: str | None = None, + token_provider: str | None = None, + channel_factory: str | None = None, + endpoint: str | None = None, **kwargs ): self.id = id diff --git a/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/client/configuration_channel_host.py b/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/client/configuration_channel_host.py index 0c48dbf15..07dde3dc6 100644 --- a/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/client/configuration_channel_host.py +++ b/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/client/configuration_channel_host.py @@ -24,8 +24,8 @@ def __init__( self.connections = connections self.configuration = configuration self.channels: dict[str, ChannelInfoProtocol] = {} - self.host_endpoint: str = None - self.host_app_id: str = None + self.host_endpoint: str | None = None + self.host_app_id: str | None = None channel_host_configuration = configuration.CHANNEL_HOST_CONFIGURATION() diff --git a/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/client/http_agent_channel.py b/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/client/http_agent_channel.py index ecc1cbbef..b7895baf2 100644 --- a/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/client/http_agent_channel.py +++ b/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/client/http_agent_channel.py @@ -31,7 +31,7 @@ async def post_activity( conversation_id: str, activity: Activity, *, - response_body_type: type[AgentsModel] = None, + response_body_type: type[AgentsModel] | None = None, **kwargs, ) -> InvokeResponse: if not endpoint: diff --git a/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/connector/agent_sign_in_base.py b/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/connector/agent_sign_in_base.py index 75b262ebc..e4f81b006 100644 --- a/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/connector/agent_sign_in_base.py +++ b/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/connector/agent_sign_in_base.py @@ -9,9 +9,9 @@ class AgentSignInBase(Protocol): async def get_sign_in_url( self, state: str, - code_challenge: str = None, - emulator_url: str = None, - final_redirect: str = None, + code_challenge: str | None = None, + emulator_url: str | None = None, + final_redirect: str | None = None, ) -> str: raise NotImplementedError() @@ -19,8 +19,8 @@ async def get_sign_in_url( async def get_sign_in_resource( self, state: str, - code_challenge: str = None, - emulator_url: str = None, - final_redirect: str = None, + code_challenge: str | None = None, + emulator_url: str | None = None, + final_redirect: str | None = None, ) -> SignInResource: raise NotImplementedError() diff --git a/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/connector/user_token_base.py b/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/connector/user_token_base.py index 32c21abe9..3ae669ab9 100644 --- a/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/connector/user_token_base.py +++ b/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/connector/user_token_base.py @@ -19,8 +19,8 @@ async def get_token( self, user_id: str, connection_name: str, - channel_id: str = None, - code: str = None, + channel_id: str | None = None, + code: str | None = None, ) -> TokenResponse: """ Get sign-in URL. @@ -63,8 +63,8 @@ async def get_aad_tokens( self, user_id: str, connection_name: str, - channel_id: str = None, - body: dict = None, + channel_id: str | None = None, + body: dict | None = None, ) -> dict[str, TokenResponse]: """ Gets Azure Active Directory tokens for a user and connection. @@ -79,7 +79,10 @@ async def get_aad_tokens( @abstractmethod async def sign_out( - self, user_id: str, connection_name: str = None, channel_id: str = None + self, + user_id: str, + connection_name: str | None = None, + channel_id: str | None = None, ) -> None: """ Signs the user out from the specified connection. @@ -92,7 +95,7 @@ async def sign_out( @abstractmethod async def get_token_status( - self, user_id: str, channel_id: str = None, include: str = None + self, user_id: str, channel_id: str | None = None, include: str | None = None ) -> list[TokenStatus]: """ Gets token status for the user. @@ -106,7 +109,11 @@ async def get_token_status( @abstractmethod async def exchange_token( - self, user_id: str, connection_name: str, channel_id: str, body: dict = None + self, + user_id: str, + connection_name: str, + channel_id: str, + body: dict | None = None, ) -> TokenResponse: """ Exchanges a token. diff --git a/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/http/_channel_service_routes.py b/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/http/_channel_service_routes.py index 16adf3813..3c7e5bcbe 100644 --- a/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/http/_channel_service_routes.py +++ b/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/http/_channel_service_routes.py @@ -3,7 +3,7 @@ """Channel service route definitions (framework-agnostic logic).""" -from typing import Type, List, Union +from typing import Type from microsoft_agents.activity import ( AgentsModel, @@ -47,7 +47,7 @@ async def deserialize_from_body( return target_model.model_validate(body) @staticmethod - def serialize_model(model_or_list: Union[AgentsModel, List[AgentsModel]]) -> dict: + def serialize_model(model_or_list: AgentsModel | list[AgentsModel]) -> dict: """Serialize model or list of models to JSON-compatible dict.""" if isinstance(model_or_list, AgentsModel): return model_or_list.model_dump( diff --git a/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/http/_http_adapter_base.py b/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/http/_http_adapter_base.py index b2fea448c..4ea5438b6 100644 --- a/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/http/_http_adapter_base.py +++ b/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/http/_http_adapter_base.py @@ -32,8 +32,8 @@ class HttpAdapterBase(ChannelServiceAdapter, ABC): def __init__( self, *, - connection_manager: Connections = None, - channel_service_client_factory: ChannelServiceClientFactoryBase = None, + connection_manager: Connections | None = None, + channel_service_client_factory: ChannelServiceClientFactoryBase | None = None, ): """Initialize the HTTP adapter. diff --git a/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/http/_http_request_protocol.py b/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/http/_http_request_protocol.py index f99dc1d80..c38749871 100644 --- a/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/http/_http_request_protocol.py +++ b/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/http/_http_request_protocol.py @@ -3,7 +3,7 @@ """Protocol for abstracting HTTP request objects across frameworks.""" -from typing import Protocol, Dict, Any, Optional +from typing import Protocol, Any, Optional class HttpRequestProtocol(Protocol): @@ -19,11 +19,11 @@ def method(self) -> str: ... @property - def headers(self) -> Dict[str, str]: + def headers(self) -> dict[str, str]: """Request headers.""" ... - async def json(self) -> Dict[str, Any]: + async def json(self) -> dict[str, Any]: """Parse request body as JSON.""" ... diff --git a/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/http/_http_response.py b/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/http/_http_response.py index d593cdee9..955ff2ad9 100644 --- a/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/http/_http_response.py +++ b/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/http/_http_response.py @@ -3,7 +3,7 @@ """HTTP response abstraction.""" -from typing import Any, Optional, Dict +from typing import Any, Optional from dataclasses import dataclass @@ -13,7 +13,7 @@ class HttpResponse: status_code: int body: Optional[Any] = None - headers: Optional[Dict[str, str]] = None + headers: Optional[dict[str, str]] = None content_type: Optional[str] = "application/json" diff --git a/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/message_factory.py b/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/message_factory.py index 424a4024d..91a557302 100644 --- a/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/message_factory.py +++ b/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/message_factory.py @@ -17,8 +17,8 @@ def attachment_activity( attachment_layout: AttachmentLayoutTypes, attachments: list[Attachment], - text: str = None, - speak: str = None, + text: str | None = None, + speak: str | None = None, input_hint: InputHints | str = InputHints.accepting_input, ) -> Activity: message = Activity( @@ -44,7 +44,7 @@ class MessageFactory: @staticmethod def text( text: str, - speak: str = None, + speak: str | None = None, input_hint: InputHints | str = InputHints.accepting_input, ) -> Activity: """ @@ -68,8 +68,8 @@ def text( @staticmethod def suggested_actions( actions: list[CardAction], - text: str = None, - speak: str = None, + text: str | None = None, + speak: str | None = None, input_hint: InputHints | str = InputHints.accepting_input, ) -> Activity: """ @@ -101,9 +101,9 @@ def suggested_actions( @staticmethod def attachment( attachment: Attachment, - text: str = None, - speak: str = None, - input_hint: InputHints | str = None, + text: str | None = None, + speak: str | None = None, + input_hint: InputHints | str | None = None, ): """ Returns a single message activity containing an attachment. @@ -129,8 +129,8 @@ def attachment( @staticmethod def list( attachments: list[Attachment], - text: str = None, - speak: str = None, + text: str | None = None, + speak: str | None = None, input_hint: InputHints | str = None, ) -> Activity: """ @@ -161,8 +161,8 @@ def list( @staticmethod def carousel( attachments: list[Attachment], - text: str = None, - speak: str = None, + text: str | None = None, + speak: str | None = None, input_hint: InputHints | str = None, ) -> Activity: """ @@ -194,10 +194,10 @@ def carousel( def content_url( url: str, content_type: str, - name: str = None, - text: str = None, - speak: str = None, - input_hint: InputHints | str = None, + name: str | None = None, + text: str | None = None, + speak: str | None = None, + input_hint: InputHints | str | None = None, ): """ Returns a message that will display a single image or video to a user. diff --git a/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/state/agent_state.py b/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/state/agent_state.py index 02df1a6ce..8006e0247 100644 --- a/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/state/agent_state.py +++ b/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/state/agent_state.py @@ -5,7 +5,7 @@ from abc import abstractmethod from copy import deepcopy -from typing import Callable, Dict, Union, Type +from typing import Callable, Type from microsoft_agents.hosting.core.storage import Storage, StoreItem @@ -18,7 +18,7 @@ class CachedAgentState(StoreItem): Internal cached Agent state. """ - def __init__(self, state: Dict[str, StoreItem | dict] = None): + def __init__(self, state: dict[str, StoreItem | dict] | None = None): if state: self.state = state self.hash = self.compute_hash() @@ -85,7 +85,7 @@ def __init__(self, storage: Storage, context_service_key: str): self.state_key = "state" self._storage = storage self._context_service_key = context_service_key - self._cached_state: CachedAgentState = None + self._cached_state: CachedAgentState | None = None def get_cached_state(self, turn_context: TurnContext) -> CachedAgentState: """ @@ -113,7 +113,7 @@ def create_property(self, name: str) -> StatePropertyAccessor: ) return BotStatePropertyAccessor(self, name) - def get(self, turn_context: TurnContext) -> Dict[str, StoreItem]: + def get(self, turn_context: TurnContext) -> dict[str, StoreItem]: cached = self.get_cached_state(turn_context) return getattr(cached, "state", None) @@ -148,7 +148,7 @@ async def save(self, turn_context: TurnContext, force: bool = False) -> None: if force or (self._cached_state is not None and self._cached_state.is_changed): storage_key = self.get_storage_key(turn_context) - changes: Dict[str, StoreItem] = {storage_key: self._cached_state} + changes: dict[str, StoreItem] = {storage_key: self._cached_state} await self._storage.write(changes) self._cached_state.hash = self._cached_state.compute_hash() @@ -185,14 +185,14 @@ async def delete(self, turn_context: TurnContext) -> None: @abstractmethod def get_storage_key( - self, turn_context: TurnContext, *, target_cls: Type[StoreItem] = None + self, turn_context: TurnContext, *, target_cls: Type[StoreItem] | None = None ) -> str: raise NotImplementedError() def get_value( self, property_name: str, - default_value_factory: Callable[[], StoreItem] = None, + default_value_factory: Callable[[], StoreItem] | None = None, *, target_cls: Type[StoreItem] = None, ) -> StoreItem: @@ -312,9 +312,9 @@ async def delete(self, turn_context: TurnContext) -> None: async def get( self, turn_context: TurnContext, - default_value_or_factory: Union[Callable, StoreItem] = None, + default_value_or_factory: Callable | StoreItem | None = None, *, - target_cls: Type[StoreItem] = None, + target_cls: Type[StoreItem] | None = None, ) -> StoreItem: """ Gets the property value. diff --git a/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/state/state_property_accessor.py b/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/state/state_property_accessor.py index 4ad564894..145d26f90 100644 --- a/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/state/state_property_accessor.py +++ b/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/state/state_property_accessor.py @@ -3,7 +3,7 @@ from abc import abstractmethod from collections.abc import Callable -from typing import Protocol, Type, Union +from typing import Protocol, Type from microsoft_agents.hosting.core.storage import StoreItem @@ -15,9 +15,9 @@ class StatePropertyAccessor(Protocol): async def get( self, turn_context: TurnContext, - default_value_or_factory: Union[Callable, StoreItem] = None, + default_value_or_factory: Callable | StoreItem | None = None, *, - target_cls: Type[StoreItem] = None + target_cls: Type[StoreItem] | None = None ) -> object: """ Get the property value from the source diff --git a/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/storage/memory_storage.py b/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/storage/memory_storage.py index 31560b276..17ba2d1fd 100644 --- a/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/storage/memory_storage.py +++ b/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/storage/memory_storage.py @@ -12,7 +12,7 @@ class MemoryStorage(Storage): - def __init__(self, state: dict[str, JSON] = None): + def __init__(self, state: dict[str, JSON] | None = None): self._memory: dict[str, JSON] = state or {} self._lock = Lock() diff --git a/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/storage/storage.py b/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/storage/storage.py index 1e9ddd86e..380def139 100644 --- a/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/storage/storage.py +++ b/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/storage/storage.py @@ -1,7 +1,7 @@ # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. -from typing import Protocol, TypeVar, Type, Union +from typing import Protocol, TypeVar, Type from abc import abstractmethod from asyncio import gather @@ -13,7 +13,7 @@ class Storage(Protocol): async def read( - self, keys: list[str], *, target_cls: Type[StoreItemT] = None, **kwargs + self, keys: list[str], *, target_cls: Type[StoreItemT] | None = None, **kwargs ) -> dict[str, StoreItemT]: """Reads multiple items from storage. @@ -53,8 +53,8 @@ async def initialize(self) -> None: @abstractmethod async def _read_item( - self, key: str, *, target_cls: Type[StoreItemT] = None, **kwargs - ) -> tuple[Union[str, None], Union[StoreItemT, None]]: + self, key: str, *, target_cls: Type[StoreItemT] | None = None, **kwargs + ) -> tuple[str | None, StoreItemT | None]: """Reads a single item from storage by key. Returns a tuple of (key, StoreItem) if found, or (None, None) if not found. @@ -62,7 +62,7 @@ async def _read_item( pass async def read( - self, keys: list[str], *, target_cls: Type[StoreItemT] = None, **kwargs + self, keys: list[str], *, target_cls: Type[StoreItemT] | None = None, **kwargs ) -> dict[str, StoreItemT]: if not keys: raise ValueError("Storage.read(): Keys are required when reading.") @@ -72,13 +72,8 @@ async def read( with spans.StorageRead(len(keys)): await self.initialize() - items: list[tuple[Union[str, None], Union[StoreItemT, None]]] = ( - await gather( - *[ - self._read_item(key, target_cls=target_cls, **kwargs) - for key in keys - ] - ) + items: list[tuple[str | None, StoreItemT | None]] = await gather( + *[self._read_item(key, target_cls=target_cls, **kwargs) for key in keys] ) return {key: value for key, value in items if key is not None} diff --git a/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/storage/transcript_file_store.py b/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/storage/transcript_file_store.py index d40bf092b..f69cd20c9 100644 --- a/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/storage/transcript_file_store.py +++ b/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/storage/transcript_file_store.py @@ -10,7 +10,7 @@ from datetime import datetime, timezone from pathlib import Path -from typing import Any, Dict, Iterable, List, Optional, Tuple, Union +from typing import Any, Optional from .transcript_logger import TranscriptLogger from .transcript_logger import PagedResult @@ -41,7 +41,7 @@ class FileTranscriptStore(TranscriptLogger): - Microsoft.Agents.Storage.Transcript namespace overview [AGENTS] """ - def __init__(self, root_folder: Union[str, Path]) -> None: + def __init__(self, root_folder: str | Path) -> None: self._root = Path(root_folder).expanduser().resolve() self._root.mkdir(parents=True, exist_ok=True) @@ -87,10 +87,10 @@ async def list_transcripts(self, channel_id: str) -> PagedResult[TranscriptInfo] :param channel_id: The channel ID to list transcripts for.""" channel_dir = self._channel_dir(channel_id) - def _list() -> List[TranscriptInfo]: + def _list() -> list[TranscriptInfo]: if not channel_dir.exists(): return [] - results: List[TranscriptInfo] = [] + results: list[TranscriptInfo] = [] for p in channel_dir.glob("*.transcript"): # mtime is a reasonable proxy for 'created/updated' created = datetime.fromtimestamp(p.stat().st_mtime, tz=timezone.utc) @@ -127,12 +127,12 @@ async def get_transcript_activities( """ file_path = self._file_path(channel_id, conversation_id) - def _read_page() -> Tuple[List[Activity], Optional[str]]: + def _read_page() -> tuple[list[Activity], Optional[str]]: if not file_path.exists(): return [], None offset = int(continuation_token) if continuation_token else 0 - results: List[Activity] = [] + results: list[Activity] = [] with open(file_path, "rb") as f: f.seek(0, os.SEEK_END) @@ -215,7 +215,7 @@ def _sanitize(pattern: re.Pattern[str], value: str) -> str: return value or "unknown" -def _get_ids(activity: Activity) -> Tuple[str, str]: +def _get_ids(activity: Activity) -> tuple[str, str]: # Works with both dict-like and object-like Activity def _get(obj: Any, *path: str) -> Optional[Any]: cur = obj @@ -235,7 +235,7 @@ def _get(obj: Any, *path: str) -> Optional[Any]: return str(channel_id), str(conversation_id) -def _to_plain_dict(activity: Activity) -> Dict[str, Any]: +def _to_plain_dict(activity: Activity) -> dict[str, Any]: if isinstance(activity, dict): return activity diff --git a/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/storage/transcript_logger.py b/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/storage/transcript_logger.py index 41df1ffda..93b78baea 100644 --- a/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/storage/transcript_logger.py +++ b/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/storage/transcript_logger.py @@ -5,11 +5,10 @@ import string import json -from typing import Any, Optional from abc import ABC, abstractmethod from datetime import datetime, timezone from queue import Queue -from typing import Awaitable, Callable, List, Optional +from typing import Awaitable, Callable, Optional from dataclasses import dataclass from microsoft_agents.activity import Activity, ChannelAccount @@ -24,7 +23,7 @@ @dataclass class PagedResult(Generic[T]): - items: List[T] + items: list[T] continuation_token: Optional[str] = None @@ -139,7 +138,7 @@ async def on_turn( # pylint: disable=unused-argument async def send_activities_handler( ctx: TurnContext, - activities: List[Activity], + activities: list[Activity], next_send: Callable[[], Awaitable[None]], ): # Run full pipeline diff --git a/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/storage/transcript_memory_store.py b/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/storage/transcript_memory_store.py index 6bc170f11..6ae74684f 100644 --- a/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/storage/transcript_memory_store.py +++ b/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/storage/transcript_memory_store.py @@ -50,7 +50,7 @@ async def get_transcript_activities( self, channel_id: str, conversation_id: str, - continuation_token: str = None, + continuation_token: str | None = None, start_date: datetime = datetime.min.replace(tzinfo=timezone.utc), ) -> PagedResult[Activity]: """ @@ -127,7 +127,7 @@ async def delete_transcript(self, channel_id: str, conversation_id: str) -> None ] async def list_transcripts( - self, channel_id: str, continuation_token: str = None + self, channel_id: str, continuation_token: str | None = None ) -> PagedResult[TranscriptInfo]: """ Lists all transcripts (unique conversation IDs) for a given channel. diff --git a/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/storage/transcript_store.py b/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/storage/transcript_store.py index 8e660bf89..4170863be 100644 --- a/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/storage/transcript_store.py +++ b/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/storage/transcript_store.py @@ -14,7 +14,7 @@ async def get_transcript_activities( self, channel_id: str, conversation_id: str, - continuation_token: str = None, + continuation_token: str | None = None, start_date: datetime = datetime.min.replace(tzinfo=timezone.utc), ) -> tuple[list[Activity], str]: """ @@ -30,7 +30,7 @@ async def get_transcript_activities( @abstractmethod async def list_transcripts( - self, channel_id: str, continuation_token: str = None + self, channel_id: str, continuation_token: str | None = None ) -> tuple[list[TranscriptInfo, str]]: """ Asynchronously lists transcripts for a given channel. diff --git a/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/turn_context.py b/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/turn_context.py index 46eeba4ab..af71ffac2 100644 --- a/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/turn_context.py +++ b/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/turn_context.py @@ -30,8 +30,8 @@ class TurnContext(TurnContextProtocol): def __init__( self, adapter_or_context, - request: Activity = None, - identity: ClaimsIdentity = None, + request: Activity | None = None, + identity: ClaimsIdentity | None = None, ): """ Creates a new TurnContext instance. @@ -185,8 +185,8 @@ def set(self, key: str, value: object) -> None: async def send_activity( self, activity_or_text: Activity | str, - speak: str = None, - input_hint: str = None, + speak: str | None = None, + input_hint: str | None = None, ) -> ResourceResponse | None: """ Sends a single activity or message to the user. @@ -338,7 +338,11 @@ async def next_handler(): return await logic async def send_trace_activity( - self, name: str, value: object = None, value_type: str = None, label: str = None + self, + name: str, + value: object = None, + value_type: str | None = None, + label: str | None = None, ) -> ResourceResponse: trace_activity = Activity( type=ActivityTypes.trace, diff --git a/libraries/microsoft-agents-hosting-dialogs/microsoft_agents/hosting/dialogs/object_path.py b/libraries/microsoft-agents-hosting-dialogs/microsoft_agents/hosting/dialogs/object_path.py index 0e6a4460c..4b7deb2b7 100644 --- a/libraries/microsoft-agents-hosting-dialogs/microsoft_agents/hosting/dialogs/object_path.py +++ b/libraries/microsoft-agents-hosting-dialogs/microsoft_agents/hosting/dialogs/object_path.py @@ -2,7 +2,7 @@ # Licensed under the MIT License. import copy -from typing import Union, Callable +from typing import Callable class ObjectPath: @@ -11,7 +11,7 @@ class ObjectPath: """ @staticmethod - def assign(start_object, overlay_object, default: Union[Callable, object] = None): + def assign(start_object, overlay_object, default: Callable | object = None): """ Creates a new object by overlaying values in start_object with non-null values from overlay_object. @@ -106,9 +106,7 @@ def set_path_value(obj, path: str, value: object): ObjectPath.__set_object_segment(current, last_segment, value) @staticmethod - def get_path_value( - obj, path: str, default: Union[Callable, object] = None - ) -> object: + def get_path_value(obj, path: str, default: Callable | object = None) -> object: """ Get the value for a path relative to an object. """ diff --git a/libraries/microsoft-agents-hosting-dialogs/microsoft_agents/hosting/dialogs/prompts/prompt_validator_context.py b/libraries/microsoft-agents-hosting-dialogs/microsoft_agents/hosting/dialogs/prompts/prompt_validator_context.py index 7a19231a9..35917975a 100644 --- a/libraries/microsoft-agents-hosting-dialogs/microsoft_agents/hosting/dialogs/prompts/prompt_validator_context.py +++ b/libraries/microsoft-agents-hosting-dialogs/microsoft_agents/hosting/dialogs/prompts/prompt_validator_context.py @@ -1,6 +1,7 @@ # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. -from typing import Dict, cast + +from typing import cast from microsoft_agents.hosting.core import TurnContext from .prompt_options import PromptOptions from .prompt_recognizer_result import PromptRecognizerResult @@ -11,7 +12,7 @@ def __init__( self, turn_context: TurnContext, recognized: PromptRecognizerResult, - state: Dict[str, object], + state: dict[str, object], options: PromptOptions, ): """Creates contextual information passed to a custom `PromptValidator`. diff --git a/libraries/microsoft-agents-hosting-slack/microsoft_agents/hosting/slack/_path_navigator.py b/libraries/microsoft-agents-hosting-slack/microsoft_agents/hosting/slack/_path_navigator.py index a6a817256..f7cc736ba 100644 --- a/libraries/microsoft-agents-hosting-slack/microsoft_agents/hosting/slack/_path_navigator.py +++ b/libraries/microsoft-agents-hosting-slack/microsoft_agents/hosting/slack/_path_navigator.py @@ -5,7 +5,7 @@ from __future__ import annotations -from typing import Any, Optional, Tuple +from typing import Any, Optional def _parse_path(path: str) -> Optional[list[int | str]]: @@ -60,7 +60,7 @@ def emit() -> None: return segments -def _resolve_segment(current: Any, segment: Any) -> Tuple[bool, Any]: +def _resolve_segment(current: Any, segment: Any) -> tuple[bool, Any]: """Resolve one path segment against the current node. Returns ``(found, value)``. ``found`` is False when the segment cannot be @@ -89,7 +89,7 @@ def _resolve_segment(current: Any, segment: Any) -> Tuple[bool, Any]: return False, None -def try_get_path_value(data: Any, path: str) -> Tuple[bool, Any]: +def try_get_path_value(data: Any, path: str) -> tuple[bool, Any]: """Walk ``path`` against ``data``. Returns ``(found, value)``.""" if data is None: return False, None diff --git a/libraries/microsoft-agents-hosting-slack/microsoft_agents/hosting/slack/api/slack_stream.py b/libraries/microsoft-agents-hosting-slack/microsoft_agents/hosting/slack/api/slack_stream.py index 9b158a223..ec9f352b2 100644 --- a/libraries/microsoft-agents-hosting-slack/microsoft_agents/hosting/slack/api/slack_stream.py +++ b/libraries/microsoft-agents-hosting-slack/microsoft_agents/hosting/slack/api/slack_stream.py @@ -6,7 +6,7 @@ from __future__ import annotations import json -from typing import Any, Optional, Union +from typing import Any, Optional from pydantic import BaseModel @@ -64,7 +64,7 @@ async def start( async def append( self, - chunk_or_text: Union[str, BaseModel, list[BaseModel]], + chunk_or_text: str | BaseModel | list[BaseModel], ) -> "SlackStream": """Append one or more chunks to the stream. @@ -107,7 +107,7 @@ async def append( async def stop( self, chunks: Optional[list[BaseModel]] = None, - blocks: Union[str, list[Any], dict, None] = None, + blocks: str | list[Any] | dict | None = None, ) -> None: """Stop the active stream, optionally finalizing with chunks and/or Block Kit blocks. diff --git a/libraries/microsoft-agents-hosting-slack/microsoft_agents/hosting/slack/slack_agent_extension.py b/libraries/microsoft-agents-hosting-slack/microsoft_agents/hosting/slack/slack_agent_extension.py index 29313d590..faa928db0 100644 --- a/libraries/microsoft-agents-hosting-slack/microsoft_agents/hosting/slack/slack_agent_extension.py +++ b/libraries/microsoft-agents-hosting-slack/microsoft_agents/hosting/slack/slack_agent_extension.py @@ -6,7 +6,7 @@ from __future__ import annotations import re -from typing import Any, Callable, Generic, Optional, Pattern, TypeVar, Union +from typing import Any, Callable, Generic, Optional, Pattern, TypeVar from microsoft_agents.activity import ActivityTypes, Channels from microsoft_agents.hosting.core import TurnContext @@ -22,7 +22,7 @@ StateT = TypeVar("StateT", bound=TurnState) -TextSelector = Union[str, Pattern[str], None] +TextSelector = str | Pattern[str], None _SLACK_API_SERVICE_KEY = "microsoft_agents.hosting.slack.SlackApi" diff --git a/libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/teams_activity_handler.py b/libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/teams_activity_handler.py index ba753bb29..c0511e2d7 100644 --- a/libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/teams_activity_handler.py +++ b/libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/teams_activity_handler.py @@ -4,7 +4,7 @@ """ from http import HTTPStatus -from typing import Any, List +from typing import Any from microsoft_agents.hosting.core import ActivityHandler, TurnContext from microsoft_agents.hosting.teams.errors import teams_errors @@ -621,7 +621,7 @@ async def on_teams_message_soft_delete(self, turn_context: TurnContext) -> None: async def on_teams_members_added_dispatch( self, - members_added: List[ChannelAccount], + members_added: list[ChannelAccount], team_info: TeamInfo, turn_context: TurnContext, ) -> None: @@ -672,7 +672,7 @@ async def on_teams_members_added_dispatch( async def on_teams_members_added( self, - teams_members_added: List[TeamsChannelAccount], + teams_members_added: list[TeamsChannelAccount], team_info: TeamInfo, turn_context: TurnContext, ) -> None: @@ -686,7 +686,7 @@ async def on_teams_members_added( async def on_teams_members_removed_dispatch( self, - members_removed: List[ChannelAccount], + members_removed: list[ChannelAccount], team_info: TeamInfo, turn_context: TurnContext, ) -> None: @@ -706,7 +706,7 @@ async def on_teams_members_removed_dispatch( async def on_teams_members_removed( self, - teams_members_removed: List[TeamsChannelAccount], + teams_members_removed: list[TeamsChannelAccount], team_info: TeamInfo, turn_context: TurnContext, ) -> None: diff --git a/libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/teams_agent_extension.py b/libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/teams_agent_extension.py index 6209bdd2a..34cdef2fd 100644 --- a/libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/teams_agent_extension.py +++ b/libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/teams_agent_extension.py @@ -7,7 +7,7 @@ import re from http import HTTPStatus -from typing import Any, Callable, Generic, Optional, Pattern, TypeVar, Union +from typing import Any, Callable, Generic, Optional, Pattern, TypeVar from microsoft_agents.activity import Activity, ActivityTypes, InvokeResponse from microsoft_agents.hosting.core import TurnContext @@ -30,7 +30,7 @@ StateT = TypeVar("StateT", bound=TurnState) -CommandSelector = Union[str, Pattern[str], None] +CommandSelector = str | Pattern[str] | None def _match_selector(selector: CommandSelector, value: Optional[str]) -> bool: diff --git a/libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/teams_info.py b/libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/teams_info.py index 05cb1ab3a..b90eb6f84 100644 --- a/libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/teams_info.py +++ b/libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/teams_info.py @@ -3,7 +3,7 @@ """Teams information utilities for Microsoft Agents.""" -from typing import Optional, Tuple, Dict, Any, List +from typing import Optional, Any from microsoft_agents.activity import Activity, Channels, ConversationParameters @@ -145,7 +145,7 @@ async def send_message_to_teams_channel( activity: Activity, teams_channel_id: str, app_id: Optional[str] = None, - ) -> Tuple[Dict[str, Any], str]: + ) -> tuple[dict[str, Any], str]: """ Sends a message to a Teams channel. @@ -225,7 +225,7 @@ async def _conversation_callback( @staticmethod async def get_team_channels( context: TurnContext, team_id: Optional[str] = None - ) -> List[ChannelInfo]: + ) -> list[ChannelInfo]: """ Gets the channels of a team. @@ -425,7 +425,7 @@ async def send_message_to_list_of_users( context: TurnContext, activity: Activity, tenant_id: str, - members: List[TeamsMember], + members: list[TeamsMember], ) -> TeamsBatchOperationResponse: """ Sends a message to a list of users. @@ -524,7 +524,7 @@ async def send_message_to_list_of_channels( context: TurnContext, activity: Activity, tenant_id: str, - members: List[TeamsMember], + members: list[TeamsMember], ) -> TeamsBatchOperationResponse: """ Sends a message to a list of channels. diff --git a/libraries/microsoft-agents-storage-blob/microsoft_agents/storage/blob/blob_storage.py b/libraries/microsoft-agents-storage-blob/microsoft_agents/storage/blob/blob_storage.py index 595c62fd5..a88c60783 100644 --- a/libraries/microsoft-agents-storage-blob/microsoft_agents/storage/blob/blob_storage.py +++ b/libraries/microsoft-agents-storage-blob/microsoft_agents/storage/blob/blob_storage.py @@ -1,5 +1,5 @@ import json -from typing import TypeVar, Union +from typing import TypeVar from io import BytesIO from azure.storage.blob.aio import ( @@ -63,8 +63,8 @@ async def initialize(self) -> None: self._initialized = True async def _read_item( - self, key: str, *, target_cls: StoreItemT = None, **kwargs - ) -> tuple[Union[str, None], Union[StoreItemT, None]]: + self, key: str, *, target_cls: StoreItemT | None = None, **kwargs + ) -> tuple[str | None, StoreItemT | None]: item = await ignore_error( self._container_client.download_blob(blob=key, timeout=5), is_status_code_error(404), diff --git a/libraries/microsoft-agents-storage-blob/microsoft_agents/storage/blob/blob_storage_config.py b/libraries/microsoft-agents-storage-blob/microsoft_agents/storage/blob/blob_storage_config.py index dec5e206a..bbf9a9056 100644 --- a/libraries/microsoft-agents-storage-blob/microsoft_agents/storage/blob/blob_storage_config.py +++ b/libraries/microsoft-agents-storage-blob/microsoft_agents/storage/blob/blob_storage_config.py @@ -1,5 +1,3 @@ -from typing import Union - from azure.core.credentials_async import AsyncTokenCredential @@ -11,7 +9,7 @@ def __init__( container_name: str, connection_string: str = "", url: str = "", - credential: Union[AsyncTokenCredential, None] = None, + credential: AsyncTokenCredential | None = None, ): """Configuration settings for BlobStorage. @@ -25,4 +23,4 @@ def __init__( self.container_name: str = container_name self.connection_string: str = connection_string self.url: str = url - self.credential: Union[AsyncTokenCredential, None] = credential + self.credential: AsyncTokenCredential | None = credential diff --git a/libraries/microsoft-agents-storage-cosmos/microsoft_agents/storage/cosmos/cosmos_db_storage.py b/libraries/microsoft-agents-storage-cosmos/microsoft_agents/storage/cosmos/cosmos_db_storage.py index 96df0352b..5dc612737 100644 --- a/libraries/microsoft-agents-storage-cosmos/microsoft_agents/storage/cosmos/cosmos_db_storage.py +++ b/libraries/microsoft-agents-storage-cosmos/microsoft_agents/storage/cosmos/cosmos_db_storage.py @@ -1,12 +1,11 @@ # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. -from typing import TypeVar, Union +from typing import TypeVar import asyncio from azure.cosmos import ( documents, - http_constants, CosmosDict, ) from azure.cosmos.aio import ( @@ -46,8 +45,8 @@ def __init__(self, config: CosmosDBStorageConfig): self._config: CosmosDBStorageConfig = config self._client: CosmosClient = self._create_client() - self._database: DatabaseProxy = None - self._container: ContainerProxy = None + self._database: DatabaseProxy | None = None + self._container: ContainerProxy | None = None self._compatability_mode_partition_key: bool = False # Lock used for synchronizing container creation self._lock: asyncio.Lock = asyncio.Lock() @@ -88,8 +87,8 @@ def _sanitize(self, key: str) -> str: ) async def _read_item( - self, key: str, *, target_cls: StoreItemT = None, **kwargs - ) -> tuple[Union[str, None], Union[StoreItemT, None]]: + self, key: str, *, target_cls: StoreItemT | None = None, **kwargs + ) -> tuple[str | None, StoreItemT | None]: if key == "": raise ValueError(str(storage_errors.CosmosDbKeyCannotBeEmpty)) diff --git a/libraries/microsoft-agents-storage-cosmos/microsoft_agents/storage/cosmos/cosmos_db_storage_config.py b/libraries/microsoft-agents-storage-cosmos/microsoft_agents/storage/cosmos/cosmos_db_storage_config.py index 4e0e15ac0..596a10360 100644 --- a/libraries/microsoft-agents-storage-cosmos/microsoft_agents/storage/cosmos/cosmos_db_storage_config.py +++ b/libraries/microsoft-agents-storage-cosmos/microsoft_agents/storage/cosmos/cosmos_db_storage_config.py @@ -1,5 +1,4 @@ import json -from typing import Union from azure.core.credentials_async import AsyncTokenCredential from microsoft_agents.storage.cosmos.errors import storage_errors @@ -16,12 +15,12 @@ def __init__( auth_key: str = "", database_id: str = "", container_id: str = "", - cosmos_client_options: dict = None, + cosmos_client_options: dict | None = None, container_throughput: int | None = None, key_suffix: str = "", compatibility_mode: bool = False, url: str = "", - credential: Union[AsyncTokenCredential, None] = None, + credential: AsyncTokenCredential | None = None, **kwargs, ): """Create the Config object. @@ -62,7 +61,7 @@ def __init__( "compatibility_mode", False ) self.url = url or kwargs.get("url", "") - self.credential: Union[AsyncTokenCredential, None] = credential + self.credential: AsyncTokenCredential | None = credential @staticmethod def validate_cosmos_db_config(config: "CosmosDBStorageConfig") -> None: From c8a4dfa75d0141876ca2decb2dc972a2066822eb Mon Sep 17 00:00:00 2001 From: Rodrigo Brandao Date: Wed, 17 Jun 2026 12:43:31 -0700 Subject: [PATCH 2/8] Another commit --- .../microsoft_agents/hosting/teams/_utils.py | 45 ++ .../hosting/teams/meeting/meeting.py | 142 ++++ .../__init__.py} | 0 .../message_extension/message_extension.py | 475 ++++++++++++ .../teams/message_extension/route_handlers.py | 114 +++ .../hosting/teams/route_handlers.py | 118 +++ .../hosting/teams/task_module/__init__.py | 0 .../teams/task_module/route_handlers.py | 27 + .../hosting/teams/task_module/task_module.py | 121 +++ .../hosting/teams/teams_agent_extension.py | 714 ------------------ .../hosting/teams/teams_turn_context.py | 12 + .../hosting/teams/type_defs.py | 23 + 12 files changed, 1077 insertions(+), 714 deletions(-) create mode 100644 libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/_utils.py create mode 100644 libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/meeting/meeting.py rename libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/{teams_cloud_adapter.py => message_extension/__init__.py} (100%) create mode 100644 libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/message_extension/message_extension.py create mode 100644 libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/message_extension/route_handlers.py create mode 100644 libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/route_handlers.py create mode 100644 libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/task_module/__init__.py create mode 100644 libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/task_module/route_handlers.py create mode 100644 libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/task_module/task_module.py create mode 100644 libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/teams_turn_context.py create mode 100644 libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/type_defs.py diff --git a/libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/_utils.py b/libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/_utils.py new file mode 100644 index 000000000..9ba3b0780 --- /dev/null +++ b/libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/_utils.py @@ -0,0 +1,45 @@ +import re + +from typing import Any, Optional + +from microsoft_agents.activity import Activity +from microsoft_agents.hosting.core import TurnContext + +from .type_defs import ( + CommandSelector +) + +def _match_selector(selector: CommandSelector, value: Optional[str]) -> bool: + if selector is None: + return True + if value is None: + return False + if isinstance(selector, str): + return selector == value + return bool(re.match(selector, value)) + + +def _get_channel_event_type(context: TurnContext) -> Optional[str]: + data = context.activity.channel_data + if data is None: + return None + if isinstance(data, dict): + return data.get("eventType") or data.get("event_type") + return getattr(data, "event_type", None) + + +async def _send_invoke_response(context: TurnContext, body: Any = None) -> None: + serialized_body = None + if body is not None: + if hasattr(body, "model_dump"): + serialized_body = body.model_dump( + mode="json", by_alias=True, exclude_none=True + ) + else: + serialized_body = body + await context.send_activity( + Activity( + type=ActivityTypes.invoke_response, + value=InvokeResponse(status=int(HTTPStatus.OK), body=serialized_body), + ) + ) diff --git a/libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/meeting/meeting.py b/libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/meeting/meeting.py new file mode 100644 index 000000000..1ec9cce68 --- /dev/null +++ b/libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/meeting/meeting.py @@ -0,0 +1,142 @@ +class Meeting(Generic[StateT]): + """ + Route registration for Teams Meeting event activities. + Access via TeamsAgentExtension.meeting. + """ + + def __init__(self, app: AgentApplication[StateT]) -> None: + self._app = app + + def on_start( + self, + handler: Optional[Callable] = None, + *, + auth_handlers: Optional[list[str]] = None, + rank: RouteRank = RouteRank.DEFAULT, + ) -> Callable: + """Register a handler for meeting start events.""" + + def __selector(context: TurnContext) -> bool: + return ( + context.activity.type == ActivityTypes.event + and context.activity.name == "application/vnd.microsoft.meetingStart" + ) + + def __register(func: Callable) -> Callable: + async def __handler(context: TurnContext, state: StateT) -> None: + meeting = MeetingDetails.model_validate(context.activity.value or {}) + await func(context, state, meeting) + + self._app.add_route( + __selector, + __handler, + rank=rank, + auth_handlers=auth_handlers, + ) + return func + + if handler is not None: + return __register(handler) + return __register + + def on_end( + self, + handler: Optional[Callable] = None, + *, + auth_handlers: Optional[list[str]] = None, + rank: RouteRank = RouteRank.DEFAULT, + ) -> Callable: + """Register a handler for meeting end events.""" + + def __selector(context: TurnContext) -> bool: + return ( + context.activity.type == ActivityTypes.event + and context.activity.name == "application/vnd.microsoft.meetingEnd" + ) + + def __register(func: Callable) -> Callable: + async def __handler(context: TurnContext, state: StateT) -> None: + meeting = MeetingDetails.model_validate(context.activity.value or {}) + await func(context, state, meeting) + + self._app.add_route( + __selector, + __handler, + rank=rank, + auth_handlers=auth_handlers, + ) + return func + + if handler is not None: + return __register(handler) + return __register + + def on_participants_join( + self, + handler: Optional[Callable] = None, + *, + auth_handlers: Optional[list[str]] = None, + rank: RouteRank = RouteRank.DEFAULT, + ) -> Callable: + """Register a handler for meeting participant join events.""" + + def __selector(context: TurnContext) -> bool: + return ( + context.activity.type == ActivityTypes.event + and context.activity.name + == "application/vnd.microsoft.meetingParticipantJoin" + ) + + def __register(func: Callable) -> Callable: + async def __handler(context: TurnContext, state: StateT) -> None: + details = MeetingParticipantsEventDetails.model_validate( + context.activity.value or {} + ) + await func(context, state, details) + + self._app.add_route( + __selector, + __handler, + rank=rank, + auth_handlers=auth_handlers, + ) + return func + + if handler is not None: + return __register(handler) + return __register + + def on_participants_leave( + self, + handler: Optional[Callable] = None, + *, + auth_handlers: Optional[list[str]] = None, + rank: RouteRank = RouteRank.DEFAULT, + ) -> Callable: + """Register a handler for meeting participant leave events.""" + + def __selector(context: TurnContext) -> bool: + return ( + context.activity.type == ActivityTypes.event + and context.activity.name + == "application/vnd.microsoft.meetingParticipantLeave" + ) + + def __register(func: Callable) -> Callable: + async def __handler(context: TurnContext, state: StateT) -> None: + details = MeetingParticipantsEventDetails.model_validate( + context.activity.value or {} + ) + await func(context, state, details) + + self._app.add_route( + __selector, + __handler, + rank=rank, + auth_handlers=auth_handlers, + ) + return func + + if handler is not None: + return __register(handler) + return __register diff --git a/libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/teams_cloud_adapter.py b/libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/message_extension/__init__.py similarity index 100% rename from libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/teams_cloud_adapter.py rename to libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/message_extension/__init__.py diff --git a/libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/message_extension/message_extension.py b/libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/message_extension/message_extension.py new file mode 100644 index 000000000..adaf48c37 --- /dev/null +++ b/libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/message_extension/message_extension.py @@ -0,0 +1,475 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +from typing import Generic, Optional, Callable + +from microsoft_teams.api.models import ( + MessagingExtensionQuery, + MessagingExtensionAction, + MessagingExtensionResponse, + O365ConnectorCardActionQuery, + AppBasedLinkQuery +) + +from microsoft_agents.activity import ( + Activity, + ActivityTypes +) + +from microsoft_agents.hosting.core import ( + AgentApplication, + RouteRank, + TurnContext, +) + +from microsoft_agents.hosting.teams.teams_turn_context import TeamsTurnContext +from microsoft_agents.hosting.teams.type_defs import ( + StateT, + CommandSelector, + _RouteDecorator +) + +from microsoft_agents.hosting.teams._utils import ( + _match_selector, + _send_invoke_response +) + +from .route_handlers import ( + FetchActionHandler, + SubmitActionHandler, + MessagePreviewEditHandler, + QueryHandler, + SelectItemHandler, + QueryLinkHandler, + QueryUrlSettingHandler, + ConfigureSettingsHandler, + CardButtonClickedHandler +) + +class MessageExtension(Generic[StateT]): + """ + Route registration for Teams Message Extension (composeExtension) invoke activities. + Access via TeamsAgentExtension.message_extension. + """ + + def __init__(self, app: AgentApplication[StateT]) -> None: + self._app = app + + def on_query( + self, + command_id: CommandSelector = None, + *, + auth_handlers: Optional[list[str]] = None, + rank: RouteRank = RouteRank.DEFAULT, + ) -> _RouteDecorator[QueryHandler[StateT]]: + """Register a handler for composeExtension/query invokes.""" + + def __selector(context: TurnContext) -> bool: + if ( + context.activity.type != ActivityTypes.invoke + or context.activity.name != "composeExtension/query" + ): + return False + + value = context.activity.value + command_value: Optional[str] = None + if isinstance(value, dict): + command_value = value.get("commandId") or value.get("command_id") + elif value is not None: + command_value = getattr(value, "commandId", None) or getattr( + value, "command_id", None + ) + + return _match_selector(command_id, command_value) + + def __call(func: QueryHandler[StateT]) -> QueryHandler[StateT]: + async def __handler(context: TurnContext, state: StateT) -> None: + teams_context = TeamsTurnContext(context) + query = MessagingExtensionQuery.model_validate( + context.activity.value or {} + ) + response = await func(teams_context, state, query) + if response is not None: + await _send_invoke_response(context, response) + + self._app.add_route( + __selector, + __handler, + is_invoke=True, + rank=rank, + auth_handlers=auth_handlers, + ) + return func + + return __call + + def on_select_item( + self, + *, + auth_handlers: Optional[list[str]] = None, + rank: RouteRank = RouteRank.DEFAULT, + ) -> _RouteDecorator[SelectItemHandler[StateT]]: + """Register a handler for composeExtension/selectItem invokes.""" + + def __selector(context: TurnContext) -> bool: + return ( + context.activity.type == ActivityTypes.invoke + and context.activity.name == "composeExtension/selectItem" + ) + + def __call(func: SelectItemHandler[StateT]) -> SelectItemHandler[StateT]: + async def __handler(context: TurnContext, state: StateT) -> None: + teams_context = TeamsTurnContext(context) + response = await func(teams_context, state, context.activity.value) + if response is not None: + await _send_invoke_response(context, response) + + self._app.add_route( + __selector, + __handler, + is_invoke=True, + rank=rank, + auth_handlers=auth_handlers, + ) + return func + + return __call + + def on_submit_action( + self, + command_id: CommandSelector = None, + *, + auth_handlers: Optional[list[str]] = None, + rank: RouteRank = RouteRank.DEFAULT, + ) -> _RouteDecorator[SubmitActionHandler[StateT]]: + """Register a handler for composeExtension/submitAction invokes (not bot message preview).""" + + def __selector(context: TurnContext) -> bool: + if ( + context.activity.type != ActivityTypes.invoke + or context.activity.name != "composeExtension/submitAction" + ): + return False + value = context.activity.value + if isinstance(value, dict): + bot_message_preview_action = value.get("botMessagePreviewAction") + resolved_command_id = value.get("commandId") or value.get("command_id") + else: + bot_message_preview_action = getattr( + value, "botMessagePreviewAction", None + ) + resolved_command_id = getattr(value, "commandId", None) or getattr( + value, "command_id", None + ) + if bot_message_preview_action: + return False + return _match_selector(command_id, resolved_command_id) + + def __call(func: SubmitActionHandler[StateT]) -> SubmitActionHandler[StateT]: + async def __handler(context: TurnContext, state: StateT) -> None: + teams_context = TeamsTurnContext(context) + action = MessagingExtensionAction.model_validate( + context.activity.value or {} + ) + response = await func(teams_context, state, action) + if response is not None: + await _send_invoke_response(context, response) + + self._app.add_route( + __selector, + __handler, + is_invoke=True, + rank=rank, + auth_handlers=auth_handlers, + ) + return func + + return __call + + def on_message_preview_edit( + self, + command_id: CommandSelector = None, + *, + auth_handlers: Optional[list[str]] = None, + rank: RouteRank = RouteRank.DEFAULT, + ) -> _RouteDecorator[MessagePreviewEditHandler[StateT]]: + """Register a handler for composeExtension/submitAction with botMessagePreviewAction == 'edit'.""" + + def __selector(context: TurnContext) -> bool: + if ( + context.activity.type != ActivityTypes.invoke + or context.activity.name != "composeExtension/submitAction" + ): + return False + value = context.activity.value or {} + if value.get("botMessagePreviewAction") != "edit": + return False + return _match_selector(command_id, value.get("commandId")) + + def __call(func: MessagePreviewEditHandler[StateT]) -> MessagePreviewEditHandler[StateT]: + async def __handler(context: TurnContext, state: StateT) -> None: + teams_context = TeamsTurnContext(context) + action = MessagingExtensionAction.model_validate( + context.activity.value or {} + ) + response = await func(teams_context, state, action) + if response is not None: + await _send_invoke_response(context, response) + + self._app.add_route( + __selector, + __handler, + is_invoke=True, + rank=rank, + auth_handlers=auth_handlers, + ) + return func + + return __call + + def on_message_preview_send( + self, + command_id: CommandSelector = None, + *, + auth_handlers: Optional[list[str]] = None, + rank: RouteRank = RouteRank.DEFAULT, + ) -> _RouteDecorator[MessagePreviewSendHandler[StateT]]: + """Register a handler for composeExtension/submitAction with botMessagePreviewAction == 'send'.""" + + def __selector(context: TurnContext) -> bool: + if ( + context.activity.type != ActivityTypes.invoke + or context.activity.name != "composeExtension/submitAction" + ): + return False + value = context.activity.value or {} + if value.get("botMessagePreviewAction") != "send": + return False + return _match_selector(command_id, value.get("commandId")) + + def __call(func: MessagePreviewSendHandler[StateT]) -> MessagePreviewSendHandler[StateT]: + async def __handler(context: TurnContext, state: StateT) -> None: + teams_context = TeamsTurnContext(context) + action = MessagingExtensionAction.model_validate( + context.activity.value or {} + ) + response = await func(teams_context, state, action) + if response is not None: + await _send_invoke_response(context, response) + + self._app.add_route( + __selector, + __handler, + is_invoke=True, + rank=rank, + auth_handlers=auth_handlers, + ) + return func + + return __call + + def on_task_fetch( + self, + command_id: CommandSelector = None, + *, + auth_handlers: Optional[list[str]] = None, + rank: RouteRank = RouteRank.DEFAULT, + ) -> _RouteDecorator[TaskFetchHandler[StateT]]: + """Register a handler for composeExtension/fetchTask invokes.""" + + def __selector(context: TurnContext) -> bool: + return ( + context.activity.type == ActivityTypes.invoke + and context.activity.name == "composeExtension/fetchTask" + and _match_selector( + command_id, + (context.activity.value or {}).get("commandId"), + ) + ) + + def __call(func: TaskFetchHandler[StateT]) -> TaskFetchHandler[StateT]: + async def __handler(context: TurnContext, state: StateT) -> None: + action = MessagingExtensionAction.model_validate( + context.activity.value or {} + ) + response = await func(context, state, action) + if response is not None: + await _send_invoke_response(context, response) + + self._app.add_route( + __selector, + __handler, + is_invoke=True, + rank=rank, + auth_handlers=auth_handlers, + ) + return func + + return __call + + def on_query_link( + self, + *, + auth_handlers: Optional[list[str]] = None, + rank: RouteRank = RouteRank.DEFAULT, + ) -> _RouteDecorator[QueryLinkHandler[StateT]]: + """Register a handler for composeExtension/queryLink invokes.""" + + def __selector(context: TurnContext) -> bool: + return ( + context.activity.type == ActivityTypes.invoke + and context.activity.name == "composeExtension/queryLink" + ) + + def __call(func: QueryLinkHandler[StateT]) -> QueryLinkHandler[StateT]: + async def __handler(context: TurnContext, state: StateT) -> None: + teams_context = TeamsTurnContext(context) + query = AppBasedLinkQuery.model_validate(context.activity.value or {}) + response = await func(teams_context, state, query) + if response is not None: + await _send_invoke_response(context, response) + + self._app.add_route( + __selector, + __handler, + is_invoke=True, + rank=rank, + auth_handlers=auth_handlers, + ) + return func + + return __call + + def on_anonymous_query_link( + self, + *, + auth_handlers: Optional[list[str]] = None, + rank: RouteRank = RouteRank.DEFAULT, + ) -> _RouteDecorator[QueryLinkHandler[StateT]]: + """Register a handler for composeExtension/anonymousQueryLink invokes.""" + + def __selector(context: TurnContext) -> bool: + return ( + context.activity.type == ActivityTypes.invoke + and context.activity.name == "composeExtension/anonymousQueryLink" + ) + + def __register(func: QueryLinkHandler[StateT]) -> QueryLinkHandler[StateT]: + async def __handler(context: TurnContext, state: StateT) -> None: + teams_context = TeamsTurnContext(context) + query = AppBasedLinkQuery.model_validate(context.activity.value or {}) + response = await func(teams_context, state, query) + if response is not None: + await _send_invoke_response(context, response) + + self._app.add_route( + __selector, + __handler, + is_invoke=True, + rank=rank, + auth_handlers=auth_handlers, + ) + return func + + return __register + + def on_query_url_setting( + self, + *, + auth_handlers: Optional[list[str]] = None, + rank: RouteRank = RouteRank.DEFAULT, + ) -> _RouteDecorator[QueryUrlSettingHandler[StateT]]: + """Register a handler for composeExtension/querySettingUrl invokes.""" + + def __selector(context: TurnContext) -> bool: + return ( + context.activity.type == ActivityTypes.invoke + and context.activity.name == "composeExtension/querySettingUrl" + ) + + def __register(func: QueryUrlSettingHandler[StateT]) -> QueryUrlSettingHandler[StateT]: + async def __handler(context: TurnContext, state: StateT) -> None: + teams_context = TeamsTurnContext(context) + query = MessagingExtensionQuery.model_validate( + context.activity.value or {} + ) + response = await func(teams_context, state, query) + if response is not None: + await _send_invoke_response(context, response) + + self._app.add_route( + __selector, + __handler, + is_invoke=True, + rank=rank, + auth_handlers=auth_handlers, + ) + return func + + return __register + + def on_configure_settings( + self, + handler: Optional[Callable] = None, + *, + auth_handlers: Optional[list[str]] = None, + rank: RouteRank = RouteRank.DEFAULT, + ) -> Callable: + """Register a handler for composeExtension/setting invokes.""" + + def __selector(context: TurnContext) -> bool: + return ( + context.activity.type == ActivityTypes.invoke + and context.activity.name == "composeExtension/setting" + ) + + def __register(func: Callable) -> Callable: + async def __handler(context: TurnContext, state: StateT) -> None: + await func(context, state, context.activity.value) + await _send_invoke_response(context) + + self._app.add_route( + __selector, + __handler, + is_invoke=True, + rank=rank, + auth_handlers=auth_handlers, + ) + return func + + if handler is not None: + return __register(handler) + return __register + + def on_card_button_clicked( + self, + handler: Optional[Callable] = None, + *, + auth_handlers: Optional[list[str]] = None, + rank: RouteRank = RouteRank.DEFAULT, + ) -> Callable: + """Register a handler for composeExtension/onCardButtonClicked invokes.""" + + def __selector(context: TurnContext) -> bool: + return ( + context.activity.type == ActivityTypes.invoke + and context.activity.name == "composeExtension/onCardButtonClicked" + ) + + def __register(func: Callable) -> Callable: + async def __handler(context: TurnContext, state: StateT) -> None: + await func(context, state, context.activity.value) + await _send_invoke_response(context) + + self._app.add_route( + __selector, + __handler, + is_invoke=True, + rank=rank, + auth_handlers=auth_handlers, + ) + return func + + if handler is not None: + return __register(handler) + return __register diff --git a/libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/message_extension/route_handlers.py b/libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/message_extension/route_handlers.py new file mode 100644 index 000000000..a37c79fa4 --- /dev/null +++ b/libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/message_extension/route_handlers.py @@ -0,0 +1,114 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +from typing import ( + Any, + Awaitable, + Protocol, +) + +from microsoft_teams.api.models import ( + Channel, + Team, + MeetingDetails, + MeetingParticipantsEventDetails, + MessageExtensionAction, + MessageExtensionQuery, + MessageExtensionActionResponse, + MessageExtensionResponse, + O365ConnectorCardActionQuery, + TaskModuleRequest, + TaskModuleResponse, + AppBasedLinkQuery +) + +from microsoft_agents.activity import Activity + +from microsoft_agents.hosting.teams_turn_context import TeamsTurnContext +from microsoft_agents.hosting.teams.type_defs import StateT + +class FetchActionHandler(Protocol[StateT]): + def __call__( + self, + context: TeamsTurnContext, + state: StateT, + action: MessageExtensionAction + ) -> Awaitable[MessageExtensionActionResponse]: ... + +class SubmitActionHandler(Protocol[StateT]): + def __call__( + self, + context: TeamsTurnContext, + state: StateT, + action: MessageExtensionAction + ) -> Awaitable[MessageExtensionResponse]: ... + +class MessagePreviewEditHandler(Protocol[StateT]): + def __call__( + self, + context: TeamsTurnContext, + state: StateT, + activity_preview: Activity + ) -> Awaitable[MessageExtensionResponse]: + ... + +class MessagePreviewSendHandler(Protocol[StateT]): + def __call__( + self, + context: TeamsTurnContext, + activity_preview: Activity + ) -> Awaitable[None]: + ... + +class QueryHandler(Protocol[StateT]): + def __call__( + self, + context: TeamsTurnContext, + state: StateT, + query: MessageExtensionQuery + ) -> Awaitable[MessageExtensionResponse]: + ... + +class SelectItemHandler(Protocol[StateT]): + def __call__( + self, + context: TeamsTurnContext, + state: StateT, + item: Any + ) -> Awaitable[MessageExtensionResponse]: + ... + +class QueryLinkHandler(Protocol[StateT]): + def __call__( + self, + context: TeamsTurnContext, + state: StateT, + query: AppBasedLinkQuery + ) -> Awaitable[MessageExtensionResponse]: + ... + +class QueryUrlSettingHandler(Protocol[StateT]): + def __call__( + self, + context: TeamsTurnContext, + state: StateT, + ) -> Awaitable[MessageExtensionResponse]: + ... + +class ConfigureSettingsHandler(Protocol[StateT]): + def __call__( + self, + context: TeamsTurnContext, + state: StateT, + query: MessageExtensionQuery + ) -> Awaitable[MessageExtensionResponse]: + ... + +class CardButtonClickedHandler(Protocol[StateT]): + def __call__( + self, + context: TeamsTurnContext, + state: StateT, + card: Any + ) -> Awaitable[None]: + ... \ No newline at end of file diff --git a/libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/route_handlers.py b/libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/route_handlers.py new file mode 100644 index 000000000..e81e237a6 --- /dev/null +++ b/libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/route_handlers.py @@ -0,0 +1,118 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +from typing import ( + Any, + Awaitable, + Protocol, +) + +from microsoft_agents.activity import Activity + +from microsoft_teams.api.models import ( + Channel, + Team, + MeetingDetails, + MeetingParticipantsEventDetails, + MessagingExtensionAction, + MessagingExtensionQuery, + MessageExtensionActionResponse, + MessagingExtensionResponse, + O365ConnectorCardActionQuery, + TaskModuleRequest, + TaskModuleResponse +) + +from .teams_turn_context import TeamsTurnContext +from .type_defs import StateT + +class TeamsRouteHandler(Protocol[StateT]): + def __call__(self, context: TeamsTurnContext, state: StateT) -> Awaitable[None]: ... + +# Meetings route handlers + +class MeetingStartHandler(Protocol[StateT]): + def __call__( + self, + context: TeamsTurnContext, + state: StateT, + meeting: MeetingDetails + ) -> Awaitable[None]: ... + +class MeetingEndHandler(Protocol[StateT]): + def __call__( + self, + context: TeamsTurnContext, + state: StateT, + meeting: MeetingDetails + ) -> Awaitable[None]: ... + +class MeetingParticipantsEventHandler(Protocol[StateT]): + def __call__( + self, + context: TeamsTurnContext, + state: StateT, + meeting: MeetingParticipantsEventDetails + ) -> Awaitable[None]: ... + +# Message extension route handlers + +# Messages route handler + +class O365ConnectorCardActionHandler(Protocol[StateT]): + def __call__( + self, + context: TeamsTurnContext, + state: StateT, + query: O365ConnectorCardActionQuery + ) -> Awaitable[None]: + ... + +class ReadReceiptHandler(Protocol[StateT]): + def __call__( + self, + context: TeamsTurnContext, + state: StateT, + data: dict + ) -> Awaitable[None]: + ... + +# Task Modules route handlers + +class TaskFetchHandler(Protocol[StateT]): + def __call__( + self, + context: TeamsTurnContext, + state: StateT, + request: TaskModuleRequest, + ) -> Awaitable[TaskModuleResponse]: + ... + +class TaskSubmitHandler(Protocol[StateT]): + def __call__( + self, + context: TeamsTurnContext, + state: StateT, + request: TaskModuleRequest, + ) -> Awaitable[TaskModuleResponse]: + ... + +# Teams Channels route handlers + +class ChannelUpdateHandler(Protocol[StateT]): + def __call__( + self, + context: TeamsTurnContext, + state: StateT, + data: Channel + ) -> Awaitable[None]: + ... + +class TeamUpdateHandler(Protocol[StateT]): + def __call__( + self, + context: TeamsTurnContext, + state: StateT, + data: Team + ) -> Awaitable[None]: + ... \ No newline at end of file diff --git a/libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/task_module/__init__.py b/libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/task_module/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/task_module/route_handlers.py b/libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/task_module/route_handlers.py new file mode 100644 index 000000000..c0489cd71 --- /dev/null +++ b/libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/task_module/route_handlers.py @@ -0,0 +1,27 @@ +from typing import Awaitable, Protocol + +from microsoft_teams.api.models import ( + TaskModuleRequest, + TaskModuleResponse +) + +from microsoft_agents.hosting.teams.teams_turn_context import TeamsTurnContext +from microsoft_agents.hosting.teams.type_defs import StateT + +class TaskFetchHandler(Protocol[StateT]): + def __call__( + self, + context: TeamsTurnContext, + state: StateT, + request: TaskModuleRequest, + ) -> Awaitable[TaskModuleResponse]: + ... + +class TaskSubmitHandler(Protocol[StateT]): + def __call__( + self, + context: TeamsTurnContext, + state: StateT, + request: TaskModuleRequest, + ) -> Awaitable[TaskModuleResponse]: + ... \ No newline at end of file diff --git a/libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/task_module/task_module.py b/libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/task_module/task_module.py new file mode 100644 index 000000000..f0e776f10 --- /dev/null +++ b/libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/task_module/task_module.py @@ -0,0 +1,121 @@ +from typing import ( + Any, + Generic, + Optional +) + +from microsoft_agents.hosting.core import ( + AgentApplication, + RouteRank, + TurnContext +) + +from microsoft_agents.hosting.teams.teams_turn_context import TeamsTurnContext +from microsoft_agents.hosting.teams._type_defs import ( + CommandSelector, + RouteDecorator, + StateT, +) +from microsoft_agents.hosting.teams._utils import ( + _match_selector, + _send_invoke_response, +) + +from .route_handlers import ( +) + +class TaskModule(Generic[StateT]): + """ + Route registration for Teams Task Module (task/fetch, task/submit) invoke activities. + Access via TeamsAgentExtension.task_module. + """ + + def __init__(self, app: AgentApplication[StateT]) -> None: + self._app = app + + @staticmethod + def _get_verb(value: Optional[Any]) -> Optional[str]: + if not isinstance(value, dict): + return None + data = value.get("data") + if isinstance(data, dict): + return data.get("verb") + return None + + def on_fetch( + self, + verb: CommandSelector = None, + *, + auth_handlers: Optional[list[str]] = None, + rank: RouteRank = RouteRank.DEFAULT, + ) -> RouteDecorator[]: + """Register a handler for task/fetch invokes. + + :param verb: Optional verb string or regex to match against task data. + If None, matches all task/fetch invokes. + """ + + def __selector(context: TurnContext) -> bool: + if ( + context.activity.type != ActivityTypes.invoke + or context.activity.name != "task/fetch" + ): + return False + return _match_selector(verb, TaskModule._get_verb(context.activity.value)) + + def __call(func: Callable) -> Callable: + async def __handler(context: TurnContext, state: StateT) -> None: + request = TaskModuleRequest.model_validate(context.activity.value or {}) + response = await func(context, state, request) + if response is not None: + await _send_invoke_response(context, response) + + self._app.add_route( + __selector, + __handler, + is_invoke=True, + rank=rank, + auth_handlers=auth_handlers, + ) + return func + + return __call + + def on_submit( + self, + verb: CommandSelector = None, + *, + auth_handlers: Optional[list[str]] = None, + rank: RouteRank = RouteRank.DEFAULT, + ) -> Callable: + """Register a handler for task/submit invokes. + + :param verb: Optional verb string or regex to match against task data. + If None, matches all task/submit invokes. + """ + + def __selector(context: TurnContext) -> bool: + if ( + context.activity.type != ActivityTypes.invoke + or context.activity.name != "task/submit" + ): + return False + return _match_selector(verb, TaskModule._get_verb(context.activity.value)) + + def __call(func: Callable) -> Callable: + async def __handler(context: TurnContext, state: StateT) -> None: + request = TaskModuleRequest.model_validate(context.activity.value or {}) + response = await func(context, state, request) + if response is not None: + await _send_invoke_response(context, response) + + self._app.add_route( + __selector, + __handler, + is_invoke=True, + rank=rank, + auth_handlers=auth_handlers, + ) + return func + + return __call \ No newline at end of file diff --git a/libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/teams_agent_extension.py b/libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/teams_agent_extension.py index 34cdef2fd..2115b0d66 100644 --- a/libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/teams_agent_extension.py +++ b/libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/teams_agent_extension.py @@ -28,720 +28,6 @@ TaskModuleRequest, ) -StateT = TypeVar("StateT", bound=TurnState) - -CommandSelector = str | Pattern[str] | None - - -def _match_selector(selector: CommandSelector, value: Optional[str]) -> bool: - if selector is None: - return True - if value is None: - return False - if isinstance(selector, str): - return selector == value - return bool(re.match(selector, value)) - - -def _get_channel_event_type(context: TurnContext) -> Optional[str]: - data = context.activity.channel_data - if data is None: - return None - if isinstance(data, dict): - return data.get("eventType") or data.get("event_type") - return getattr(data, "event_type", None) - - -async def _send_invoke_response(context: TurnContext, body: Any = None) -> None: - serialized_body = None - if body is not None: - if hasattr(body, "model_dump"): - serialized_body = body.model_dump( - mode="json", by_alias=True, exclude_none=True - ) - else: - serialized_body = body - await context.send_activity( - Activity( - type=ActivityTypes.invoke_response, - value=InvokeResponse(status=int(HTTPStatus.OK), body=serialized_body), - ) - ) - - -class MessageExtension(Generic[StateT]): - """ - Route registration for Teams Message Extension (composeExtension) invoke activities. - Access via TeamsAgentExtension.message_extension. - """ - - def __init__(self, app: AgentApplication[StateT]) -> None: - self._app = app - - def on_query( - self, - command_id: CommandSelector = None, - *, - auth_handlers: Optional[list[str]] = None, - rank: RouteRank = RouteRank.DEFAULT, - ) -> Callable: - """Register a handler for composeExtension/query invokes.""" - - def __selector(context: TurnContext) -> bool: - if ( - context.activity.type != ActivityTypes.invoke - or context.activity.name != "composeExtension/query" - ): - return False - - value = context.activity.value - command_value: Optional[str] = None - if isinstance(value, dict): - command_value = value.get("commandId") or value.get("command_id") - elif value is not None: - command_value = getattr(value, "commandId", None) or getattr( - value, "command_id", None - ) - - return _match_selector(command_id, command_value) - - def __call(func: Callable) -> Callable: - async def __handler(context: TurnContext, state: StateT) -> None: - query = MessagingExtensionQuery.model_validate( - context.activity.value or {} - ) - response = await func(context, state, query) - if response is not None: - await _send_invoke_response(context, response) - - self._app.add_route( - __selector, - __handler, - is_invoke=True, - rank=rank, - auth_handlers=auth_handlers, - ) - return func - - return __call - - def on_select_item( - self, - handler: Optional[Callable] = None, - *, - auth_handlers: Optional[list[str]] = None, - rank: RouteRank = RouteRank.DEFAULT, - ) -> Callable: - """Register a handler for composeExtension/selectItem invokes.""" - - def __selector(context: TurnContext) -> bool: - return ( - context.activity.type == ActivityTypes.invoke - and context.activity.name == "composeExtension/selectItem" - ) - - def __register(func: Callable) -> Callable: - async def __handler(context: TurnContext, state: StateT) -> None: - response = await func(context, state, context.activity.value) - if response is not None: - await _send_invoke_response(context, response) - - self._app.add_route( - __selector, - __handler, - is_invoke=True, - rank=rank, - auth_handlers=auth_handlers, - ) - return func - - if handler is not None: - return __register(handler) - return __register - - def on_submit_action( - self, - command_id: CommandSelector = None, - *, - auth_handlers: Optional[list[str]] = None, - rank: RouteRank = RouteRank.DEFAULT, - ) -> Callable: - """Register a handler for composeExtension/submitAction invokes (not bot message preview).""" - - def __selector(context: TurnContext) -> bool: - if ( - context.activity.type != ActivityTypes.invoke - or context.activity.name != "composeExtension/submitAction" - ): - return False - value = context.activity.value - if isinstance(value, dict): - bot_message_preview_action = value.get("botMessagePreviewAction") - resolved_command_id = value.get("commandId") or value.get("command_id") - else: - bot_message_preview_action = getattr( - value, "botMessagePreviewAction", None - ) - resolved_command_id = getattr(value, "commandId", None) or getattr( - value, "command_id", None - ) - if bot_message_preview_action: - return False - return _match_selector(command_id, resolved_command_id) - - def __call(func: Callable) -> Callable: - async def __handler(context: TurnContext, state: StateT) -> None: - action = MessagingExtensionAction.model_validate( - context.activity.value or {} - ) - response = await func(context, state, action) - if response is not None: - await _send_invoke_response(context, response) - - self._app.add_route( - __selector, - __handler, - is_invoke=True, - rank=rank, - auth_handlers=auth_handlers, - ) - return func - - return __call - - def on_agent_message_preview_edit( - self, - command_id: CommandSelector = None, - *, - auth_handlers: Optional[list[str]] = None, - rank: RouteRank = RouteRank.DEFAULT, - ) -> Callable: - """Register a handler for composeExtension/submitAction with botMessagePreviewAction == 'edit'.""" - - def __selector(context: TurnContext) -> bool: - if ( - context.activity.type != ActivityTypes.invoke - or context.activity.name != "composeExtension/submitAction" - ): - return False - value = context.activity.value or {} - if value.get("botMessagePreviewAction") != "edit": - return False - return _match_selector(command_id, value.get("commandId")) - - def __call(func: Callable) -> Callable: - async def __handler(context: TurnContext, state: StateT) -> None: - action = MessagingExtensionAction.model_validate( - context.activity.value or {} - ) - response = await func(context, state, action) - if response is not None: - await _send_invoke_response(context, response) - - self._app.add_route( - __selector, - __handler, - is_invoke=True, - rank=rank, - auth_handlers=auth_handlers, - ) - return func - - return __call - - def on_agent_message_preview_send( - self, - command_id: CommandSelector = None, - *, - auth_handlers: Optional[list[str]] = None, - rank: RouteRank = RouteRank.DEFAULT, - ) -> Callable: - """Register a handler for composeExtension/submitAction with botMessagePreviewAction == 'send'.""" - - def __selector(context: TurnContext) -> bool: - if ( - context.activity.type != ActivityTypes.invoke - or context.activity.name != "composeExtension/submitAction" - ): - return False - value = context.activity.value or {} - if value.get("botMessagePreviewAction") != "send": - return False - return _match_selector(command_id, value.get("commandId")) - - def __call(func: Callable) -> Callable: - async def __handler(context: TurnContext, state: StateT) -> None: - action = MessagingExtensionAction.model_validate( - context.activity.value or {} - ) - response = await func(context, state, action) - if response is not None: - await _send_invoke_response(context, response) - - self._app.add_route( - __selector, - __handler, - is_invoke=True, - rank=rank, - auth_handlers=auth_handlers, - ) - return func - - return __call - - def on_fetch_task( - self, - command_id: CommandSelector = None, - *, - auth_handlers: Optional[list[str]] = None, - rank: RouteRank = RouteRank.DEFAULT, - ) -> Callable: - """Register a handler for composeExtension/fetchTask invokes.""" - - def __selector(context: TurnContext) -> bool: - return ( - context.activity.type == ActivityTypes.invoke - and context.activity.name == "composeExtension/fetchTask" - and _match_selector( - command_id, - (context.activity.value or {}).get("commandId"), - ) - ) - - def __call(func: Callable) -> Callable: - async def __handler(context: TurnContext, state: StateT) -> None: - action = MessagingExtensionAction.model_validate( - context.activity.value or {} - ) - response = await func(context, state, action) - if response is not None: - await _send_invoke_response(context, response) - - self._app.add_route( - __selector, - __handler, - is_invoke=True, - rank=rank, - auth_handlers=auth_handlers, - ) - return func - - return __call - - def on_query_link( - self, - handler: Optional[Callable] = None, - *, - auth_handlers: Optional[list[str]] = None, - rank: RouteRank = RouteRank.DEFAULT, - ) -> Callable: - """Register a handler for composeExtension/queryLink invokes.""" - - def __selector(context: TurnContext) -> bool: - return ( - context.activity.type == ActivityTypes.invoke - and context.activity.name == "composeExtension/queryLink" - ) - - def __register(func: Callable) -> Callable: - async def __handler(context: TurnContext, state: StateT) -> None: - query = AppBasedLinkQuery.model_validate(context.activity.value or {}) - response = await func(context, state, query) - if response is not None: - await _send_invoke_response(context, response) - - self._app.add_route( - __selector, - __handler, - is_invoke=True, - rank=rank, - auth_handlers=auth_handlers, - ) - return func - - if handler is not None: - return __register(handler) - return __register - - def on_anonymous_query_link( - self, - handler: Optional[Callable] = None, - *, - auth_handlers: Optional[list[str]] = None, - rank: RouteRank = RouteRank.DEFAULT, - ) -> Callable: - """Register a handler for composeExtension/anonymousQueryLink invokes.""" - - def __selector(context: TurnContext) -> bool: - return ( - context.activity.type == ActivityTypes.invoke - and context.activity.name == "composeExtension/anonymousQueryLink" - ) - - def __register(func: Callable) -> Callable: - async def __handler(context: TurnContext, state: StateT) -> None: - query = AppBasedLinkQuery.model_validate(context.activity.value or {}) - response = await func(context, state, query) - if response is not None: - await _send_invoke_response(context, response) - - self._app.add_route( - __selector, - __handler, - is_invoke=True, - rank=rank, - auth_handlers=auth_handlers, - ) - return func - - if handler is not None: - return __register(handler) - return __register - - def on_query_url_setting( - self, - handler: Optional[Callable] = None, - *, - auth_handlers: Optional[list[str]] = None, - rank: RouteRank = RouteRank.DEFAULT, - ) -> Callable: - """Register a handler for composeExtension/querySettingUrl invokes.""" - - def __selector(context: TurnContext) -> bool: - return ( - context.activity.type == ActivityTypes.invoke - and context.activity.name == "composeExtension/querySettingUrl" - ) - - def __register(func: Callable) -> Callable: - async def __handler(context: TurnContext, state: StateT) -> None: - query = MessagingExtensionQuery.model_validate( - context.activity.value or {} - ) - response = await func(context, state, query) - if response is not None: - await _send_invoke_response(context, response) - - self._app.add_route( - __selector, - __handler, - is_invoke=True, - rank=rank, - auth_handlers=auth_handlers, - ) - return func - - if handler is not None: - return __register(handler) - return __register - - def on_configure_settings( - self, - handler: Optional[Callable] = None, - *, - auth_handlers: Optional[list[str]] = None, - rank: RouteRank = RouteRank.DEFAULT, - ) -> Callable: - """Register a handler for composeExtension/setting invokes.""" - - def __selector(context: TurnContext) -> bool: - return ( - context.activity.type == ActivityTypes.invoke - and context.activity.name == "composeExtension/setting" - ) - - def __register(func: Callable) -> Callable: - async def __handler(context: TurnContext, state: StateT) -> None: - await func(context, state, context.activity.value) - await _send_invoke_response(context) - - self._app.add_route( - __selector, - __handler, - is_invoke=True, - rank=rank, - auth_handlers=auth_handlers, - ) - return func - - if handler is not None: - return __register(handler) - return __register - - def on_card_button_clicked( - self, - handler: Optional[Callable] = None, - *, - auth_handlers: Optional[list[str]] = None, - rank: RouteRank = RouteRank.DEFAULT, - ) -> Callable: - """Register a handler for composeExtension/onCardButtonClicked invokes.""" - - def __selector(context: TurnContext) -> bool: - return ( - context.activity.type == ActivityTypes.invoke - and context.activity.name == "composeExtension/onCardButtonClicked" - ) - - def __register(func: Callable) -> Callable: - async def __handler(context: TurnContext, state: StateT) -> None: - await func(context, state, context.activity.value) - await _send_invoke_response(context) - - self._app.add_route( - __selector, - __handler, - is_invoke=True, - rank=rank, - auth_handlers=auth_handlers, - ) - return func - - if handler is not None: - return __register(handler) - return __register - - -class TaskModule(Generic[StateT]): - """ - Route registration for Teams Task Module (task/fetch, task/submit) invoke activities. - Access via TeamsAgentExtension.task_module. - """ - - def __init__(self, app: AgentApplication[StateT]) -> None: - self._app = app - - @staticmethod - def _get_verb(value: Optional[Any]) -> Optional[str]: - if not isinstance(value, dict): - return None - data = value.get("data") - if isinstance(data, dict): - return data.get("verb") - return None - - def on_fetch( - self, - verb: CommandSelector = None, - *, - auth_handlers: Optional[list[str]] = None, - rank: RouteRank = RouteRank.DEFAULT, - ) -> Callable: - """Register a handler for task/fetch invokes. - - :param verb: Optional verb string or regex to match against task data. - If None, matches all task/fetch invokes. - """ - - def __selector(context: TurnContext) -> bool: - if ( - context.activity.type != ActivityTypes.invoke - or context.activity.name != "task/fetch" - ): - return False - return _match_selector(verb, TaskModule._get_verb(context.activity.value)) - - def __call(func: Callable) -> Callable: - async def __handler(context: TurnContext, state: StateT) -> None: - request = TaskModuleRequest.model_validate(context.activity.value or {}) - response = await func(context, state, request) - if response is not None: - await _send_invoke_response(context, response) - - self._app.add_route( - __selector, - __handler, - is_invoke=True, - rank=rank, - auth_handlers=auth_handlers, - ) - return func - - return __call - - def on_submit( - self, - verb: CommandSelector = None, - *, - auth_handlers: Optional[list[str]] = None, - rank: RouteRank = RouteRank.DEFAULT, - ) -> Callable: - """Register a handler for task/submit invokes. - - :param verb: Optional verb string or regex to match against task data. - If None, matches all task/submit invokes. - """ - - def __selector(context: TurnContext) -> bool: - if ( - context.activity.type != ActivityTypes.invoke - or context.activity.name != "task/submit" - ): - return False - return _match_selector(verb, TaskModule._get_verb(context.activity.value)) - - def __call(func: Callable) -> Callable: - async def __handler(context: TurnContext, state: StateT) -> None: - request = TaskModuleRequest.model_validate(context.activity.value or {}) - response = await func(context, state, request) - if response is not None: - await _send_invoke_response(context, response) - - self._app.add_route( - __selector, - __handler, - is_invoke=True, - rank=rank, - auth_handlers=auth_handlers, - ) - return func - - return __call - - -class Meeting(Generic[StateT]): - """ - Route registration for Teams Meeting event activities. - Access via TeamsAgentExtension.meeting. - """ - - def __init__(self, app: AgentApplication[StateT]) -> None: - self._app = app - - def on_start( - self, - handler: Optional[Callable] = None, - *, - auth_handlers: Optional[list[str]] = None, - rank: RouteRank = RouteRank.DEFAULT, - ) -> Callable: - """Register a handler for meeting start events.""" - - def __selector(context: TurnContext) -> bool: - return ( - context.activity.type == ActivityTypes.event - and context.activity.name == "application/vnd.microsoft.meetingStart" - ) - - def __register(func: Callable) -> Callable: - async def __handler(context: TurnContext, state: StateT) -> None: - meeting = MeetingDetails.model_validate(context.activity.value or {}) - await func(context, state, meeting) - - self._app.add_route( - __selector, - __handler, - rank=rank, - auth_handlers=auth_handlers, - ) - return func - - if handler is not None: - return __register(handler) - return __register - - def on_end( - self, - handler: Optional[Callable] = None, - *, - auth_handlers: Optional[list[str]] = None, - rank: RouteRank = RouteRank.DEFAULT, - ) -> Callable: - """Register a handler for meeting end events.""" - - def __selector(context: TurnContext) -> bool: - return ( - context.activity.type == ActivityTypes.event - and context.activity.name == "application/vnd.microsoft.meetingEnd" - ) - - def __register(func: Callable) -> Callable: - async def __handler(context: TurnContext, state: StateT) -> None: - meeting = MeetingDetails.model_validate(context.activity.value or {}) - await func(context, state, meeting) - - self._app.add_route( - __selector, - __handler, - rank=rank, - auth_handlers=auth_handlers, - ) - return func - - if handler is not None: - return __register(handler) - return __register - - def on_participants_join( - self, - handler: Optional[Callable] = None, - *, - auth_handlers: Optional[list[str]] = None, - rank: RouteRank = RouteRank.DEFAULT, - ) -> Callable: - """Register a handler for meeting participant join events.""" - - def __selector(context: TurnContext) -> bool: - return ( - context.activity.type == ActivityTypes.event - and context.activity.name - == "application/vnd.microsoft.meetingParticipantJoin" - ) - - def __register(func: Callable) -> Callable: - async def __handler(context: TurnContext, state: StateT) -> None: - details = MeetingParticipantsEventDetails.model_validate( - context.activity.value or {} - ) - await func(context, state, details) - - self._app.add_route( - __selector, - __handler, - rank=rank, - auth_handlers=auth_handlers, - ) - return func - - if handler is not None: - return __register(handler) - return __register - - def on_participants_leave( - self, - handler: Optional[Callable] = None, - *, - auth_handlers: Optional[list[str]] = None, - rank: RouteRank = RouteRank.DEFAULT, - ) -> Callable: - """Register a handler for meeting participant leave events.""" - - def __selector(context: TurnContext) -> bool: - return ( - context.activity.type == ActivityTypes.event - and context.activity.name - == "application/vnd.microsoft.meetingParticipantLeave" - ) - - def __register(func: Callable) -> Callable: - async def __handler(context: TurnContext, state: StateT) -> None: - details = MeetingParticipantsEventDetails.model_validate( - context.activity.value or {} - ) - await func(context, state, details) - - self._app.add_route( - __selector, - __handler, - rank=rank, - auth_handlers=auth_handlers, - ) - return func - - if handler is not None: - return __register(handler) - return __register - class TeamsAgentExtension(Generic[StateT]): """ diff --git a/libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/teams_turn_context.py b/libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/teams_turn_context.py new file mode 100644 index 000000000..8460aaa56 --- /dev/null +++ b/libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/teams_turn_context.py @@ -0,0 +1,12 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +from microsoft_agents.hosting.core import TurnContext + +class TeamsTurnContext(TurnContext): + """A context object for handling Teams-specific turn functionality.""" + + def __init__(self, context: TurnContext): + super().__init__(context) + + self._context = context \ No newline at end of file diff --git a/libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/type_defs.py b/libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/type_defs.py new file mode 100644 index 000000000..f53e52060 --- /dev/null +++ b/libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/type_defs.py @@ -0,0 +1,23 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +from typing import ( + Callable, + TypeVar, + Pattern, + Protocol, +) + +from microsoft_agents.hosting.core import TurnState + +from .teams_turn_context import TeamsTurnContext + +TeamsRouteSelector = Callable[[TeamsTurnContext], bool] + +StateT = TypeVar("StateT", bound=TurnState) +RouteHandlerT = TypeVar("RouteHandlerT", bound=Callable) + +CommandSelector = str | Pattern[str] | None + +class _RouteDecorator(Protocol[RouteHandlerT]): + def __call__(self, func: RouteHandlerT) -> RouteHandlerT: ... \ No newline at end of file From 05f00e7431e9c8094c9e21560e34297a0aa91200 Mon Sep 17 00:00:00 2001 From: Rodrigo Brandao Date: Wed, 17 Jun 2026 15:43:39 -0700 Subject: [PATCH 3/8] Separating route hubs --- .../microsoft_agents/hosting/teams/_utils.py | 2 +- .../hosting/teams/channel/__init__.py | 0 .../hosting/teams/channel/channel.py | 0 .../hosting/teams/channel/route_handlers.py | 0 .../hosting/teams/configuration/__init__.py | 0 .../teams/configuration/configuration.py | 0 .../teams/configuration/route_handlers.py | 0 .../hosting/teams/file_consent/__init__.py | 0 .../teams/file_consent/file_consent.py | 0 .../teams/file_consent/route_handlers.py | 0 .../hosting/teams/meeting/__init__.py | 0 .../hosting/teams/meeting/meeting.py | 94 +++++++---- .../hosting/teams/meeting/route_handlers.py | 36 +++++ .../hosting/teams/message/__init__.py | 0 .../hosting/teams/message/message.py | 150 ++++++++++++++++++ .../hosting/teams/message/route_handlers.py | 24 +++ .../message_extension/message_extension.py | 60 +++---- .../teams/message_extension/route_handlers.py | 3 +- .../hosting/teams/route_handlers.py | 71 ++------- .../teams/task_module/route_handlers.py | 7 +- .../hosting/teams/task_module/task_module.py | 32 ++-- .../hosting/teams/team/__init__.py | 0 .../hosting/teams/team/team.py | 1 + .../hosting/teams/teams_agent_extension.py | 91 ++++++----- 24 files changed, 406 insertions(+), 165 deletions(-) create mode 100644 libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/channel/__init__.py create mode 100644 libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/channel/channel.py create mode 100644 libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/channel/route_handlers.py create mode 100644 libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/configuration/__init__.py create mode 100644 libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/configuration/configuration.py create mode 100644 libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/configuration/route_handlers.py create mode 100644 libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/file_consent/__init__.py create mode 100644 libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/file_consent/file_consent.py create mode 100644 libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/file_consent/route_handlers.py create mode 100644 libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/meeting/__init__.py create mode 100644 libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/meeting/route_handlers.py create mode 100644 libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/message/__init__.py create mode 100644 libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/message/message.py create mode 100644 libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/message/route_handlers.py create mode 100644 libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/team/__init__.py create mode 100644 libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/team/team.py diff --git a/libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/_utils.py b/libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/_utils.py index 9ba3b0780..bce969664 100644 --- a/libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/_utils.py +++ b/libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/_utils.py @@ -42,4 +42,4 @@ async def _send_invoke_response(context: TurnContext, body: Any = None) -> None: type=ActivityTypes.invoke_response, value=InvokeResponse(status=int(HTTPStatus.OK), body=serialized_body), ) - ) + ) \ No newline at end of file diff --git a/libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/channel/__init__.py b/libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/channel/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/channel/channel.py b/libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/channel/channel.py new file mode 100644 index 000000000..e69de29bb diff --git a/libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/channel/route_handlers.py b/libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/channel/route_handlers.py new file mode 100644 index 000000000..e69de29bb diff --git a/libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/configuration/__init__.py b/libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/configuration/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/configuration/configuration.py b/libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/configuration/configuration.py new file mode 100644 index 000000000..e69de29bb diff --git a/libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/configuration/route_handlers.py b/libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/configuration/route_handlers.py new file mode 100644 index 000000000..e69de29bb diff --git a/libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/file_consent/__init__.py b/libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/file_consent/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/file_consent/file_consent.py b/libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/file_consent/file_consent.py new file mode 100644 index 000000000..e69de29bb diff --git a/libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/file_consent/route_handlers.py b/libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/file_consent/route_handlers.py new file mode 100644 index 000000000..e69de29bb diff --git a/libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/meeting/__init__.py b/libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/meeting/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/meeting/meeting.py b/libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/meeting/meeting.py index 1ec9cce68..3af6e948c 100644 --- a/libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/meeting/meeting.py +++ b/libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/meeting/meeting.py @@ -1,3 +1,39 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +from typing import ( + Callable, + Generic, + Optional +) + +from microsoft_teams.api.models import ( + MeetingDetails, + MeetingParticipantsEventDetails, +) + +from microsoft_agents.activity import ( + Activity, + ActivityTypes, +) +from microsoft_agents.hosting.core import ( + AgentApplication, + RouteRank, + TurnContext, +) + +from microsoft_agents.hosting.teams.teams_turn_context import TeamsTurnContext +from microsoft_agents.hosting.teams.type_defs import ( + _RouteDecorator, + StateT, +) + +from .route_handlers import ( + MeetingStartHandler, + MeetingEndHandler, + MeetingParticipantsEventHandler +) + class Meeting(Generic[StateT]): """ Route registration for Teams Meeting event activities. @@ -7,13 +43,20 @@ class Meeting(Generic[StateT]): def __init__(self, app: AgentApplication[StateT]) -> None: self._app = app + + # + # @meeting.on_start + # def on_start_handler(self, context: TeamsTurnContext, state: StateT, meeting: MeetingDetails) -> None: + # pass + + # meeting.on_start()(on_start_handler) + def on_start( self, - handler: Optional[Callable] = None, *, auth_handlers: Optional[list[str]] = None, rank: RouteRank = RouteRank.DEFAULT, - ) -> Callable: + ) -> _RouteDecorator[MeetingStartHandler[StateT]]: """Register a handler for meeting start events.""" def __selector(context: TurnContext) -> bool: @@ -22,10 +65,11 @@ def __selector(context: TurnContext) -> bool: and context.activity.name == "application/vnd.microsoft.meetingStart" ) - def __register(func: Callable) -> Callable: + def __call(func: MeetingStartHandler[StateT]) -> MeetingStartHandler[StateT]: async def __handler(context: TurnContext, state: StateT) -> None: + teams_context = TeamsTurnContext(context) meeting = MeetingDetails.model_validate(context.activity.value or {}) - await func(context, state, meeting) + await func(teams_context, state, meeting) self._app.add_route( __selector, @@ -35,17 +79,14 @@ async def __handler(context: TurnContext, state: StateT) -> None: ) return func - if handler is not None: - return __register(handler) - return __register + return __call def on_end( self, - handler: Optional[Callable] = None, *, auth_handlers: Optional[list[str]] = None, rank: RouteRank = RouteRank.DEFAULT, - ) -> Callable: + ) -> _RouteDecorator[MeetingEndHandler[StateT]]: """Register a handler for meeting end events.""" def __selector(context: TurnContext) -> bool: @@ -54,10 +95,11 @@ def __selector(context: TurnContext) -> bool: and context.activity.name == "application/vnd.microsoft.meetingEnd" ) - def __register(func: Callable) -> Callable: + def __call(func: MeetingEndHandler[StateT]) -> MeetingEndHandler[StateT]: async def __handler(context: TurnContext, state: StateT) -> None: + teams_context = TeamsTurnContext(context) meeting = MeetingDetails.model_validate(context.activity.value or {}) - await func(context, state, meeting) + await func(teams_context, state, meeting) self._app.add_route( __selector, @@ -67,17 +109,14 @@ async def __handler(context: TurnContext, state: StateT) -> None: ) return func - if handler is not None: - return __register(handler) - return __register + return __call def on_participants_join( self, - handler: Optional[Callable] = None, *, auth_handlers: Optional[list[str]] = None, rank: RouteRank = RouteRank.DEFAULT, - ) -> Callable: + ) -> _RouteDecorator[MeetingParticipantsEventHandler[StateT]]: """Register a handler for meeting participant join events.""" def __selector(context: TurnContext) -> bool: @@ -87,12 +126,13 @@ def __selector(context: TurnContext) -> bool: == "application/vnd.microsoft.meetingParticipantJoin" ) - def __register(func: Callable) -> Callable: + def __call(func: MeetingParticipantsEventHandler[StateT]) -> MeetingParticipantsEventHandler[StateT]: async def __handler(context: TurnContext, state: StateT) -> None: + teams_context = TeamsTurnContext(context) details = MeetingParticipantsEventDetails.model_validate( context.activity.value or {} ) - await func(context, state, details) + await func(teams_context, state, details) self._app.add_route( __selector, @@ -102,17 +142,14 @@ async def __handler(context: TurnContext, state: StateT) -> None: ) return func - if handler is not None: - return __register(handler) - return __register + return __call def on_participants_leave( self, - handler: Optional[Callable] = None, *, auth_handlers: Optional[list[str]] = None, rank: RouteRank = RouteRank.DEFAULT, - ) -> Callable: + ) -> _RouteDecorator[MeetingParticipantsEventHandler[StateT]]: """Register a handler for meeting participant leave events.""" def __selector(context: TurnContext) -> bool: @@ -122,12 +159,13 @@ def __selector(context: TurnContext) -> bool: == "application/vnd.microsoft.meetingParticipantLeave" ) - def __register(func: Callable) -> Callable: + def __call(func: MeetingParticipantsEventHandler[StateT]) -> MeetingParticipantsEventHandler[StateT]: async def __handler(context: TurnContext, state: StateT) -> None: + teams_context = TeamsTurnContext(context) details = MeetingParticipantsEventDetails.model_validate( context.activity.value or {} ) - await func(context, state, details) + await func(teams_context, state, details) self._app.add_route( __selector, @@ -136,7 +174,5 @@ async def __handler(context: TurnContext, state: StateT) -> None: auth_handlers=auth_handlers, ) return func - - if handler is not None: - return __register(handler) - return __register + + return __call diff --git a/libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/meeting/route_handlers.py b/libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/meeting/route_handlers.py new file mode 100644 index 000000000..50a84d5bd --- /dev/null +++ b/libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/meeting/route_handlers.py @@ -0,0 +1,36 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +from typing import Awaitable, Protocol + +from microsoft_teams.api.models import ( + MeetingDetails, + MeetingParticipantsEventDetails +) + +from microsoft_agents.hosting.teams.teams_turn_context import TeamsTurnContext +from microsoft_agents.hosting.teams.type_defs import StateT + +class MeetingStartHandler(Protocol[StateT]): + def __call__( + self, + context: TeamsTurnContext, + state: StateT, + meeting: MeetingDetails + ) -> Awaitable[None]: ... + +class MeetingEndHandler(Protocol[StateT]): + def __call__( + self, + context: TeamsTurnContext, + state: StateT, + meeting: MeetingDetails + ) -> Awaitable[None]: ... + +class MeetingParticipantsEventHandler(Protocol[StateT]): + def __call__( + self, + context: TeamsTurnContext, + state: StateT, + meeting: MeetingParticipantsEventDetails + ) -> Awaitable[None]: ... \ No newline at end of file diff --git a/libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/message/__init__.py b/libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/message/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/message/message.py b/libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/message/message.py new file mode 100644 index 000000000..f500e5e5d --- /dev/null +++ b/libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/message/message.py @@ -0,0 +1,150 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +from typing import ( + Callable, + Generic, + Optional +) + +from microsoft_teams.api.models.o365 import O365ConnectorCardActionQuery + +from microsoft_agents.activity import ActivityTypes +from microsoft_agents.hosting.core import ( + AgentApplication, + RouteRank, + TurnContext, +) + +from microsoft_agents.hosting.teams.route_handlers import TeamsRouteHandler +from microsoft_agents.hosting.teams.teams_turn_context import TeamsTurnContext +from microsoft_agents.hosting.teams.type_defs import ( + _RouteDecorator, + StateT, +) +from microsoft_agetns.hosting.teams._utils import _get_channel_event_type + +from .route_handlers import ( + O365ConnectorCardActionHandler, + ReadReceiptHandler +) + +class Message(Generic[StateT]): + + def __init__(self, app: AgentApplication[StateT]): + self._app = app + + def _create_basic_decorator( + self, + event_type: str, + *, + auth_handlers: Optional[list[str]] = None, + rank: RouteRank = RouteRank.DEFAULT, + ) -> _RouteDecorator[TeamsRouteHandler[StateT]]: + def __selector(context: TurnContext) -> bool: + return ( + context.activity.type == ActivityTypes.message_update + and context.activity.channel_id == "msteams" + and _get_channel_event_type(context) == event_type + ) + + def __call(func: TeamsRouteHandler[StateT]) -> TeamsRouteHandler[StateT]: + self._app.add_route( + __selector, + TeamsRouteHandler[StateT].wrap(func), + rank=rank, + auth_handlers=auth_handlers + ) + return func + + return __call + + def edit( + self, + *, + auth_handlers: Optional[list[str]] = None, + rank: RouteRank = RouteRank.DEFAULT, + ) -> _RouteDecorator[TeamsRouteHandler[StateT]]: + """Register a handler for Teams editMessage events.""" + return self._create_basic_decorator("editMessage", auth_handlers=auth_handlers, rank=rank) + + def undelete( + self, + *, + auth_handlers: Optional[list[str]] = None, + rank: RouteRank = RouteRank.DEFAULT, + ) -> _RouteDecorator[TeamsRouteHandler[StateT]]: + """Register a handler for Teams undeleteMessage events.""" + return self._create_basic_decorator("undeleteMessage", auth_handlers=auth_handlers, rank=rank) + + def soft_delete( + self, + handler: Optional[Callable] = None, + *, + auth_handlers: Optional[list[str]] = None, + rank: RouteRank = RouteRank.DEFAULT, + ) -> Callable: + """Register a handler for Teams softDeleteMessage events.""" + return self._create_basic_decorator("softDeleteMessage", auth_handlers=auth_handlers, rank=rank) + + # ── Read receipt ─────────────────────────────────────────────────────── + + def on_read_receipt( + self, + *, + auth_handlers: Optional[list[str]] = None, + rank: RouteRank = RouteRank.DEFAULT, + ) -> _RouteDecorator[ReadReceiptHandler[StateT]]: + """Register a handler for Teams readReceipt events.""" + + def __selector(context: TurnContext) -> bool: + return ( + context.activity.type == ActivityTypes.event + and context.activity.name == "application/vnd.microsoft.readReceipt" + ) + + def __call(func: ReadReceiptHandler[StateT]) -> ReadReceiptHandler[StateT]: + async def __handler(context: TurnContext, state: StateT) -> None: + teams_context = TeamsTurnContext(context) + await func(teams_context, state) + + self._app.add_route( + __selector, __handler, rank=rank, auth_handlers=auth_handlers + ) + return func + + return __call + + def o365_connector_card_action( + self, + *, + auth_handlers: Optional[list[str]] = None, + rank: RouteRank = RouteRank.DEFAULT, + ) -> _RouteDecorator[O365ConnectorCardActionHandler[StateT]]: + """Register a handler for actionableMessage/executeAction invokes.""" + + def __selector(context: TurnContext) -> bool: + return ( + context.activity.type == ActivityTypes.invoke + and context.activity.name == "actionableMessage/executeAction" + ) + + def __call(func: O365ConnectorCardActionHandler[StateT]) -> O365ConnectorCardActionHandler[StateT]: + async def __handler(context: TurnContext, state: StateT) -> None: + teams_context = TeamsTurnContext(context) + query = O365ConnectorCardActionQuery.model_validate( + context.activity.value or {} + ) + await func(teams_context, state, query) + await _send_invoke_response(context) + + self._app.add_route( + __selector, + __handler, + is_invoke=True, + rank=rank, + auth_handlers=auth_handlers, + ) + return func + + return __call \ No newline at end of file diff --git a/libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/message/route_handlers.py b/libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/message/route_handlers.py new file mode 100644 index 000000000..940606631 --- /dev/null +++ b/libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/message/route_handlers.py @@ -0,0 +1,24 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +from typing import Awaitable, Protocol + +from microsoft_teams.api.models.o365 import ( + O365ConnectorCardActionQuery +) + +from microsoft_agents.hosting.teams.teams_turn_context import TeamsTurnContext +from microsoft_agents.hosting.teams.type_defs import StateT + +class O365ConnectorCardActionHandler(Protocol[StateT]): + def __call__( + self, + context: TeamsTurnContext, + state: StateT, + query: O365ConnectorCardActionQuery) -> Awaitable[None]: ... + +class ReadReceiptHandler(Protocol[StateT]): + def __call__( + self, + context: TeamsTurnContext, + data: dict) -> Awaitable[None]: ... \ No newline at end of file diff --git a/libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/message_extension/message_extension.py b/libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/message_extension/message_extension.py index adaf48c37..d37beaa03 100644 --- a/libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/message_extension/message_extension.py +++ b/libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/message_extension/message_extension.py @@ -35,9 +35,11 @@ ) from .route_handlers import ( - FetchActionHandler, + FetchTaskHandler, + QueryHandler, SubmitActionHandler, MessagePreviewEditHandler, + MessagePreviewSendHandler, QueryHandler, SelectItemHandler, QueryLinkHandler, @@ -55,7 +57,7 @@ class MessageExtension(Generic[StateT]): def __init__(self, app: AgentApplication[StateT]) -> None: self._app = app - def on_query( + def query( self, command_id: CommandSelector = None, *, @@ -103,7 +105,7 @@ async def __handler(context: TurnContext, state: StateT) -> None: return __call - def on_select_item( + def select_item( self, *, auth_handlers: Optional[list[str]] = None, @@ -135,7 +137,7 @@ async def __handler(context: TurnContext, state: StateT) -> None: return __call - def on_submit_action( + def submit_action( self, command_id: CommandSelector = None, *, @@ -186,7 +188,7 @@ async def __handler(context: TurnContext, state: StateT) -> None: return __call - def on_message_preview_edit( + def message_preview_edit( self, command_id: CommandSelector = None, *, @@ -227,7 +229,7 @@ async def __handler(context: TurnContext, state: StateT) -> None: return __call - def on_message_preview_send( + def message_preview_send( self, command_id: CommandSelector = None, *, @@ -253,6 +255,11 @@ async def __handler(context: TurnContext, state: StateT) -> None: action = MessagingExtensionAction.model_validate( context.activity.value or {} ) + # activity_preview: Activity | None = None + # if action.bot_activity_preview: + # activity_preview = Activity.model_validate(action.bot_activity_preview[0]) + # activity_preview = + # action.bot_activity_preview[0] response = await func(teams_context, state, action) if response is not None: await _send_invoke_response(context, response) @@ -268,13 +275,13 @@ async def __handler(context: TurnContext, state: StateT) -> None: return __call - def on_task_fetch( + def fetch_task( self, command_id: CommandSelector = None, *, auth_handlers: Optional[list[str]] = None, rank: RouteRank = RouteRank.DEFAULT, - ) -> _RouteDecorator[TaskFetchHandler[StateT]]: + ) -> _RouteDecorator[FetchTaskHandler[StateT]]: """Register a handler for composeExtension/fetchTask invokes.""" def __selector(context: TurnContext) -> bool: @@ -287,7 +294,7 @@ def __selector(context: TurnContext) -> bool: ) ) - def __call(func: TaskFetchHandler[StateT]) -> TaskFetchHandler[StateT]: + def __call(func: FetchTaskHandler[StateT]) -> FetchTaskHandler[StateT]: async def __handler(context: TurnContext, state: StateT) -> None: action = MessagingExtensionAction.model_validate( context.activity.value or {} @@ -307,7 +314,7 @@ async def __handler(context: TurnContext, state: StateT) -> None: return __call - def on_query_link( + def query_link( self, *, auth_handlers: Optional[list[str]] = None, @@ -340,7 +347,7 @@ async def __handler(context: TurnContext, state: StateT) -> None: return __call - def on_anonymous_query_link( + def anonymous_query_link( self, *, auth_handlers: Optional[list[str]] = None, @@ -354,7 +361,7 @@ def __selector(context: TurnContext) -> bool: and context.activity.name == "composeExtension/anonymousQueryLink" ) - def __register(func: QueryLinkHandler[StateT]) -> QueryLinkHandler[StateT]: + def __call(func: QueryLinkHandler[StateT]) -> QueryLinkHandler[StateT]: async def __handler(context: TurnContext, state: StateT) -> None: teams_context = TeamsTurnContext(context) query = AppBasedLinkQuery.model_validate(context.activity.value or {}) @@ -371,9 +378,9 @@ async def __handler(context: TurnContext, state: StateT) -> None: ) return func - return __register + return __call - def on_query_url_setting( + def query_setting_url( self, *, auth_handlers: Optional[list[str]] = None, @@ -387,7 +394,7 @@ def __selector(context: TurnContext) -> bool: and context.activity.name == "composeExtension/querySettingUrl" ) - def __register(func: QueryUrlSettingHandler[StateT]) -> QueryUrlSettingHandler[StateT]: + def __call(func: QueryUrlSettingHandler[StateT]) -> QueryUrlSettingHandler[StateT]: async def __handler(context: TurnContext, state: StateT) -> None: teams_context = TeamsTurnContext(context) query = MessagingExtensionQuery.model_validate( @@ -406,15 +413,15 @@ async def __handler(context: TurnContext, state: StateT) -> None: ) return func - return __register + return __call - def on_configure_settings( + def configure_settings( self, handler: Optional[Callable] = None, *, auth_handlers: Optional[list[str]] = None, rank: RouteRank = RouteRank.DEFAULT, - ) -> Callable: + ) -> _RouteDecorator[ConfigureSettingsHandler[StateT]]: """Register a handler for composeExtension/setting invokes.""" def __selector(context: TurnContext) -> bool: @@ -423,7 +430,7 @@ def __selector(context: TurnContext) -> bool: and context.activity.name == "composeExtension/setting" ) - def __register(func: Callable) -> Callable: + def __call(func: Callable) -> Callable: async def __handler(context: TurnContext, state: StateT) -> None: await func(context, state, context.activity.value) await _send_invoke_response(context) @@ -438,16 +445,15 @@ async def __handler(context: TurnContext, state: StateT) -> None: return func if handler is not None: - return __register(handler) - return __register + return __call(handler) + return __call - def on_card_button_clicked( + def card_button_clicked( self, - handler: Optional[Callable] = None, *, auth_handlers: Optional[list[str]] = None, rank: RouteRank = RouteRank.DEFAULT, - ) -> Callable: + ) -> _RouteDecorator[CardButtonClickedHandler[StateT]]: """Register a handler for composeExtension/onCardButtonClicked invokes.""" def __selector(context: TurnContext) -> bool: @@ -456,7 +462,7 @@ def __selector(context: TurnContext) -> bool: and context.activity.name == "composeExtension/onCardButtonClicked" ) - def __register(func: Callable) -> Callable: + def __call(func: CardButtonClickedHandler[StateT]) -> CardButtonClickedHandler[StateT]: async def __handler(context: TurnContext, state: StateT) -> None: await func(context, state, context.activity.value) await _send_invoke_response(context) @@ -470,6 +476,4 @@ async def __handler(context: TurnContext, state: StateT) -> None: ) return func - if handler is not None: - return __register(handler) - return __register + return __call diff --git a/libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/message_extension/route_handlers.py b/libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/message_extension/route_handlers.py index a37c79fa4..69e7e2b3c 100644 --- a/libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/message_extension/route_handlers.py +++ b/libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/message_extension/route_handlers.py @@ -27,7 +27,7 @@ from microsoft_agents.hosting.teams_turn_context import TeamsTurnContext from microsoft_agents.hosting.teams.type_defs import StateT -class FetchActionHandler(Protocol[StateT]): +class FetchTaskHandler(Protocol[StateT]): def __call__( self, context: TeamsTurnContext, @@ -92,6 +92,7 @@ def __call__( self, context: TeamsTurnContext, state: StateT, + query: MessageExtensionQuery ) -> Awaitable[MessageExtensionResponse]: ... diff --git a/libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/route_handlers.py b/libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/route_handlers.py index e81e237a6..3b1734e7b 100644 --- a/libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/route_handlers.py +++ b/libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/route_handlers.py @@ -1,26 +1,16 @@ # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. +from __future__ import annotations + from typing import ( - Any, Awaitable, Protocol, ) -from microsoft_agents.activity import Activity - -from microsoft_teams.api.models import ( - Channel, - Team, - MeetingDetails, - MeetingParticipantsEventDetails, - MessagingExtensionAction, - MessagingExtensionQuery, - MessageExtensionActionResponse, - MessagingExtensionResponse, - O365ConnectorCardActionQuery, - TaskModuleRequest, - TaskModuleResponse +from microsoft_agents.hosting.core import ( + RouteHandler, + TurnContext, ) from .teams_turn_context import TeamsTurnContext @@ -29,31 +19,14 @@ class TeamsRouteHandler(Protocol[StateT]): def __call__(self, context: TeamsTurnContext, state: StateT) -> Awaitable[None]: ... -# Meetings route handlers - -class MeetingStartHandler(Protocol[StateT]): - def __call__( - self, - context: TeamsTurnContext, - state: StateT, - meeting: MeetingDetails - ) -> Awaitable[None]: ... - -class MeetingEndHandler(Protocol[StateT]): - def __call__( - self, - context: TeamsTurnContext, - state: StateT, - meeting: MeetingDetails - ) -> Awaitable[None]: ... + @staticmethod + def wrap(handler: TeamsRouteHandler[StateT]) -> RouteHandler[StateT]: + async def __func(context: TurnContext, state: StateT) -> None: + teams_context = TeamsTurnContext(context) + await handler(teams_context, state) + return __func -class MeetingParticipantsEventHandler(Protocol[StateT]): - def __call__( - self, - context: TeamsTurnContext, - state: StateT, - meeting: MeetingParticipantsEventDetails - ) -> Awaitable[None]: ... +# Meetings route handlers # Message extension route handlers @@ -77,26 +50,6 @@ def __call__( ) -> Awaitable[None]: ... -# Task Modules route handlers - -class TaskFetchHandler(Protocol[StateT]): - def __call__( - self, - context: TeamsTurnContext, - state: StateT, - request: TaskModuleRequest, - ) -> Awaitable[TaskModuleResponse]: - ... - -class TaskSubmitHandler(Protocol[StateT]): - def __call__( - self, - context: TeamsTurnContext, - state: StateT, - request: TaskModuleRequest, - ) -> Awaitable[TaskModuleResponse]: - ... - # Teams Channels route handlers class ChannelUpdateHandler(Protocol[StateT]): diff --git a/libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/task_module/route_handlers.py b/libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/task_module/route_handlers.py index c0489cd71..2f5fdf258 100644 --- a/libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/task_module/route_handlers.py +++ b/libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/task_module/route_handlers.py @@ -1,3 +1,6 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + from typing import Awaitable, Protocol from microsoft_teams.api.models import ( @@ -8,7 +11,7 @@ from microsoft_agents.hosting.teams.teams_turn_context import TeamsTurnContext from microsoft_agents.hosting.teams.type_defs import StateT -class TaskFetchHandler(Protocol[StateT]): +class FetchHandler(Protocol[StateT]): def __call__( self, context: TeamsTurnContext, @@ -17,7 +20,7 @@ def __call__( ) -> Awaitable[TaskModuleResponse]: ... -class TaskSubmitHandler(Protocol[StateT]): +class SubmitHandler(Protocol[StateT]): def __call__( self, context: TeamsTurnContext, diff --git a/libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/task_module/task_module.py b/libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/task_module/task_module.py index f0e776f10..3b8417860 100644 --- a/libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/task_module/task_module.py +++ b/libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/task_module/task_module.py @@ -1,9 +1,19 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + + from typing import ( Any, Generic, Optional ) +from microsoft_teams.api.models import ( + TaskModuleRequest, +) + +from microsoft_agents.activity import ActivityTypes + from microsoft_agents.hosting.core import ( AgentApplication, RouteRank, @@ -11,9 +21,9 @@ ) from microsoft_agents.hosting.teams.teams_turn_context import TeamsTurnContext -from microsoft_agents.hosting.teams._type_defs import ( +from microsoft_agents.hosting.teams.type_defs import ( CommandSelector, - RouteDecorator, + _RouteDecorator, StateT, ) from microsoft_agents.hosting.teams._utils import ( @@ -22,6 +32,8 @@ ) from .route_handlers import ( + FetchHandler, + SubmitHandler ) class TaskModule(Generic[StateT]): @@ -42,13 +54,13 @@ def _get_verb(value: Optional[Any]) -> Optional[str]: return data.get("verb") return None - def on_fetch( + def fetch( self, verb: CommandSelector = None, *, auth_handlers: Optional[list[str]] = None, rank: RouteRank = RouteRank.DEFAULT, - ) -> RouteDecorator[]: + ) -> _RouteDecorator[FetchHandler[StateT]]: """Register a handler for task/fetch invokes. :param verb: Optional verb string or regex to match against task data. @@ -63,10 +75,11 @@ def __selector(context: TurnContext) -> bool: return False return _match_selector(verb, TaskModule._get_verb(context.activity.value)) - def __call(func: Callable) -> Callable: + def __call(func: FetchHandler[StateT]) -> FetchHandler[StateT]: async def __handler(context: TurnContext, state: StateT) -> None: + teams_context = TeamsTurnContext(context) request = TaskModuleRequest.model_validate(context.activity.value or {}) - response = await func(context, state, request) + response = await func(teams_context, state, request) if response is not None: await _send_invoke_response(context, response) @@ -87,7 +100,7 @@ def on_submit( *, auth_handlers: Optional[list[str]] = None, rank: RouteRank = RouteRank.DEFAULT, - ) -> Callable: + ) -> _RouteDecorator[SubmitHandler[StateT]]: """Register a handler for task/submit invokes. :param verb: Optional verb string or regex to match against task data. @@ -102,10 +115,11 @@ def __selector(context: TurnContext) -> bool: return False return _match_selector(verb, TaskModule._get_verb(context.activity.value)) - def __call(func: Callable) -> Callable: + def __call(func: SubmitHandler[StateT]) -> SubmitHandler[StateT]: async def __handler(context: TurnContext, state: StateT) -> None: + teams_context = TeamsTurnContext(context) request = TaskModuleRequest.model_validate(context.activity.value or {}) - response = await func(context, state, request) + response = await func(teams_context, state, request) if response is not None: await _send_invoke_response(context, response) diff --git a/libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/team/__init__.py b/libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/team/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/team/team.py b/libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/team/team.py new file mode 100644 index 000000000..519cbf67a --- /dev/null +++ b/libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/team/team.py @@ -0,0 +1 @@ +m. \ No newline at end of file diff --git a/libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/teams_agent_extension.py b/libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/teams_agent_extension.py index 2115b0d66..a4f777df9 100644 --- a/libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/teams_agent_extension.py +++ b/libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/teams_agent_extension.py @@ -28,6 +28,17 @@ TaskModuleRequest, ) +from .channel import Channel +from .meeting import Meeting +from .message import Message +from .message_extension import MessageExtension +from .task_module import TaskModule +from .team import Team + +from .type_defs import ( + StateT +) + class TeamsAgentExtension(Generic[StateT]): """ @@ -56,6 +67,9 @@ def __init__(self, app: AgentApplication[StateT]) -> None: self._message_extension: MessageExtension[StateT] = MessageExtension(app) self._task_module: TaskModule[StateT] = TaskModule(app) self._meeting: Meeting[StateT] = Meeting(app) + self._message: Message[StateT] = Message(app) + self._team: Team[StateT] = Team(app) + self._channel: Channel[StateT] = Channel(app) @property def message_extension(self) -> MessageExtension[StateT]: @@ -71,6 +85,11 @@ def task_module(self) -> TaskModule[StateT]: def meeting(self) -> Meeting[StateT]: """Route registration for Meeting lifecycle events.""" return self._meeting + + @property + def message(self) -> Message[StateT]: + """Route registration for messaging activities.""" + return self._message # ── Message update / delete ──────────────────────────────────────────── @@ -90,15 +109,15 @@ def __selector(context: TurnContext) -> bool: and _get_channel_event_type(context) == "editMessage" ) - def __register(func: Callable) -> Callable: + def __call(func: Callable) -> Callable: self._app.add_route( __selector, func, rank=rank, auth_handlers=auth_handlers ) return func if handler is not None: - return __register(handler) - return __register + return __call(handler) + return __call def on_message_undelete( self, @@ -116,15 +135,15 @@ def __selector(context: TurnContext) -> bool: and _get_channel_event_type(context) == "undeleteMessage" ) - def __register(func: Callable) -> Callable: + def __call(func: Callable) -> Callable: self._app.add_route( __selector, func, rank=rank, auth_handlers=auth_handlers ) return func if handler is not None: - return __register(handler) - return __register + return __call(handler) + return __call def on_message_soft_delete( self, @@ -142,15 +161,15 @@ def __selector(context: TurnContext) -> bool: and _get_channel_event_type(context) == "softDeleteMessage" ) - def __register(func: Callable) -> Callable: + def __call(func: Callable) -> Callable: self._app.add_route( __selector, func, rank=rank, auth_handlers=auth_handlers ) return func if handler is not None: - return __register(handler) - return __register + return __call(handler) + return __call # ── Read receipt ─────────────────────────────────────────────────────── @@ -169,7 +188,7 @@ def __selector(context: TurnContext) -> bool: and context.activity.name == "application/vnd.microsoft.readReceipt" ) - def __register(func: Callable) -> Callable: + def __call(func: Callable) -> Callable: async def __handler(context: TurnContext, state: StateT) -> None: receipt = ReadReceiptInfo.model_validate(context.activity.value or {}) await func(context, state, receipt) @@ -180,8 +199,8 @@ async def __handler(context: TurnContext, state: StateT) -> None: return func if handler is not None: - return __register(handler) - return __register + return __call(handler) + return __call # ── Config ───────────────────────────────────────────────────────────── @@ -200,7 +219,7 @@ def __selector(context: TurnContext) -> bool: and context.activity.name == "config/fetch" ) - def __register(func: Callable) -> Callable: + def __call(func: Callable) -> Callable: async def __handler(context: TurnContext, state: StateT) -> None: response = await func(context, state, context.activity.value) if response is not None: @@ -216,8 +235,8 @@ async def __handler(context: TurnContext, state: StateT) -> None: return func if handler is not None: - return __register(handler) - return __register + return __call(handler) + return __call def on_config_submit( self, @@ -234,7 +253,7 @@ def __selector(context: TurnContext) -> bool: and context.activity.name == "config/submit" ) - def __register(func: Callable) -> Callable: + def __call(func: Callable) -> Callable: async def __handler(context: TurnContext, state: StateT) -> None: response = await func(context, state, context.activity.value) if response is not None: @@ -250,8 +269,8 @@ async def __handler(context: TurnContext, state: StateT) -> None: return func if handler is not None: - return __register(handler) - return __register + return __call(handler) + return __call # ── File consent ─────────────────────────────────────────────────────── @@ -272,7 +291,7 @@ def __selector(context: TurnContext) -> bool: and context.activity.value.get("action") == "accept" ) - def __register(func: Callable) -> Callable: + def __call(func: Callable) -> Callable: async def __handler(context: TurnContext, state: StateT) -> None: file_consent = FileConsentCardResponse.model_validate( context.activity.value or {} @@ -290,8 +309,8 @@ async def __handler(context: TurnContext, state: StateT) -> None: return func if handler is not None: - return __register(handler) - return __register + return __call(handler) + return __call def on_file_consent_decline( self, @@ -310,7 +329,7 @@ def __selector(context: TurnContext) -> bool: and context.activity.value.get("action") == "decline" ) - def __register(func: Callable) -> Callable: + def __call(func: Callable) -> Callable: async def __handler(context: TurnContext, state: StateT) -> None: file_consent = FileConsentCardResponse.model_validate( context.activity.value or {} @@ -328,8 +347,8 @@ async def __handler(context: TurnContext, state: StateT) -> None: return func if handler is not None: - return __register(handler) - return __register + return __call(handler) + return __call # ── O365 Connector ───────────────────────────────────────────────────── @@ -348,7 +367,7 @@ def __selector(context: TurnContext) -> bool: and context.activity.name == "actionableMessage/executeAction" ) - def __register(func: Callable) -> Callable: + def __call(func: Callable) -> Callable: async def __handler(context: TurnContext, state: StateT) -> None: query = O365ConnectorCardActionQuery.model_validate( context.activity.value or {} @@ -366,8 +385,8 @@ async def __handler(context: TurnContext, state: StateT) -> None: return func if handler is not None: - return __register(handler) - return __register + return __call(handler) + return __call # ── Conversation update events ───────────────────────────────────────── @@ -388,15 +407,15 @@ def __selector(context: TurnContext) -> bool: and len(context.activity.members_added) > 0 ) - def __register(func: Callable) -> Callable: + def __call(func: Callable) -> Callable: self._app.add_route( __selector, func, rank=rank, auth_handlers=auth_handlers ) return func if handler is not None: - return __register(handler) - return __register + return __call(handler) + return __call def on_members_removed( self, @@ -415,15 +434,15 @@ def __selector(context: TurnContext) -> bool: and len(context.activity.members_removed) > 0 ) - def __register(func: Callable) -> Callable: + def __call(func: Callable) -> Callable: self._app.add_route( __selector, func, rank=rank, auth_handlers=auth_handlers ) return func if handler is not None: - return __register(handler) - return __register + return __call(handler) + return __call def on_channel_created( self, @@ -560,12 +579,12 @@ def __selector(context: TurnContext) -> bool: and _get_channel_event_type(context) == event_type ) - def __register(func: Callable) -> Callable: + def __call(func: Callable) -> Callable: self._app.add_route( __selector, func, rank=rank, auth_handlers=auth_handlers ) return func if handler is not None: - return __register(handler) - return __register + return __call(handler) + return __call From 4290f74e3a0d570651abd42c6f5a08a89d4e19ad Mon Sep 17 00:00:00 2001 From: Rodrigo Brandao Date: Wed, 17 Jun 2026 16:02:09 -0700 Subject: [PATCH 4/8] Adding file consent routes --- .../teams/file_consent/file_consent.py | 81 ++++++++++++ .../teams/file_consent/route_handlers.py | 17 +++ .../hosting/teams/message/__init__.py | 8 ++ .../hosting/teams/message/message.py | 5 +- .../hosting/teams/teams_agent_extension.py | 118 +----------------- 5 files changed, 111 insertions(+), 118 deletions(-) diff --git a/libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/file_consent/file_consent.py b/libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/file_consent/file_consent.py index e69de29bb..127c52bfc 100644 --- a/libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/file_consent/file_consent.py +++ b/libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/file_consent/file_consent.py @@ -0,0 +1,81 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +from typing import Generic, Optional + +from microsoft_teams.api.models import FileConsentCardResponse + +from microsoft_agents.activity import ActivityTypes +from microsoft_agents.hosting.core import ( + AgentApplication, + RouteRank, + TurnContext, +) + +from microsoft_agents.hosting.teams.teams_turn_context import TeamsTurnContext +from microsoft_agents.hosting.teams.type_defs import ( + _RouteDecorator, + StateT, +) +from microsoft_agents.hosting.teams._utils import _send_invoke_response + +from .route_handlers import FileConsentHandler + +class FileConsent(Generic[StateT]): + + def __init__(self, app: AgentApplication[StateT]): + self._app = app + + def _create_decorator( + self, + action: str, + *, + auth_handlers: Optional[list[str]] = None, + rank: RouteRank = RouteRank.DEFAULT, + ) -> _RouteDecorator[FileConsentHandler[StateT]]: + + def __selector(context: TurnContext) -> bool: + return ( + context.activity.type == ActivityTypes.invoke + and context.activity.name == "fileConsent/invoke" + and isinstance(context.activity.value, dict) + and context.activity.value.get("action") == action + ) + + def __call(func: FileConsentHandler[StateT]) -> FileConsentHandler[StateT]: + async def __handler(context: TurnContext, state: StateT) -> None: + teams_context = TeamsTurnContext(context) + file_consent = FileConsentCardResponse.model_validate( + context.activity.value or {} + ) + await func(teams_context, state, file_consent) + await _send_invoke_response(context) + + self._app.add_route( + __selector, + __handler, + is_invoke=True, + rank=rank, + auth_handlers=auth_handlers, + ) + return func + + return __call + + def on_file_consent_accept( + self, + *, + auth_handlers: Optional[list[str]] = None, + rank: RouteRank = RouteRank.DEFAULT, + ) -> _RouteDecorator[FileConsentHandler[StateT]]: + """Register a handler for fileConsent/invoke with action == 'accept'.""" + return self._create_decorator("accept") + + def on_file_consent_decline( + self, + *, + auth_handlers: Optional[list[str]] = None, + rank: RouteRank = RouteRank.DEFAULT, + ) -> _RouteDecorator[FileConsentHandler[StateT]]: + """Register a handler for fileConsent/invoke with action == 'decline'.""" + return self._create_decorator("decline") \ No newline at end of file diff --git a/libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/file_consent/route_handlers.py b/libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/file_consent/route_handlers.py index e69de29bb..826379b90 100644 --- a/libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/file_consent/route_handlers.py +++ b/libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/file_consent/route_handlers.py @@ -0,0 +1,17 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +from typing import Awaitable, Protocol + +from microsoft_teams.api.models import FileConsentCardResponse + +from microsoft_agents.hosting.teams.teams_turn_context import TeamsTurnContext +from microsoft_agents.hosting.teams.type_defs import StateT + +class FileConsentHandler(Protocol[StateT]): + def __call__( + self, + context: TeamsTurnContext, + state: StateT, + file_consent: FileConsentCardResponse + ) -> Awaitable[None]: ... \ No newline at end of file diff --git a/libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/message/__init__.py b/libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/message/__init__.py index e69de29bb..4ea36343a 100644 --- a/libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/message/__init__.py +++ b/libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/message/__init__.py @@ -0,0 +1,8 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +from .message import Message + +__all__ = [ + "Message", +] \ No newline at end of file diff --git a/libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/message/message.py b/libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/message/message.py index f500e5e5d..c8c0cf7fb 100644 --- a/libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/message/message.py +++ b/libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/message/message.py @@ -22,7 +22,10 @@ _RouteDecorator, StateT, ) -from microsoft_agetns.hosting.teams._utils import _get_channel_event_type +from microsoft_agetns.hosting.teams._utils import ( + _get_channel_event_type, + _send_invoke_response, +) from .route_handlers import ( O365ConnectorCardActionHandler, diff --git a/libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/teams_agent_extension.py b/libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/teams_agent_extension.py index a4f777df9..c542d7c79 100644 --- a/libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/teams_agent_extension.py +++ b/libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/teams_agent_extension.py @@ -271,123 +271,7 @@ async def __handler(context: TurnContext, state: StateT) -> None: if handler is not None: return __call(handler) return __call - - # ── File consent ─────────────────────────────────────────────────────── - - def on_file_consent_accept( - self, - handler: Optional[Callable] = None, - *, - auth_handlers: Optional[list[str]] = None, - rank: RouteRank = RouteRank.DEFAULT, - ) -> Callable: - """Register a handler for fileConsent/invoke with action == 'accept'.""" - - def __selector(context: TurnContext) -> bool: - return ( - context.activity.type == ActivityTypes.invoke - and context.activity.name == "fileConsent/invoke" - and isinstance(context.activity.value, dict) - and context.activity.value.get("action") == "accept" - ) - - def __call(func: Callable) -> Callable: - async def __handler(context: TurnContext, state: StateT) -> None: - file_consent = FileConsentCardResponse.model_validate( - context.activity.value or {} - ) - await func(context, state, file_consent) - await _send_invoke_response(context) - - self._app.add_route( - __selector, - __handler, - is_invoke=True, - rank=rank, - auth_handlers=auth_handlers, - ) - return func - - if handler is not None: - return __call(handler) - return __call - - def on_file_consent_decline( - self, - handler: Optional[Callable] = None, - *, - auth_handlers: Optional[list[str]] = None, - rank: RouteRank = RouteRank.DEFAULT, - ) -> Callable: - """Register a handler for fileConsent/invoke with action == 'decline'.""" - - def __selector(context: TurnContext) -> bool: - return ( - context.activity.type == ActivityTypes.invoke - and context.activity.name == "fileConsent/invoke" - and isinstance(context.activity.value, dict) - and context.activity.value.get("action") == "decline" - ) - - def __call(func: Callable) -> Callable: - async def __handler(context: TurnContext, state: StateT) -> None: - file_consent = FileConsentCardResponse.model_validate( - context.activity.value or {} - ) - await func(context, state, file_consent) - await _send_invoke_response(context) - - self._app.add_route( - __selector, - __handler, - is_invoke=True, - rank=rank, - auth_handlers=auth_handlers, - ) - return func - - if handler is not None: - return __call(handler) - return __call - - # ── O365 Connector ───────────────────────────────────────────────────── - - def on_o365_connector_card_action( - self, - handler: Optional[Callable] = None, - *, - auth_handlers: Optional[list[str]] = None, - rank: RouteRank = RouteRank.DEFAULT, - ) -> Callable: - """Register a handler for actionableMessage/executeAction invokes.""" - - def __selector(context: TurnContext) -> bool: - return ( - context.activity.type == ActivityTypes.invoke - and context.activity.name == "actionableMessage/executeAction" - ) - - def __call(func: Callable) -> Callable: - async def __handler(context: TurnContext, state: StateT) -> None: - query = O365ConnectorCardActionQuery.model_validate( - context.activity.value or {} - ) - await func(context, state, query) - await _send_invoke_response(context) - - self._app.add_route( - __selector, - __handler, - is_invoke=True, - rank=rank, - auth_handlers=auth_handlers, - ) - return func - - if handler is not None: - return __call(handler) - return __call - + # ── Conversation update events ───────────────────────────────────────── def on_members_added( From 7537bc232556281333b35e71a193d0a6dc1508e2 Mon Sep 17 00:00:00 2001 From: Rodrigo Brandao Date: Wed, 17 Jun 2026 16:09:11 -0700 Subject: [PATCH 5/8] Adding refactored configuration routes --- .../teams/configuration/configuration.py | 74 +++++++++++++++++++ .../teams/configuration/route_handlers.py | 17 +++++ .../teams/file_consent/file_consent.py | 4 +- .../hosting/teams/teams_agent_extension.py | 68 ----------------- 4 files changed, 93 insertions(+), 70 deletions(-) diff --git a/libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/configuration/configuration.py b/libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/configuration/configuration.py index e69de29bb..24f8224e1 100644 --- a/libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/configuration/configuration.py +++ b/libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/configuration/configuration.py @@ -0,0 +1,74 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +from typing import Generic, Optional + +from microsoft_agents.activity import ActivityTypes +from microsoft_agents.hosting.core import ( + AgentApplication, + RouteRank, + TurnContext, +) + +from microsoft_agents.hosting.teams.teams_turn_context import TeamsTurnContext +from microsoft_agents.hosting.teams.type_defs import ( + _RouteDecorator, + StateT, +) +from microsoft_agents.hosting.teams._utils import _send_invoke_response + +from .route_handlers import ConfigurationHandler + +class Configuration(Generic[StateT]): + + def __init__(self, app: AgentApplication[StateT]): + self._app = app + + def _create_decorator( + self, + name: str, + *, + auth_handlers: Optional[list[str]] = None, + rank: RouteRank = RouteRank.DEFAULT, + ) -> _RouteDecorator[ConfigurationHandler[StateT]]: + def __selector(context: TurnContext) -> bool: + return ( + context.activity.type == ActivityTypes.invoke + and context.activity.name == name + ) + + def __call(func: ConfigurationHandler[StateT]) -> ConfigurationHandler[StateT]: + async def __handler(context: TurnContext, state: StateT) -> None: + teams_context = TeamsTurnContext(context) + response = await func(teams_context, state, context.activity.value) + if response is not None: + await _send_invoke_response(context, response) + + self._app.add_route( + __selector, + __handler, + is_invoke=True, + rank=rank, + auth_handlers=auth_handlers, + ) + return func + + return __call + + def on_config_fetch( + self, + *, + auth_handlers: Optional[list[str]] = None, + rank: RouteRank = RouteRank.DEFAULT, + ) -> _RouteDecorator[ConfigurationHandler[StateT]]: + """Register a handler for config/fetch invokes.""" + return self._create_decorator("config/fetch", auth_handlers=auth_handlers, rank=rank) + + def on_config_submit( + self, + *, + auth_handlers: Optional[list[str]] = None, + rank: RouteRank = RouteRank.DEFAULT, + ) -> _RouteDecorator[ConfigurationHandler[StateT]]: + """Register a handler for config/submit invokes.""" + return self._create_decorator("config/submit", auth_handlers=auth_handlers, rank=rank) \ No newline at end of file diff --git a/libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/configuration/route_handlers.py b/libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/configuration/route_handlers.py index e69de29bb..0b999e515 100644 --- a/libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/configuration/route_handlers.py +++ b/libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/configuration/route_handlers.py @@ -0,0 +1,17 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +from typing import Any, Awaitable, Protocol + +from microsoft_teams.api.models.config import ConfigResponse + +from microsoft_agents.hosting.teams.teams_turn_context import TeamsTurnContext +from microsoft_agents.hosting.teams.type_defs import StateT + +class ConfigurationHandler(Protocol[StateT]): + def __call__( + self, + context: TeamsTurnContext, + state: StateT, + config_data: Any, + ) -> Awaitable[ConfigResponse]: ... \ No newline at end of file diff --git a/libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/file_consent/file_consent.py b/libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/file_consent/file_consent.py index 127c52bfc..11005514a 100644 --- a/libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/file_consent/file_consent.py +++ b/libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/file_consent/file_consent.py @@ -69,7 +69,7 @@ def on_file_consent_accept( rank: RouteRank = RouteRank.DEFAULT, ) -> _RouteDecorator[FileConsentHandler[StateT]]: """Register a handler for fileConsent/invoke with action == 'accept'.""" - return self._create_decorator("accept") + return self._create_decorator("accept", auth_handlers=auth_handlers, rank=rank) def on_file_consent_decline( self, @@ -78,4 +78,4 @@ def on_file_consent_decline( rank: RouteRank = RouteRank.DEFAULT, ) -> _RouteDecorator[FileConsentHandler[StateT]]: """Register a handler for fileConsent/invoke with action == 'decline'.""" - return self._create_decorator("decline") \ No newline at end of file + return self._create_decorator("decline", auth_handlers=auth_handlers, rank=rank) \ No newline at end of file diff --git a/libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/teams_agent_extension.py b/libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/teams_agent_extension.py index c542d7c79..9522c5625 100644 --- a/libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/teams_agent_extension.py +++ b/libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/teams_agent_extension.py @@ -203,74 +203,6 @@ async def __handler(context: TurnContext, state: StateT) -> None: return __call # ── Config ───────────────────────────────────────────────────────────── - - def on_config_fetch( - self, - handler: Optional[Callable] = None, - *, - auth_handlers: Optional[list[str]] = None, - rank: RouteRank = RouteRank.DEFAULT, - ) -> Callable: - """Register a handler for config/fetch invokes.""" - - def __selector(context: TurnContext) -> bool: - return ( - context.activity.type == ActivityTypes.invoke - and context.activity.name == "config/fetch" - ) - - def __call(func: Callable) -> Callable: - async def __handler(context: TurnContext, state: StateT) -> None: - response = await func(context, state, context.activity.value) - if response is not None: - await _send_invoke_response(context, response) - - self._app.add_route( - __selector, - __handler, - is_invoke=True, - rank=rank, - auth_handlers=auth_handlers, - ) - return func - - if handler is not None: - return __call(handler) - return __call - - def on_config_submit( - self, - handler: Optional[Callable] = None, - *, - auth_handlers: Optional[list[str]] = None, - rank: RouteRank = RouteRank.DEFAULT, - ) -> Callable: - """Register a handler for config/submit invokes.""" - - def __selector(context: TurnContext) -> bool: - return ( - context.activity.type == ActivityTypes.invoke - and context.activity.name == "config/submit" - ) - - def __call(func: Callable) -> Callable: - async def __handler(context: TurnContext, state: StateT) -> None: - response = await func(context, state, context.activity.value) - if response is not None: - await _send_invoke_response(context, response) - - self._app.add_route( - __selector, - __handler, - is_invoke=True, - rank=rank, - auth_handlers=auth_handlers, - ) - return func - - if handler is not None: - return __call(handler) - return __call # ── Conversation update events ───────────────────────────────────────── From 50867ec55b30e1330c79d8ce788c1dd90fbbf269 Mon Sep 17 00:00:00 2001 From: Rodrigo Brandao Date: Wed, 17 Jun 2026 16:29:36 -0700 Subject: [PATCH 6/8] Refactor Team routes --- .../microsoft_agents/hosting/teams/_utils.py | 2 +- .../hosting/teams/app/route_handlers.py | 0 .../hosting/teams/channel/__init__.py | 6 + .../hosting/teams/channel/channel.py | 111 +++++++ .../hosting/teams/channel/route_handlers.py | 17 + .../hosting/teams/configuration/__init__.py | 6 + .../hosting/teams/file_consent/__init__.py | 6 + .../hosting/teams/meeting/__init__.py | 6 + .../teams/message_extension/__init__.py | 6 + .../hosting/teams/task_module/__init__.py | 6 + .../hosting/teams/team/__init__.py | 6 + .../hosting/teams/team/route_handlers.py | 17 + .../hosting/teams/team/team.py | 139 +++++++- .../hosting/teams/teams_agent_extension.py | 299 ++---------------- 14 files changed, 358 insertions(+), 269 deletions(-) create mode 100644 libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/app/route_handlers.py create mode 100644 libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/team/route_handlers.py diff --git a/libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/_utils.py b/libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/_utils.py index bce969664..9ba3b0780 100644 --- a/libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/_utils.py +++ b/libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/_utils.py @@ -42,4 +42,4 @@ async def _send_invoke_response(context: TurnContext, body: Any = None) -> None: type=ActivityTypes.invoke_response, value=InvokeResponse(status=int(HTTPStatus.OK), body=serialized_body), ) - ) \ No newline at end of file + ) diff --git a/libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/app/route_handlers.py b/libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/app/route_handlers.py new file mode 100644 index 000000000..e69de29bb diff --git a/libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/channel/__init__.py b/libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/channel/__init__.py index e69de29bb..d003f17a9 100644 --- a/libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/channel/__init__.py +++ b/libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/channel/__init__.py @@ -0,0 +1,6 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +from .channel import Channel + +__all__ = ["Channel"] \ No newline at end of file diff --git a/libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/channel/channel.py b/libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/channel/channel.py index e69de29bb..6437d03cc 100644 --- a/libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/channel/channel.py +++ b/libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/channel/channel.py @@ -0,0 +1,111 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +from typing import Generic, Optional + +from microsoft_teams.api.models.channel_data import ChannelData + +from microsoft_agents.activity import ActivityTypes +from microsoft_agents.hosting.core import ( + AgentApplication, + RouteRank, + TurnContext, +) + +from microsoft_agents.hosting.teams.teams_turn_context import TeamsTurnContext +from microsoft_agents.hosting.teams.type_defs import ( + _RouteDecorator, + StateT, +) +from microsoft_agents.hosting.teams._utils import ( + _send_invoke_response, + _get_channel_event_type, +) + +from .route_handlers import ChannelUpdateHandler + +def _get_channel_data(context: TurnContext) -> ChannelData: + data = context.activity.channel_data + if data is None: + return ChannelData() + if isinstance(data, dict): + return ChannelData(**data) + return ChannelData.model_validate(data) + +class Channel(Generic[StateT]): + + def __init__(self, app: AgentApplication[StateT]): + self._app = app + + def _create_decorator( + self, + event_type: str, + *, + auth_handlers: Optional[list[str]] = None, + rank: RouteRank = RouteRank.DEFAULT, + ) -> _RouteDecorator[ChannelUpdateHandler[StateT]]: + def __selector(context: TurnContext) -> bool: + return ( + context.activity.type == ActivityTypes.conversation_update + and context.activity.channel_id == "msteams" + and _get_channel_event_type(context) == event_type + ) + + def __call(func: ChannelUpdateHandler[StateT]) -> ChannelUpdateHandler[StateT]: + + async def __handler(context: TurnContext, state: StateT) -> None: + teams_context = TeamsTurnContext(context) + channel_data = _get_channel_data(context) + await func(teams_context, state, channel_data) + + self._app.add_route( + __selector, __handler, rank=rank, auth_handlers=auth_handlers + ) + return func + + return __call + + + def created( + self, + *, + auth_handlers: Optional[list[str]] = None, + rank: RouteRank = RouteRank.DEFAULT, + ) -> _RouteDecorator[ChannelUpdateHandler[StateT]]: + """Register a handler for Teams channelCreated conversation update events.""" + return self._create_decorator( + "channelCreated", auth_handlers=auth_handlers, rank=rank + ) + + def deleted( + self, + *, + auth_handlers: Optional[list[str]] = None, + rank: RouteRank = RouteRank.DEFAULT, + ) -> _RouteDecorator[ChannelUpdateHandler[StateT]]: + """Register a handler for Teams channelDeleted conversation update events.""" + return self._create_decorator( + "channelDeleted", auth_handlers=auth_handlers, rank=rank + ) + + def renamed( + self, + *, + auth_handlers: Optional[list[str]] = None, + rank: RouteRank = RouteRank.DEFAULT, + ) -> _RouteDecorator[ChannelUpdateHandler[StateT]]: + """Register a handler for Teams channelRenamed conversation update events.""" + return self._create_decorator( + "channelRenamed", auth_handlers=auth_handlers, rank=rank + ) + + def restored( + self, + *, + auth_handlers: Optional[list[str]] = None, + rank: RouteRank = RouteRank.DEFAULT, + ) -> _RouteDecorator[ChannelUpdateHandler[StateT]]: + """Register a handler for Teams channelRestored conversation update events.""" + return self._create_decorator( + "channelRestored", auth_handlers=auth_handlers, rank=rank + ) \ No newline at end of file diff --git a/libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/channel/route_handlers.py b/libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/channel/route_handlers.py index e69de29bb..42c50d060 100644 --- a/libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/channel/route_handlers.py +++ b/libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/channel/route_handlers.py @@ -0,0 +1,17 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +from typing import Any, Awaitable, Protocol + +from microsoft_teams.api.models.channel_data import ChannelData + +from microsoft_agents.hosting.teams.teams_turn_context import TeamsTurnContext +from microsoft_agents.hosting.teams.type_defs import StateT + +class ChannelUpdateHandler(Protocol[StateT]): + def __call__( + self, + context: TeamsTurnContext, + state: StateT, + data: ChannelData, + ) -> Awaitable[None]: ... \ No newline at end of file diff --git a/libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/configuration/__init__.py b/libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/configuration/__init__.py index e69de29bb..edbb3adbf 100644 --- a/libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/configuration/__init__.py +++ b/libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/configuration/__init__.py @@ -0,0 +1,6 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +from .configuration import Configuration + +__all__ = ["Configuration"] \ No newline at end of file diff --git a/libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/file_consent/__init__.py b/libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/file_consent/__init__.py index e69de29bb..165a7bdf7 100644 --- a/libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/file_consent/__init__.py +++ b/libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/file_consent/__init__.py @@ -0,0 +1,6 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +from .file_consent import FileConsent + +__all__ = ["FileConsent"] \ No newline at end of file diff --git a/libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/meeting/__init__.py b/libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/meeting/__init__.py index e69de29bb..e2ae72b4f 100644 --- a/libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/meeting/__init__.py +++ b/libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/meeting/__init__.py @@ -0,0 +1,6 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +from .meeting import Meeting + +__all__ = ["Meeting"] \ No newline at end of file diff --git a/libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/message_extension/__init__.py b/libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/message_extension/__init__.py index e69de29bb..5ca78a985 100644 --- a/libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/message_extension/__init__.py +++ b/libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/message_extension/__init__.py @@ -0,0 +1,6 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +from .message_extension import MessageExtension + +__all__ = ["MessageExtension"] \ No newline at end of file diff --git a/libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/task_module/__init__.py b/libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/task_module/__init__.py index e69de29bb..102cc9157 100644 --- a/libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/task_module/__init__.py +++ b/libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/task_module/__init__.py @@ -0,0 +1,6 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +from .task_module import TaskModule + +__all__ = ["TaskModule"] \ No newline at end of file diff --git a/libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/team/__init__.py b/libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/team/__init__.py index e69de29bb..0451c92bb 100644 --- a/libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/team/__init__.py +++ b/libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/team/__init__.py @@ -0,0 +1,6 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +from .team import Team + +__all__ = ["Team"] \ No newline at end of file diff --git a/libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/team/route_handlers.py b/libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/team/route_handlers.py new file mode 100644 index 000000000..a7e9a1678 --- /dev/null +++ b/libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/team/route_handlers.py @@ -0,0 +1,17 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +from typing import Awaitable, Protocol + +from microsoft_teams.api.models import Team + +from microsoft_agents.hosting.teams.teams_turn_context import TeamsTurnContext +from microsoft_agents.hosting.teams.type_defs import StateT + +class TeamUpdateHandler(Protocol[StateT]): + def __call__( + self, + context: TeamsTurnContext, + state: StateT, + data: Team + ) -> Awaitable[None]: ... \ No newline at end of file diff --git a/libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/team/team.py b/libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/team/team.py index 519cbf67a..0f93f03f6 100644 --- a/libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/team/team.py +++ b/libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/team/team.py @@ -1 +1,138 @@ -m. \ No newline at end of file +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +from typing import ( + Callable, + Generic, + Optional +) + +from microsoft_teams.api.models import Team + +from microsoft_agents.activity import ActivityTypes +from microsoft_agents.hosting.core import ( + AgentApplication, + RouteRank, + TurnContext, +) + +from microsoft_agents.hosting.teams.route_handlers import TeamsRouteHandler +from microsoft_agents.hosting.teams.teams_turn_context import TeamsTurnContext +from microsoft_agents.hosting.teams.type_defs import ( + _RouteDecorator, + StateT, +) +from microsoft_agetns.hosting.teams._utils import ( + _get_channel_event_type, +) + +from .route_handlers import TeamUpdateHandler + +def _get_team_data(context: TurnContext) -> Team: + data = context.activity.channel_data + if data is None: + raise ValueError("Channel data is required") + if isinstance(data, dict): + return Team(**data) + return Team.model_validate(data) + +class Team(Generic[StateT]): + + def __init__(self, app: AgentApplication[StateT]): + self._app = app + + def _create_decorator( + self, + event_type: str, + *, + auth_handlers: Optional[list[str]] = None, + rank: RouteRank = RouteRank.DEFAULT, + ) -> _RouteDecorator[TeamUpdateHandler[StateT]]: + def __selector(context: TurnContext) -> bool: + return ( + context.activity.type == ActivityTypes.conversation_update + and context.activity.channel_id == "msteams" + and _get_channel_event_type(context) == event_type + ) + + def __call(func: TeamUpdateHandler[StateT]) -> TeamUpdateHandler[StateT]: + + async def __handler(context: TurnContext, state: StateT) -> None: + teams_context = TeamsTurnContext(context) + team_data = _get_team_data(context) + await func(teams_context, state, team_data) + + self._app.add_route( + __selector, __handler, rank=rank, auth_handlers=auth_handlers + ) + return func + + return __call + + + def archived( + self, + *, + auth_handlers: Optional[list[str]] = None, + rank: RouteRank = RouteRank.DEFAULT, + ) -> _RouteDecorator[TeamUpdateHandler[StateT]]: + """Register a handler for Teams teamArchived conversation update events.""" + return self._create_decorator( + "teamArchived", auth_handlers=auth_handlers, rank=rank + ) + + def deleted( + self, + *, + auth_handlers: Optional[list[str]] = None, + rank: RouteRank = RouteRank.DEFAULT, + ) -> _RouteDecorator[TeamUpdateHandler[StateT]]: + """Register a handler for Teams teamDeleted conversation update events.""" + return self._create_decorator( + "teamDeleted", auth_handlers=auth_handlers, rank=rank + ) + + def hard_deleted( + self, + *, + auth_handlers: Optional[list[str]] = None, + rank: RouteRank = RouteRank.DEFAULT, + ) -> _RouteDecorator[TeamUpdateHandler[StateT]]: + """Register a handler for Teams teamHardDeleted conversation update events.""" + return self._create_decorator( + "teamHardDeleted", auth_handlers=auth_handlers, rank=rank + ) + + def renamed( + self, + *, + auth_handlers: Optional[list[str]] = None, + rank: RouteRank = RouteRank.DEFAULT, + ) -> _RouteDecorator[TeamUpdateHandler[StateT]]: + """Register a handler for Teams teamRenamed conversation update events.""" + return self._create_decorator( + "teamRenamed", auth_handlers=auth_handlers, rank=rank + ) + + def restored( + self, + *, + auth_handlers: Optional[list[str]] = None, + rank: RouteRank = RouteRank.DEFAULT, + ) -> _RouteDecorator[TeamUpdateHandler[StateT]]: + """Register a handler for Teams teamRestored conversation update events.""" + return self._create_decorator( + "teamRestored", auth_handlers=auth_handlers, rank=rank + ) + + def unarchived( + self, + handler: Optional[Callable] = None, + *, + auth_handlers: Optional[list[str]] = None, + rank: RouteRank = RouteRank.DEFAULT, + ) -> _RouteDecorator[TeamUpdateHandler[StateT]]: + """Register a handler for Teams teamUnarchived conversation update events.""" + return self._create_decorator( + "teamUnarchived", auth_handlers=auth_handlers, rank=rank + ) \ No newline at end of file diff --git a/libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/teams_agent_extension.py b/libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/teams_agent_extension.py index 9522c5625..8ee419e51 100644 --- a/libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/teams_agent_extension.py +++ b/libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/teams_agent_extension.py @@ -29,6 +29,8 @@ ) from .channel import Channel +from .configuration import Configuration +from .file_consent import FileConsent from .meeting import Meeting from .message import Message from .message_extension import MessageExtension @@ -64,145 +66,53 @@ async def handle_meeting_start(context, state, meeting: MeetingDetails): def __init__(self, app: AgentApplication[StateT]) -> None: self._app = app - self._message_extension: MessageExtension[StateT] = MessageExtension(app) - self._task_module: TaskModule[StateT] = TaskModule(app) + + + self._channel: Channel[StateT] = Channel(app) + self._configuration: Configuration[StateT] = Configuration(app) + self._file_consent: FileConsent[StateT] = FileConsent(app) self._meeting: Meeting[StateT] = Meeting(app) self._message: Message[StateT] = Message(app) + self._message_extension: MessageExtension[StateT] = MessageExtension(app) + self._task_module: TaskModule[StateT] = TaskModule(app) self._team: Team[StateT] = Team(app) - self._channel: Channel[StateT] = Channel(app) - + @property - def message_extension(self) -> MessageExtension[StateT]: - """Route registration for Message Extension (composeExtension) invokes.""" - return self._message_extension + def channel(self) -> Channel[StateT]: + """Route registration for Channel events.""" + return self._channel @property - def task_module(self) -> TaskModule[StateT]: - """Route registration for Task Module (task/fetch, task/submit) invokes.""" - return self._task_module + def configuration(self) -> Configuration[StateT]: + """Route registration for Configuration events.""" + return self._configuration + + @property + def file_consent(self) -> FileConsent[StateT]: + """Route registration for File Consent events.""" + return self._file_consent @property def meeting(self) -> Meeting[StateT]: """Route registration for Meeting lifecycle events.""" return self._meeting - + @property def message(self) -> Message[StateT]: """Route registration for messaging activities.""" return self._message - # ── Message update / delete ──────────────────────────────────────────── - - def on_message_edit( - self, - handler: Optional[Callable] = None, - *, - auth_handlers: Optional[list[str]] = None, - rank: RouteRank = RouteRank.DEFAULT, - ) -> Callable: - """Register a handler for Teams editMessage events.""" - - def __selector(context: TurnContext) -> bool: - return ( - context.activity.type == ActivityTypes.message_update - and context.activity.channel_id == "msteams" - and _get_channel_event_type(context) == "editMessage" - ) - - def __call(func: Callable) -> Callable: - self._app.add_route( - __selector, func, rank=rank, auth_handlers=auth_handlers - ) - return func - - if handler is not None: - return __call(handler) - return __call - - def on_message_undelete( - self, - handler: Optional[Callable] = None, - *, - auth_handlers: Optional[list[str]] = None, - rank: RouteRank = RouteRank.DEFAULT, - ) -> Callable: - """Register a handler for Teams undeleteMessage events.""" - - def __selector(context: TurnContext) -> bool: - return ( - context.activity.type == ActivityTypes.message_update - and context.activity.channel_id == "msteams" - and _get_channel_event_type(context) == "undeleteMessage" - ) - - def __call(func: Callable) -> Callable: - self._app.add_route( - __selector, func, rank=rank, auth_handlers=auth_handlers - ) - return func - - if handler is not None: - return __call(handler) - return __call - - def on_message_soft_delete( - self, - handler: Optional[Callable] = None, - *, - auth_handlers: Optional[list[str]] = None, - rank: RouteRank = RouteRank.DEFAULT, - ) -> Callable: - """Register a handler for Teams softDeleteMessage events.""" - - def __selector(context: TurnContext) -> bool: - return ( - context.activity.type == ActivityTypes.message_delete - and context.activity.channel_id == "msteams" - and _get_channel_event_type(context) == "softDeleteMessage" - ) - - def __call(func: Callable) -> Callable: - self._app.add_route( - __selector, func, rank=rank, auth_handlers=auth_handlers - ) - return func - - if handler is not None: - return __call(handler) - return __call - - # ── Read receipt ─────────────────────────────────────────────────────── - - def on_read_receipt( - self, - handler: Optional[Callable] = None, - *, - auth_handlers: Optional[list[str]] = None, - rank: RouteRank = RouteRank.DEFAULT, - ) -> Callable: - """Register a handler for Teams readReceipt events.""" - - def __selector(context: TurnContext) -> bool: - return ( - context.activity.type == ActivityTypes.event - and context.activity.name == "application/vnd.microsoft.readReceipt" - ) - - def __call(func: Callable) -> Callable: - async def __handler(context: TurnContext, state: StateT) -> None: - receipt = ReadReceiptInfo.model_validate(context.activity.value or {}) - await func(context, state, receipt) - - self._app.add_route( - __selector, __handler, rank=rank, auth_handlers=auth_handlers - ) - return func + @property + def message_extension(self) -> MessageExtension[StateT]: + """Route registration for Message Extension (composeExtension) invokes.""" + return self._message_extension - if handler is not None: - return __call(handler) - return __call + @property + def task_module(self) -> TaskModule[StateT]: + """Route registration for Task Module (task/fetch, task/submit) invokes.""" + return self._task_module + - # ── Config ───────────────────────────────────────────────────────────── # ── Conversation update events ───────────────────────────────────────── @@ -258,149 +168,4 @@ def __call(func: Callable) -> Callable: if handler is not None: return __call(handler) - return __call - - def on_channel_created( - self, - handler: Optional[Callable] = None, - *, - auth_handlers: Optional[list[str]] = None, - rank: RouteRank = RouteRank.DEFAULT, - ) -> Callable: - """Register a handler for Teams channelCreated conversation update events.""" - return self._on_teams_channel_event( - "channelCreated", handler, auth_handlers=auth_handlers, rank=rank - ) - - def on_channel_deleted( - self, - handler: Optional[Callable] = None, - *, - auth_handlers: Optional[list[str]] = None, - rank: RouteRank = RouteRank.DEFAULT, - ) -> Callable: - """Register a handler for Teams channelDeleted conversation update events.""" - return self._on_teams_channel_event( - "channelDeleted", handler, auth_handlers=auth_handlers, rank=rank - ) - - def on_channel_renamed( - self, - handler: Optional[Callable] = None, - *, - auth_handlers: Optional[list[str]] = None, - rank: RouteRank = RouteRank.DEFAULT, - ) -> Callable: - """Register a handler for Teams channelRenamed conversation update events.""" - return self._on_teams_channel_event( - "channelRenamed", handler, auth_handlers=auth_handlers, rank=rank - ) - - def on_channel_restored( - self, - handler: Optional[Callable] = None, - *, - auth_handlers: Optional[list[str]] = None, - rank: RouteRank = RouteRank.DEFAULT, - ) -> Callable: - """Register a handler for Teams channelRestored conversation update events.""" - return self._on_teams_channel_event( - "channelRestored", handler, auth_handlers=auth_handlers, rank=rank - ) - - def on_team_archived( - self, - handler: Optional[Callable] = None, - *, - auth_handlers: Optional[list[str]] = None, - rank: RouteRank = RouteRank.DEFAULT, - ) -> Callable: - """Register a handler for Teams teamArchived conversation update events.""" - return self._on_teams_channel_event( - "teamArchived", handler, auth_handlers=auth_handlers, rank=rank - ) - - def on_team_deleted( - self, - handler: Optional[Callable] = None, - *, - auth_handlers: Optional[list[str]] = None, - rank: RouteRank = RouteRank.DEFAULT, - ) -> Callable: - """Register a handler for Teams teamDeleted conversation update events.""" - return self._on_teams_channel_event( - "teamDeleted", handler, auth_handlers=auth_handlers, rank=rank - ) - - def on_team_hard_deleted( - self, - handler: Optional[Callable] = None, - *, - auth_handlers: Optional[list[str]] = None, - rank: RouteRank = RouteRank.DEFAULT, - ) -> Callable: - """Register a handler for Teams teamHardDeleted conversation update events.""" - return self._on_teams_channel_event( - "teamHardDeleted", handler, auth_handlers=auth_handlers, rank=rank - ) - - def on_team_renamed( - self, - handler: Optional[Callable] = None, - *, - auth_handlers: Optional[list[str]] = None, - rank: RouteRank = RouteRank.DEFAULT, - ) -> Callable: - """Register a handler for Teams teamRenamed conversation update events.""" - return self._on_teams_channel_event( - "teamRenamed", handler, auth_handlers=auth_handlers, rank=rank - ) - - def on_team_restored( - self, - handler: Optional[Callable] = None, - *, - auth_handlers: Optional[list[str]] = None, - rank: RouteRank = RouteRank.DEFAULT, - ) -> Callable: - """Register a handler for Teams teamRestored conversation update events.""" - return self._on_teams_channel_event( - "teamRestored", handler, auth_handlers=auth_handlers, rank=rank - ) - - def on_team_unarchived( - self, - handler: Optional[Callable] = None, - *, - auth_handlers: Optional[list[str]] = None, - rank: RouteRank = RouteRank.DEFAULT, - ) -> Callable: - """Register a handler for Teams teamUnarchived conversation update events.""" - return self._on_teams_channel_event( - "teamUnarchived", handler, auth_handlers=auth_handlers, rank=rank - ) - - def _on_teams_channel_event( - self, - event_type: str, - handler: Optional[Callable], - *, - auth_handlers: Optional[list[str]] = None, - rank: RouteRank = RouteRank.DEFAULT, - ) -> Callable: - def __selector(context: TurnContext) -> bool: - return ( - context.activity.type == ActivityTypes.conversation_update - and context.activity.channel_id == "msteams" - and _get_channel_event_type(context) == event_type - ) - - def __call(func: Callable) -> Callable: - self._app.add_route( - __selector, func, rank=rank, auth_handlers=auth_handlers - ) - return func - - if handler is not None: - return __call(handler) - return __call + return __call \ No newline at end of file From 130b7abf5d4eade1966cbc4fba5904c6ee540f58 Mon Sep 17 00:00:00 2001 From: Rodrigo Brandao Date: Wed, 17 Jun 2026 16:36:08 -0700 Subject: [PATCH 7/8] Cleaned up TeamsAgentExtension --- .../hosting/teams/channel/channel.py | 66 +++++++++++++- .../hosting/teams/teams_agent_extension.py | 89 ++----------------- 2 files changed, 69 insertions(+), 86 deletions(-) diff --git a/libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/channel/channel.py b/libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/channel/channel.py index 6437d03cc..6f70b2bcc 100644 --- a/libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/channel/channel.py +++ b/libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/channel/channel.py @@ -99,7 +99,7 @@ def renamed( "channelRenamed", auth_handlers=auth_handlers, rank=rank ) - def restored( + def rest( self, *, auth_handlers: Optional[list[str]] = None, @@ -108,4 +108,66 @@ def restored( """Register a handler for Teams channelRestored conversation update events.""" return self._create_decorator( "channelRestored", auth_handlers=auth_handlers, rank=rank - ) \ No newline at end of file + ) + + def on_members_added( + self, + *, + auth_handlers: Optional[list[str]] = None, + rank: RouteRank = RouteRank.DEFAULT, + ) -> _RouteDecorator[ChannelUpdateHandler[StateT]]: + """Register a handler for Teams membersAdded conversation update events.""" + + def __selector(context: TurnContext) -> bool: + return ( + context.activity.type == ActivityTypes.conversation_update + and context.activity.channel_id == "msteams" + and isinstance(context.activity.members_added, list) + and len(context.activity.members_added) > 0 + ) + + def __call(func: ChannelUpdateHandler[StateT]) -> ChannelUpdateHandler[StateT]: + + async def __func(context: TurnContext, state: StateT) -> None: + teams_context = TeamsTurnContext(context) + channel_data = _get_channel_data(context) + await func(teams_context, state, channel_data) + + self._app.add_route( + __selector, __func, rank=rank, auth_handlers=auth_handlers + ) + + return func + + return __call + + def on_members_removed( + self, + *, + auth_handlers: Optional[list[str]] = None, + rank: RouteRank = RouteRank.DEFAULT, + ) -> _RouteDecorator[ChannelUpdateHandler[StateT]]: + """Register a handler for Teams membersRemoved conversation update events.""" + + def __selector(context: TurnContext) -> bool: + return ( + context.activity.type == ActivityTypes.conversation_update + and context.activity.channel_id == "msteams" + and isinstance(context.activity.members_removed, list) + and len(context.activity.members_removed) > 0 + ) + + def __call(func: ChannelUpdateHandler[StateT]) -> ChannelUpdateHandler[StateT]: + + async def __func(context: TurnContext, state: StateT) -> None: + teams_context = TeamsTurnContext(context) + channel_data = _get_channel_data(context) + await func(teams_context, state, channel_data) + + self._app.add_route( + __selector, __func, rank=rank, auth_handlers=auth_handlers + ) + + return func + + return __call \ No newline at end of file diff --git a/libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/teams_agent_extension.py b/libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/teams_agent_extension.py index 8ee419e51..d45befe0f 100644 --- a/libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/teams_agent_extension.py +++ b/libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/teams_agent_extension.py @@ -5,28 +5,9 @@ from __future__ import annotations -import re -from http import HTTPStatus -from typing import Any, Callable, Generic, Optional, Pattern, TypeVar - -from microsoft_agents.activity import Activity, ActivityTypes, InvokeResponse -from microsoft_agents.hosting.core import TurnContext -from microsoft_agents.hosting.core.app import AgentApplication, RouteRank -from microsoft_agents.hosting.core.app.state import TurnState - -from microsoft_agents.activity.teams import ( - MeetingParticipantsEventDetails, - ReadReceiptInfo, -) -from microsoft_teams.api.models import ( - AppBasedLinkQuery, - FileConsentCardResponse, - MeetingDetails, - MessagingExtensionAction, - MessagingExtensionQuery, - O365ConnectorCardActionQuery, - TaskModuleRequest, -) +from typing import Generic + +from microsoft_agents.hosting.core.app import AgentApplication from .channel import Channel from .configuration import Configuration @@ -37,9 +18,7 @@ from .task_module import TaskModule from .team import Team -from .type_defs import ( - StateT -) +from .type_defs import StateT class TeamsAgentExtension(Generic[StateT]): @@ -110,62 +89,4 @@ def message_extension(self) -> MessageExtension[StateT]: @property def task_module(self) -> TaskModule[StateT]: """Route registration for Task Module (task/fetch, task/submit) invokes.""" - return self._task_module - - - - # ── Conversation update events ───────────────────────────────────────── - - def on_members_added( - self, - handler: Optional[Callable] = None, - *, - auth_handlers: Optional[list[str]] = None, - rank: RouteRank = RouteRank.DEFAULT, - ) -> Callable: - """Register a handler for Teams membersAdded conversation update events.""" - - def __selector(context: TurnContext) -> bool: - return ( - context.activity.type == ActivityTypes.conversation_update - and context.activity.channel_id == "msteams" - and isinstance(context.activity.members_added, list) - and len(context.activity.members_added) > 0 - ) - - def __call(func: Callable) -> Callable: - self._app.add_route( - __selector, func, rank=rank, auth_handlers=auth_handlers - ) - return func - - if handler is not None: - return __call(handler) - return __call - - def on_members_removed( - self, - handler: Optional[Callable] = None, - *, - auth_handlers: Optional[list[str]] = None, - rank: RouteRank = RouteRank.DEFAULT, - ) -> Callable: - """Register a handler for Teams membersRemoved conversation update events.""" - - def __selector(context: TurnContext) -> bool: - return ( - context.activity.type == ActivityTypes.conversation_update - and context.activity.channel_id == "msteams" - and isinstance(context.activity.members_removed, list) - and len(context.activity.members_removed) > 0 - ) - - def __call(func: Callable) -> Callable: - self._app.add_route( - __selector, func, rank=rank, auth_handlers=auth_handlers - ) - return func - - if handler is not None: - return __call(handler) - return __call \ No newline at end of file + return self._task_module \ No newline at end of file From f3d1b31481210ee8fe3d2d2c5c8dd3865f903d0c Mon Sep 17 00:00:00 2001 From: Rodrigo Brandao Date: Wed, 17 Jun 2026 16:38:06 -0700 Subject: [PATCH 8/8] Removing duplicate logic --- .../hosting/teams/route_handlers.py | 46 +------------------ 1 file changed, 1 insertion(+), 45 deletions(-) diff --git a/libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/route_handlers.py b/libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/route_handlers.py index 3b1734e7b..95587c8a7 100644 --- a/libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/route_handlers.py +++ b/libraries/microsoft-agents-hosting-teams/microsoft_agents/hosting/teams/route_handlers.py @@ -24,48 +24,4 @@ def wrap(handler: TeamsRouteHandler[StateT]) -> RouteHandler[StateT]: async def __func(context: TurnContext, state: StateT) -> None: teams_context = TeamsTurnContext(context) await handler(teams_context, state) - return __func - -# Meetings route handlers - -# Message extension route handlers - -# Messages route handler - -class O365ConnectorCardActionHandler(Protocol[StateT]): - def __call__( - self, - context: TeamsTurnContext, - state: StateT, - query: O365ConnectorCardActionQuery - ) -> Awaitable[None]: - ... - -class ReadReceiptHandler(Protocol[StateT]): - def __call__( - self, - context: TeamsTurnContext, - state: StateT, - data: dict - ) -> Awaitable[None]: - ... - -# Teams Channels route handlers - -class ChannelUpdateHandler(Protocol[StateT]): - def __call__( - self, - context: TeamsTurnContext, - state: StateT, - data: Channel - ) -> Awaitable[None]: - ... - -class TeamUpdateHandler(Protocol[StateT]): - def __call__( - self, - context: TeamsTurnContext, - state: StateT, - data: Team - ) -> Awaitable[None]: - ... \ No newline at end of file + return __func \ No newline at end of file