From 0616063b860c5ba42bb84b8e33d7ed8d0df7b792 Mon Sep 17 00:00:00 2001 From: Rodrigo Brandao Date: Wed, 8 Jul 2026 08:22:43 -0700 Subject: [PATCH 1/4] Passing in missing arguments to TeamsActivityHandler method hooks --- .../activity/teams/teams_channel_account.py | 2 + .../hosting/core/activity_handler.py | 3 +- .../hosting/teams/teams_activity_handler.py | 142 +++++++++++++----- 3 files changed, 109 insertions(+), 38 deletions(-) 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 0f06fa12..1ec0a5fa 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 @@ -30,6 +30,8 @@ class TeamsChannelAccount(AgentsModel): model_config = ConfigDict(extra="allow") id: str = None + aad_object_id: str = None + role: str = None name: str = None given_name: str = None surname: str = None diff --git a/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/activity_handler.py b/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/activity_handler.py index 102ce272..4235dc60 100644 --- a/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/activity_handler.py +++ b/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/activity_handler.py @@ -3,7 +3,6 @@ from __future__ import annotations from http import HTTPStatus -from typing import Awaitable from pydantic import BaseModel from microsoft_agents.activity import TurnContextProtocol @@ -539,7 +538,7 @@ async def on_adaptive_card_invoke( raise _InvokeResponseException(HTTPStatus.NOT_IMPLEMENTED) @staticmethod - def _create_invoke_response(body: BaseModel = None) -> InvokeResponse: + def _create_invoke_response(body: BaseModel | None = None) -> InvokeResponse: serialized_body = ( body.model_dump(mode="json", by_alias=True, exclude_none=True) if body 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 c0511e2d..28cd42c3 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 @@ -74,20 +74,24 @@ async def on_invoke_activity(self, turn_context: TurnContext) -> InvokeResponse: await self.on_teams_config_submit(turn_context, value) ) elif name == "fileConsent/invoke": + card_response = FileConsentCardResponse.model_validate(value) return self._create_invoke_response( - await self.on_teams_file_consent(turn_context, value) + await self.on_teams_file_consent(turn_context, card_response) ) elif name == "actionableMessage/executeAction": - await self.on_teams_o365_connector_card_action(turn_context, value) + query = O365ConnectorCardActionQuery.model_validate(value) + await self.on_teams_o365_connector_card_action(turn_context, query) return self._create_invoke_response() elif name == "composeExtension/queryLink": + query = AppBasedLinkQuery.model_validate(value) return self._create_invoke_response( - await self.on_teams_app_based_link_query(turn_context, value) + await self.on_teams_app_based_link_query(turn_context, query) ) elif name == "composeExtension/anonymousQueryLink": + query = AppBasedLinkQuery.model_validate(value) return self._create_invoke_response( await self.on_teams_anonymous_app_based_link_query( - turn_context, value + turn_context, query ) ) elif name == "composeExtension/query": @@ -104,21 +108,24 @@ async def on_invoke_activity(self, turn_context: TurnContext) -> InvokeResponse: ) ) elif name == "composeExtension/submitAction": + action = MessagingExtensionAction.model_validate(value) return self._create_invoke_response( await self.on_teams_messaging_extension_submit_action_dispatch( - turn_context, value + turn_context, action ) ) elif name == "composeExtension/fetchTask": + action = MessagingExtensionAction.model_validate(value) return self._create_invoke_response( await self.on_teams_messaging_extension_fetch_task( - turn_context, value + turn_context, action ) ) elif name == "composeExtension/querySettingUrl": + query = MessagingExtensionQuery.model_validate(value) return self._create_invoke_response( await self.on_teams_messaging_extension_configuration_query_setting_url( - turn_context, value + turn_context, query ) ) elif name == "composeExtension/setting": @@ -146,15 +153,20 @@ async def on_invoke_activity(self, turn_context: TurnContext) -> InvokeResponse: ) ) elif name == "tab/fetch": + tab_request = TabRequest.model_validate(value) return self._create_invoke_response( - await self.on_teams_tab_fetch(turn_context, value) + await self.on_teams_tab_fetch(turn_context, tab_request) ) elif name == "tab/submit": + tab_submit = TabSubmit.model_validate(value) return self._create_invoke_response( - await self.on_teams_tab_submit(turn_context, value) + await self.on_teams_tab_submit(turn_context, tab_submit) ) else: - return await super().on_invoke_activity(turn_context) + return ( + await super().on_invoke_activity(turn_context) + or InvokeResponse() + ) except Exception as err: if str(err) == str(teams_errors.TeamsNotImplemented): return InvokeResponse(status=int(HTTPStatus.NOT_IMPLEMENTED)) @@ -490,6 +502,24 @@ async def on_teams_tab_submit( """ raise NotImplementedError(str(teams_errors.TeamsNotImplemented)) + @staticmethod + def _get_list_team_members( + members: list[ChannelAccount], + ) -> list[TeamsChannelAccount]: + """ + Converts a list of ChannelAccount to a list of TeamsChannelAccount. + + :param members: The list of ChannelAccount. + :return: A list of TeamsChannelAccount. + """ + teams_members = [] + for member in members: + teams_member = TeamsChannelAccount.model_validate( + member.model_dump(by_alias=True, exclude_unset=True) + ) + teams_members.append(teams_member) + return teams_members + async def on_conversation_update_activity(self, turn_context: TurnContext): """ Dispatches conversation update activity. @@ -498,10 +528,8 @@ async def on_conversation_update_activity(self, turn_context: TurnContext): :return: None """ if turn_context.activity.channel_id == "msteams": - channel_data = ( - TeamsChannelData.model_validate(turn_context.activity.channel_data) - if turn_context.activity.channel_data - else None + channel_data = TeamsChannelData.model_validate( + turn_context.activity.channel_data ) if ( @@ -509,16 +537,18 @@ async def on_conversation_update_activity(self, turn_context: TurnContext): and len(turn_context.activity.members_added) > 0 ): return await self.on_teams_members_added_dispatch( - turn_context.activity.members_added, - channel_data.team if channel_data else None, - turn_context, + turn_context.activity.members_added, channel_data.team, turn_context ) if ( turn_context.activity.members_removed and len(turn_context.activity.members_removed) > 0 ): - return await self.on_teams_members_removed(turn_context) + return await self.on_teams_members_removed_dispatch( + turn_context.activity.members_removed, + channel_data.team, + turn_context, + ) if not channel_data or not channel_data.event_type: return await super().on_conversation_update_activity(turn_context) @@ -526,25 +556,41 @@ async def on_conversation_update_activity(self, turn_context: TurnContext): event_type = channel_data.event_type if event_type == "channelCreated": - return await self.on_teams_channel_created(turn_context) + return await self.on_teams_channel_created( + channel_data.channel, channel_data.team, turn_context + ) elif event_type == "channelDeleted": - return await self.on_teams_channel_deleted(turn_context) + return await self.on_teams_channel_deleted( + channel_data.channel, channel_data.team, turn_context + ) elif event_type == "channelRenamed": - return await self.on_teams_channel_renamed(turn_context) + return await self.on_teams_channel_renamed( + channel_data.channel, channel_data.team, turn_context + ) elif event_type == "teamArchived": - return await self.on_teams_team_archived(turn_context) + return await self.on_teams_team_archived( + channel_data.team, turn_context + ) elif event_type == "teamDeleted": - return await self.on_teams_team_deleted(turn_context) + return await self.on_teams_team_deleted(channel_data.team, turn_context) elif event_type == "teamHardDeleted": - return await self.on_teams_team_hard_deleted(turn_context) + return await self.on_teams_team_hard_deleted( + channel_data.team, turn_context + ) elif event_type == "channelRestored": - return await self.on_teams_channel_restored(turn_context) + return await self.on_teams_channel_restored( + channel_data.channel, channel_data.team, turn_context + ) elif event_type == "teamRenamed": - return await self.on_teams_team_renamed(turn_context) + return await self.on_teams_team_renamed(channel_data.team, turn_context) elif event_type == "teamRestored": - return await self.on_teams_team_restored(turn_context) + return await self.on_teams_team_restored( + channel_data.team, turn_context + ) elif event_type == "teamUnarchived": - return await self.on_teams_team_unarchived(turn_context) + return await self.on_teams_team_unarchived( + channel_data.team, turn_context + ) return await super().on_conversation_update_activity(turn_context) @@ -668,6 +714,7 @@ async def on_teams_members_added_dispatch( # Propagate any other errors raise + await self.on_members_added_activity(members_added, turn_context) await self.on_teams_members_added(teams_members_added, team_info, turn_context) async def on_teams_members_added( @@ -682,7 +729,7 @@ async def on_teams_members_added( :param turn_context: The context object for the turn. :return: None """ - await self.on_members_added_activity(teams_members_added, turn_context) + return async def on_teams_members_removed_dispatch( self, @@ -700,7 +747,9 @@ async def on_teams_members_removed_dispatch( member.model_dump(by_alias=True, exclude_unset=True) ) ) - return await self.on_teams_members_removed( + + await self.on_members_removed_activity(members_removed, turn_context) + await self.on_teams_members_removed( teams_members_removed, team_info, turn_context ) @@ -716,7 +765,7 @@ async def on_teams_members_removed( :param turn_context: The context object for the turn. :return: None """ - await self.on_members_removed_activity(teams_members_removed, turn_context) + return async def on_teams_channel_created( self, channel_info: ChannelInfo, team_info: TeamInfo, turn_context: TurnContext @@ -837,21 +886,42 @@ async def on_event_activity(self, turn_context: TurnContext) -> None: """ if turn_context.activity.channel_id == "msteams": if turn_context.activity.name == "application/vnd.microsoft.readReceipt": - return await self.on_teams_read_receipt(turn_context) + return await self.on_teams_read_receipt( + ReadReceiptInfo.model_validate(turn_context.activity.value), + turn_context, + ) elif turn_context.activity.name == "application/vnd.microsoft.meetingStart": - return await self.on_teams_meeting_start(turn_context) + return await self.on_teams_meeting_start( + MeetingStartEventDetails.model_validate( + turn_context.activity.value + ), + turn_context, + ) elif turn_context.activity.name == "application/vnd.microsoft.meetingEnd": - return await self.on_teams_meeting_end(turn_context) + return await self.on_teams_meeting_end( + MeetingEndEventDetails.model_validate(turn_context.activity.value), + turn_context, + ) elif ( turn_context.activity.name == "application/vnd.microsoft.meetingParticipantJoin" ): - return await self.on_teams_meeting_participants_join(turn_context) + return await self.on_teams_meeting_participants_join( + MeetingParticipantsEventDetails.model_validate( + turn_context.activity.value + ), + turn_context, + ) elif ( turn_context.activity.name == "application/vnd.microsoft.meetingParticipantLeave" ): - return await self.on_teams_meeting_participants_leave(turn_context) + return await self.on_teams_meeting_participants_leave( + MeetingParticipantsEventDetails.model_validate( + turn_context.activity.value + ), + turn_context, + ) return await super().on_event_activity(turn_context) From 8aaf5b28dea3d59e56e7e1d382c8dee2273d5256 Mon Sep 17 00:00:00 2001 From: Rodrigo Brandao Date: Wed, 8 Jul 2026 08:34:08 -0700 Subject: [PATCH 2/4] Fixing docstrings and removing unused helpers --- .../hosting/teams/teams_activity_handler.py | 49 +++++++++++-------- 1 file changed, 28 insertions(+), 21 deletions(-) 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 28cd42c3..990920f2 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 @@ -163,9 +163,11 @@ async def on_invoke_activity(self, turn_context: TurnContext) -> InvokeResponse: await self.on_teams_tab_submit(turn_context, tab_submit) ) else: - return ( - await super().on_invoke_activity(turn_context) - or InvokeResponse() + return await super().on_invoke_activity( + turn_context + ) or InvokeResponse( + status=500, + body={"error": f"Invoke activity not handled for name: {name}"}, ) except Exception as err: if str(err) == str(teams_errors.TeamsNotImplemented): @@ -502,24 +504,6 @@ async def on_teams_tab_submit( """ raise NotImplementedError(str(teams_errors.TeamsNotImplemented)) - @staticmethod - def _get_list_team_members( - members: list[ChannelAccount], - ) -> list[TeamsChannelAccount]: - """ - Converts a list of ChannelAccount to a list of TeamsChannelAccount. - - :param members: The list of ChannelAccount. - :return: A list of TeamsChannelAccount. - """ - teams_members = [] - for member in members: - teams_member = TeamsChannelAccount.model_validate( - member.model_dump(by_alias=True, exclude_unset=True) - ) - teams_members.append(teams_member) - return teams_members - async def on_conversation_update_activity(self, turn_context: TurnContext): """ Dispatches conversation update activity. @@ -726,6 +710,8 @@ async def on_teams_members_added( """ Handles Teams members added. + :param teams_members_added: The list of TeamsChannelAccount objects representing the members added to the conversation. + :param team_info: The team info object. :param turn_context: The context object for the turn. :return: None """ @@ -762,6 +748,8 @@ async def on_teams_members_removed( """ Handles Teams members removed. + :param teams_members_removed: The list of TeamsChannelAccount objects representing the members removed from the conversation. + :param team_info: The team info object. :param turn_context: The context object for the turn. :return: None """ @@ -773,6 +761,8 @@ async def on_teams_channel_created( """ Handles Teams channel created. + :param channel_info: The channel info object. + :param team_info: The team info object. :param turn_context: The context object for the turn. :return: None """ @@ -784,6 +774,8 @@ async def on_teams_channel_deleted( """ Handles Teams channel deleted. + :param channel_info: The channel info object. + :param team_info: The team info object. :param turn_context: The context object for the turn. :return: None """ @@ -795,6 +787,8 @@ async def on_teams_channel_renamed( """ Handles Teams channel renamed. + :param channel_info: The channel info object. + :param team_info: The team info object. :param turn_context: The context object for the turn. :return: None """ @@ -806,6 +800,7 @@ async def on_teams_team_archived( """ Handles Teams team archived. + :param team_info: The team info object. :param turn_context: The context object for the turn. :return: None """ @@ -817,6 +812,7 @@ async def on_teams_team_deleted( """ Handles Teams team deleted. + :param team_info: The team info object. :param turn_context: The context object for the turn. :return: None """ @@ -828,6 +824,7 @@ async def on_teams_team_hard_deleted( """ Handles Teams team hard deleted. + :param team_info: The team info object. :param turn_context: The context object for the turn. :return: None """ @@ -839,6 +836,8 @@ async def on_teams_channel_restored( """ Handles Teams channel restored. + :param channel_info: The channel info object. + :param team_info: The team info object. :param turn_context: The context object for the turn. :return: None """ @@ -850,6 +849,7 @@ async def on_teams_team_renamed( """ Handles Teams team renamed. + :param team_info: The team info object. :param turn_context: The context object for the turn. :return: None """ @@ -861,6 +861,7 @@ async def on_teams_team_restored( """ Handles Teams team restored. + :param team_info: The team info object. :param turn_context: The context object for the turn. :return: None """ @@ -872,6 +873,7 @@ async def on_teams_team_unarchived( """ Handles Teams team unarchived. + :param team_info: The team info object. :param turn_context: The context object for the turn. :return: None """ @@ -931,6 +933,7 @@ async def on_teams_meeting_start( """ Handles Teams meeting start. + :param meeting: The meeting start event details. :param turn_context: The context object for the turn. :return: None """ @@ -942,6 +945,7 @@ async def on_teams_meeting_end( """ Handles Teams meeting end. + :param meeting: The meeting end event details. :param turn_context: The context object for the turn. :return: None """ @@ -953,6 +957,7 @@ async def on_teams_read_receipt( """ Handles Teams read receipt. + :param read_receipt: The read receipt info. :param turn_context: The context object for the turn. :return: None """ @@ -964,6 +969,7 @@ async def on_teams_meeting_participants_join( """ Handles Teams meeting participants join. + :param meeting: The meeting participants event details. :param turn_context: The context object for the turn. :return: None """ @@ -975,6 +981,7 @@ async def on_teams_meeting_participants_leave( """ Handles Teams meeting participants leave. + :param meeting: The meeting participants event details. :param turn_context: The context object for the turn. :return: None """ From 985ba74f80eefcb8dee3a285d97735a6b9f7db5d Mon Sep 17 00:00:00 2001 From: rodrigobr-msft Date: Wed, 8 Jul 2026 12:22:19 -0700 Subject: [PATCH 3/4] Apply suggestions from code review Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- .../hosting/teams/teams_activity_handler.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) 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 990920f2..2c171d5f 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 @@ -163,12 +163,7 @@ async def on_invoke_activity(self, turn_context: TurnContext) -> InvokeResponse: await self.on_teams_tab_submit(turn_context, tab_submit) ) else: - return await super().on_invoke_activity( - turn_context - ) or InvokeResponse( - status=500, - body={"error": f"Invoke activity not handled for name: {name}"}, - ) + return await super().on_invoke_activity(turn_context) except Exception as err: if str(err) == str(teams_errors.TeamsNotImplemented): return InvokeResponse(status=int(HTTPStatus.NOT_IMPLEMENTED)) From 8b8f4660241fb62260aca3b33f02e403d0c8b2f2 Mon Sep 17 00:00:00 2001 From: Rodrigo Brandao Date: Fri, 10 Jul 2026 10:23:13 -0700 Subject: [PATCH 4/4] Updating docstring --- .../microsoft_agents/activity/teams/teams_channel_account.py | 4 ++++ 1 file changed, 4 insertions(+) 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 1ec0a5fa..337a114b 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 @@ -11,6 +11,10 @@ class TeamsChannelAccount(AgentsModel): :param id: Channel id for the user or bot on this channel (Example: ``joe@smith.com``, or ``@joesmith`` or ``123456``) :type id: str + :param aad_object_id: Azure Active Directory object id for the user. + :type aad_object_id: str + :param role: Role of the user in the channel. + :type role: str :param name: Display friendly name :type name: str :param given_name: Given name part of the user name.