From 8b6f20f46cd94e857d237fc459b581ed607723a4 Mon Sep 17 00:00:00 2001 From: Axel Suarez Date: Mon, 3 Feb 2025 16:32:04 -0800 Subject: [PATCH 01/21] WIP client library --- .../microsoft/agents/client/__init__.py | 0 .../agents/client/channel_host_protocol.py | 25 +++++++++++++++++++ .../agents/client/channel_info_protocol.py | 0 .../agents/client/channel_protocol.py | 5 ++++ .../microsoft-agents-client/pyproject.toml | 22 ++++++++++++++++ 5 files changed, 52 insertions(+) create mode 100644 libraries/Client/microsoft-agents-client/microsoft/agents/client/__init__.py create mode 100644 libraries/Client/microsoft-agents-client/microsoft/agents/client/channel_host_protocol.py create mode 100644 libraries/Client/microsoft-agents-client/microsoft/agents/client/channel_info_protocol.py create mode 100644 libraries/Client/microsoft-agents-client/microsoft/agents/client/channel_protocol.py create mode 100644 libraries/Client/microsoft-agents-client/pyproject.toml diff --git a/libraries/Client/microsoft-agents-client/microsoft/agents/client/__init__.py b/libraries/Client/microsoft-agents-client/microsoft/agents/client/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/libraries/Client/microsoft-agents-client/microsoft/agents/client/channel_host_protocol.py b/libraries/Client/microsoft-agents-client/microsoft/agents/client/channel_host_protocol.py new file mode 100644 index 00000000..593a5ddb --- /dev/null +++ b/libraries/Client/microsoft-agents-client/microsoft/agents/client/channel_host_protocol.py @@ -0,0 +1,25 @@ +from typing import Protocol + +from .channel_protocol import ChannelProtocol +from .channel_info_protocol import ChannelInfoProtocol + + +class ChannelHostProtocol(Protocol): + @property + def host_endpoint(self) -> str: + raise NotImplementedError() + + @property + def host_app_id(self) -> str: + raise NotImplementedError() + + @property + def channels(self) -> dict[str, ChannelInfoProtocol]: + raise NotImplementedError() + + def get_channel(self, channel_info: ChannelInfoProtocol) -> ChannelProtocol: + raise NotImplementedError() + + def get_channel(self, name: str) -> ChannelProtocol: + raise NotImplementedError() + diff --git a/libraries/Client/microsoft-agents-client/microsoft/agents/client/channel_info_protocol.py b/libraries/Client/microsoft-agents-client/microsoft/agents/client/channel_info_protocol.py new file mode 100644 index 00000000..e69de29b diff --git a/libraries/Client/microsoft-agents-client/microsoft/agents/client/channel_protocol.py b/libraries/Client/microsoft-agents-client/microsoft/agents/client/channel_protocol.py new file mode 100644 index 00000000..34b358f4 --- /dev/null +++ b/libraries/Client/microsoft-agents-client/microsoft/agents/client/channel_protocol.py @@ -0,0 +1,5 @@ +from typing import Protocol + +class ChannelProtocol(Protocol): + async def post_activity(self, to_bot_id: str) -> dict: + raise NotImplementedError() \ No newline at end of file diff --git a/libraries/Client/microsoft-agents-client/pyproject.toml b/libraries/Client/microsoft-agents-client/pyproject.toml new file mode 100644 index 00000000..d7b167c6 --- /dev/null +++ b/libraries/Client/microsoft-agents-client/pyproject.toml @@ -0,0 +1,22 @@ +[build-system] +requires = ["setuptools"] +build-backend = "setuptools.build_meta" + +[project] +name = "microsoft-agents-client" +version = "0.0.0a1" +description = "A client library for Microsoft Agents" +authors = [{name = "Microsoft Corporation"}] +requires-python = ">=3.9" +classifiers = [ + "Programming Language :: Python :: 3", + "License :: OSI Approved :: MIT License", + "Operating System :: OS Independent", +] +dependencies = [ + "microsoft-agents-core", + "microsoft-agents-authentication" +] + +[project.urls] +"Homepage" = "https://github.com/microsoft/microsoft-agents-protocol" From 132ca71994b055452a37103b65d729a385738baf Mon Sep 17 00:00:00 2001 From: Axel Suarez Date: Mon, 3 Feb 2025 16:32:30 -0800 Subject: [PATCH 02/21] WIP client library - formatting --- .../microsoft/agents/client/channel_host_protocol.py | 9 ++++----- .../microsoft/agents/client/channel_protocol.py | 3 ++- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/libraries/Client/microsoft-agents-client/microsoft/agents/client/channel_host_protocol.py b/libraries/Client/microsoft-agents-client/microsoft/agents/client/channel_host_protocol.py index 593a5ddb..b958f340 100644 --- a/libraries/Client/microsoft-agents-client/microsoft/agents/client/channel_host_protocol.py +++ b/libraries/Client/microsoft-agents-client/microsoft/agents/client/channel_host_protocol.py @@ -8,18 +8,17 @@ class ChannelHostProtocol(Protocol): @property def host_endpoint(self) -> str: raise NotImplementedError() - + @property def host_app_id(self) -> str: raise NotImplementedError() - + @property def channels(self) -> dict[str, ChannelInfoProtocol]: raise NotImplementedError() - + def get_channel(self, channel_info: ChannelInfoProtocol) -> ChannelProtocol: raise NotImplementedError() - + def get_channel(self, name: str) -> ChannelProtocol: raise NotImplementedError() - diff --git a/libraries/Client/microsoft-agents-client/microsoft/agents/client/channel_protocol.py b/libraries/Client/microsoft-agents-client/microsoft/agents/client/channel_protocol.py index 34b358f4..631ce855 100644 --- a/libraries/Client/microsoft-agents-client/microsoft/agents/client/channel_protocol.py +++ b/libraries/Client/microsoft-agents-client/microsoft/agents/client/channel_protocol.py @@ -1,5 +1,6 @@ from typing import Protocol + class ChannelProtocol(Protocol): async def post_activity(self, to_bot_id: str) -> dict: - raise NotImplementedError() \ No newline at end of file + raise NotImplementedError() From f73fae1bc26257305ea9574e4455080a4dea709f Mon Sep 17 00:00:00 2001 From: Axel Suarez Martinez Date: Wed, 5 Feb 2025 13:32:15 -0800 Subject: [PATCH 03/21] HttpBotChannel WIP --- .../agents/client/channel_info_protocol.py | 51 +++++++++++++++++++ .../agents/client/channel_protocol.py | 12 ++++- .../agents/client/http_bot_channel.py | 28 ++++++++++ .../agents/core/models/invoke_response.py | 8 ++- 4 files changed, 96 insertions(+), 3 deletions(-) create mode 100644 libraries/Client/microsoft-agents-client/microsoft/agents/client/http_bot_channel.py diff --git a/libraries/Client/microsoft-agents-client/microsoft/agents/client/channel_info_protocol.py b/libraries/Client/microsoft-agents-client/microsoft/agents/client/channel_info_protocol.py index e69de29b..08ddcf1d 100644 --- a/libraries/Client/microsoft-agents-client/microsoft/agents/client/channel_info_protocol.py +++ b/libraries/Client/microsoft-agents-client/microsoft/agents/client/channel_info_protocol.py @@ -0,0 +1,51 @@ +from typing import Protocol + + +class ChannelInfoProtocol(Protocol): + @property + def id(self) -> str: + raise NotImplementedError() + + @property.setter + def id(self, value: str): + raise NotImplementedError() + + @property + def app_id(self) -> str: + raise NotImplementedError() + + @property.setter + def app_id(self, value: str): + raise NotImplementedError() + + @property + def resource_url(self) -> str: + raise NotImplementedError() + + @property.setter + def resource_url(self, value: str): + raise NotImplementedError() + + @property + def token_provider(self) -> str: + raise NotImplementedError() + + @property.setter + def token_provider(self, value: str): + raise NotImplementedError() + + @property + def channel_factory(self) -> str: + raise NotImplementedError() + + @property.setter + def channel_factory(self, value: str): + raise NotImplementedError() + + @property + def endpoint(self) -> str: + raise NotImplementedError() + + @property.setter + def endpoint(self, value: str): + raise NotImplementedError() diff --git a/libraries/Client/microsoft-agents-client/microsoft/agents/client/channel_protocol.py b/libraries/Client/microsoft-agents-client/microsoft/agents/client/channel_protocol.py index 631ce855..b28abc97 100644 --- a/libraries/Client/microsoft-agents-client/microsoft/agents/client/channel_protocol.py +++ b/libraries/Client/microsoft-agents-client/microsoft/agents/client/channel_protocol.py @@ -1,6 +1,16 @@ from typing import Protocol +from microsoft.agents.core.models import Activity, InvokeResponse + class ChannelProtocol(Protocol): - async def post_activity(self, to_bot_id: str) -> dict: + async def post_activity( + self, + to_bot_id: str, + to_bot_resource: str, + endpoint: str, + service_url: str, + conversation_id: str, + activity: Activity, + ) -> InvokeResponse: raise NotImplementedError() diff --git a/libraries/Client/microsoft-agents-client/microsoft/agents/client/http_bot_channel.py b/libraries/Client/microsoft-agents-client/microsoft/agents/client/http_bot_channel.py new file mode 100644 index 00000000..927a8488 --- /dev/null +++ b/libraries/Client/microsoft-agents-client/microsoft/agents/client/http_bot_channel.py @@ -0,0 +1,28 @@ +from aiohttp import ClientSession +from microsoft.agents.authentication import AccessTokenProviderBase +from microsoft.agents.core.models import Activity + +from .channel_protocol import ChannelProtocol + + +class HttpBotChannel(ChannelProtocol): + def __init__(self, token_access: AccessTokenProviderBase) -> None: + self._token_access = token_access + + async def post_activity( + self, + to_bot_id: str, + to_bot_resource: str, + endpoint: str, + service_url: str, + conversation_id: str, + activity: Activity, + ) -> dict: + if not endpoint: + raise ValueError("HttpBotChannel.post_activity: Endpoint is required") + if not service_url: + raise ValueError("HttpBotChannel.post_activity: Service URL is required") + if not conversation_id: + raise ValueError( + "HttpBotChannel.post_activity: Conversation ID is required" + ) diff --git a/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/invoke_response.py b/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/invoke_response.py index d52edd17..0c51733c 100644 --- a/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/invoke_response.py +++ b/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/invoke_response.py @@ -1,7 +1,11 @@ from ._agents_model import AgentsModel +from typing import Generic, TypeVar -class InvokeResponse(AgentsModel): +T = TypeVar("T") + + +class InvokeResponse(AgentsModel, Generic[T]): """ Tuple class containing an HTTP Status Code and a JSON serializable object. The HTTP Status code is, in the invoke activity scenario, what will @@ -13,7 +17,7 @@ class InvokeResponse(AgentsModel): """ status: int = None - body: object = None + body: T = None def is_successful_status_code(self) -> bool: """ From 17f2f0d9b57bc7ff8d11be1310236460126b7e4b Mon Sep 17 00:00:00 2001 From: Axel Suarez Martinez Date: Wed, 5 Feb 2025 23:27:11 -0800 Subject: [PATCH 04/21] ConversationIdFactory WIP --- .../client/bot_conversation_reference.py | 7 ++ .../agents/client/channel_protocol.py | 5 +- .../agents/client/conversation_constants.py | 5 ++ .../client/conversation_id_factory_options.py | 18 +++++ .../conversation_id_factory_protocol.py | 31 +++++++++ .../agents/client/http_bot_channel.py | 65 ++++++++++++++++++- .../microsoft/agents/core/models/__init__.py | 2 + .../microsoft/agents/core/models/activity.py | 2 +- .../models/adaptive_card_invoke_action.py | 2 +- .../models/adaptive_card_invoke_response.py | 2 +- .../core/models/adaptive_card_invoke_value.py | 2 +- .../{_agents_model.py => agents_model.py} | 0 .../agents/core/models/animation_card.py | 2 +- .../agents/core/models/attachment.py | 2 +- .../agents/core/models/attachment_data.py | 2 +- .../agents/core/models/attachment_info.py | 2 +- .../agents/core/models/attachment_view.py | 2 +- .../agents/core/models/audio_card.py | 2 +- .../agents/core/models/basic_card.py | 2 +- .../agents/core/models/card_action.py | 2 +- .../agents/core/models/card_image.py | 2 +- .../agents/core/models/channel_account.py | 2 +- .../core/models/conversation_account.py | 2 +- .../core/models/conversation_members.py | 2 +- .../core/models/conversation_parameters.py | 2 +- .../core/models/conversation_reference.py | 2 +- .../models/conversation_resource_response.py | 2 +- .../core/models/conversations_result.py | 2 +- .../microsoft/agents/core/models/entity.py | 2 +- .../microsoft/agents/core/models/error.py | 2 +- .../agents/core/models/error_response.py | 2 +- .../agents/core/models/expected_replies.py | 2 +- .../microsoft/agents/core/models/fact.py | 2 +- .../agents/core/models/geo_coordinates.py | 2 +- .../microsoft/agents/core/models/hero_card.py | 2 +- .../agents/core/models/inner_http_error.py | 2 +- .../agents/core/models/invoke_response.py | 8 +-- .../agents/core/models/media_card.py | 2 +- .../agents/core/models/media_event_value.py | 2 +- .../microsoft/agents/core/models/media_url.py | 2 +- .../microsoft/agents/core/models/mention.py | 2 +- .../agents/core/models/message_reaction.py | 2 +- .../agents/core/models/oauth_card.py | 2 +- .../core/models/paged_members_result.py | 2 +- .../microsoft/agents/core/models/place.py | 2 +- .../agents/core/models/receipt_card.py | 2 +- .../agents/core/models/receipt_item.py | 2 +- .../agents/core/models/resource_response.py | 2 +- .../agents/core/models/semantic_action.py | 2 +- .../agents/core/models/signin_card.py | 2 +- .../agents/core/models/suggested_actions.py | 2 +- .../agents/core/models/text_highlight.py | 2 +- .../microsoft/agents/core/models/thing.py | 2 +- .../agents/core/models/thumbnail_card.py | 2 +- .../agents/core/models/thumbnail_url.py | 2 +- .../models/token_exchange_invoke_request.py | 2 +- .../models/token_exchange_invoke_response.py | 2 +- .../core/models/token_exchange_state.py | 2 +- .../agents/core/models/token_request.py | 2 +- .../agents/core/models/token_response.py | 2 +- .../agents/core/models/transcript.py | 2 +- .../agents/core/models/video_card.py | 2 +- 62 files changed, 186 insertions(+), 61 deletions(-) create mode 100644 libraries/Client/microsoft-agents-client/microsoft/agents/client/bot_conversation_reference.py create mode 100644 libraries/Client/microsoft-agents-client/microsoft/agents/client/conversation_constants.py create mode 100644 libraries/Client/microsoft-agents-client/microsoft/agents/client/conversation_id_factory_options.py create mode 100644 libraries/Client/microsoft-agents-client/microsoft/agents/client/conversation_id_factory_protocol.py rename libraries/Core/microsoft-agents-core/microsoft/agents/core/models/{_agents_model.py => agents_model.py} (100%) diff --git a/libraries/Client/microsoft-agents-client/microsoft/agents/client/bot_conversation_reference.py b/libraries/Client/microsoft-agents-client/microsoft/agents/client/bot_conversation_reference.py new file mode 100644 index 00000000..1e612189 --- /dev/null +++ b/libraries/Client/microsoft-agents-client/microsoft/agents/client/bot_conversation_reference.py @@ -0,0 +1,7 @@ +from microsoft.agents.core.models import ConversationReference + + +class BotConversationReference: + def __init__(self, conversation_reference: ConversationReference, oauth_scope: str): + self.conversation_reference = conversation_reference + self.oauth_scope = oauth_scope diff --git a/libraries/Client/microsoft-agents-client/microsoft/agents/client/channel_protocol.py b/libraries/Client/microsoft-agents-client/microsoft/agents/client/channel_protocol.py index b28abc97..4fb4dee9 100644 --- a/libraries/Client/microsoft-agents-client/microsoft/agents/client/channel_protocol.py +++ b/libraries/Client/microsoft-agents-client/microsoft/agents/client/channel_protocol.py @@ -1,6 +1,6 @@ from typing import Protocol -from microsoft.agents.core.models import Activity, InvokeResponse +from microsoft.agents.core.models import AgentsModel, Activity, InvokeResponse class ChannelProtocol(Protocol): @@ -12,5 +12,8 @@ async def post_activity( service_url: str, conversation_id: str, activity: Activity, + *, + response_body_type: type[AgentsModel] = None, + **kwargs, ) -> InvokeResponse: raise NotImplementedError() diff --git a/libraries/Client/microsoft-agents-client/microsoft/agents/client/conversation_constants.py b/libraries/Client/microsoft-agents-client/microsoft/agents/client/conversation_constants.py new file mode 100644 index 00000000..162a0d11 --- /dev/null +++ b/libraries/Client/microsoft-agents-client/microsoft/agents/client/conversation_constants.py @@ -0,0 +1,5 @@ +from abc import ABC + + +class ConversationConstants(ABC): + CONVERSATION_ID_HTTP_HEADER_NAME = "x-ms-conversation-id" diff --git a/libraries/Client/microsoft-agents-client/microsoft/agents/client/conversation_id_factory_options.py b/libraries/Client/microsoft-agents-client/microsoft/agents/client/conversation_id_factory_options.py new file mode 100644 index 00000000..05334903 --- /dev/null +++ b/libraries/Client/microsoft-agents-client/microsoft/agents/client/conversation_id_factory_options.py @@ -0,0 +1,18 @@ +from microsoft.agents.core.models import Activity + +from .channel_info_protocol import ChannelInfoProtocol + + +class ConversationIdFactoryOptions: + def __init__( + self, + from_oauth_scope: str, + from_bot_id: str, + activity: Activity, + bot: ChannelInfoProtocol, + ) -> None: + self.from_oauth_scope = from_oauth_scope + self.from_bot_id = from_bot_id + # TODO: implement Activity and types as protocols and replace here + self.activity = activity + self.bot = bot diff --git a/libraries/Client/microsoft-agents-client/microsoft/agents/client/conversation_id_factory_protocol.py b/libraries/Client/microsoft-agents-client/microsoft/agents/client/conversation_id_factory_protocol.py new file mode 100644 index 00000000..eb5460fe --- /dev/null +++ b/libraries/Client/microsoft-agents-client/microsoft/agents/client/conversation_id_factory_protocol.py @@ -0,0 +1,31 @@ +from typing import Protocol +from abc import abstractmethod + +from .bot_conversation_reference import BotConversationReference + + +class ConversationIdFactoryProtocol(Protocol): + @abstractmethod + def create_conversation_id(self, options: "ConversationIdFactoryOptions") -> str: + """ + Creates a conversation ID for a bot conversation. + :param options: A ConversationIdFactoryOptions instance. + :return: A unique conversation ID. + """ + + @abstractmethod + def get_bot_conversation_reference( + self, bot_conversation_id: str + ) -> BotConversationReference: + """ + Gets the BotConversationReference for a conversation ID. + :param bot_conversation_id: An ID created with create_conversation_id. + :return: BotConversationReference or None if not found. + """ + + @abstractmethod + def delete_conversation_reference(self, bot_conversation_id: str) -> None: + """ + Deletes a bot conversation reference. + :param bot_conversation_id: A conversation ID created with create_conversation_id. + """ diff --git a/libraries/Client/microsoft-agents-client/microsoft/agents/client/http_bot_channel.py b/libraries/Client/microsoft-agents-client/microsoft/agents/client/http_bot_channel.py index 927a8488..d6e9dcb8 100644 --- a/libraries/Client/microsoft-agents-client/microsoft/agents/client/http_bot_channel.py +++ b/libraries/Client/microsoft-agents-client/microsoft/agents/client/http_bot_channel.py @@ -1,8 +1,18 @@ -from aiohttp import ClientSession +from copy import deepcopy, copy + +from aiohttp import ClientSession, ClientResponseError from microsoft.agents.authentication import AccessTokenProviderBase -from microsoft.agents.core.models import Activity +from microsoft.agents.core.models import ( + AgentsModel, + Activity, + ConversationReference, + ChannelAccount, + InvokeResponse, + RoleTypes, +) from .channel_protocol import ChannelProtocol +from .conversation_constants import ConversationConstants class HttpBotChannel(ChannelProtocol): @@ -17,7 +27,10 @@ async def post_activity( service_url: str, conversation_id: str, activity: Activity, - ) -> dict: + *, + response_body_type: type[AgentsModel] = None, + **kwargs, + ) -> InvokeResponse[AgentsModel]: if not endpoint: raise ValueError("HttpBotChannel.post_activity: Endpoint is required") if not service_url: @@ -26,3 +39,49 @@ async def post_activity( raise ValueError( "HttpBotChannel.post_activity: Conversation ID is required" ) + if not activity: + raise ValueError("HttpBotChannel.post_activity: Activity is required") + + activity_copy = deepcopy(activity) + + # TODO: should conversation should be a deep copy instead of shallow? + activity_copy.relates_to = ConversationReference( + service_url=service_url, + activity_id=activity_copy.id, + channel_id=activity_copy.channel_id, + locale=activity_copy.locale, + conversation=copy(activity_copy.conversation), + ) + + activity_copy.conversation.id = conversation_id + activity_copy.service_url = service_url + activity_copy.recipient = activity_copy.recipient or ChannelAccount() + activity_copy.recipient.role = RoleTypes.skill + + token_result = await self._token_access.get_access_token( + to_bot_resource, f"{to_bot_id}/.default" + ) + headers = { + "Authorization": f"Bearer {token_result}", + "Content-Type": "application/json", + ConversationConstants.CONVERSATION_ID_HTTP_HEADER_NAME: conversation_id, + } + async with ClientSession() as session: + async with session.post( + endpoint, + headers=headers, + json=activity_copy.model_dump(by_alias=True, exclude_unset=True), + ) as response: + + if response.ok: + content = await response.json() + if response_body_type: + content = response_body_type.model_validate(content) + + return InvokeResponse(status=response.status, body=content) + + else: + # TODO: Log error + content = await response.text() + + return InvokeResponse(status=response.status, body=content) diff --git a/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/__init__.py b/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/__init__.py index a67c0217..71eda3fa 100644 --- a/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/__init__.py +++ b/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/__init__.py @@ -1,3 +1,4 @@ +from .agents_model import AgentsModel from .activity import Activity from .activity_event_names import ActivityEventNames from .activity_types import ActivityTypes @@ -78,6 +79,7 @@ from .caller_id_constants import CallerIdConstants __all__ = [ + "AgentsModel", "Activity", "ActivityEventNames", "AdaptiveCardInvokeAction", diff --git a/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/activity.py b/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/activity.py index 836b94dd..2b8ea392 100644 --- a/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/activity.py +++ b/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/activity.py @@ -14,7 +14,7 @@ from .conversation_reference import ConversationReference from .text_highlight import TextHighlight from .semantic_action import SemanticAction -from ._agents_model import AgentsModel +from .agents_model import AgentsModel from ._type_aliases import NonEmptyString diff --git a/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/adaptive_card_invoke_action.py b/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/adaptive_card_invoke_action.py index 4a9ffa4e..815d42fe 100644 --- a/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/adaptive_card_invoke_action.py +++ b/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/adaptive_card_invoke_action.py @@ -1,4 +1,4 @@ -from ._agents_model import AgentsModel +from .agents_model import AgentsModel from ._type_aliases import NonEmptyString diff --git a/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/adaptive_card_invoke_response.py b/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/adaptive_card_invoke_response.py index c6c76507..c142fd4a 100644 --- a/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/adaptive_card_invoke_response.py +++ b/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/adaptive_card_invoke_response.py @@ -1,4 +1,4 @@ -from ._agents_model import AgentsModel +from .agents_model import AgentsModel from ._type_aliases import NonEmptyString diff --git a/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/adaptive_card_invoke_value.py b/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/adaptive_card_invoke_value.py index f2d1a859..b6e35110 100644 --- a/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/adaptive_card_invoke_value.py +++ b/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/adaptive_card_invoke_value.py @@ -1,6 +1,6 @@ from .adaptive_card_invoke_action import AdaptiveCardInvokeAction from .token_exchange_invoke_request import TokenExchangeInvokeRequest -from ._agents_model import AgentsModel +from .agents_model import AgentsModel from ._type_aliases import NonEmptyString diff --git a/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/_agents_model.py b/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/agents_model.py similarity index 100% rename from libraries/Core/microsoft-agents-core/microsoft/agents/core/models/_agents_model.py rename to libraries/Core/microsoft-agents-core/microsoft/agents/core/models/agents_model.py diff --git a/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/animation_card.py b/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/animation_card.py index d945937f..ed6b8115 100644 --- a/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/animation_card.py +++ b/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/animation_card.py @@ -1,7 +1,7 @@ from .thumbnail_url import ThumbnailUrl from .media_url import MediaUrl from .card_action import CardAction -from ._agents_model import AgentsModel +from .agents_model import AgentsModel from ._type_aliases import NonEmptyString diff --git a/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/attachment.py b/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/attachment.py index a36fdb47..8cbf04c6 100644 --- a/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/attachment.py +++ b/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/attachment.py @@ -1,4 +1,4 @@ -from ._agents_model import AgentsModel +from .agents_model import AgentsModel from ._type_aliases import NonEmptyString diff --git a/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/attachment_data.py b/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/attachment_data.py index a6f91681..f0e38a7a 100644 --- a/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/attachment_data.py +++ b/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/attachment_data.py @@ -1,4 +1,4 @@ -from ._agents_model import AgentsModel +from .agents_model import AgentsModel from ._type_aliases import NonEmptyString diff --git a/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/attachment_info.py b/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/attachment_info.py index be8ab59f..e1ac76bd 100644 --- a/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/attachment_info.py +++ b/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/attachment_info.py @@ -1,5 +1,5 @@ from .attachment_view import AttachmentView -from ._agents_model import AgentsModel +from .agents_model import AgentsModel from ._type_aliases import NonEmptyString diff --git a/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/attachment_view.py b/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/attachment_view.py index 87a129a6..ce0039e9 100644 --- a/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/attachment_view.py +++ b/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/attachment_view.py @@ -1,4 +1,4 @@ -from ._agents_model import AgentsModel +from .agents_model import AgentsModel from ._type_aliases import NonEmptyString diff --git a/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/audio_card.py b/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/audio_card.py index acf2a515..d6cef0e2 100644 --- a/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/audio_card.py +++ b/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/audio_card.py @@ -1,4 +1,4 @@ -from ._agents_model import AgentsModel +from .agents_model import AgentsModel from .thumbnail_url import ThumbnailUrl from .media_url import MediaUrl from .card_action import CardAction diff --git a/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/basic_card.py b/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/basic_card.py index c148ea98..76d8e0d8 100644 --- a/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/basic_card.py +++ b/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/basic_card.py @@ -1,4 +1,4 @@ -from ._agents_model import AgentsModel +from .agents_model import AgentsModel from .card_image import CardImage from .card_action import CardAction from ._type_aliases import NonEmptyString diff --git a/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/card_action.py b/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/card_action.py index 25fe52d7..0c7c7903 100644 --- a/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/card_action.py +++ b/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/card_action.py @@ -1,4 +1,4 @@ -from ._agents_model import AgentsModel +from .agents_model import AgentsModel from ._type_aliases import NonEmptyString diff --git a/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/card_image.py b/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/card_image.py index 13ec58b0..a47555f9 100644 --- a/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/card_image.py +++ b/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/card_image.py @@ -1,5 +1,5 @@ from .card_action import CardAction -from ._agents_model import AgentsModel +from .agents_model import AgentsModel from ._type_aliases import NonEmptyString diff --git a/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/channel_account.py b/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/channel_account.py index 333bb3de..3f601da9 100644 --- a/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/channel_account.py +++ b/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/channel_account.py @@ -1,4 +1,4 @@ -from ._agents_model import AgentsModel +from .agents_model import AgentsModel from ._type_aliases import NonEmptyString diff --git a/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/conversation_account.py b/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/conversation_account.py index 59221fb0..c8c466f7 100644 --- a/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/conversation_account.py +++ b/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/conversation_account.py @@ -1,4 +1,4 @@ -from ._agents_model import AgentsModel +from .agents_model import AgentsModel from ._type_aliases import NonEmptyString diff --git a/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/conversation_members.py b/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/conversation_members.py index 09a5029b..c61c6e33 100644 --- a/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/conversation_members.py +++ b/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/conversation_members.py @@ -1,5 +1,5 @@ from .channel_account import ChannelAccount -from ._agents_model import AgentsModel +from .agents_model import AgentsModel from ._type_aliases import NonEmptyString diff --git a/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/conversation_parameters.py b/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/conversation_parameters.py index 12340225..cada9e04 100644 --- a/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/conversation_parameters.py +++ b/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/conversation_parameters.py @@ -1,6 +1,6 @@ from .channel_account import ChannelAccount from .activity import Activity -from ._agents_model import AgentsModel +from .agents_model import AgentsModel from ._type_aliases import NonEmptyString diff --git a/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/conversation_reference.py b/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/conversation_reference.py index 91558845..0d03ab21 100644 --- a/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/conversation_reference.py +++ b/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/conversation_reference.py @@ -3,7 +3,7 @@ from .channel_account import ChannelAccount from .conversation_account import ConversationAccount -from ._agents_model import AgentsModel +from .agents_model import AgentsModel from ._type_aliases import NonEmptyString from .activity_types import ActivityTypes from .activity_event_names import ActivityEventNames diff --git a/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/conversation_resource_response.py b/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/conversation_resource_response.py index cd23495a..c2bf7b0b 100644 --- a/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/conversation_resource_response.py +++ b/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/conversation_resource_response.py @@ -1,4 +1,4 @@ -from ._agents_model import AgentsModel +from .agents_model import AgentsModel from ._type_aliases import NonEmptyString diff --git a/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/conversations_result.py b/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/conversations_result.py index a8bb0e97..40a8e710 100644 --- a/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/conversations_result.py +++ b/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/conversations_result.py @@ -1,5 +1,5 @@ from .conversation_members import ConversationMembers -from ._agents_model import AgentsModel +from .agents_model import AgentsModel from ._type_aliases import NonEmptyString diff --git a/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/entity.py b/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/entity.py index 7cb9507c..9636980b 100644 --- a/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/entity.py +++ b/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/entity.py @@ -1,4 +1,4 @@ -from ._agents_model import AgentsModel +from .agents_model import AgentsModel from ._type_aliases import NonEmptyString diff --git a/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/error.py b/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/error.py index e8ae0041..55f4d54a 100644 --- a/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/error.py +++ b/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/error.py @@ -1,5 +1,5 @@ from .inner_http_error import InnerHttpError -from ._agents_model import AgentsModel +from .agents_model import AgentsModel from ._type_aliases import NonEmptyString diff --git a/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/error_response.py b/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/error_response.py index 6e611bce..f2506cd8 100644 --- a/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/error_response.py +++ b/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/error_response.py @@ -1,4 +1,4 @@ -from ._agents_model import AgentsModel +from .agents_model import AgentsModel from .error import Error diff --git a/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/expected_replies.py b/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/expected_replies.py index 8d9bd8fd..ad5ae2a7 100644 --- a/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/expected_replies.py +++ b/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/expected_replies.py @@ -1,5 +1,5 @@ from .activity import Activity -from ._agents_model import AgentsModel +from .agents_model import AgentsModel class ExpectedReplies(AgentsModel): diff --git a/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/fact.py b/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/fact.py index a53b8116..b66b8073 100644 --- a/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/fact.py +++ b/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/fact.py @@ -1,4 +1,4 @@ -from ._agents_model import AgentsModel +from .agents_model import AgentsModel from ._type_aliases import NonEmptyString diff --git a/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/geo_coordinates.py b/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/geo_coordinates.py index 7b00ff8b..1c457f8e 100644 --- a/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/geo_coordinates.py +++ b/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/geo_coordinates.py @@ -1,4 +1,4 @@ -from ._agents_model import AgentsModel +from .agents_model import AgentsModel from ._type_aliases import NonEmptyString diff --git a/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/hero_card.py b/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/hero_card.py index d9cd4d92..ff6c99fc 100644 --- a/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/hero_card.py +++ b/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/hero_card.py @@ -1,6 +1,6 @@ from .card_action import CardAction from .card_image import CardImage -from ._agents_model import AgentsModel +from .agents_model import AgentsModel from ._type_aliases import NonEmptyString diff --git a/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/inner_http_error.py b/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/inner_http_error.py index 8baac4dd..44e37cbb 100644 --- a/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/inner_http_error.py +++ b/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/inner_http_error.py @@ -1,4 +1,4 @@ -from ._agents_model import AgentsModel +from .agents_model import AgentsModel class InnerHttpError(AgentsModel): diff --git a/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/invoke_response.py b/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/invoke_response.py index 0c51733c..1cdbed11 100644 --- a/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/invoke_response.py +++ b/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/invoke_response.py @@ -1,11 +1,11 @@ -from ._agents_model import AgentsModel +from .agents_model import AgentsModel from typing import Generic, TypeVar -T = TypeVar("T") +AgentModelT = TypeVar("T", bound=AgentsModel) -class InvokeResponse(AgentsModel, Generic[T]): +class InvokeResponse(AgentsModel, Generic[AgentModelT]): """ Tuple class containing an HTTP Status Code and a JSON serializable object. The HTTP Status code is, in the invoke activity scenario, what will @@ -17,7 +17,7 @@ class InvokeResponse(AgentsModel, Generic[T]): """ status: int = None - body: T = None + body: AgentModelT = None def is_successful_status_code(self) -> bool: """ diff --git a/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/media_card.py b/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/media_card.py index 3960b9ea..94c22f7b 100644 --- a/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/media_card.py +++ b/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/media_card.py @@ -1,7 +1,7 @@ from .thumbnail_url import ThumbnailUrl from .media_url import MediaUrl from .card_action import CardAction -from ._agents_model import AgentsModel +from .agents_model import AgentsModel from ._type_aliases import NonEmptyString diff --git a/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/media_event_value.py b/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/media_event_value.py index 2e0f71f6..478b19c2 100644 --- a/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/media_event_value.py +++ b/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/media_event_value.py @@ -1,4 +1,4 @@ -from ._agents_model import AgentsModel +from .agents_model import AgentsModel class MediaEventValue(AgentsModel): diff --git a/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/media_url.py b/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/media_url.py index 7e26b1e1..b7b5e9a9 100644 --- a/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/media_url.py +++ b/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/media_url.py @@ -1,4 +1,4 @@ -from ._agents_model import AgentsModel +from .agents_model import AgentsModel from ._type_aliases import NonEmptyString diff --git a/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/mention.py b/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/mention.py index e305f62d..c96d8d29 100644 --- a/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/mention.py +++ b/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/mention.py @@ -1,5 +1,5 @@ from .channel_account import ChannelAccount -from ._agents_model import AgentsModel +from .agents_model import AgentsModel from ._type_aliases import NonEmptyString diff --git a/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/message_reaction.py b/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/message_reaction.py index 3911613e..158ea94e 100644 --- a/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/message_reaction.py +++ b/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/message_reaction.py @@ -1,4 +1,4 @@ -from ._agents_model import AgentsModel +from .agents_model import AgentsModel from ._type_aliases import NonEmptyString diff --git a/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/oauth_card.py b/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/oauth_card.py index acae472b..59e28692 100644 --- a/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/oauth_card.py +++ b/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/oauth_card.py @@ -1,5 +1,5 @@ from .card_action import CardAction -from ._agents_model import AgentsModel +from .agents_model import AgentsModel from ._type_aliases import NonEmptyString diff --git a/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/paged_members_result.py b/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/paged_members_result.py index b552d308..0718263e 100644 --- a/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/paged_members_result.py +++ b/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/paged_members_result.py @@ -1,6 +1,6 @@ from .channel_account import ChannelAccount from ._type_aliases import NonEmptyString -from ._agents_model import AgentsModel +from .agents_model import AgentsModel class PagedMembersResult(AgentsModel): diff --git a/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/place.py b/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/place.py index 91fde9d7..cbf86e90 100644 --- a/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/place.py +++ b/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/place.py @@ -1,4 +1,4 @@ -from ._agents_model import AgentsModel +from .agents_model import AgentsModel from ._type_aliases import NonEmptyString diff --git a/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/receipt_card.py b/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/receipt_card.py index 7f0f3798..17f88214 100644 --- a/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/receipt_card.py +++ b/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/receipt_card.py @@ -1,7 +1,7 @@ from .fact import Fact from .receipt_item import ReceiptItem from .card_action import CardAction -from ._agents_model import AgentsModel +from .agents_model import AgentsModel from ._type_aliases import NonEmptyString diff --git a/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/receipt_item.py b/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/receipt_item.py index 97bca249..0074cc30 100644 --- a/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/receipt_item.py +++ b/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/receipt_item.py @@ -1,6 +1,6 @@ from .card_image import CardImage from .card_action import CardAction -from ._agents_model import AgentsModel +from .agents_model import AgentsModel from ._type_aliases import NonEmptyString diff --git a/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/resource_response.py b/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/resource_response.py index 2d482995..8f1802ea 100644 --- a/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/resource_response.py +++ b/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/resource_response.py @@ -1,4 +1,4 @@ -from ._agents_model import AgentsModel +from .agents_model import AgentsModel from ._type_aliases import NonEmptyString diff --git a/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/semantic_action.py b/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/semantic_action.py index 7660919f..bfa846aa 100644 --- a/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/semantic_action.py +++ b/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/semantic_action.py @@ -1,4 +1,4 @@ -from ._agents_model import AgentsModel +from .agents_model import AgentsModel from ._type_aliases import NonEmptyString diff --git a/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/signin_card.py b/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/signin_card.py index 9bd0c9e2..a6396748 100644 --- a/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/signin_card.py +++ b/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/signin_card.py @@ -1,5 +1,5 @@ from .card_action import CardAction -from ._agents_model import AgentsModel +from .agents_model import AgentsModel from ._type_aliases import NonEmptyString diff --git a/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/suggested_actions.py b/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/suggested_actions.py index 51e89ea1..951c82eb 100644 --- a/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/suggested_actions.py +++ b/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/suggested_actions.py @@ -1,5 +1,5 @@ from .card_action import CardAction -from ._agents_model import AgentsModel +from .agents_model import AgentsModel from ._type_aliases import NonEmptyString diff --git a/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/text_highlight.py b/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/text_highlight.py index df58cf9f..58115779 100644 --- a/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/text_highlight.py +++ b/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/text_highlight.py @@ -1,4 +1,4 @@ -from ._agents_model import AgentsModel +from .agents_model import AgentsModel from ._type_aliases import NonEmptyString diff --git a/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/thing.py b/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/thing.py index 8d712f8b..f2bb28c3 100644 --- a/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/thing.py +++ b/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/thing.py @@ -1,4 +1,4 @@ -from ._agents_model import AgentsModel +from .agents_model import AgentsModel from ._type_aliases import NonEmptyString diff --git a/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/thumbnail_card.py b/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/thumbnail_card.py index fea3bc8b..a22bffaa 100644 --- a/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/thumbnail_card.py +++ b/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/thumbnail_card.py @@ -1,6 +1,6 @@ from .card_image import CardImage from .card_action import CardAction -from ._agents_model import AgentsModel +from .agents_model import AgentsModel from ._type_aliases import NonEmptyString diff --git a/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/thumbnail_url.py b/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/thumbnail_url.py index 69876e10..3b8f3244 100644 --- a/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/thumbnail_url.py +++ b/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/thumbnail_url.py @@ -1,4 +1,4 @@ -from ._agents_model import AgentsModel +from .agents_model import AgentsModel from ._type_aliases import NonEmptyString diff --git a/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/token_exchange_invoke_request.py b/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/token_exchange_invoke_request.py index 625f9e53..889ef773 100644 --- a/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/token_exchange_invoke_request.py +++ b/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/token_exchange_invoke_request.py @@ -1,4 +1,4 @@ -from ._agents_model import AgentsModel +from .agents_model import AgentsModel from ._type_aliases import NonEmptyString diff --git a/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/token_exchange_invoke_response.py b/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/token_exchange_invoke_response.py index e25888d0..e09cd999 100644 --- a/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/token_exchange_invoke_response.py +++ b/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/token_exchange_invoke_response.py @@ -1,4 +1,4 @@ -from ._agents_model import AgentsModel +from .agents_model import AgentsModel from ._type_aliases import NonEmptyString diff --git a/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/token_exchange_state.py b/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/token_exchange_state.py index 88deea03..fa821449 100644 --- a/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/token_exchange_state.py +++ b/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/token_exchange_state.py @@ -1,5 +1,5 @@ from .conversation_reference import ConversationReference -from ._agents_model import AgentsModel +from .agents_model import AgentsModel from ._type_aliases import NonEmptyString diff --git a/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/token_request.py b/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/token_request.py index b6b31ca8..b35a5e08 100644 --- a/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/token_request.py +++ b/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/token_request.py @@ -1,4 +1,4 @@ -from ._agents_model import AgentsModel +from .agents_model import AgentsModel from ._type_aliases import NonEmptyString diff --git a/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/token_response.py b/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/token_response.py index 7e8c05be..682c534b 100644 --- a/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/token_response.py +++ b/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/token_response.py @@ -1,7 +1,7 @@ # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. -from ._agents_model import AgentsModel +from .agents_model import AgentsModel from ._type_aliases import NonEmptyString diff --git a/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/transcript.py b/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/transcript.py index 969ae709..bbcaaf84 100644 --- a/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/transcript.py +++ b/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/transcript.py @@ -1,5 +1,5 @@ from .activity import Activity -from ._agents_model import AgentsModel +from .agents_model import AgentsModel class Transcript(AgentsModel): diff --git a/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/video_card.py b/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/video_card.py index fa2868bd..ce8f7c57 100644 --- a/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/video_card.py +++ b/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/video_card.py @@ -1,7 +1,7 @@ from .thumbnail_url import ThumbnailUrl from .media_url import MediaUrl from .card_action import CardAction -from ._agents_model import AgentsModel +from .agents_model import AgentsModel from ._type_aliases import NonEmptyString From d5db8ce3ed543d475bf13a293413f7dfdfee3a3d Mon Sep 17 00:00:00 2001 From: Axel Suarez Martinez Date: Thu, 6 Feb 2025 13:16:14 -0800 Subject: [PATCH 05/21] WIP --- .../microsoft/agents/client/configuration_channel_host.py | 0 .../agents/client/conversation_id_factory_protocol.py | 3 ++- 2 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 libraries/Client/microsoft-agents-client/microsoft/agents/client/configuration_channel_host.py diff --git a/libraries/Client/microsoft-agents-client/microsoft/agents/client/configuration_channel_host.py b/libraries/Client/microsoft-agents-client/microsoft/agents/client/configuration_channel_host.py new file mode 100644 index 00000000..e69de29b diff --git a/libraries/Client/microsoft-agents-client/microsoft/agents/client/conversation_id_factory_protocol.py b/libraries/Client/microsoft-agents-client/microsoft/agents/client/conversation_id_factory_protocol.py index eb5460fe..159b2cf0 100644 --- a/libraries/Client/microsoft-agents-client/microsoft/agents/client/conversation_id_factory_protocol.py +++ b/libraries/Client/microsoft-agents-client/microsoft/agents/client/conversation_id_factory_protocol.py @@ -2,11 +2,12 @@ from abc import abstractmethod from .bot_conversation_reference import BotConversationReference +from .conversation_id_factory_options import ConversationIdFactoryOptions class ConversationIdFactoryProtocol(Protocol): @abstractmethod - def create_conversation_id(self, options: "ConversationIdFactoryOptions") -> str: + def create_conversation_id(self, options: ConversationIdFactoryOptions) -> str: """ Creates a conversation ID for a bot conversation. :param options: A ConversationIdFactoryOptions instance. From eb6e6cf37beb61a047b045b668d99ffba0575473 Mon Sep 17 00:00:00 2001 From: Axel Suarez Date: Thu, 6 Feb 2025 19:08:08 -0800 Subject: [PATCH 06/21] WIP: just missing ConversationIdFactory --- .../agents/client/channel_factory_protocol.py | 9 ++++ .../agents/client/channel_info_protocol.py | 53 +++---------------- .../agents/client/channels_configuration.py | 25 +++++++++ .../client/configuration_channel_host.py | 48 +++++++++++++++++ .../agents/client/http_bot_channel_factory.py | 9 ++++ 5 files changed, 97 insertions(+), 47 deletions(-) create mode 100644 libraries/Client/microsoft-agents-client/microsoft/agents/client/channel_factory_protocol.py create mode 100644 libraries/Client/microsoft-agents-client/microsoft/agents/client/channels_configuration.py create mode 100644 libraries/Client/microsoft-agents-client/microsoft/agents/client/http_bot_channel_factory.py diff --git a/libraries/Client/microsoft-agents-client/microsoft/agents/client/channel_factory_protocol.py b/libraries/Client/microsoft-agents-client/microsoft/agents/client/channel_factory_protocol.py new file mode 100644 index 00000000..1d7c3f0f --- /dev/null +++ b/libraries/Client/microsoft-agents-client/microsoft/agents/client/channel_factory_protocol.py @@ -0,0 +1,9 @@ +from typing import Protocol + +from microsoft.agents.authentication import AccessTokenProviderBase + +from .channel_protocol import ChannelProtocol + +class ChannelFactoryProtocol(Protocol): + def create_channel(self, token_access: AccessTokenProviderBase) -> ChannelProtocol: + pass \ No newline at end of file diff --git a/libraries/Client/microsoft-agents-client/microsoft/agents/client/channel_info_protocol.py b/libraries/Client/microsoft-agents-client/microsoft/agents/client/channel_info_protocol.py index 08ddcf1d..85ddc55e 100644 --- a/libraries/Client/microsoft-agents-client/microsoft/agents/client/channel_info_protocol.py +++ b/libraries/Client/microsoft-agents-client/microsoft/agents/client/channel_info_protocol.py @@ -2,50 +2,9 @@ class ChannelInfoProtocol(Protocol): - @property - def id(self) -> str: - raise NotImplementedError() - - @property.setter - def id(self, value: str): - raise NotImplementedError() - - @property - def app_id(self) -> str: - raise NotImplementedError() - - @property.setter - def app_id(self, value: str): - raise NotImplementedError() - - @property - def resource_url(self) -> str: - raise NotImplementedError() - - @property.setter - def resource_url(self, value: str): - raise NotImplementedError() - - @property - def token_provider(self) -> str: - raise NotImplementedError() - - @property.setter - def token_provider(self, value: str): - raise NotImplementedError() - - @property - def channel_factory(self) -> str: - raise NotImplementedError() - - @property.setter - def channel_factory(self, value: str): - raise NotImplementedError() - - @property - def endpoint(self) -> str: - raise NotImplementedError() - - @property.setter - def endpoint(self, value: str): - raise NotImplementedError() + id: str + app_id: str + resource_url: str + token_provider: str + channel_factory: str + endpoint: str diff --git a/libraries/Client/microsoft-agents-client/microsoft/agents/client/channels_configuration.py b/libraries/Client/microsoft-agents-client/microsoft/agents/client/channels_configuration.py new file mode 100644 index 00000000..493e104c --- /dev/null +++ b/libraries/Client/microsoft-agents-client/microsoft/agents/client/channels_configuration.py @@ -0,0 +1,25 @@ +from typing import Protocol + +from .channel_info_protocol import ChannelInfoProtocol + +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, **kwargs): + self.id = id + self.app_id = app_id + self.resource_url = resource_url + self.token_provider = token_provider + self.channel_factory = channel_factory + self.endpoint = endpoint + +class ChannelHostConfiguration(Protocol): + + CHANNELS: list[ChannelInfoProtocol] + HOST_ENDPOINT: str + HOST_APP_ID: str + +class ChannelsConfiguration(Protocol): + + @staticmethod + def CHANNEL_HOST_CONFIGURATION() -> ChannelHostConfiguration: + pass \ No newline at end of file diff --git a/libraries/Client/microsoft-agents-client/microsoft/agents/client/configuration_channel_host.py b/libraries/Client/microsoft-agents-client/microsoft/agents/client/configuration_channel_host.py index e69de29b..4c36b581 100644 --- a/libraries/Client/microsoft-agents-client/microsoft/agents/client/configuration_channel_host.py +++ b/libraries/Client/microsoft-agents-client/microsoft/agents/client/configuration_channel_host.py @@ -0,0 +1,48 @@ +from copy import copy + +from microsoft.agents.authentication import Connections + +from .channels_configuration import ChannelsConfiguration +from .channel_factory_protocol import ChannelFactoryProtocol +from .channel_host_protocol import ChannelHostProtocol +from .channel_info_protocol import ChannelInfoProtocol +from .channel_protocol import ChannelProtocol +from .http_bot_channel import HttpBotChannel + +class ConfigurationChannelHost(ChannelHostProtocol): + def __init__(self, channel_factory: ChannelFactoryProtocol, connections: Connections, configuration: ChannelsConfiguration, default_channel_name: str): + self._channel_factory = channel_factory + self.connections = connections + self.configuration = configuration + self.channels: dict[str, ChannelInfoProtocol] = {} + self.endpoint: str = None + self.host_app_id: str = None + + channel_host_configuration = configuration.CHANNEL_HOST_CONFIGURATION() + + if channel_host_configuration: + if channel_host_configuration.CHANNELS: + for bot_from_config in channel_host_configuration.CHANNELS: + bot = copy(bot_from_config) + if not bot.channel_factory: + bot.channel_factory = default_channel_name + self.channels[bot.id] = bot + + self.endpoint = channel_host_configuration.HOST_ENDPOINT + self.host_app_id = channel_host_configuration.HOST_APP_ID + + def get_channel_from_name(self, name: str) -> ChannelProtocol: + if not name in self.channels: + raise ValueError(f"ChannelInfo not found for '{name}'") + return self.get_channel_from_channel_info(self.channels[name]) + + def get_channel_from_channel_info(self, channel_info: ChannelInfoProtocol) -> ChannelProtocol: + if not channel_info: + raise ValueError(f"ConfigurationChannelHost.get_channel_from_channel_info(): channel_info cannot be None") + + token_provider = self.connections.get_token_provider(channel_info.token_provider) + if not token_provider: + raise ValueError(f"ConfigurationChannelHost.get_channel_from_channel_info(): token_provider not found for '{channel_info.token_provider}'") + + return self._channel_factory.create_channel(token_provider) + diff --git a/libraries/Client/microsoft-agents-client/microsoft/agents/client/http_bot_channel_factory.py b/libraries/Client/microsoft-agents-client/microsoft/agents/client/http_bot_channel_factory.py new file mode 100644 index 00000000..cbf21748 --- /dev/null +++ b/libraries/Client/microsoft-agents-client/microsoft/agents/client/http_bot_channel_factory.py @@ -0,0 +1,9 @@ +from microsoft.agents.authentication import AccessTokenProviderBase + +from .channel_factory_protocol import ChannelFactoryProtocol +from .channel_protocol import ChannelProtocol +from .http_bot_channel import HttpBotChannel + +class HttpBotChannelFactory(ChannelFactoryProtocol): + def create_channel(self, token_access: AccessTokenProviderBase) -> ChannelProtocol: + return HttpBotChannel(token_access) \ No newline at end of file From 5d93ba833eca903a9c2cc31172eade305dae58e7 Mon Sep 17 00:00:00 2001 From: Axel Suarez Date: Thu, 6 Feb 2025 19:08:39 -0800 Subject: [PATCH 07/21] WIP: just missing ConversationIdFactory - Formatting --- .../agents/client/channel_factory_protocol.py | 3 +- .../agents/client/channels_configuration.py | 18 +++++++-- .../client/configuration_channel_host.py | 37 +++++++++++++------ .../agents/client/http_bot_channel_factory.py | 3 +- 4 files changed, 44 insertions(+), 17 deletions(-) diff --git a/libraries/Client/microsoft-agents-client/microsoft/agents/client/channel_factory_protocol.py b/libraries/Client/microsoft-agents-client/microsoft/agents/client/channel_factory_protocol.py index 1d7c3f0f..6ecd17ee 100644 --- a/libraries/Client/microsoft-agents-client/microsoft/agents/client/channel_factory_protocol.py +++ b/libraries/Client/microsoft-agents-client/microsoft/agents/client/channel_factory_protocol.py @@ -4,6 +4,7 @@ from .channel_protocol import ChannelProtocol + class ChannelFactoryProtocol(Protocol): def create_channel(self, token_access: AccessTokenProviderBase) -> ChannelProtocol: - pass \ No newline at end of file + pass diff --git a/libraries/Client/microsoft-agents-client/microsoft/agents/client/channels_configuration.py b/libraries/Client/microsoft-agents-client/microsoft/agents/client/channels_configuration.py index 493e104c..ec6342f70 100644 --- a/libraries/Client/microsoft-agents-client/microsoft/agents/client/channels_configuration.py +++ b/libraries/Client/microsoft-agents-client/microsoft/agents/client/channels_configuration.py @@ -2,9 +2,19 @@ from .channel_info_protocol import ChannelInfoProtocol + 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, **kwargs): + + 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, + **kwargs + ): self.id = id self.app_id = app_id self.resource_url = resource_url @@ -12,14 +22,16 @@ def __init__(self, id: str = None, app_id: str = None, resource_url: str = None, self.channel_factory = channel_factory self.endpoint = endpoint + class ChannelHostConfiguration(Protocol): CHANNELS: list[ChannelInfoProtocol] HOST_ENDPOINT: str HOST_APP_ID: str + class ChannelsConfiguration(Protocol): @staticmethod def CHANNEL_HOST_CONFIGURATION() -> ChannelHostConfiguration: - pass \ No newline at end of file + pass diff --git a/libraries/Client/microsoft-agents-client/microsoft/agents/client/configuration_channel_host.py b/libraries/Client/microsoft-agents-client/microsoft/agents/client/configuration_channel_host.py index 4c36b581..8c4a0db1 100644 --- a/libraries/Client/microsoft-agents-client/microsoft/agents/client/configuration_channel_host.py +++ b/libraries/Client/microsoft-agents-client/microsoft/agents/client/configuration_channel_host.py @@ -7,10 +7,16 @@ from .channel_host_protocol import ChannelHostProtocol from .channel_info_protocol import ChannelInfoProtocol from .channel_protocol import ChannelProtocol -from .http_bot_channel import HttpBotChannel + class ConfigurationChannelHost(ChannelHostProtocol): - def __init__(self, channel_factory: ChannelFactoryProtocol, connections: Connections, configuration: ChannelsConfiguration, default_channel_name: str): + def __init__( + self, + channel_factory: ChannelFactoryProtocol, + connections: Connections, + configuration: ChannelsConfiguration, + default_channel_name: str, + ): self._channel_factory = channel_factory self.connections = connections self.configuration = configuration @@ -27,22 +33,29 @@ def __init__(self, channel_factory: ChannelFactoryProtocol, connections: Connect if not bot.channel_factory: bot.channel_factory = default_channel_name self.channels[bot.id] = bot - + self.endpoint = channel_host_configuration.HOST_ENDPOINT self.host_app_id = channel_host_configuration.HOST_APP_ID - + def get_channel_from_name(self, name: str) -> ChannelProtocol: if not name in self.channels: raise ValueError(f"ChannelInfo not found for '{name}'") return self.get_channel_from_channel_info(self.channels[name]) - - def get_channel_from_channel_info(self, channel_info: ChannelInfoProtocol) -> ChannelProtocol: + + def get_channel_from_channel_info( + self, channel_info: ChannelInfoProtocol + ) -> ChannelProtocol: if not channel_info: - raise ValueError(f"ConfigurationChannelHost.get_channel_from_channel_info(): channel_info cannot be None") - - token_provider = self.connections.get_token_provider(channel_info.token_provider) + raise ValueError( + f"ConfigurationChannelHost.get_channel_from_channel_info(): channel_info cannot be None" + ) + + token_provider = self.connections.get_token_provider( + channel_info.token_provider + ) if not token_provider: - raise ValueError(f"ConfigurationChannelHost.get_channel_from_channel_info(): token_provider not found for '{channel_info.token_provider}'") - - return self._channel_factory.create_channel(token_provider) + raise ValueError( + f"ConfigurationChannelHost.get_channel_from_channel_info(): token_provider not found for '{channel_info.token_provider}'" + ) + return self._channel_factory.create_channel(token_provider) diff --git a/libraries/Client/microsoft-agents-client/microsoft/agents/client/http_bot_channel_factory.py b/libraries/Client/microsoft-agents-client/microsoft/agents/client/http_bot_channel_factory.py index cbf21748..935601e1 100644 --- a/libraries/Client/microsoft-agents-client/microsoft/agents/client/http_bot_channel_factory.py +++ b/libraries/Client/microsoft-agents-client/microsoft/agents/client/http_bot_channel_factory.py @@ -4,6 +4,7 @@ from .channel_protocol import ChannelProtocol from .http_bot_channel import HttpBotChannel + class HttpBotChannelFactory(ChannelFactoryProtocol): def create_channel(self, token_access: AccessTokenProviderBase) -> ChannelProtocol: - return HttpBotChannel(token_access) \ No newline at end of file + return HttpBotChannel(token_access) From ac6f31141c743859ce7ac96bd87c986c59b04d60 Mon Sep 17 00:00:00 2001 From: Axel Suarez Date: Fri, 7 Feb 2025 16:04:51 -0800 Subject: [PATCH 08/21] MemoryStorage almost done, id factory pending --- .../agents/client/conversation_id_factory.py | 0 .../agents/core/models/invoke_response.py | 2 +- .../microsoft/agents/storage/__init__.py | 0 .../agents/storage/memory_storage.py | 38 +++++++++++++++++++ .../microsoft/agents/storage/storage.py | 19 ++++++++++ .../microsoft/agents/storage/store_item.py | 15 ++++++++ .../microsoft-agents-storage/pyproject.toml | 0 7 files changed, 73 insertions(+), 1 deletion(-) create mode 100644 libraries/Client/microsoft-agents-client/microsoft/agents/client/conversation_id_factory.py create mode 100644 libraries/Storage/microsoft-agents-storage/microsoft/agents/storage/__init__.py create mode 100644 libraries/Storage/microsoft-agents-storage/microsoft/agents/storage/memory_storage.py create mode 100644 libraries/Storage/microsoft-agents-storage/microsoft/agents/storage/storage.py create mode 100644 libraries/Storage/microsoft-agents-storage/microsoft/agents/storage/store_item.py create mode 100644 libraries/Storage/microsoft-agents-storage/pyproject.toml diff --git a/libraries/Client/microsoft-agents-client/microsoft/agents/client/conversation_id_factory.py b/libraries/Client/microsoft-agents-client/microsoft/agents/client/conversation_id_factory.py new file mode 100644 index 00000000..e69de29b diff --git a/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/invoke_response.py b/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/invoke_response.py index 1cdbed11..022ccbd2 100644 --- a/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/invoke_response.py +++ b/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/invoke_response.py @@ -2,7 +2,7 @@ from typing import Generic, TypeVar -AgentModelT = TypeVar("T", bound=AgentsModel) +AgentModelT = TypeVar("T", AgentsModel) class InvokeResponse(AgentsModel, Generic[AgentModelT]): diff --git a/libraries/Storage/microsoft-agents-storage/microsoft/agents/storage/__init__.py b/libraries/Storage/microsoft-agents-storage/microsoft/agents/storage/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/libraries/Storage/microsoft-agents-storage/microsoft/agents/storage/memory_storage.py b/libraries/Storage/microsoft-agents-storage/microsoft/agents/storage/memory_storage.py new file mode 100644 index 00000000..8ec131db --- /dev/null +++ b/libraries/Storage/microsoft-agents-storage/microsoft/agents/storage/memory_storage.py @@ -0,0 +1,38 @@ +from typing import Type, TypeVar +from threading import Lock + +from .storage import Storage +from .store_item import StoreItem + + +class MemoryStorage(Storage): + def __init__(self, state: dict[str, StoreItem] = None): + self._memory: dict[str, StoreItem] = state or {} + self._lock = Lock() + self._e_tag = 0 + + async def read(self, keys: list[str]) -> dict[str, StoreItem]: + result: dict[str, StoreItem] = {} + with self._lock: + for key in keys: + if key in self._memory: + store_item = self._memory[key] + if isinstance(store_item, StoreItem): + result[key] = store_item.from_json_to_store_item( + self._memory[key] + ) + else: + raise TypeError( + f"MemoryStorage.read(): store_item is not of type StoreItem" + ) + return result + + async def write(self, changes: dict[str, StoreItem]): + for key in changes: + self._memory[key] = changes[key] + + async def delete(self, keys: list[str]): + with self._lock: + for key in keys: + if key in self._memory: + del self._memory[key] diff --git a/libraries/Storage/microsoft-agents-storage/microsoft/agents/storage/storage.py b/libraries/Storage/microsoft-agents-storage/microsoft/agents/storage/storage.py new file mode 100644 index 00000000..b90ebc69 --- /dev/null +++ b/libraries/Storage/microsoft-agents-storage/microsoft/agents/storage/storage.py @@ -0,0 +1,19 @@ +from typing import Protocol, TypeVar, Type + +from .store_item import StoreItem + + +StoreItemT = TypeVar("StoreItemT", StoreItem) + + +class Storage(Protocol): + async def read( + self, keys: list[str], *, store_item_cls: Type[StoreItemT] = None, **kwargs + ) -> dict[str, StoreItemT]: + pass + + async def write(self, changes: dict[str, StoreItemT]) -> None: + pass + + async def delete(self, keys: list[str]) -> None: + pass diff --git a/libraries/Storage/microsoft-agents-storage/microsoft/agents/storage/store_item.py b/libraries/Storage/microsoft-agents-storage/microsoft/agents/storage/store_item.py new file mode 100644 index 00000000..e1c05282 --- /dev/null +++ b/libraries/Storage/microsoft-agents-storage/microsoft/agents/storage/store_item.py @@ -0,0 +1,15 @@ +from typing import Any, MutableMapping, Protocol +from typing_extensions import Self + +JSON = MutableMapping[str, Any] + + +class StoreItem(Protocol): + e_tag: str + + def store_item_to_json(self) -> JSON: + pass + + @staticmethod + def from_json_to_store_item(json_data: JSON) -> Self: + pass diff --git a/libraries/Storage/microsoft-agents-storage/pyproject.toml b/libraries/Storage/microsoft-agents-storage/pyproject.toml new file mode 100644 index 00000000..e69de29b From b9531bf9adff815d4b778e44198375c27b03a5c4 Mon Sep 17 00:00:00 2001 From: Axel Suarez Martinez Date: Wed, 12 Feb 2025 22:56:11 -0600 Subject: [PATCH 09/21] Conversation id factory WIP --- .../agents/client/conversation_id_factory.py | 47 +++++++++++++++++++ .../microsoft/agents/core/models/channels.py | 1 + .../agents/core/models/invoke_response.py | 2 +- .../microsoft/agents/storage/__init__.py | 5 ++ .../microsoft/agents/storage/_type_aliases.py | 3 ++ .../agents/storage/memory_storage.py | 40 +++++++++++----- .../microsoft/agents/storage/storage.py | 5 +- .../microsoft/agents/storage/store_item.py | 8 ++-- .../microsoft-agents-storage/pyproject.toml | 20 ++++++++ 9 files changed, 113 insertions(+), 18 deletions(-) create mode 100644 libraries/Storage/microsoft-agents-storage/microsoft/agents/storage/_type_aliases.py diff --git a/libraries/Client/microsoft-agents-client/microsoft/agents/client/conversation_id_factory.py b/libraries/Client/microsoft-agents-client/microsoft/agents/client/conversation_id_factory.py index e69de29b..bb5d5295 100644 --- a/libraries/Client/microsoft-agents-client/microsoft/agents/client/conversation_id_factory.py +++ b/libraries/Client/microsoft-agents-client/microsoft/agents/client/conversation_id_factory.py @@ -0,0 +1,47 @@ +from uuid import uuid4 +from functools import partial +from typing import Type + +from microsoft.agents.core.models import AgentsModel +from microsoft.agents.storage import Storage, StoreItem + +from .bot_conversation_reference import BotConversationReference +from .conversation_id_factory_protocol import ConversationIdFactoryProtocol + + +class ConversationIdFactory(ConversationIdFactoryProtocol): + def __init__(self, storage: Storage) -> None: + if not storage: + raise ValueError("ConversationIdFactory.__init__(): storage cannot be None") + self._storage = storage + + def create_conversation_id(self, options) -> str: + if not options: + raise ValueError( + "ConversationIdFactory.create_conversation_id(): options cannot be None" + ) + + conversation_reference = options.activity.get_conversation_reference() + bot_conversation_id = str(uuid4()) + + bot_conversation_reference = BotConversationReference( + conversation_reference=conversation_reference, + oauth_scope=options.from_oauth_scope, + ) + + conversation_info = {bot_conversation_id: bot_conversation_reference} + + def get_bot_conversation_reference(self, bot_conversation_id): + raise NotImplementedError() + + def delete_conversation_reference(self, bot_conversation_id): + raise NotImplementedError() + + @staticmethod + def _implement_store_item_for_agents_model_cls(model_instance: AgentsModel): + if not isinstance(model_instance, StoreItem): + instance_cls = type(model_instance) + setattr( + instance_cls, + partial(model_instance.model_dump, mode="json", exclude_none=True), + ) diff --git a/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/channels.py b/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/channels.py index ce52cd30..ca20b701 100644 --- a/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/channels.py +++ b/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/channels.py @@ -64,6 +64,7 @@ class Channels(str, Enum): webchat = "webchat" """WebChat channel.""" + # TODO: validate the need of Self annotations in the following methods @staticmethod def supports_suggested_actions(channel_id: Self, button_cnt: int = 100) -> bool: """Determine if a number of Suggested Actions are supported by a Channel. diff --git a/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/invoke_response.py b/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/invoke_response.py index 022ccbd2..1cdbed11 100644 --- a/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/invoke_response.py +++ b/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/invoke_response.py @@ -2,7 +2,7 @@ from typing import Generic, TypeVar -AgentModelT = TypeVar("T", AgentsModel) +AgentModelT = TypeVar("T", bound=AgentsModel) class InvokeResponse(AgentsModel, Generic[AgentModelT]): diff --git a/libraries/Storage/microsoft-agents-storage/microsoft/agents/storage/__init__.py b/libraries/Storage/microsoft-agents-storage/microsoft/agents/storage/__init__.py index e69de29b..be2e6079 100644 --- a/libraries/Storage/microsoft-agents-storage/microsoft/agents/storage/__init__.py +++ b/libraries/Storage/microsoft-agents-storage/microsoft/agents/storage/__init__.py @@ -0,0 +1,5 @@ +from .store_item import StoreItem +from .storage import Storage +from .memory_storage import MemoryStorage + +__all__ = ["StoreItem", "Storage", "MemoryStorage"] diff --git a/libraries/Storage/microsoft-agents-storage/microsoft/agents/storage/_type_aliases.py b/libraries/Storage/microsoft-agents-storage/microsoft/agents/storage/_type_aliases.py new file mode 100644 index 00000000..f800f57f --- /dev/null +++ b/libraries/Storage/microsoft-agents-storage/microsoft/agents/storage/_type_aliases.py @@ -0,0 +1,3 @@ +from typing import MutableMapping, Any + +JSON = MutableMapping[str, Any] diff --git a/libraries/Storage/microsoft-agents-storage/microsoft/agents/storage/memory_storage.py b/libraries/Storage/microsoft-agents-storage/microsoft/agents/storage/memory_storage.py index 8ec131db..ee5b494b 100644 --- a/libraries/Storage/microsoft-agents-storage/microsoft/agents/storage/memory_storage.py +++ b/libraries/Storage/microsoft-agents-storage/microsoft/agents/storage/memory_storage.py @@ -1,35 +1,53 @@ -from typing import Type, TypeVar from threading import Lock +from typing import TypeVar +from ._type_aliases import JSON from .storage import Storage from .store_item import StoreItem +StoreItemT = TypeVar("StoreItemT", bound=StoreItem) + + class MemoryStorage(Storage): - def __init__(self, state: dict[str, StoreItem] = None): - self._memory: dict[str, StoreItem] = state or {} + def __init__(self, state: dict[str, JSON] = None): + self._memory: dict[str, JSON] = state or {} self._lock = Lock() self._e_tag = 0 - async def read(self, keys: list[str]) -> dict[str, StoreItem]: + async def read( + self, keys: list[str], *, target_cls: StoreItemT = None, **kwargs + ) -> dict[str, StoreItemT]: result: dict[str, StoreItem] = {} with self._lock: for key in keys: if key in self._memory: - store_item = self._memory[key] - if isinstance(store_item, StoreItem): - result[key] = store_item.from_json_to_store_item( + try: + result[key] = target_cls.from_json_to_store_item( self._memory[key] ) - else: + except TypeError as error: raise TypeError( - f"MemoryStorage.read(): store_item is not of type StoreItem" + f"MemoryStorage.read(): could not deserialize in-memory item into {target_cls} class. Error: {error}" ) return result async def write(self, changes: dict[str, StoreItem]): - for key in changes: - self._memory[key] = changes[key] + if not changes: + raise ValueError("MemoryStorage.write(): changes cannot be None") + + with self._lock: + for key in changes: + old_e_tag = self._memory.get(key, {}).get("e_tag") + if not hasattr(changes[key], "e_tag") or changes[key].e_tag == "*": + self._e_tag += 1 + changes[key].e_tag = self._e_tag + elif old_e_tag != changes[key].e_tag: + raise ValueError( + f"MemoryStorage.write(): e_tag conflict.\r\n\r\nOriginal: {changes[key].e_tag}\r\nCurrent: {old_e_tag}" + ) + changes[key].e_tag += 1 + self._memory[key] = changes[key].store_item_to_json() async def delete(self, keys: list[str]): with self._lock: diff --git a/libraries/Storage/microsoft-agents-storage/microsoft/agents/storage/storage.py b/libraries/Storage/microsoft-agents-storage/microsoft/agents/storage/storage.py index b90ebc69..2ec1d292 100644 --- a/libraries/Storage/microsoft-agents-storage/microsoft/agents/storage/storage.py +++ b/libraries/Storage/microsoft-agents-storage/microsoft/agents/storage/storage.py @@ -1,14 +1,15 @@ from typing import Protocol, TypeVar, Type +from ._type_aliases import JSON from .store_item import StoreItem -StoreItemT = TypeVar("StoreItemT", StoreItem) +StoreItemT = TypeVar("StoreItemT", bound=StoreItem) class Storage(Protocol): async def read( - self, keys: list[str], *, store_item_cls: Type[StoreItemT] = None, **kwargs + self, keys: list[str], *, target_cls: Type[StoreItemT] = None, **kwargs ) -> dict[str, StoreItemT]: pass diff --git a/libraries/Storage/microsoft-agents-storage/microsoft/agents/storage/store_item.py b/libraries/Storage/microsoft-agents-storage/microsoft/agents/storage/store_item.py index e1c05282..318ca4b0 100644 --- a/libraries/Storage/microsoft-agents-storage/microsoft/agents/storage/store_item.py +++ b/libraries/Storage/microsoft-agents-storage/microsoft/agents/storage/store_item.py @@ -1,9 +1,9 @@ -from typing import Any, MutableMapping, Protocol -from typing_extensions import Self +from typing import Protocol, runtime_checkable -JSON = MutableMapping[str, Any] +from ._type_aliases import JSON +@runtime_checkable class StoreItem(Protocol): e_tag: str @@ -11,5 +11,5 @@ def store_item_to_json(self) -> JSON: pass @staticmethod - def from_json_to_store_item(json_data: JSON) -> Self: + def from_json_to_store_item(json_data: JSON) -> "StoreItem": pass diff --git a/libraries/Storage/microsoft-agents-storage/pyproject.toml b/libraries/Storage/microsoft-agents-storage/pyproject.toml index e69de29b..6cad46e1 100644 --- a/libraries/Storage/microsoft-agents-storage/pyproject.toml +++ b/libraries/Storage/microsoft-agents-storage/pyproject.toml @@ -0,0 +1,20 @@ +[build-system] +requires = ["setuptools"] +build-backend = "setuptools.build_meta" + +[project] +name = "microsoft-agents-storage" +version = "0.0.0a1" +description = "A storage library for Microsoft Agents" +authors = [{name = "Microsoft Corporation"}] +requires-python = ">=3.9" +classifiers = [ + "Programming Language :: Python :: 3", + "License :: OSI Approved :: MIT License", + "Operating System :: OS Independent", +] +dependencies = [ +] + +[project.urls] +"Homepage" = "https://github.com/microsoft/microsoft-agents-protocol" From 95e982bddb5356a679ff2b5ffe420855a5cea798 Mon Sep 17 00:00:00 2001 From: Axel Suarez Martinez Date: Thu, 13 Feb 2025 16:06:24 -0600 Subject: [PATCH 10/21] Bot1 WIP --- .../microsoft/agents/botbuilder/__init__.py | 4 + .../channel_api_handler_protocol.py | 159 +++++++++++++++ .../agents/botbuilder/turn_context.py | 3 +- .../microsoft/agents/client/__init__.py | 29 +++ .../agents/client/conversation_id_factory.py | 46 +++-- .../microsoft/agents/core/__init__.py | 7 + .../agents/core/channel_adapter_protocol.py | 65 ++++++ .../agents/core/turn_context_protocol.py | 64 ++++++ .../agents/storage/memory_storage.py | 6 +- test_samples/bot_to_bot/bot_1/bot1.py | 188 ++++++++++++++++++ test_samples/echo_bot/app.py | 14 +- test_samples/echo_bot/config.py | 4 +- test_samples/echo_bot/echo_bot.py | 2 +- 13 files changed, 571 insertions(+), 20 deletions(-) create mode 100644 libraries/Botbuilder/microsoft-agents-botbuilder/microsoft/agents/botbuilder/channel_api_handler_protocol.py create mode 100644 libraries/Core/microsoft-agents-core/microsoft/agents/core/__init__.py create mode 100644 libraries/Core/microsoft-agents-core/microsoft/agents/core/channel_adapter_protocol.py create mode 100644 libraries/Core/microsoft-agents-core/microsoft/agents/core/turn_context_protocol.py create mode 100644 test_samples/bot_to_bot/bot_1/bot1.py diff --git a/libraries/Botbuilder/microsoft-agents-botbuilder/microsoft/agents/botbuilder/__init__.py b/libraries/Botbuilder/microsoft-agents-botbuilder/microsoft/agents/botbuilder/__init__.py index 275cb6c9..e9ffa8dc 100644 --- a/libraries/Botbuilder/microsoft-agents-botbuilder/microsoft/agents/botbuilder/__init__.py +++ b/libraries/Botbuilder/microsoft-agents-botbuilder/microsoft/agents/botbuilder/__init__.py @@ -1,6 +1,8 @@ # Import necessary modules from .activity_handler import ActivityHandler from .bot import Bot +from .channel_adapter import ChannelAdapter +from .channel_api_handler_protocol import ChannelApiHandlerProtocol from .channel_service_adapter import ChannelServiceAdapter from .channel_service_client_factory_base import ChannelServiceClientFactoryBase from .message_factory import MessageFactory @@ -12,6 +14,8 @@ __all__ = [ "ActivityHandler", "Bot", + "ChannelAdapter", + "ChannelApiHandlerProtocol", "ChannelServiceAdapter", "ChannelServiceClientFactoryBase", "MessageFactory", diff --git a/libraries/Botbuilder/microsoft-agents-botbuilder/microsoft/agents/botbuilder/channel_api_handler_protocol.py b/libraries/Botbuilder/microsoft-agents-botbuilder/microsoft/agents/botbuilder/channel_api_handler_protocol.py new file mode 100644 index 00000000..680bb80d --- /dev/null +++ b/libraries/Botbuilder/microsoft-agents-botbuilder/microsoft/agents/botbuilder/channel_api_handler_protocol.py @@ -0,0 +1,159 @@ +from abc import abstractmethod +from typing import Protocol, Optional + +from microsoft.agents.core.models import ( + Activity, + AttachmentData, + ChannelAccount, + ConversationResourceResponse, + ConversationsResult, + ConversationParameters, + ResourceResponse, + PagedMembersResult, + Transcript, +) + +from microsoft.agents.authentication import ClaimsIdentity + + +class ChannelApiHandlerProtocol(Protocol): + @abstractmethod + async def on_get_conversations( + self, + claims_identity: ClaimsIdentity, + conversation_id: str, + continuation_token: Optional[str] = None, + ) -> ConversationsResult: + """ + List the Conversations in which this bot has participated. + """ + raise NotImplementedError() + + @abstractmethod + async def on_create_conversation( + self, claims_identity: ClaimsIdentity, parameters: ConversationParameters + ) -> ConversationResourceResponse: + """ + Create a new Conversation. + """ + raise NotImplementedError() + + @abstractmethod + async def on_send_to_conversation( + self, claims_identity: ClaimsIdentity, conversation_id: str, activity: Activity + ) -> ResourceResponse: + """ + Send an activity to the end of a conversation. + """ + raise NotImplementedError() + + @abstractmethod + async def on_send_conversation_history( + self, + claims_identity: ClaimsIdentity, + conversation_id: str, + transcript: Transcript, + ) -> ResourceResponse: + """ + Upload the historic activities to the conversation. + """ + raise NotImplementedError() + + @abstractmethod + async def on_update_activity( + self, + claims_identity: ClaimsIdentity, + conversation_id: str, + activity_id: str, + activity: Activity, + ) -> ResourceResponse: + """ + Edit an existing activity. + """ + raise NotImplementedError() + + @abstractmethod + async def on_reply_to_activity( + self, + claims_identity: ClaimsIdentity, + conversation_id: str, + activity_id: str, + activity: Activity, + ) -> ResourceResponse: + """ + Reply to an activity. + """ + raise NotImplementedError() + + @abstractmethod + async def on_delete_activity( + self, claims_identity: ClaimsIdentity, conversation_id: str, activity_id: str + ): + """ + Delete an existing activity. + """ + raise NotImplementedError() + + @abstractmethod + async def on_get_conversation_members( + self, claims_identity: ClaimsIdentity, conversation_id: str + ) -> list[ChannelAccount]: + """ + Enumerate the members of a conversation. + """ + raise NotImplementedError() + + @abstractmethod + async def on_get_conversation_member( + self, + claims_identity: ClaimsIdentity, + user_id: str, + conversation_id: str, + ) -> ChannelAccount: + """ + Enumerate the members of a conversation. + """ + raise NotImplementedError() + + @abstractmethod + async def on_get_conversation_paged_members( + self, + claims_identity: ClaimsIdentity, + conversation_id: str, + page_size: Optional[int] = None, + continuation_token: Optional[str] = None, + ) -> PagedMembersResult: + """ + Enumerate the members of a conversation one page at a time. + """ + raise NotImplementedError() + + @abstractmethod + async def on_delete_conversation_member( + self, claims_identity: ClaimsIdentity, conversation_id: str, member_id: str + ): + """ + Deletes a member from a conversation. + """ + raise NotImplementedError() + + @abstractmethod + async def on_get_activity_members( + self, claims_identity: ClaimsIdentity, conversation_id: str, activity_id: str + ) -> list[ChannelAccount]: + """ + Enumerate the members of an activity. + """ + raise NotImplementedError() + + @abstractmethod + async def on_upload_attachment( + self, + claims_identity: ClaimsIdentity, + conversation_id: str, + attachment_upload: AttachmentData, + ) -> ResourceResponse: + """ + Upload an attachment directly into a channel's storage. + """ + raise NotImplementedError() diff --git a/libraries/Botbuilder/microsoft-agents-botbuilder/microsoft/agents/botbuilder/turn_context.py b/libraries/Botbuilder/microsoft-agents-botbuilder/microsoft/agents/botbuilder/turn_context.py index 39e15c53..eb95c661 100644 --- a/libraries/Botbuilder/microsoft-agents-botbuilder/microsoft/agents/botbuilder/turn_context.py +++ b/libraries/Botbuilder/microsoft-agents-botbuilder/microsoft/agents/botbuilder/turn_context.py @@ -7,6 +7,7 @@ from copy import copy, deepcopy from collections.abc import Callable from datetime import datetime, timezone +from microsoft.agents.core import TurnContextProtocol from microsoft.agents.core.models import ( Activity, ActivityTypes, @@ -18,7 +19,7 @@ ) -class TurnContext: +class TurnContext(TurnContextProtocol): # Same constant as in the BF Adapter, duplicating here to avoid circular dependency _INVOKE_RESPONSE_KEY = "BotFrameworkAdapter.InvokeResponse" diff --git a/libraries/Client/microsoft-agents-client/microsoft/agents/client/__init__.py b/libraries/Client/microsoft-agents-client/microsoft/agents/client/__init__.py index e69de29b..c0a9953b 100644 --- a/libraries/Client/microsoft-agents-client/microsoft/agents/client/__init__.py +++ b/libraries/Client/microsoft-agents-client/microsoft/agents/client/__init__.py @@ -0,0 +1,29 @@ +from .bot_conversation_reference import BotConversationReference +from .channel_factory_protocol import ChannelFactoryProtocol +from .channel_host_protocol import ChannelHostProtocol +from .channel_info_protocol import ChannelInfoProtocol +from .channel_protocol import ChannelProtocol +from .channels_configuration import ChannelsConfiguration +from .configuration_channel_host import ConfigurationChannelHost +from .conversation_constants import ConversationConstants +from .conversation_id_factory_options import ConversationIdFactoryOptions +from .conversation_id_factory_protocol import ConversationIdFactoryProtocol +from .conversation_id_factory import ConversationIdFactory +from .http_bot_channel_factory import HttpBotChannelFactory +from .http_bot_channel import HttpBotChannel + +__all__ = [ + "BotConversationReference", + "ChannelFactoryProtocol", + "ChannelHostProtocol", + "ChannelInfoProtocol", + "ChannelProtocol", + "ChannelsConfiguration", + "ConfigurationChannelHost", + "ConversationConstants", + "ConversationIdFactoryOptions", + "ConversationIdFactoryProtocol", + "ConversationIdFactory", + "HttpBotChannelFactory", + "HttpBotChannel", +] diff --git a/libraries/Client/microsoft-agents-client/microsoft/agents/client/conversation_id_factory.py b/libraries/Client/microsoft-agents-client/microsoft/agents/client/conversation_id_factory.py index bb5d5295..29513398 100644 --- a/libraries/Client/microsoft-agents-client/microsoft/agents/client/conversation_id_factory.py +++ b/libraries/Client/microsoft-agents-client/microsoft/agents/client/conversation_id_factory.py @@ -9,13 +9,26 @@ from .conversation_id_factory_protocol import ConversationIdFactoryProtocol +def _implement_store_item_for_agents_model_cls(model_instance: AgentsModel): + if not hasattr(model_instance, "e_tag"): + setattr(model_instance, "e_tag", 0) + if not isinstance(model_instance, StoreItem): + instance_cls = type(model_instance) + setattr( + instance_cls, + "store_item_to_json", + partial(model_instance.model_dump, mode="json", exclude_none=True), + ) + instance_cls.from_json_to_store_item = classmethod(instance_cls.model_validate) + + class ConversationIdFactory(ConversationIdFactoryProtocol): def __init__(self, storage: Storage) -> None: if not storage: raise ValueError("ConversationIdFactory.__init__(): storage cannot be None") self._storage = storage - def create_conversation_id(self, options) -> str: + async def create_conversation_id(self, options) -> str: if not options: raise ValueError( "ConversationIdFactory.create_conversation_id(): options cannot be None" @@ -29,19 +42,26 @@ def create_conversation_id(self, options) -> str: oauth_scope=options.from_oauth_scope, ) - conversation_info = {bot_conversation_id: bot_conversation_reference} + _implement_store_item_for_agents_model_cls(bot_conversation_reference) - def get_bot_conversation_reference(self, bot_conversation_id): - raise NotImplementedError() + conversation_info = {bot_conversation_id: bot_conversation_reference} + await self._storage.write(conversation_info) - def delete_conversation_reference(self, bot_conversation_id): - raise NotImplementedError() + return bot_conversation_id - @staticmethod - def _implement_store_item_for_agents_model_cls(model_instance: AgentsModel): - if not isinstance(model_instance, StoreItem): - instance_cls = type(model_instance) - setattr( - instance_cls, - partial(model_instance.model_dump, mode="json", exclude_none=True), + async def get_bot_conversation_reference( + self, bot_conversation_id + ) -> BotConversationReference: + if not bot_conversation_id: + raise ValueError( + "ConversationIdFactory.get_bot_conversation_reference(): bot_conversation_id cannot be None" ) + + bot_conversation_info = await self._storage.read( + [bot_conversation_id], target_cls=BotConversationReference + ) + + return bot_conversation_info + + async def delete_conversation_reference(self, bot_conversation_id): + await self._storage.delete([bot_conversation_id]) diff --git a/libraries/Core/microsoft-agents-core/microsoft/agents/core/__init__.py b/libraries/Core/microsoft-agents-core/microsoft/agents/core/__init__.py new file mode 100644 index 00000000..d8a18591 --- /dev/null +++ b/libraries/Core/microsoft-agents-core/microsoft/agents/core/__init__.py @@ -0,0 +1,7 @@ +from .channel_adapter_protocol import ChannelAdapterProtocol +from .turn_context_protocol import TurnContextProtocol + +__all__ = [ + "ChannelAdapterProtocol", + "TurnContextProtocol", +] diff --git a/libraries/Core/microsoft-agents-core/microsoft/agents/core/channel_adapter_protocol.py b/libraries/Core/microsoft-agents-core/microsoft/agents/core/channel_adapter_protocol.py new file mode 100644 index 00000000..cdef830d --- /dev/null +++ b/libraries/Core/microsoft-agents-core/microsoft/agents/core/channel_adapter_protocol.py @@ -0,0 +1,65 @@ +from abc import abstractmethod +from typing import Protocol, List, Callable, Awaitable, Optional + +from .turn_context_protocol import TurnContextProtocol +from microsoft.agents.core.models import ( + Activity, + ResourceResponse, + ConversationReference, + ConversationParameters, +) + + +class ChannelAdapterProtocol(Protocol): + on_turn_error: Optional[Callable[[TurnContextProtocol, Exception], Awaitable]] + + @abstractmethod + async def send_activities( + self, context: TurnContextProtocol, activities: List[Activity] + ) -> List[ResourceResponse]: + pass + + @abstractmethod + async def update_activity( + self, context: TurnContextProtocol, activity: Activity + ) -> None: + pass + + @abstractmethod + async def delete_activity( + self, context: TurnContextProtocol, reference: ConversationReference + ) -> None: + pass + + @abstractmethod + def use(self, middleware: object) -> "ChannelAdapterProtocol": + pass + + @abstractmethod + async def continue_conversation( + self, + bot_id: str, + reference: ConversationReference, + callback: Callable[[TurnContextProtocol], Awaitable], + ) -> None: + pass + + @abstractmethod + async def create_conversation( + self, + bot_app_id: str, + channel_id: str, + service_url: str, + audience: str, + conversation_parameters: ConversationParameters, + callback: Callable[[TurnContextProtocol], Awaitable], + ) -> None: + pass + + @abstractmethod + async def run_pipeline( + self, + context: TurnContextProtocol, + callback: Callable[[TurnContextProtocol], Awaitable], + ) -> None: + pass diff --git a/libraries/Core/microsoft-agents-core/microsoft/agents/core/turn_context_protocol.py b/libraries/Core/microsoft-agents-core/microsoft/agents/core/turn_context_protocol.py new file mode 100644 index 00000000..fb71bc79 --- /dev/null +++ b/libraries/Core/microsoft-agents-core/microsoft/agents/core/turn_context_protocol.py @@ -0,0 +1,64 @@ +from __future__ import annotations + +from typing import Protocol, List, Callable, Awaitable, Optional, Generic, TypeVar +from abc import abstractmethod + +from microsoft.agents.core.models import ( + Activity, + ResourceResponse, + ConversationReference, +) + +from .channel_adapter_protocol import ChannelAdapterProtocol + +T = TypeVar("T", bound=Activity) + + +class TurnContextProtocol(Protocol, Generic[T]): + adapter: ChannelAdapterProtocol + activity: Activity | T + responded: bool + turn_state: dict + + @abstractmethod + async def send_activity( + self, + activity_or_text: Activity | str, + speak: Optional[str] = None, + input_hint: Optional[str] = None, + ) -> Optional[ResourceResponse]: + pass + + @abstractmethod + async def send_activities( + self, activities: List[Activity] + ) -> List[ResourceResponse]: + pass + + @abstractmethod + async def update_activity(self, activity: Activity) -> Optional[ResourceResponse]: + pass + + @abstractmethod + async def delete_activity( + self, id_or_reference: str | ConversationReference + ) -> None: + pass + + @abstractmethod + def on_send_activities(self, handler: Callable) -> "TurnContextProtocol": + pass + + @abstractmethod + def on_update_activity(self, handler: Callable) -> "TurnContextProtocol": + pass + + @abstractmethod + def on_delete_activity(self, handler: Callable) -> "TurnContextProtocol": + pass + + @abstractmethod + async def send_trace_activity( + self, name: str, value: object = None, value_type: str = None, label: str = None + ) -> ResourceResponse: + pass diff --git a/libraries/Storage/microsoft-agents-storage/microsoft/agents/storage/memory_storage.py b/libraries/Storage/microsoft-agents-storage/microsoft/agents/storage/memory_storage.py index ee5b494b..64ed55d0 100644 --- a/libraries/Storage/microsoft-agents-storage/microsoft/agents/storage/memory_storage.py +++ b/libraries/Storage/microsoft-agents-storage/microsoft/agents/storage/memory_storage.py @@ -39,7 +39,11 @@ async def write(self, changes: dict[str, StoreItem]): with self._lock: for key in changes: old_e_tag = self._memory.get(key, {}).get("e_tag") - if not hasattr(changes[key], "e_tag") or changes[key].e_tag == "*": + if ( + not old_e_tag + or not hasattr(changes[key], "e_tag") + or changes[key].e_tag == "*" + ): self._e_tag += 1 changes[key].e_tag = self._e_tag elif old_e_tag != changes[key].e_tag: diff --git a/test_samples/bot_to_bot/bot_1/bot1.py b/test_samples/bot_to_bot/bot_1/bot1.py new file mode 100644 index 00000000..cc11194f --- /dev/null +++ b/test_samples/bot_to_bot/bot_1/bot1.py @@ -0,0 +1,188 @@ +from typing import Any + +from aiohttp.web import HTTPException + +from microsoft.agents.core import ChannelAdapterProtocol, TurnContextProtocol +from microsoft.agents.core.models import ( + ActivityTypes, + Activity, + CallerIdConstants, + ChannelAccount, + ResourceResponse, +) +from microsoft.agents.authentication import BotClaims, ClaimsIdentity +from microsoft.agents.client import ( + ChannelHostProtocol, + ChannelInfoProtocol, + ConversationIdFactoryProtocol, + ConversationIdFactoryOptions, +) +from microsoft.agents.botbuilder import ( + ActivityHandler, + ChannelApiHandlerProtocol, + ChannelAdapter, +) + + +class Bot1(ActivityHandler, ChannelApiHandlerProtocol): + ACTIVE_SKILL_PROPERTY_NAME = "Bot1.ActiveSkillProperty" + _active_bot_client = False + + def __init__( + self, + adapter: ChannelAdapterProtocol, + channel_host: ChannelHostProtocol, + conversation_id_factory: ConversationIdFactoryProtocol, + ): + if not adapter: + raise ValueError("Bot1.__init__(): adapter cannot be None") + if not channel_host: + raise ValueError("Bot1.__init__(): channel_host cannot be None") + if not conversation_id_factory: + raise ValueError("Bot1.__init__(): conversation_id_factory cannot be None") + + self._adapter = adapter + self._channel_host = channel_host + self._conversation_id_factory = conversation_id_factory + + target_skill_id = "EchoSkillBot" + self._target_skill = self._channel_host.channels.get(target_skill_id) + + async def on_turn(self, turn_context: TurnContextProtocol): + # Forward all activities except EndOfConversation to the skill + if turn_context.activity.type == ActivityTypes.end_of_conversation: + # Try to get the active skill + if Bot1._active_bot_client: + # await Bot1._active_bot_client.on_turn(turn_context) + return + + await super().on_turn(turn_context) + + # update when doing activity type protocols + async def on_message_activity(self, turn_context: TurnContextProtocol): + if "agent" in turn_context.activity.text.lower(): + # TODO: review activity | str interface for send_activity + await turn_context.send_activity("Got it, connecting you to the agent...") + + Bot1._active_bot_client = True + + # send to bot + return + + await turn_context.send_activity('Say "agent" and I\'ll patch you through') + + async def on_end_of_conversation_activity(self, turn_context: TurnContextProtocol): + # Clear the active skill + Bot1._active_bot_client = False + + # Show status message, text and value returned by the skill + eoc_activity_message = f"Received {turn_context.activity.type}. Code: {turn_context.activity.code}." + if turn_context.activity.text: + eoc_activity_message += f" Text: {turn_context.activity.text}" + + if turn_context.activity.value: + eoc_activity_message += f" Value: {turn_context.activity.value}" + + await turn_context.send_activity(eoc_activity_message) + await turn_context.send_activity( + 'Back in the root bot. Say "agent" and I\'ll patch you through' + ) + + async def on_members_added_activity( + self, members_added: list[ChannelAccount], turn_context: TurnContextProtocol + ): + for member in members_added: + if member.id != turn_context.activity.recipient.id: + await turn_context.send_activity( + 'Hello and welcome! Say "agent" and I\'ll patch you through' + ) + + async def _send_to_bot( + self, turn_context: TurnContextProtocol, target_channel: ChannelInfoProtocol + ): + # Create a conversation ID to communicate with the skill + options = ConversationIdFactoryOptions( + from_oauth_scope=turn_context.turn_state.get( + ChannelAdapter.OAUTH_SCOPE_KEY + ), + from_bot_id=self._channel_host.host_app_id, + activity=turn_context.activity, + bot=target_channel, + ) + + conversation_id = await self._conversation_id_factory.create_conversation_id( + options + ) + + # TODO: might need to close connection, tbd + channel = self._channel_host.get_channel(target_channel) + + # Route activity to the skill + response = await channel.post_activity( + target_channel.app_id, + target_channel.resource_url, + target_channel.endpoint, + self._channel_host.host_endpoint, + conversation_id, + turn_context.activity, + ) + + if response.status < 200 or response.status >= 300: + raise HTTPException( + f'Error invoking the id: "{target_channel.id}" at "{target_channel.endpoint}" (status is {response.status}). \r\n {response.body}' + ) + + @staticmethod + def _apply_activity_to_turn_context( + turn_context: TurnContextProtocol, activity: Activity + ): + # TODO: activity.properties? + turn_context.activity.channel_data = activity.channel_data + turn_context.activity.code = activity.code + turn_context.activity.entities = activity.entities + turn_context.activity.locale = activity.locale + turn_context.activity.local_timestamp = activity.local_timestamp + turn_context.activity.name = activity.name + turn_context.activity.relates_to = activity.relates_to + turn_context.activity.reply_to_id = activity.reply_to_id + turn_context.activity.timestamp = activity.timestamp + turn_context.activity.text = activity.text + turn_context.activity.type = activity.type + turn_context.activity.value = activity.value + + async def _process_activity( + self, + claims_identity: ClaimsIdentity, + conversation_id: str, + reply_to_activity_id: str, + activity: Activity, + ): + bot_conversation_reference = ( + await self._conversation_id_factory.get_conversation_reference( + conversation_id + ) + ) + + resource_response: ResourceResponse = None + + async def bot_callback_handler(turn_context: TurnContextProtocol): + activity.apply_conversation_reference( + bot_conversation_reference.conversation_reference + ) + turn_context.activity.id = reply_to_activity_id + turn_context.activity.caller_id = f"{CallerIdConstants.bot_to_bot_prefix}{claims_identity.get_outgoing_app_id()}" + + if activity.type == ActivityTypes.end_of_conversation: + await self._conversation_id_factory.delete_conversation_reference( + conversation_id + ) + + Bot1._apply_activity_to_turn_context(turn_context, activity) + await self.on_turn(turn_context) + else: + resource_response = await turn_context.send_activity(activity) + + # TODO: fix overload + await self._adapter.continue_conversation( + activity, claims_identity, bot_callback_handler + ) diff --git a/test_samples/echo_bot/app.py b/test_samples/echo_bot/app.py index 3c7d561b..5a1af1fb 100644 --- a/test_samples/echo_bot/app.py +++ b/test_samples/echo_bot/app.py @@ -5,18 +5,26 @@ from microsoft.agents.botbuilder import RestChannelServiceClientFactory from microsoft.agents.hosting.aiohttp import CloudAdapter, jwt_authorization_middleware -from microsoft.agents.authentication import Connections, AccessTokenProviderBase, ClaimsIdentity +from microsoft.agents.authentication import ( + Connections, + AccessTokenProviderBase, + ClaimsIdentity, +) from microsoft.agents.authorization.msal import MsalAuth from echo_bot import EchoBot from config import DefaultConfig AUTH_PROVIDER = MsalAuth(DefaultConfig()) + + class DefaultConnection(Connections): def get_default_connection(self) -> AccessTokenProviderBase: pass - def get_token_provider(self, claims_identity: ClaimsIdentity, service_url: str) -> AccessTokenProviderBase: + def get_token_provider( + self, claims_identity: ClaimsIdentity, service_url: str + ) -> AccessTokenProviderBase: return AUTH_PROVIDER def get_connection(self, connection_name: str) -> AccessTokenProviderBase: @@ -49,4 +57,4 @@ async def messages(req: Request) -> Response: try: run_app(APP, host="localhost", port=CONFIG.PORT) except Exception as error: - raise error \ No newline at end of file + raise error diff --git a/test_samples/echo_bot/config.py b/test_samples/echo_bot/config.py index aa9b31c8..1fe1ea0c 100644 --- a/test_samples/echo_bot/config.py +++ b/test_samples/echo_bot/config.py @@ -1,7 +1,9 @@ from microsoft.agents.authorization.msal import AuthTypes, MsalAuthConfiguration + class DefaultConfig(MsalAuthConfiguration): - """ Bot Configuration """ + """Bot Configuration""" + AUTH_TYPE = AuthTypes.client_secret TENANT_ID = "" CLIENT_ID = "" diff --git a/test_samples/echo_bot/echo_bot.py b/test_samples/echo_bot/echo_bot.py index c3660866..74f722b0 100644 --- a/test_samples/echo_bot/echo_bot.py +++ b/test_samples/echo_bot/echo_bot.py @@ -13,4 +13,4 @@ async def on_members_added_activity( async def on_message_activity(self, turn_context: TurnContext): return await turn_context.send_activity( MessageFactory.text(f"Echo: {turn_context.activity.text}") - ) \ No newline at end of file + ) From dedfa0fc2927740f6a7f8c5aaa5bd28410142dce Mon Sep 17 00:00:00 2001 From: Axel Suarez Date: Tue, 18 Feb 2025 14:01:51 -0800 Subject: [PATCH 11/21] Bot 1 WIP --- .../agents/botbuilder/channel_adapter.py | 29 +++++++++++++++++-- .../conversation_id_factory_protocol.py | 8 +++-- .../agents/core/channel_adapter_protocol.py | 11 +++++++ test_samples/bot_to_bot/bot_1/bot1.py | 19 ++++++++---- 4 files changed, 57 insertions(+), 10 deletions(-) diff --git a/libraries/Botbuilder/microsoft-agents-botbuilder/microsoft/agents/botbuilder/channel_adapter.py b/libraries/Botbuilder/microsoft-agents-botbuilder/microsoft/agents/botbuilder/channel_adapter.py index 9305909f..d7a1bfc8 100644 --- a/libraries/Botbuilder/microsoft-agents-botbuilder/microsoft/agents/botbuilder/channel_adapter.py +++ b/libraries/Botbuilder/microsoft-agents-botbuilder/microsoft/agents/botbuilder/channel_adapter.py @@ -3,7 +3,9 @@ from abc import ABC, abstractmethod from collections.abc import Callable -from typing import List, Awaitable, Protocol +from typing import List, Awaitable +from microsoft.agents.authentication import ClaimsIdentity +from microsoft.agents.core import ChannelAdapterProtocol from microsoft.agents.core.models import ( Activity, ConversationReference, @@ -15,7 +17,7 @@ from .middleware_set import MiddlewareSet -class ChannelAdapter(ABC): +class ChannelAdapter(ABC, ChannelAdapterProtocol): BOT_IDENTITY_KEY = "BotIdentity" OAUTH_SCOPE_KEY = "Microsoft.Agents.BotBuilder.ChannelAdapter.OAuthScope" INVOKE_RESPONSE_KEY = "ChannelAdapter.InvokeResponse" @@ -104,6 +106,29 @@ async def continue_conversation( context = TurnContext(self, reference.get_continuation_activity()) return await self.run_pipeline(context, callback) + async def continue_conversation_with_claims( + self, + claims_identity: ClaimsIdentity, + continuation_activity: Activity, + callback: Callable[[TurnContext], Awaitable], + audience: str = None, + ): + """ + Sends a proactive message to a conversation. Call this method to proactively send a message to a conversation. + Most channels require a user to initiate a conversation with a bot before the bot can send activities + to the user. + + :param claims_identity: A :class:`botframework.connector.auth.ClaimsIdentity` for the conversation. + :type claims_identity: :class:`botframework.connector.auth.ClaimsIdentity` + :param continuation_activity: The activity to send. + :type continuation_activity: :class:`botbuilder + :param callback: The method to call for the resulting bot turn. + :type callback: :class:`typing.Callable` + :param audience: A value signifying the recipient of the proactive message. + :type audience: str + """ + raise NotImplementedError() + async def create_conversation( self, bot_app_id: str, diff --git a/libraries/Client/microsoft-agents-client/microsoft/agents/client/conversation_id_factory_protocol.py b/libraries/Client/microsoft-agents-client/microsoft/agents/client/conversation_id_factory_protocol.py index 159b2cf0..b8b92c7c 100644 --- a/libraries/Client/microsoft-agents-client/microsoft/agents/client/conversation_id_factory_protocol.py +++ b/libraries/Client/microsoft-agents-client/microsoft/agents/client/conversation_id_factory_protocol.py @@ -7,7 +7,9 @@ class ConversationIdFactoryProtocol(Protocol): @abstractmethod - def create_conversation_id(self, options: ConversationIdFactoryOptions) -> str: + async def create_conversation_id( + self, options: ConversationIdFactoryOptions + ) -> str: """ Creates a conversation ID for a bot conversation. :param options: A ConversationIdFactoryOptions instance. @@ -15,7 +17,7 @@ def create_conversation_id(self, options: ConversationIdFactoryOptions) -> str: """ @abstractmethod - def get_bot_conversation_reference( + async def get_bot_conversation_reference( self, bot_conversation_id: str ) -> BotConversationReference: """ @@ -25,7 +27,7 @@ def get_bot_conversation_reference( """ @abstractmethod - def delete_conversation_reference(self, bot_conversation_id: str) -> None: + async def delete_conversation_reference(self, bot_conversation_id: str) -> None: """ Deletes a bot conversation reference. :param bot_conversation_id: A conversation ID created with create_conversation_id. diff --git a/libraries/Core/microsoft-agents-core/microsoft/agents/core/channel_adapter_protocol.py b/libraries/Core/microsoft-agents-core/microsoft/agents/core/channel_adapter_protocol.py index cdef830d..c5ac9566 100644 --- a/libraries/Core/microsoft-agents-core/microsoft/agents/core/channel_adapter_protocol.py +++ b/libraries/Core/microsoft-agents-core/microsoft/agents/core/channel_adapter_protocol.py @@ -44,6 +44,17 @@ async def continue_conversation( ) -> None: pass + # TODO: potentially move ClaimsIdentity to core + @abstractmethod + async def continue_conversation_with_claims( + self, + claims_identity: dict, + continuation_activity: Activity, + callback: Callable[[TurnContextProtocol], Awaitable], + audience: str = None, + ): + pass + @abstractmethod async def create_conversation( self, diff --git a/test_samples/bot_to_bot/bot_1/bot1.py b/test_samples/bot_to_bot/bot_1/bot1.py index cc11194f..13d378c2 100644 --- a/test_samples/bot_to_bot/bot_1/bot1.py +++ b/test_samples/bot_to_bot/bot_1/bot1.py @@ -1,4 +1,4 @@ -from typing import Any +from uuid import uuid4 from aiohttp.web import HTTPException @@ -10,7 +10,7 @@ ChannelAccount, ResourceResponse, ) -from microsoft.agents.authentication import BotClaims, ClaimsIdentity +from microsoft.agents.authentication import ClaimsIdentity from microsoft.agents.client import ( ChannelHostProtocol, ChannelInfoProtocol, @@ -158,7 +158,7 @@ async def _process_activity( activity: Activity, ): bot_conversation_reference = ( - await self._conversation_id_factory.get_conversation_reference( + await self._conversation_id_factory.get_bot_conversation_reference( conversation_id ) ) @@ -180,9 +180,18 @@ async def bot_callback_handler(turn_context: TurnContextProtocol): Bot1._apply_activity_to_turn_context(turn_context, activity) await self.on_turn(turn_context) else: + nonlocal resource_response resource_response = await turn_context.send_activity(activity) # TODO: fix overload - await self._adapter.continue_conversation( - activity, claims_identity, bot_callback_handler + continuation_activity = ( + bot_conversation_reference.conversation_reference.get_continuation_activity() ) + await self._adapter.continue_conversation_with_claims( + claims_identity=claims_identity, + continuation_activity=continuation_activity, + callback=bot_callback_handler, + audience=bot_conversation_reference.oauth_scope, + ) + + return resource_response or ResourceResponse(id=str(uuid4())) From ddf962974605b05b7516c3a1c64eab32f5d1baf6 Mon Sep 17 00:00:00 2001 From: Axel Suarez Date: Tue, 18 Feb 2025 16:56:37 -0800 Subject: [PATCH 12/21] Adding route table helper (aiohttp) --- .../agents/hosting/aiohttp/__init__.py | 8 +- .../aiohttp/channel_service_route_table.py | 194 ++++++++++++++++++ 2 files changed, 201 insertions(+), 1 deletion(-) create mode 100644 libraries/Hosting/microsoft-agents-hosting-aiohttp/microsoft/agents/hosting/aiohttp/channel_service_route_table.py diff --git a/libraries/Hosting/microsoft-agents-hosting-aiohttp/microsoft/agents/hosting/aiohttp/__init__.py b/libraries/Hosting/microsoft-agents-hosting-aiohttp/microsoft/agents/hosting/aiohttp/__init__.py index bb0a5753..cf0232ed 100644 --- a/libraries/Hosting/microsoft-agents-hosting-aiohttp/microsoft/agents/hosting/aiohttp/__init__.py +++ b/libraries/Hosting/microsoft-agents-hosting-aiohttp/microsoft/agents/hosting/aiohttp/__init__.py @@ -1,5 +1,11 @@ from .bot_http_adapter import BotHttpAdapter +from .channel_service_route_table import channel_service_route_table from .cloud_adapter import CloudAdapter from .jwt_authorization_middleware import jwt_authorization_middleware -__all__ = ["BotHttpAdapter", "CloudAdapter", "jwt_authorization_middleware"] +__all__ = [ + "BotHttpAdapter", + "CloudAdapter", + "jwt_authorization_middleware", + "channel_service_route_table", +] diff --git a/libraries/Hosting/microsoft-agents-hosting-aiohttp/microsoft/agents/hosting/aiohttp/channel_service_route_table.py b/libraries/Hosting/microsoft-agents-hosting-aiohttp/microsoft/agents/hosting/aiohttp/channel_service_route_table.py new file mode 100644 index 00000000..463cc7c8 --- /dev/null +++ b/libraries/Hosting/microsoft-agents-hosting-aiohttp/microsoft/agents/hosting/aiohttp/channel_service_route_table.py @@ -0,0 +1,194 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. +import json +from typing import List, Union, Type + +from aiohttp.web import RouteTableDef, Request, Response + +from microsoft.agents.core.models import ( + AgentsModel, + Activity, + AttachmentData, + ConversationParameters, + Transcript, +) +from microsoft.agents.botbuilder import ChannelApiHandlerProtocol + + +async def deserialize_from_body( + request: Request, target_model: Type[AgentsModel] +) -> Activity: + if "application/json" in request.headers["Content-Type"]: + body = await request.json() + else: + return Response(status=415) + + return target_model().model_validate(body) + + +def get_serialized_response( + model_or_list: Union[AgentsModel, List[AgentsModel]] +) -> Response: + if isinstance(model_or_list, AgentsModel): + json_obj = model_or_list.model_dump( + mode="json", exclude_unset=True, by_alias=True + ) + else: + json_obj = [ + model.model_dump(mode="json", exclude_unset=True, by_alias=True) + for model in model_or_list + ] + + return Response(body=json.dumps(json_obj), content_type="application/json") + + +def channel_service_route_table( + handler: ChannelApiHandlerProtocol, base_url: str = "" +) -> RouteTableDef: + # pylint: disable=unused-variable + routes = RouteTableDef() + + @routes.post(base_url + "/v3/conversations/{conversation_id}/activities") + async def send_to_conversation(request: Request): + activity = await deserialize_from_body(request, Activity) + result = await handler.on_send_to_conversation( + request.get("claims_identity"), + request.match_info["conversation_id"], + activity, + ) + + return get_serialized_response(result) + + @routes.post( + base_url + "/v3/conversations/{conversation_id}/activities/{activity_id}" + ) + async def reply_to_activity(request: Request): + activity = await deserialize_from_body(request, Activity) + result = await handler.on_reply_to_activity( + request.get("claims_identity"), + request.match_info["conversation_id"], + request.match_info["activity_id"], + activity, + ) + + return get_serialized_response(result) + + @routes.put( + base_url + "/v3/conversations/{conversation_id}/activities/{activity_id}" + ) + async def update_activity(request: Request): + activity = await deserialize_from_body(request, Activity) + result = await handler.on_update_activity( + request.get("claims_identity"), + request.match_info["conversation_id"], + request.match_info["activity_id"], + activity, + ) + + return get_serialized_response(result) + + @routes.delete( + base_url + "/v3/conversations/{conversation_id}/activities/{activity_id}" + ) + async def delete_activity(request: Request): + await handler.on_delete_activity( + request.get("claims_identity"), + request.match_info["conversation_id"], + request.match_info["activity_id"], + ) + + return Response() + + @routes.get( + base_url + + "/v3/conversations/{conversation_id}/activities/{activity_id}/members" + ) + async def get_activity_members(request: Request): + result = await handler.on_get_activity_members( + request.get("claims_identity"), + request.match_info["conversation_id"], + request.match_info["activity_id"], + ) + + return get_serialized_response(result) + + @routes.post(base_url + "/") + async def create_conversation(request: Request): + conversation_parameters = deserialize_from_body(request, ConversationParameters) + result = await handler.on_create_conversation( + request.get("claims_identity"), conversation_parameters + ) + + return get_serialized_response(result) + + @routes.get(base_url + "/") + async def get_conversation(request: Request): + # TODO: continuation token? conversation_id? + result = await handler.on_get_conversations( + request.get("claims_identity"), None + ) + + return get_serialized_response(result) + + @routes.get(base_url + "/v3/conversations/{conversation_id}/members") + async def get_conversation_members(request: Request): + result = await handler.on_get_conversation_members( + request.get("claims_identity"), + request.match_info["conversation_id"], + ) + + return get_serialized_response(result) + + @routes.get(base_url + "/v3/conversations/{conversation_id}/members/{member_id}") + async def get_conversation_member(request: Request): + result = await handler.on_get_conversation_member( + request.get("claims_identity"), + request.match_info["member_id"], + request.match_info["conversation_id"], + ) + + return get_serialized_response(result) + + @routes.get(base_url + "/v3/conversations/{conversation_id}/pagedmembers") + async def get_conversation_paged_members(request: Request): + # TODO: continuation token? page size? + result = await handler.on_get_conversation_paged_members( + request.get("claims_identity"), + request.match_info["conversation_id"], + ) + + return get_serialized_response(result) + + @routes.delete(base_url + "/v3/conversations/{conversation_id}/members/{member_id}") + async def delete_conversation_member(request: Request): + result = await handler.on_delete_conversation_member( + request.get("claims_identity"), + request.match_info["conversation_id"], + request.match_info["member_id"], + ) + + return get_serialized_response(result) + + @routes.post(base_url + "/v3/conversations/{conversation_id}/activities/history") + async def send_conversation_history(request: Request): + transcript = deserialize_from_body(request, Transcript) + result = await handler.on_send_conversation_history( + request.get("claims_identity"), + request.match_info["conversation_id"], + transcript, + ) + + return get_serialized_response(result) + + @routes.post(base_url + "/v3/conversations/{conversation_id}/attachments") + async def upload_attachment(request: Request): + attachment_data = deserialize_from_body(request, AttachmentData) + result = await handler.on_upload_attachment( + request.get("claims_identity"), + request.match_info["conversation_id"], + attachment_data, + ) + + return get_serialized_response(result) + + return routes From 73623a859f54a854ceeb9b8f52c58a6b6fa19b3c Mon Sep 17 00:00:00 2001 From: Axel Suarez Date: Wed, 19 Feb 2025 17:26:15 -0800 Subject: [PATCH 13/21] Sample WIP --- .../agents/botbuilder/activity_handler.py | 50 +++++---- test_samples/bot_to_bot/bot_1/app.py | 64 +++++++++++ test_samples/bot_to_bot/bot_1/bot1.py | 105 +++++++++++++++++- test_samples/bot_to_bot/bot_1/config.py | 11 ++ 4 files changed, 205 insertions(+), 25 deletions(-) create mode 100644 test_samples/bot_to_bot/bot_1/app.py create mode 100644 test_samples/bot_to_bot/bot_1/config.py diff --git a/libraries/Botbuilder/microsoft-agents-botbuilder/microsoft/agents/botbuilder/activity_handler.py b/libraries/Botbuilder/microsoft-agents-botbuilder/microsoft/agents/botbuilder/activity_handler.py index 22eedebb..1410329c 100644 --- a/libraries/Botbuilder/microsoft-agents-botbuilder/microsoft/agents/botbuilder/activity_handler.py +++ b/libraries/Botbuilder/microsoft-agents-botbuilder/microsoft/agents/botbuilder/activity_handler.py @@ -4,6 +4,7 @@ from http import HTTPStatus from pydantic import BaseModel +from microsoft.agents.core import TurnContextProtocol from microsoft.agents.core.models import ( Activity, ActivityTypes, @@ -16,7 +17,6 @@ ) from .bot import Bot -from .turn_context import TurnContext class ActivityHandler(Bot): @@ -30,7 +30,7 @@ class ActivityHandler(Bot): """ async def on_turn( - self, turn_context: TurnContext + self, turn_context: TurnContextProtocol ): # pylint: disable=arguments-differ """ Called by the adapter (for example, :class:`BotFrameworkAdapter`) at runtime @@ -97,7 +97,7 @@ async def on_turn( await self.on_unrecognized_activity_type(turn_context) async def on_message_activity( # pylint: disable=unused-argument - self, turn_context: TurnContext + self, turn_context: TurnContextProtocol ): """ Override this method in a derived class to provide logic specific to activities, @@ -111,7 +111,7 @@ async def on_message_activity( # pylint: disable=unused-argument return async def on_message_update_activity( # pylint: disable=unused-argument - self, turn_context: TurnContext + self, turn_context: TurnContextProtocol ): """ Override this method in a derived class to provide logic specific to activities, @@ -125,7 +125,7 @@ async def on_message_update_activity( # pylint: disable=unused-argument return async def on_message_delete_activity( # pylint: disable=unused-argument - self, turn_context: TurnContext + self, turn_context: TurnContextProtocol ): """ Override this method in a derived class to provide logic specific to activities, @@ -138,7 +138,7 @@ async def on_message_delete_activity( # pylint: disable=unused-argument """ return - async def on_conversation_update_activity(self, turn_context: TurnContext): + async def on_conversation_update_activity(self, turn_context: TurnContextProtocol): """ Invoked when a conversation update activity is received from the channel when the base behavior of :meth:`on_turn()` is used. @@ -176,7 +176,7 @@ async def on_conversation_update_activity(self, turn_context: TurnContext): return async def on_members_added_activity( - self, members_added: list[ChannelAccount], turn_context: TurnContext + self, members_added: list[ChannelAccount], turn_context: TurnContextProtocol ): # pylint: disable=unused-argument """ Override this method in a derived class to provide logic for when members other than the bot join @@ -198,7 +198,7 @@ async def on_members_added_activity( return async def on_members_removed_activity( - self, members_removed: list[ChannelAccount], turn_context: TurnContext + self, members_removed: list[ChannelAccount], turn_context: TurnContextProtocol ): # pylint: disable=unused-argument """ Override this method in a derived class to provide logic for when members other than the bot leave @@ -220,7 +220,7 @@ async def on_members_removed_activity( return - async def on_message_reaction_activity(self, turn_context: TurnContext): + async def on_message_reaction_activity(self, turn_context: TurnContextProtocol): """ Invoked when an event activity is received from the connector when the base behavior of :meth:`on_turn()` is used. @@ -261,7 +261,9 @@ async def on_message_reaction_activity(self, turn_context: TurnContext): ) async def on_reactions_added( # pylint: disable=unused-argument - self, message_reactions: list[MessageReaction], turn_context: TurnContext + self, + message_reactions: list[MessageReaction], + turn_context: TurnContextProtocol, ): """ Override this method in a derived class to provide logic for when reactions to a previous activity @@ -285,7 +287,9 @@ async def on_reactions_added( # pylint: disable=unused-argument return async def on_reactions_removed( # pylint: disable=unused-argument - self, message_reactions: list[MessageReaction], turn_context: TurnContext + self, + message_reactions: list[MessageReaction], + turn_context: TurnContextProtocol, ): """ Override this method in a derived class to provide logic for when reactions to a previous activity @@ -307,7 +311,7 @@ async def on_reactions_removed( # pylint: disable=unused-argument """ return - async def on_event_activity(self, turn_context: TurnContext): + async def on_event_activity(self, turn_context: TurnContextProtocol): """ Invoked when an event activity is received from the connector when the base behavior of :meth:`on_turn()` is used. @@ -336,7 +340,7 @@ async def on_event_activity(self, turn_context: TurnContext): return await self.on_event(turn_context) async def on_token_response_event( # pylint: disable=unused-argument - self, turn_context: TurnContext + self, turn_context: TurnContextProtocol ): """ Invoked when a `tokens/response` event is received when the base behavior of @@ -356,7 +360,7 @@ async def on_token_response_event( # pylint: disable=unused-argument return async def on_event( # pylint: disable=unused-argument - self, turn_context: TurnContext + self, turn_context: TurnContextProtocol ): """ Invoked when an event other than `tokens/response` is received when the base behavior of @@ -376,7 +380,7 @@ async def on_event( # pylint: disable=unused-argument return async def on_end_of_conversation_activity( # pylint: disable=unused-argument - self, turn_context: TurnContext + self, turn_context: TurnContextProtocol ): """ Invoked when a conversation end activity is received from the channel. @@ -388,7 +392,7 @@ async def on_end_of_conversation_activity( # pylint: disable=unused-argument return async def on_typing_activity( # pylint: disable=unused-argument - self, turn_context: TurnContext + self, turn_context: TurnContextProtocol ): """ Override this in a derived class to provide logic specific to @@ -401,7 +405,7 @@ async def on_typing_activity( # pylint: disable=unused-argument return async def on_installation_update( # pylint: disable=unused-argument - self, turn_context: TurnContext + self, turn_context: TurnContextProtocol ): """ Override this in a derived class to provide logic specific to @@ -418,7 +422,7 @@ async def on_installation_update( # pylint: disable=unused-argument return async def on_installation_update_add( # pylint: disable=unused-argument - self, turn_context: TurnContext + self, turn_context: TurnContextProtocol ): """ Override this in a derived class to provide logic specific to @@ -431,7 +435,7 @@ async def on_installation_update_add( # pylint: disable=unused-argument return async def on_installation_update_remove( # pylint: disable=unused-argument - self, turn_context: TurnContext + self, turn_context: TurnContextProtocol ): """ Override this in a derived class to provide logic specific to @@ -444,7 +448,7 @@ async def on_installation_update_remove( # pylint: disable=unused-argument return async def on_unrecognized_activity_type( # pylint: disable=unused-argument - self, turn_context: TurnContext + self, turn_context: TurnContextProtocol ): """ Invoked when an activity other than a message, conversation update, or event is received when the base @@ -463,7 +467,7 @@ async def on_unrecognized_activity_type( # pylint: disable=unused-argument return async def on_invoke_activity( # pylint: disable=unused-argument - self, turn_context: TurnContext + self, turn_context: TurnContextProtocol ) -> InvokeResponse | None: """ Registers an activity event handler for the _invoke_ event, emitted for every incoming event activity. @@ -496,7 +500,7 @@ async def on_invoke_activity( # pylint: disable=unused-argument return invoke_exception.create_invoke_response() async def on_sign_in_invoke( # pylint: disable=unused-argument - self, turn_context: TurnContext + self, turn_context: TurnContextProtocol ): """ Invoked when a signin/verifyState or signin/tokenExchange event is received when the base behavior of @@ -512,7 +516,7 @@ async def on_sign_in_invoke( # pylint: disable=unused-argument raise _InvokeResponseException(HTTPStatus.NOT_IMPLEMENTED) async def on_adaptive_card_invoke( - self, turn_context: TurnContext, invoke_value: AdaptiveCardInvokeValue + self, turn_context: TurnContextProtocol, invoke_value: AdaptiveCardInvokeValue ) -> AdaptiveCardInvokeResponse: """ Invoked when the bot is sent an Adaptive Card Action Execute. diff --git a/test_samples/bot_to_bot/bot_1/app.py b/test_samples/bot_to_bot/bot_1/app.py new file mode 100644 index 00000000..601fbcef --- /dev/null +++ b/test_samples/bot_to_bot/bot_1/app.py @@ -0,0 +1,64 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +from aiohttp.web import Application, Request, Response, run_app + +from microsoft.agents.botbuilder import RestChannelServiceClientFactory +from microsoft.agents.hosting.aiohttp import CloudAdapter, jwt_authorization_middleware +from microsoft.agents.authentication import ( + Connections, + AccessTokenProviderBase, + ClaimsIdentity, +) +from microsoft.agents.authorization.msal import MsalAuth +from microsoft.agents.client import ConfigurationChannelHost, HttpBotChannelFactory + +from bot1 import Bot1 +from config import DefaultConfig + +AUTH_PROVIDER = MsalAuth(DefaultConfig()) + + +class DefaultConnection(Connections): + def get_default_connection(self) -> AccessTokenProviderBase: + pass + + def get_token_provider( + self, claims_identity: ClaimsIdentity, service_url: str + ) -> AccessTokenProviderBase: + return AUTH_PROVIDER + + def get_connection(self, connection_name: str) -> AccessTokenProviderBase: + pass + + +CONFIG = DefaultConfig() +CHANNEL_CLIENT_FACTORY = RestChannelServiceClientFactory(CONFIG, DefaultConnection()) +BOT_CHANNEL_FACTORY = HttpBotChannelFactory() + +CHANNEL_HOST = ConfigurationChannelHost() + +# Create adapter. +# See https://aka.ms/about-bot-adapter to learn more about how bots work. +ADAPTER = CloudAdapter(CHANNEL_CLIENT_FACTORY) + +# Create the Bot +BOT = Bot1(adapter=ADAPTER) + + +# Listen for incoming requests on /api/messages +async def messages(req: Request) -> Response: + adapter: CloudAdapter = req.app["adapter"] + return await adapter.process(req, BOT) + + +APP = Application(middlewares=[jwt_authorization_middleware]) +APP.router.add_post("/api/messages", messages) +APP["bot_configuration"] = CONFIG +APP["adapter"] = ADAPTER + +if __name__ == "__main__": + try: + run_app(APP, host="localhost", port=CONFIG.PORT) + except Exception as error: + raise error diff --git a/test_samples/bot_to_bot/bot_1/bot1.py b/test_samples/bot_to_bot/bot_1/bot1.py index 13d378c2..84edbee6 100644 --- a/test_samples/bot_to_bot/bot_1/bot1.py +++ b/test_samples/bot_to_bot/bot_1/bot1.py @@ -1,3 +1,4 @@ +from typing import Optional from uuid import uuid4 from aiohttp.web import HTTPException @@ -9,6 +10,12 @@ CallerIdConstants, ChannelAccount, ResourceResponse, + AttachmentData, + PagedMembersResult, + Transcript, + ConversationParameters, + ConversationResourceResponse, + ConversationsResult, ) from microsoft.agents.authentication import ClaimsIdentity from microsoft.agents.client import ( @@ -97,6 +104,100 @@ async def on_members_added_activity( 'Hello and welcome! Say "agent" and I\'ll patch you through' ) + """ + ChannelApiHandler protocol + """ + + async def on_get_conversations( + self, + claims_identity: ClaimsIdentity, + conversation_id: str, + continuation_token: Optional[str] = None, + ) -> ConversationsResult: + pass + + async def on_create_conversation( + self, claims_identity: ClaimsIdentity, parameters: ConversationParameters + ) -> ConversationResourceResponse: + pass + + async def on_send_to_conversation( + self, claims_identity: ClaimsIdentity, conversation_id: str, activity: Activity + ) -> ResourceResponse: + return await self._process_activity( + claims_identity, conversation_id, None, activity + ) + + async def on_send_conversation_history( + self, + claims_identity: ClaimsIdentity, + conversation_id: str, + transcript: Transcript, + ) -> ResourceResponse: + pass + + async def on_update_activity( + self, + claims_identity: ClaimsIdentity, + conversation_id: str, + activity_id: str, + activity: Activity, + ) -> ResourceResponse: + pass + + async def on_reply_to_activity( + self, + claims_identity: ClaimsIdentity, + conversation_id: str, + activity_id: str, + activity: Activity, + ) -> ResourceResponse: + return await self._process_activity( + claims_identity, conversation_id, activity_id, activity + ) + + async def on_delete_activity( + self, claims_identity: ClaimsIdentity, conversation_id: str, activity_id: str + ): + pass + + async def on_get_conversation_members( + self, claims_identity: ClaimsIdentity, conversation_id: str + ) -> list[ChannelAccount]: + pass + + async def on_get_conversation_member( + self, claims_identity: ClaimsIdentity, user_id: str, conversation_id: str + ) -> ChannelAccount: + pass + + async def on_get_conversation_paged_members( + self, + claims_identity: ClaimsIdentity, + conversation_id: str, + page_size: Optional[int] = None, + continuation_token: Optional[str] = None, + ) -> PagedMembersResult: + pass + + async def on_delete_conversation_member( + self, claims_identity: ClaimsIdentity, conversation_id: str, member_id: str + ): + pass + + async def on_get_activity_members( + self, claims_identity: ClaimsIdentity, conversation_id: str, activity_id: str + ) -> list[ChannelAccount]: + pass + + async def on_upload_attachment( + self, + claims_identity: ClaimsIdentity, + conversation_id: str, + attachment_upload: AttachmentData, + ) -> ResourceResponse: + pass + async def _send_to_bot( self, turn_context: TurnContextProtocol, target_channel: ChannelInfoProtocol ): @@ -129,7 +230,7 @@ async def _send_to_bot( if response.status < 200 or response.status >= 300: raise HTTPException( - f'Error invoking the id: "{target_channel.id}" at "{target_channel.endpoint}" (status is {response.status}). \r\n {response.body}' + text=f'Error invoking the id: "{target_channel.id}" at "{target_channel.endpoint}" (status is {response.status}). \r\n {response.body}' ) @staticmethod @@ -154,7 +255,7 @@ async def _process_activity( self, claims_identity: ClaimsIdentity, conversation_id: str, - reply_to_activity_id: str, + reply_to_activity_id: Optional[str], activity: Activity, ): bot_conversation_reference = ( diff --git a/test_samples/bot_to_bot/bot_1/config.py b/test_samples/bot_to_bot/bot_1/config.py new file mode 100644 index 00000000..1fe1ea0c --- /dev/null +++ b/test_samples/bot_to_bot/bot_1/config.py @@ -0,0 +1,11 @@ +from microsoft.agents.authorization.msal import AuthTypes, MsalAuthConfiguration + + +class DefaultConfig(MsalAuthConfiguration): + """Bot Configuration""" + + AUTH_TYPE = AuthTypes.client_secret + TENANT_ID = "" + CLIENT_ID = "" + CLIENT_SECRET = "" + PORT = 3978 From c4aa97ff74e3a0a419d0e394b75030bd50e1b2f7 Mon Sep 17 00:00:00 2001 From: Axel Suarez Date: Thu, 20 Feb 2025 16:42:17 -0800 Subject: [PATCH 14/21] Bot1 multiple connection WIP --- test_samples/bot_to_bot/bot_1/app.py | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/test_samples/bot_to_bot/bot_1/app.py b/test_samples/bot_to_bot/bot_1/app.py index 601fbcef..33366a1c 100644 --- a/test_samples/bot_to_bot/bot_1/app.py +++ b/test_samples/bot_to_bot/bot_1/app.py @@ -4,14 +4,15 @@ from aiohttp.web import Application, Request, Response, run_app from microsoft.agents.botbuilder import RestChannelServiceClientFactory -from microsoft.agents.hosting.aiohttp import CloudAdapter, jwt_authorization_middleware +from microsoft.agents.hosting.aiohttp import CloudAdapter, jwt_authorization_middleware, channel_service_route_table from microsoft.agents.authentication import ( Connections, AccessTokenProviderBase, ClaimsIdentity, ) from microsoft.agents.authorization.msal import MsalAuth -from microsoft.agents.client import ConfigurationChannelHost, HttpBotChannelFactory +from microsoft.agents.client import ConfigurationChannelHost, ConversationIdFactory, HttpBotChannelFactory +from microsoft.agents.storage import MemoryStorage from bot1 import Bot1 from config import DefaultConfig @@ -31,19 +32,21 @@ def get_token_provider( def get_connection(self, connection_name: str) -> AccessTokenProviderBase: pass - +DEFAULT_CONNECTION = DefaultConnection() CONFIG = DefaultConfig() -CHANNEL_CLIENT_FACTORY = RestChannelServiceClientFactory(CONFIG, DefaultConnection()) -BOT_CHANNEL_FACTORY = HttpBotChannelFactory() +CHANNEL_CLIENT_FACTORY = RestChannelServiceClientFactory(CONFIG, DEFAULT_CONNECTION) -CHANNEL_HOST = ConfigurationChannelHost() +BOT_CHANNEL_FACTORY = HttpBotChannelFactory() +CHANNEL_HOST = ConfigurationChannelHost(BOT_CHANNEL_FACTORY, DEFAULT_CONNECTION, CONFIG, "HttpBotClient") +STORAGE = MemoryStorage() +CONVERSATION_ID_FACTORY = ConversationIdFactory(STORAGE) # Create adapter. # See https://aka.ms/about-bot-adapter to learn more about how bots work. ADAPTER = CloudAdapter(CHANNEL_CLIENT_FACTORY) # Create the Bot -BOT = Bot1(adapter=ADAPTER) +BOT = Bot1(adapter=ADAPTER, channel_host=CHANNEL_HOST, ) # Listen for incoming requests on /api/messages @@ -54,6 +57,7 @@ async def messages(req: Request) -> Response: APP = Application(middlewares=[jwt_authorization_middleware]) APP.router.add_post("/api/messages", messages) +APP.router.add_routes(channel_service_route_table(BOT, "/api/botresponse")) APP["bot_configuration"] = CONFIG APP["adapter"] = ADAPTER From 6de0c45cd7a4c9eae1fb5b9e3bae275d8e1dd703 Mon Sep 17 00:00:00 2001 From: Axel Suarez Date: Mon, 24 Feb 2025 13:49:15 -0800 Subject: [PATCH 15/21] Bot2Bot integration WIP --- .../microsoft/agents/client/__init__.py | 4 +- .../agents/client/channel_host_protocol.py | 16 ++--- .../agents/client/channels_configuration.py | 10 ++-- .../agents/core/turn_context_protocol.py | 5 +- test_samples/bot_to_bot/bot_1/app.py | 2 +- test_samples/bot_to_bot/bot_1/config.py | 21 ++++++- test_samples/bot_to_bot/bot_2/app.py | 60 +++++++++++++++++++ test_samples/bot_to_bot/bot_2/bot2.py | 24 ++++++++ test_samples/bot_to_bot/bot_2/config.py | 11 ++++ 9 files changed, 131 insertions(+), 22 deletions(-) create mode 100644 test_samples/bot_to_bot/bot_2/app.py create mode 100644 test_samples/bot_to_bot/bot_2/bot2.py create mode 100644 test_samples/bot_to_bot/bot_2/config.py diff --git a/libraries/Client/microsoft-agents-client/microsoft/agents/client/__init__.py b/libraries/Client/microsoft-agents-client/microsoft/agents/client/__init__.py index c0a9953b..1e57c79f 100644 --- a/libraries/Client/microsoft-agents-client/microsoft/agents/client/__init__.py +++ b/libraries/Client/microsoft-agents-client/microsoft/agents/client/__init__.py @@ -3,7 +3,7 @@ from .channel_host_protocol import ChannelHostProtocol from .channel_info_protocol import ChannelInfoProtocol from .channel_protocol import ChannelProtocol -from .channels_configuration import ChannelsConfiguration +from .channels_configuration import ChannelsConfiguration, ChannelHostConfiguration, ChannelInfo from .configuration_channel_host import ConfigurationChannelHost from .conversation_constants import ConversationConstants from .conversation_id_factory_options import ConversationIdFactoryOptions @@ -19,6 +19,8 @@ "ChannelInfoProtocol", "ChannelProtocol", "ChannelsConfiguration", + "ChannelHostConfiguration", + "ChannelInfo", "ConfigurationChannelHost", "ConversationConstants", "ConversationIdFactoryOptions", diff --git a/libraries/Client/microsoft-agents-client/microsoft/agents/client/channel_host_protocol.py b/libraries/Client/microsoft-agents-client/microsoft/agents/client/channel_host_protocol.py index b958f340..0327d420 100644 --- a/libraries/Client/microsoft-agents-client/microsoft/agents/client/channel_host_protocol.py +++ b/libraries/Client/microsoft-agents-client/microsoft/agents/client/channel_host_protocol.py @@ -3,19 +3,11 @@ from .channel_protocol import ChannelProtocol from .channel_info_protocol import ChannelInfoProtocol - class ChannelHostProtocol(Protocol): - @property - def host_endpoint(self) -> str: - raise NotImplementedError() - - @property - def host_app_id(self) -> str: - raise NotImplementedError() - - @property - def channels(self) -> dict[str, ChannelInfoProtocol]: - raise NotImplementedError() + def __init__(self, host_endpoint: str, host_app_id: str, channels: dict[str, ChannelInfoProtocol]): + self.host_endpoint = host_endpoint + self.host_app_id = host_app_id + self.channels = channels def get_channel(self, channel_info: ChannelInfoProtocol) -> ChannelProtocol: raise NotImplementedError() diff --git a/libraries/Client/microsoft-agents-client/microsoft/agents/client/channels_configuration.py b/libraries/Client/microsoft-agents-client/microsoft/agents/client/channels_configuration.py index ec6342f70..10eb97b2 100644 --- a/libraries/Client/microsoft-agents-client/microsoft/agents/client/channels_configuration.py +++ b/libraries/Client/microsoft-agents-client/microsoft/agents/client/channels_configuration.py @@ -23,11 +23,11 @@ def __init__( self.endpoint = endpoint -class ChannelHostConfiguration(Protocol): - - CHANNELS: list[ChannelInfoProtocol] - HOST_ENDPOINT: str - HOST_APP_ID: str +class ChannelHostConfiguration: + def __init__(self, CHANNELS: list[ChannelInfoProtocol], HOST_ENDPOINT: str, HOST_APP_ID: str): + self.CHANNELS = CHANNELS + self.HOST_ENDPOINT = HOST_ENDPOINT + self.HOST_APP_ID = HOST_APP_ID class ChannelsConfiguration(Protocol): diff --git a/libraries/Core/microsoft-agents-core/microsoft/agents/core/turn_context_protocol.py b/libraries/Core/microsoft-agents-core/microsoft/agents/core/turn_context_protocol.py index fb71bc79..d614e91d 100644 --- a/libraries/Core/microsoft-agents-core/microsoft/agents/core/turn_context_protocol.py +++ b/libraries/Core/microsoft-agents-core/microsoft/agents/core/turn_context_protocol.py @@ -9,13 +9,14 @@ ConversationReference, ) -from .channel_adapter_protocol import ChannelAdapterProtocol +# TODO: refactor circular dependency +# from .channel_adapter_protocol import ChannelAdapterProtocol T = TypeVar("T", bound=Activity) class TurnContextProtocol(Protocol, Generic[T]): - adapter: ChannelAdapterProtocol + adapter: "ChannelAdapterProtocol" activity: Activity | T responded: bool turn_state: dict diff --git a/test_samples/bot_to_bot/bot_1/app.py b/test_samples/bot_to_bot/bot_1/app.py index 33366a1c..08f813d4 100644 --- a/test_samples/bot_to_bot/bot_1/app.py +++ b/test_samples/bot_to_bot/bot_1/app.py @@ -46,7 +46,7 @@ def get_connection(self, connection_name: str) -> AccessTokenProviderBase: ADAPTER = CloudAdapter(CHANNEL_CLIENT_FACTORY) # Create the Bot -BOT = Bot1(adapter=ADAPTER, channel_host=CHANNEL_HOST, ) +BOT = Bot1(adapter=ADAPTER, channel_host=CHANNEL_HOST, conversation_id_factory=CONVERSATION_ID_FACTORY) # Listen for incoming requests on /api/messages diff --git a/test_samples/bot_to_bot/bot_1/config.py b/test_samples/bot_to_bot/bot_1/config.py index 1fe1ea0c..5ce674a6 100644 --- a/test_samples/bot_to_bot/bot_1/config.py +++ b/test_samples/bot_to_bot/bot_1/config.py @@ -1,7 +1,8 @@ from microsoft.agents.authorization.msal import AuthTypes, MsalAuthConfiguration +from microsoft.agents.client import ChannelHostConfiguration, ChannelsConfiguration, ChannelInfo -class DefaultConfig(MsalAuthConfiguration): +class DefaultConfig(MsalAuthConfiguration, ChannelsConfiguration): """Bot Configuration""" AUTH_TYPE = AuthTypes.client_secret @@ -9,3 +10,21 @@ class DefaultConfig(MsalAuthConfiguration): CLIENT_ID = "" CLIENT_SECRET = "" PORT = 3978 + + # ChannelHost configuration + @staticmethod + def CHANNEL_HOST_CONFIGURATION(): + return ChannelHostConfiguration( + CHANNELS=[ + ChannelInfo( + id="EchoSkillBot", + app_id="", + resource_url="http://localhost:3978/api/messages", + token_provider="ChannelConnection", + channel_factory="HttpBotClient", + endpoint="http://localhost:3978/api/botresponse/", + ) + ], + HOST_ENDPOINT="http://localhost:3978/api/botresponse/", + HOST_APP_ID="", + ) \ No newline at end of file diff --git a/test_samples/bot_to_bot/bot_2/app.py b/test_samples/bot_to_bot/bot_2/app.py new file mode 100644 index 00000000..041e7dba --- /dev/null +++ b/test_samples/bot_to_bot/bot_2/app.py @@ -0,0 +1,60 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +from aiohttp.web import Application, Request, Response, run_app + +from microsoft.agents.botbuilder import RestChannelServiceClientFactory +from microsoft.agents.hosting.aiohttp import CloudAdapter, jwt_authorization_middleware +from microsoft.agents.authentication import ( + Connections, + AccessTokenProviderBase, + ClaimsIdentity, +) +from microsoft.agents.authorization.msal import MsalAuth + +from bot2 import Bot2 +from config import DefaultConfig + +AUTH_PROVIDER = MsalAuth(DefaultConfig()) + + +class DefaultConnection(Connections): + def get_default_connection(self) -> AccessTokenProviderBase: + pass + + def get_token_provider( + self, claims_identity: ClaimsIdentity, service_url: str + ) -> AccessTokenProviderBase: + return AUTH_PROVIDER + + def get_connection(self, connection_name: str) -> AccessTokenProviderBase: + pass + + +CONFIG = DefaultConfig() +CHANNEL_CLIENT_FACTORY = RestChannelServiceClientFactory(CONFIG, DefaultConnection()) + +# Create adapter. +# See https://aka.ms/about-bot-adapter to learn more about how bots work. +ADAPTER = CloudAdapter(CHANNEL_CLIENT_FACTORY) + +# Create the Bot +BOT = Bot2() + + +# Listen for incoming requests on /api/messages +async def messages(req: Request) -> Response: + adapter: CloudAdapter = req.app["adapter"] + return await adapter.process(req, BOT) + + +APP = Application(middlewares=[jwt_authorization_middleware]) +APP.router.add_post("/api/messages", messages) +APP["bot_configuration"] = CONFIG +APP["adapter"] = ADAPTER + +if __name__ == "__main__": + try: + run_app(APP, host="localhost", port=CONFIG.PORT) + except Exception as error: + raise error diff --git a/test_samples/bot_to_bot/bot_2/bot2.py b/test_samples/bot_to_bot/bot_2/bot2.py new file mode 100644 index 00000000..1bffd950 --- /dev/null +++ b/test_samples/bot_to_bot/bot_2/bot2.py @@ -0,0 +1,24 @@ +from microsoft.agents.botbuilder import ActivityHandler, MessageFactory, TurnContext +from microsoft.agents.core.models import ChannelAccount, Activity, EndOfConversationCodes + + +class Bot2(ActivityHandler): + async def on_members_added_activity( + self, members_added: list[ChannelAccount], turn_context: TurnContext + ): + for member in members_added: + if member.id != turn_context.activity.recipient.id: + await turn_context.send_activity("Hi, This is Bot2") + + async def on_message_activity(self, turn_context: TurnContext): + if any(stop_message in turn_context.activity.text for stop_message in ["stop", "end"]): + await turn_context.send_activity("(Bot2) Ending conversation from Bot2") + end_of_conversation = Activity.create_end_of_conversation_activity() + end_of_conversation.code = EndOfConversationCodes.completed_successfully + await turn_context.send_activity(end_of_conversation) + else: + await turn_context.send_activity(f"Echo(Bot2): {turn_context.activity.text}") + await turn_context.send_activity("Echo(Bot2): Say \"end\" or \"stop\" and I'll end the conversation and return to the parent.") + + async def on_end_of_conversation_activity(self, turn_context): + return diff --git a/test_samples/bot_to_bot/bot_2/config.py b/test_samples/bot_to_bot/bot_2/config.py new file mode 100644 index 00000000..e3ab5f8d --- /dev/null +++ b/test_samples/bot_to_bot/bot_2/config.py @@ -0,0 +1,11 @@ +from microsoft.agents.authorization.msal import AuthTypes, MsalAuthConfiguration + + +class DefaultConfig(MsalAuthConfiguration): + """Bot Configuration""" + + AUTH_TYPE = AuthTypes.client_secret + TENANT_ID = "" + CLIENT_ID = "" + CLIENT_SECRET = "" + PORT = 3999 From 0666f7e5b58ee456bcc38dee1005537173c106ce Mon Sep 17 00:00:00 2001 From: Axel Suarez Date: Mon, 24 Feb 2025 13:52:41 -0800 Subject: [PATCH 16/21] Formatting --- .../microsoft/agents/client/__init__.py | 6 +++- .../agents/client/channel_host_protocol.py | 8 ++++- .../agents/client/channels_configuration.py | 4 ++- test_samples/bot_to_bot/bot_1/app.py | 23 ++++++++++--- test_samples/bot_to_bot/bot_1/config.py | 32 +++++++++++-------- test_samples/bot_to_bot/bot_2/bot2.py | 21 +++++++++--- 6 files changed, 68 insertions(+), 26 deletions(-) diff --git a/libraries/Client/microsoft-agents-client/microsoft/agents/client/__init__.py b/libraries/Client/microsoft-agents-client/microsoft/agents/client/__init__.py index 1e57c79f..1740902b 100644 --- a/libraries/Client/microsoft-agents-client/microsoft/agents/client/__init__.py +++ b/libraries/Client/microsoft-agents-client/microsoft/agents/client/__init__.py @@ -3,7 +3,11 @@ from .channel_host_protocol import ChannelHostProtocol from .channel_info_protocol import ChannelInfoProtocol from .channel_protocol import ChannelProtocol -from .channels_configuration import ChannelsConfiguration, ChannelHostConfiguration, ChannelInfo +from .channels_configuration import ( + ChannelsConfiguration, + ChannelHostConfiguration, + ChannelInfo, +) from .configuration_channel_host import ConfigurationChannelHost from .conversation_constants import ConversationConstants from .conversation_id_factory_options import ConversationIdFactoryOptions diff --git a/libraries/Client/microsoft-agents-client/microsoft/agents/client/channel_host_protocol.py b/libraries/Client/microsoft-agents-client/microsoft/agents/client/channel_host_protocol.py index 0327d420..2f9d94d2 100644 --- a/libraries/Client/microsoft-agents-client/microsoft/agents/client/channel_host_protocol.py +++ b/libraries/Client/microsoft-agents-client/microsoft/agents/client/channel_host_protocol.py @@ -3,8 +3,14 @@ from .channel_protocol import ChannelProtocol from .channel_info_protocol import ChannelInfoProtocol + class ChannelHostProtocol(Protocol): - def __init__(self, host_endpoint: str, host_app_id: str, channels: dict[str, ChannelInfoProtocol]): + def __init__( + self, + host_endpoint: str, + host_app_id: str, + channels: dict[str, ChannelInfoProtocol], + ): self.host_endpoint = host_endpoint self.host_app_id = host_app_id self.channels = channels diff --git a/libraries/Client/microsoft-agents-client/microsoft/agents/client/channels_configuration.py b/libraries/Client/microsoft-agents-client/microsoft/agents/client/channels_configuration.py index 10eb97b2..e6153852 100644 --- a/libraries/Client/microsoft-agents-client/microsoft/agents/client/channels_configuration.py +++ b/libraries/Client/microsoft-agents-client/microsoft/agents/client/channels_configuration.py @@ -24,7 +24,9 @@ def __init__( class ChannelHostConfiguration: - def __init__(self, CHANNELS: list[ChannelInfoProtocol], HOST_ENDPOINT: str, HOST_APP_ID: str): + def __init__( + self, CHANNELS: list[ChannelInfoProtocol], HOST_ENDPOINT: str, HOST_APP_ID: str + ): self.CHANNELS = CHANNELS self.HOST_ENDPOINT = HOST_ENDPOINT self.HOST_APP_ID = HOST_APP_ID diff --git a/test_samples/bot_to_bot/bot_1/app.py b/test_samples/bot_to_bot/bot_1/app.py index 08f813d4..00da445d 100644 --- a/test_samples/bot_to_bot/bot_1/app.py +++ b/test_samples/bot_to_bot/bot_1/app.py @@ -4,14 +4,22 @@ from aiohttp.web import Application, Request, Response, run_app from microsoft.agents.botbuilder import RestChannelServiceClientFactory -from microsoft.agents.hosting.aiohttp import CloudAdapter, jwt_authorization_middleware, channel_service_route_table +from microsoft.agents.hosting.aiohttp import ( + CloudAdapter, + jwt_authorization_middleware, + channel_service_route_table, +) from microsoft.agents.authentication import ( Connections, AccessTokenProviderBase, ClaimsIdentity, ) from microsoft.agents.authorization.msal import MsalAuth -from microsoft.agents.client import ConfigurationChannelHost, ConversationIdFactory, HttpBotChannelFactory +from microsoft.agents.client import ( + ConfigurationChannelHost, + ConversationIdFactory, + HttpBotChannelFactory, +) from microsoft.agents.storage import MemoryStorage from bot1 import Bot1 @@ -32,12 +40,15 @@ def get_token_provider( def get_connection(self, connection_name: str) -> AccessTokenProviderBase: pass + DEFAULT_CONNECTION = DefaultConnection() CONFIG = DefaultConfig() CHANNEL_CLIENT_FACTORY = RestChannelServiceClientFactory(CONFIG, DEFAULT_CONNECTION) BOT_CHANNEL_FACTORY = HttpBotChannelFactory() -CHANNEL_HOST = ConfigurationChannelHost(BOT_CHANNEL_FACTORY, DEFAULT_CONNECTION, CONFIG, "HttpBotClient") +CHANNEL_HOST = ConfigurationChannelHost( + BOT_CHANNEL_FACTORY, DEFAULT_CONNECTION, CONFIG, "HttpBotClient" +) STORAGE = MemoryStorage() CONVERSATION_ID_FACTORY = ConversationIdFactory(STORAGE) @@ -46,7 +57,11 @@ def get_connection(self, connection_name: str) -> AccessTokenProviderBase: ADAPTER = CloudAdapter(CHANNEL_CLIENT_FACTORY) # Create the Bot -BOT = Bot1(adapter=ADAPTER, channel_host=CHANNEL_HOST, conversation_id_factory=CONVERSATION_ID_FACTORY) +BOT = Bot1( + adapter=ADAPTER, + channel_host=CHANNEL_HOST, + conversation_id_factory=CONVERSATION_ID_FACTORY, +) # Listen for incoming requests on /api/messages diff --git a/test_samples/bot_to_bot/bot_1/config.py b/test_samples/bot_to_bot/bot_1/config.py index 5ce674a6..14ce00fa 100644 --- a/test_samples/bot_to_bot/bot_1/config.py +++ b/test_samples/bot_to_bot/bot_1/config.py @@ -1,5 +1,9 @@ from microsoft.agents.authorization.msal import AuthTypes, MsalAuthConfiguration -from microsoft.agents.client import ChannelHostConfiguration, ChannelsConfiguration, ChannelInfo +from microsoft.agents.client import ( + ChannelHostConfiguration, + ChannelsConfiguration, + ChannelInfo, +) class DefaultConfig(MsalAuthConfiguration, ChannelsConfiguration): @@ -15,16 +19,16 @@ class DefaultConfig(MsalAuthConfiguration, ChannelsConfiguration): @staticmethod def CHANNEL_HOST_CONFIGURATION(): return ChannelHostConfiguration( - CHANNELS=[ - ChannelInfo( - id="EchoSkillBot", - app_id="", - resource_url="http://localhost:3978/api/messages", - token_provider="ChannelConnection", - channel_factory="HttpBotClient", - endpoint="http://localhost:3978/api/botresponse/", - ) - ], - HOST_ENDPOINT="http://localhost:3978/api/botresponse/", - HOST_APP_ID="", - ) \ No newline at end of file + CHANNELS=[ + ChannelInfo( + id="EchoSkillBot", + app_id="", + resource_url="http://localhost:3978/api/messages", + token_provider="ChannelConnection", + channel_factory="HttpBotClient", + endpoint="http://localhost:3978/api/botresponse/", + ) + ], + HOST_ENDPOINT="http://localhost:3978/api/botresponse/", + HOST_APP_ID="", + ) diff --git a/test_samples/bot_to_bot/bot_2/bot2.py b/test_samples/bot_to_bot/bot_2/bot2.py index 1bffd950..43530c77 100644 --- a/test_samples/bot_to_bot/bot_2/bot2.py +++ b/test_samples/bot_to_bot/bot_2/bot2.py @@ -1,5 +1,9 @@ from microsoft.agents.botbuilder import ActivityHandler, MessageFactory, TurnContext -from microsoft.agents.core.models import ChannelAccount, Activity, EndOfConversationCodes +from microsoft.agents.core.models import ( + ChannelAccount, + Activity, + EndOfConversationCodes, +) class Bot2(ActivityHandler): @@ -11,14 +15,21 @@ async def on_members_added_activity( await turn_context.send_activity("Hi, This is Bot2") async def on_message_activity(self, turn_context: TurnContext): - if any(stop_message in turn_context.activity.text for stop_message in ["stop", "end"]): + if any( + stop_message in turn_context.activity.text + for stop_message in ["stop", "end"] + ): await turn_context.send_activity("(Bot2) Ending conversation from Bot2") end_of_conversation = Activity.create_end_of_conversation_activity() end_of_conversation.code = EndOfConversationCodes.completed_successfully await turn_context.send_activity(end_of_conversation) else: - await turn_context.send_activity(f"Echo(Bot2): {turn_context.activity.text}") - await turn_context.send_activity("Echo(Bot2): Say \"end\" or \"stop\" and I'll end the conversation and return to the parent.") - + await turn_context.send_activity( + f"Echo(Bot2): {turn_context.activity.text}" + ) + await turn_context.send_activity( + 'Echo(Bot2): Say "end" or "stop" and I\'ll end the conversation and return to the parent.' + ) + async def on_end_of_conversation_activity(self, turn_context): return From 6839cf1887314706b4560cd7c95aec6afb8222e2 Mon Sep 17 00:00:00 2001 From: Axel Suarez Date: Mon, 24 Feb 2025 19:16:41 -0800 Subject: [PATCH 17/21] B2B serialization error on the way back. Communication successful. --- .gitignore | 3 +++ .../agents/client/bot_conversation_reference.py | 9 ++++----- .../agents/client/channel_host_protocol.py | 4 ++-- .../agents/client/configuration_channel_host.py | 6 +++--- .../agents/client/conversation_id_factory.py | 3 +-- .../microsoft/agents/client/http_bot_channel.py | 7 ++++--- .../agents/core/models/conversation_reference.py | 2 +- .../agents/hosting/aiohttp/cloud_adapter.py | 3 ++- .../microsoft/agents/storage/memory_storage.py | 14 -------------- .../microsoft/agents/storage/store_item.py | 2 -- test_samples/bot_to_bot/bot_1/app.py | 3 ++- test_samples/bot_to_bot/bot_1/bot1.py | 7 ++++--- test_samples/bot_to_bot/bot_1/config.py | 6 +----- 13 files changed, 27 insertions(+), 42 deletions(-) diff --git a/.gitignore b/.gitignore index e80eb444..0723fd02 100644 --- a/.gitignore +++ b/.gitignore @@ -122,3 +122,6 @@ cython_debug/ # JetBrains Rider *.sln.iml .idea/ + +# vscode +.vscode/ diff --git a/libraries/Client/microsoft-agents-client/microsoft/agents/client/bot_conversation_reference.py b/libraries/Client/microsoft-agents-client/microsoft/agents/client/bot_conversation_reference.py index 1e612189..d4fe6bef 100644 --- a/libraries/Client/microsoft-agents-client/microsoft/agents/client/bot_conversation_reference.py +++ b/libraries/Client/microsoft-agents-client/microsoft/agents/client/bot_conversation_reference.py @@ -1,7 +1,6 @@ -from microsoft.agents.core.models import ConversationReference +from microsoft.agents.core.models import AgentsModel, ConversationReference -class BotConversationReference: - def __init__(self, conversation_reference: ConversationReference, oauth_scope: str): - self.conversation_reference = conversation_reference - self.oauth_scope = oauth_scope +class BotConversationReference(AgentsModel): + conversation_reference: ConversationReference + oauth_scope: str diff --git a/libraries/Client/microsoft-agents-client/microsoft/agents/client/channel_host_protocol.py b/libraries/Client/microsoft-agents-client/microsoft/agents/client/channel_host_protocol.py index 2f9d94d2..efbdf37e 100644 --- a/libraries/Client/microsoft-agents-client/microsoft/agents/client/channel_host_protocol.py +++ b/libraries/Client/microsoft-agents-client/microsoft/agents/client/channel_host_protocol.py @@ -15,8 +15,8 @@ def __init__( self.host_app_id = host_app_id self.channels = channels - def get_channel(self, channel_info: ChannelInfoProtocol) -> ChannelProtocol: + def get_channel_from_channel_info(self, channel_info: ChannelInfoProtocol) -> ChannelProtocol: raise NotImplementedError() - def get_channel(self, name: str) -> ChannelProtocol: + def get_channel_from_name(self, name: str) -> ChannelProtocol: raise NotImplementedError() diff --git a/libraries/Client/microsoft-agents-client/microsoft/agents/client/configuration_channel_host.py b/libraries/Client/microsoft-agents-client/microsoft/agents/client/configuration_channel_host.py index 8c4a0db1..8a6d6408 100644 --- a/libraries/Client/microsoft-agents-client/microsoft/agents/client/configuration_channel_host.py +++ b/libraries/Client/microsoft-agents-client/microsoft/agents/client/configuration_channel_host.py @@ -21,7 +21,7 @@ def __init__( self.connections = connections self.configuration = configuration self.channels: dict[str, ChannelInfoProtocol] = {} - self.endpoint: str = None + self.host_endpoint: str = None self.host_app_id: str = None channel_host_configuration = configuration.CHANNEL_HOST_CONFIGURATION() @@ -34,7 +34,7 @@ def __init__( bot.channel_factory = default_channel_name self.channels[bot.id] = bot - self.endpoint = channel_host_configuration.HOST_ENDPOINT + self.host_endpoint = channel_host_configuration.HOST_ENDPOINT self.host_app_id = channel_host_configuration.HOST_APP_ID def get_channel_from_name(self, name: str) -> ChannelProtocol: @@ -50,7 +50,7 @@ def get_channel_from_channel_info( f"ConfigurationChannelHost.get_channel_from_channel_info(): channel_info cannot be None" ) - token_provider = self.connections.get_token_provider( + token_provider = self.connections.get_connection( channel_info.token_provider ) if not token_provider: diff --git a/libraries/Client/microsoft-agents-client/microsoft/agents/client/conversation_id_factory.py b/libraries/Client/microsoft-agents-client/microsoft/agents/client/conversation_id_factory.py index 29513398..c7b905c2 100644 --- a/libraries/Client/microsoft-agents-client/microsoft/agents/client/conversation_id_factory.py +++ b/libraries/Client/microsoft-agents-client/microsoft/agents/client/conversation_id_factory.py @@ -10,8 +10,7 @@ def _implement_store_item_for_agents_model_cls(model_instance: AgentsModel): - if not hasattr(model_instance, "e_tag"): - setattr(model_instance, "e_tag", 0) + instance_cls = type(model_instance) if not isinstance(model_instance, StoreItem): instance_cls = type(model_instance) setattr( diff --git a/libraries/Client/microsoft-agents-client/microsoft/agents/client/http_bot_channel.py b/libraries/Client/microsoft-agents-client/microsoft/agents/client/http_bot_channel.py index d6e9dcb8..b6311cff 100644 --- a/libraries/Client/microsoft-agents-client/microsoft/agents/client/http_bot_channel.py +++ b/libraries/Client/microsoft-agents-client/microsoft/agents/client/http_bot_channel.py @@ -59,7 +59,7 @@ async def post_activity( activity_copy.recipient.role = RoleTypes.skill token_result = await self._token_access.get_access_token( - to_bot_resource, f"{to_bot_id}/.default" + to_bot_resource, [f"{to_bot_id}/.default"] ) headers = { "Authorization": f"Bearer {token_result}", @@ -70,7 +70,7 @@ async def post_activity( async with session.post( endpoint, headers=headers, - json=activity_copy.model_dump(by_alias=True, exclude_unset=True), + json=activity_copy.model_dump(mode="json", by_alias=True, exclude_unset=True), ) as response: if response.ok: @@ -82,6 +82,7 @@ async def post_activity( else: # TODO: Log error - content = await response.text() + # TODO: Fix generic AgentsModel serialization + content = await response.json() return InvokeResponse(status=response.status, body=content) diff --git a/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/conversation_reference.py b/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/conversation_reference.py index 0d03ab21..995196ae 100644 --- a/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/conversation_reference.py +++ b/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/conversation_reference.py @@ -36,7 +36,7 @@ class ConversationReference(AgentsModel): # optionals here are due to webchat activity_id: Optional[NonEmptyString] = None user: ChannelAccount = None - bot: ChannelAccount + bot: ChannelAccount = None conversation: ConversationAccount channel_id: NonEmptyString locale: Optional[NonEmptyString] = None diff --git a/libraries/Hosting/microsoft-agents-hosting-aiohttp/microsoft/agents/hosting/aiohttp/cloud_adapter.py b/libraries/Hosting/microsoft-agents-hosting-aiohttp/microsoft/agents/hosting/aiohttp/cloud_adapter.py index 30e2ee0c..436b0cfc 100644 --- a/libraries/Hosting/microsoft-agents-hosting-aiohttp/microsoft/agents/hosting/aiohttp/cloud_adapter.py +++ b/libraries/Hosting/microsoft-agents-hosting-aiohttp/microsoft/agents/hosting/aiohttp/cloud_adapter.py @@ -1,6 +1,6 @@ # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. - +from traceback import format_exc from typing import Optional from aiohttp.web import ( @@ -42,6 +42,7 @@ def __init__( async def on_turn_error(context: TurnContext, error: Exception): error_message = f"Exception caught : {error}" + print(format_exc()) await context.send_activity(MessageFactory.text(error_message)) diff --git a/libraries/Storage/microsoft-agents-storage/microsoft/agents/storage/memory_storage.py b/libraries/Storage/microsoft-agents-storage/microsoft/agents/storage/memory_storage.py index 64ed55d0..a3876e28 100644 --- a/libraries/Storage/microsoft-agents-storage/microsoft/agents/storage/memory_storage.py +++ b/libraries/Storage/microsoft-agents-storage/microsoft/agents/storage/memory_storage.py @@ -13,7 +13,6 @@ class MemoryStorage(Storage): def __init__(self, state: dict[str, JSON] = None): self._memory: dict[str, JSON] = state or {} self._lock = Lock() - self._e_tag = 0 async def read( self, keys: list[str], *, target_cls: StoreItemT = None, **kwargs @@ -38,19 +37,6 @@ async def write(self, changes: dict[str, StoreItem]): with self._lock: for key in changes: - old_e_tag = self._memory.get(key, {}).get("e_tag") - if ( - not old_e_tag - or not hasattr(changes[key], "e_tag") - or changes[key].e_tag == "*" - ): - self._e_tag += 1 - changes[key].e_tag = self._e_tag - elif old_e_tag != changes[key].e_tag: - raise ValueError( - f"MemoryStorage.write(): e_tag conflict.\r\n\r\nOriginal: {changes[key].e_tag}\r\nCurrent: {old_e_tag}" - ) - changes[key].e_tag += 1 self._memory[key] = changes[key].store_item_to_json() async def delete(self, keys: list[str]): diff --git a/libraries/Storage/microsoft-agents-storage/microsoft/agents/storage/store_item.py b/libraries/Storage/microsoft-agents-storage/microsoft/agents/storage/store_item.py index 318ca4b0..e2bda475 100644 --- a/libraries/Storage/microsoft-agents-storage/microsoft/agents/storage/store_item.py +++ b/libraries/Storage/microsoft-agents-storage/microsoft/agents/storage/store_item.py @@ -5,8 +5,6 @@ @runtime_checkable class StoreItem(Protocol): - e_tag: str - def store_item_to_json(self) -> JSON: pass diff --git a/test_samples/bot_to_bot/bot_1/app.py b/test_samples/bot_to_bot/bot_1/app.py index 00da445d..1c919fe3 100644 --- a/test_samples/bot_to_bot/bot_1/app.py +++ b/test_samples/bot_to_bot/bot_1/app.py @@ -38,7 +38,8 @@ def get_token_provider( return AUTH_PROVIDER def get_connection(self, connection_name: str) -> AccessTokenProviderBase: - pass + # In this case we are using the same settings for both ABS and C + return AUTH_PROVIDER DEFAULT_CONNECTION = DefaultConnection() diff --git a/test_samples/bot_to_bot/bot_1/bot1.py b/test_samples/bot_to_bot/bot_1/bot1.py index 84edbee6..90c6edca 100644 --- a/test_samples/bot_to_bot/bot_1/bot1.py +++ b/test_samples/bot_to_bot/bot_1/bot1.py @@ -57,10 +57,10 @@ def __init__( async def on_turn(self, turn_context: TurnContextProtocol): # Forward all activities except EndOfConversation to the skill - if turn_context.activity.type == ActivityTypes.end_of_conversation: + if turn_context.activity.type != ActivityTypes.end_of_conversation: # Try to get the active skill if Bot1._active_bot_client: - # await Bot1._active_bot_client.on_turn(turn_context) + await self._send_to_bot(turn_context, self._target_skill) return await super().on_turn(turn_context) @@ -74,6 +74,7 @@ async def on_message_activity(self, turn_context: TurnContextProtocol): Bot1._active_bot_client = True # send to bot + await self._send_to_bot(turn_context, self._target_skill) return await turn_context.send_activity('Say "agent" and I\'ll patch you through') @@ -216,7 +217,7 @@ async def _send_to_bot( ) # TODO: might need to close connection, tbd - channel = self._channel_host.get_channel(target_channel) + channel = self._channel_host.get_channel_from_channel_info(target_channel) # Route activity to the skill response = await channel.post_activity( diff --git a/test_samples/bot_to_bot/bot_1/config.py b/test_samples/bot_to_bot/bot_1/config.py index 14ce00fa..919e6095 100644 --- a/test_samples/bot_to_bot/bot_1/config.py +++ b/test_samples/bot_to_bot/bot_1/config.py @@ -1,9 +1,5 @@ from microsoft.agents.authorization.msal import AuthTypes, MsalAuthConfiguration -from microsoft.agents.client import ( - ChannelHostConfiguration, - ChannelsConfiguration, - ChannelInfo, -) +from microsoft.agents.client import ChannelHostConfiguration, ChannelsConfiguration, ChannelInfo class DefaultConfig(MsalAuthConfiguration, ChannelsConfiguration): From e8c0516c95df7bd5fb2cb5a563f6c5844638d160 Mon Sep 17 00:00:00 2001 From: Axel Suarez Date: Mon, 24 Feb 2025 19:18:04 -0800 Subject: [PATCH 18/21] B2B serialization error on the way back. Communication successful. - Formatting --- .../microsoft/agents/client/channel_host_protocol.py | 4 +++- .../microsoft/agents/client/configuration_channel_host.py | 4 +--- .../microsoft/agents/client/http_bot_channel.py | 4 +++- test_samples/bot_to_bot/bot_1/config.py | 6 +++++- 4 files changed, 12 insertions(+), 6 deletions(-) diff --git a/libraries/Client/microsoft-agents-client/microsoft/agents/client/channel_host_protocol.py b/libraries/Client/microsoft-agents-client/microsoft/agents/client/channel_host_protocol.py index efbdf37e..ed4940a9 100644 --- a/libraries/Client/microsoft-agents-client/microsoft/agents/client/channel_host_protocol.py +++ b/libraries/Client/microsoft-agents-client/microsoft/agents/client/channel_host_protocol.py @@ -15,7 +15,9 @@ def __init__( self.host_app_id = host_app_id self.channels = channels - def get_channel_from_channel_info(self, channel_info: ChannelInfoProtocol) -> ChannelProtocol: + def get_channel_from_channel_info( + self, channel_info: ChannelInfoProtocol + ) -> ChannelProtocol: raise NotImplementedError() def get_channel_from_name(self, name: str) -> ChannelProtocol: diff --git a/libraries/Client/microsoft-agents-client/microsoft/agents/client/configuration_channel_host.py b/libraries/Client/microsoft-agents-client/microsoft/agents/client/configuration_channel_host.py index 8a6d6408..2316a001 100644 --- a/libraries/Client/microsoft-agents-client/microsoft/agents/client/configuration_channel_host.py +++ b/libraries/Client/microsoft-agents-client/microsoft/agents/client/configuration_channel_host.py @@ -50,9 +50,7 @@ def get_channel_from_channel_info( f"ConfigurationChannelHost.get_channel_from_channel_info(): channel_info cannot be None" ) - token_provider = self.connections.get_connection( - channel_info.token_provider - ) + token_provider = self.connections.get_connection(channel_info.token_provider) if not token_provider: raise ValueError( f"ConfigurationChannelHost.get_channel_from_channel_info(): token_provider not found for '{channel_info.token_provider}'" diff --git a/libraries/Client/microsoft-agents-client/microsoft/agents/client/http_bot_channel.py b/libraries/Client/microsoft-agents-client/microsoft/agents/client/http_bot_channel.py index b6311cff..bab2bb57 100644 --- a/libraries/Client/microsoft-agents-client/microsoft/agents/client/http_bot_channel.py +++ b/libraries/Client/microsoft-agents-client/microsoft/agents/client/http_bot_channel.py @@ -70,7 +70,9 @@ async def post_activity( async with session.post( endpoint, headers=headers, - json=activity_copy.model_dump(mode="json", by_alias=True, exclude_unset=True), + json=activity_copy.model_dump( + mode="json", by_alias=True, exclude_unset=True + ), ) as response: if response.ok: diff --git a/test_samples/bot_to_bot/bot_1/config.py b/test_samples/bot_to_bot/bot_1/config.py index 919e6095..14ce00fa 100644 --- a/test_samples/bot_to_bot/bot_1/config.py +++ b/test_samples/bot_to_bot/bot_1/config.py @@ -1,5 +1,9 @@ from microsoft.agents.authorization.msal import AuthTypes, MsalAuthConfiguration -from microsoft.agents.client import ChannelHostConfiguration, ChannelsConfiguration, ChannelInfo +from microsoft.agents.client import ( + ChannelHostConfiguration, + ChannelsConfiguration, + ChannelInfo, +) class DefaultConfig(MsalAuthConfiguration, ChannelsConfiguration): From 1c973f02253ec1529106434d83dcddc56b5aff7f Mon Sep 17 00:00:00 2001 From: Axel Suarez Date: Tue, 25 Feb 2025 16:57:33 -0800 Subject: [PATCH 19/21] B2B working --- .../botbuilder/channel_service_adapter.py | 30 ++++++++++++------- .../agents/client/conversation_id_factory.py | 4 +-- .../agents/client/http_bot_channel.py | 15 +++++++--- .../microsoft/agents/core/models/activity.py | 4 +-- .../agents/core/models/invoke_response.py | 4 +-- .../aiohttp/channel_service_route_table.py | 2 +- test_samples/bot_to_bot/bot_2/config.py | 2 +- 7 files changed, 38 insertions(+), 23 deletions(-) diff --git a/libraries/Botbuilder/microsoft-agents-botbuilder/microsoft/agents/botbuilder/channel_service_adapter.py b/libraries/Botbuilder/microsoft-agents-botbuilder/microsoft/agents/botbuilder/channel_service_adapter.py index bf6b018a..e21c4b47 100644 --- a/libraries/Botbuilder/microsoft-agents-botbuilder/microsoft/agents/botbuilder/channel_service_adapter.py +++ b/libraries/Botbuilder/microsoft-agents-botbuilder/microsoft/agents/botbuilder/channel_service_adapter.py @@ -100,7 +100,9 @@ async def send_activities( activity.model_dump(by_alias=True, exclude_unset=True), ) ) - + # TODO: The connector client is not casting the response but returning the JSON, need to fix it appropiatly + if not isinstance(response, ResourceResponse): + response = ResourceResponse.model_validate(response) response = response or ResourceResponse(id=activity.id or "") responses.append(response) @@ -183,11 +185,11 @@ async def continue_conversation_with_claims( self, claims_identity: ClaimsIdentity, continuation_activity: Activity, - logic: Callable[[TurnContext], Awaitable], + callback: Callable[[TurnContext], Awaitable], audience: str = None, ): return await self.process_proactive( - claims_identity, continuation_activity, audience, logic + claims_identity, continuation_activity, audience, callback ) async def create_conversation( # pylint: disable=arguments-differ @@ -215,7 +217,7 @@ async def create_conversation( # pylint: disable=arguments-differ claims_identity.claims[AuthenticationConstants.SERVICE_URL_CLAIM] = service_url # Create the connector client to use for outbound requests. - connector_client = ( + connector_client: ConnectorClient = ( await self._channel_service_client_factory.create_connector_client( claims_identity, service_url, audience ) @@ -234,7 +236,7 @@ async def create_conversation( # pylint: disable=arguments-differ ) # Create a UserTokenClient instance for the application to use. (For example, in the OAuthPrompt.) - user_token_client = ( + user_token_client: UserTokenClient = ( await self._channel_service_client_factory.create_user_token_client( claims_identity ) @@ -253,6 +255,9 @@ async def create_conversation( # pylint: disable=arguments-differ # Run the pipeline await self.run_pipeline(context, callback) + await connector_client.close() + await user_token_client.close() + async def process_proactive( self, claims_identity: ClaimsIdentity, @@ -261,14 +266,14 @@ async def process_proactive( callback: Callable[[TurnContext], Awaitable], ): # Create the connector client to use for outbound requests. - connector_client = ( + connector_client: ConnectorClient = ( await self._channel_service_client_factory.create_connector_client( claims_identity, continuation_activity.service_url, audience ) ) # Create a UserTokenClient instance for the application to use. (For example, in the OAuthPrompt.) - user_token_client = ( + user_token_client: UserTokenClient = ( await self._channel_service_client_factory.create_user_token_client( claims_identity ) @@ -287,6 +292,9 @@ async def process_proactive( # Run the pipeline await self.run_pipeline(context, callback) + await connector_client.close() + await user_token_client.close() + async def process_activity( self, claims_identity: ClaimsIdentity, @@ -300,8 +308,8 @@ async def process_activity( :type auth_header: :class:`typing.Union[typing.str, AuthenticateRequestResult]` :param activity: The incoming activity :type activity: :class:`Activity` - :param logic: The logic to execute at the end of the adapter's middleware pipeline. - :type logic: :class:`typing.Callable` + :param callback: The callback to execute at the end of the adapter's middleware pipeline. + :type callback: :class:`typing.Callable` :return: A task that represents the work queued to execute. @@ -420,14 +428,14 @@ def _create_turn_context( oauth_scope: str, connector_client: ConnectorClientBase, user_token_client: UserTokenClientBase, - logic: Callable[[TurnContext], Awaitable], + callback: Callable[[TurnContext], Awaitable], ) -> TurnContext: context = TurnContext(self, activity) context.turn_state[self.BOT_IDENTITY_KEY] = claims_identity context.turn_state[self._BOT_CONNECTOR_CLIENT_KEY] = connector_client context.turn_state[self.USER_TOKEN_CLIENT_KEY] = user_token_client - context.turn_state[self.BOT_CALLBACK_HANDLER_KEY] = logic + context.turn_state[self.BOT_CALLBACK_HANDLER_KEY] = callback context.turn_state[self.CHANNEL_SERVICE_FACTORY_KEY] = ( self._channel_service_client_factory ) diff --git a/libraries/Client/microsoft-agents-client/microsoft/agents/client/conversation_id_factory.py b/libraries/Client/microsoft-agents-client/microsoft/agents/client/conversation_id_factory.py index c7b905c2..3822a35c 100644 --- a/libraries/Client/microsoft-agents-client/microsoft/agents/client/conversation_id_factory.py +++ b/libraries/Client/microsoft-agents-client/microsoft/agents/client/conversation_id_factory.py @@ -56,11 +56,11 @@ async def get_bot_conversation_reference( "ConversationIdFactory.get_bot_conversation_reference(): bot_conversation_id cannot be None" ) - bot_conversation_info = await self._storage.read( + storage_record = await self._storage.read( [bot_conversation_id], target_cls=BotConversationReference ) - return bot_conversation_info + return storage_record[bot_conversation_id] async def delete_conversation_reference(self, bot_conversation_id): await self._storage.delete([bot_conversation_id]) diff --git a/libraries/Client/microsoft-agents-client/microsoft/agents/client/http_bot_channel.py b/libraries/Client/microsoft-agents-client/microsoft/agents/client/http_bot_channel.py index bab2bb57..e62991b7 100644 --- a/libraries/Client/microsoft-agents-client/microsoft/agents/client/http_bot_channel.py +++ b/libraries/Client/microsoft-agents-client/microsoft/agents/client/http_bot_channel.py @@ -1,6 +1,6 @@ from copy import deepcopy, copy -from aiohttp import ClientSession, ClientResponseError +from aiohttp import ClientSession from microsoft.agents.authentication import AccessTokenProviderBase from microsoft.agents.core.models import ( AgentsModel, @@ -66,6 +66,7 @@ async def post_activity( "Content-Type": "application/json", ConversationConstants.CONVERSATION_ID_HTTP_HEADER_NAME: conversation_id, } + async with ClientSession() as session: async with session.post( endpoint, @@ -74,9 +75,12 @@ async def post_activity( mode="json", by_alias=True, exclude_unset=True ), ) as response: - + content = None if response.ok: - content = await response.json() + try: + content = await response.json() + except: + pass if response_body_type: content = response_body_type.model_validate(content) @@ -85,6 +89,9 @@ async def post_activity( else: # TODO: Log error # TODO: Fix generic AgentsModel serialization - content = await response.json() + if response.content_type == "application/json": + content = await response.json() + elif response.content_type == "text/plain": + content = await response.text() return InvokeResponse(status=response.status, body=content) diff --git a/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/activity.py b/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/activity.py index 2b8ea392..af24e1cf 100644 --- a/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/activity.py +++ b/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/activity.py @@ -17,7 +17,7 @@ from .agents_model import AgentsModel from ._type_aliases import NonEmptyString - +# TODO: B2B Bot 2 is responding with None as id, had to mark it as optional (investigate) class Activity(AgentsModel): """An Activity is the basic communication type for the Bot Framework 3.0 protocol. @@ -120,7 +120,7 @@ class Activity(AgentsModel): """ type: NonEmptyString - id: NonEmptyString = None + id: Optional[NonEmptyString] = None timestamp: datetime = None local_timestamp: datetime = None local_timezone: NonEmptyString = None diff --git a/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/invoke_response.py b/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/invoke_response.py index 1cdbed11..b6bcbae6 100644 --- a/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/invoke_response.py +++ b/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/invoke_response.py @@ -1,6 +1,6 @@ from .agents_model import AgentsModel -from typing import Generic, TypeVar +from typing import Generic, TypeVar, Optional AgentModelT = TypeVar("T", bound=AgentsModel) @@ -17,7 +17,7 @@ class InvokeResponse(AgentsModel, Generic[AgentModelT]): """ status: int = None - body: AgentModelT = None + body: Optional[AgentModelT] = None def is_successful_status_code(self) -> bool: """ diff --git a/libraries/Hosting/microsoft-agents-hosting-aiohttp/microsoft/agents/hosting/aiohttp/channel_service_route_table.py b/libraries/Hosting/microsoft-agents-hosting-aiohttp/microsoft/agents/hosting/aiohttp/channel_service_route_table.py index 463cc7c8..656fb9a7 100644 --- a/libraries/Hosting/microsoft-agents-hosting-aiohttp/microsoft/agents/hosting/aiohttp/channel_service_route_table.py +++ b/libraries/Hosting/microsoft-agents-hosting-aiohttp/microsoft/agents/hosting/aiohttp/channel_service_route_table.py @@ -23,7 +23,7 @@ async def deserialize_from_body( else: return Response(status=415) - return target_model().model_validate(body) + return target_model.model_validate(body) def get_serialized_response( diff --git a/test_samples/bot_to_bot/bot_2/config.py b/test_samples/bot_to_bot/bot_2/config.py index e3ab5f8d..1fe1ea0c 100644 --- a/test_samples/bot_to_bot/bot_2/config.py +++ b/test_samples/bot_to_bot/bot_2/config.py @@ -8,4 +8,4 @@ class DefaultConfig(MsalAuthConfiguration): TENANT_ID = "" CLIENT_ID = "" CLIENT_SECRET = "" - PORT = 3999 + PORT = 3978 From ee9d958bb19d560b3263974f50b592f6c0a74e19 Mon Sep 17 00:00:00 2001 From: Axel Suarez Date: Wed, 26 Feb 2025 10:13:07 -0800 Subject: [PATCH 20/21] B2B style changes and light cleanup --- .../microsoft/agents/core/models/activity.py | 1 + .../core/models/conversation_account.py | 2 +- test_samples/bot_to_bot/bot_1/app.py | 4 +++- test_samples/bot_to_bot/bot_1/bot1.py | 21 +++++++++---------- test_samples/bot_to_bot/bot_1/config.py | 11 +++++----- test_samples/bot_to_bot/bot_2/config.py | 2 +- 6 files changed, 22 insertions(+), 19 deletions(-) diff --git a/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/activity.py b/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/activity.py index af24e1cf..de22f225 100644 --- a/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/activity.py +++ b/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/activity.py @@ -17,6 +17,7 @@ from .agents_model import AgentsModel from ._type_aliases import NonEmptyString + # TODO: B2B Bot 2 is responding with None as id, had to mark it as optional (investigate) class Activity(AgentsModel): """An Activity is the basic communication type for the Bot Framework 3.0 protocol. diff --git a/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/conversation_account.py b/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/conversation_account.py index c8c466f7..acd08382 100644 --- a/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/conversation_account.py +++ b/libraries/Core/microsoft-agents-core/microsoft/agents/core/models/conversation_account.py @@ -19,7 +19,7 @@ class ConversationAccount(AgentsModel): :param aad_object_id: This account's object ID within Azure Active Directory (AAD) :type aad_object_id: str - :param role: Role of the entity behind the account (Example: User, Bot, Skill + :param role: Role of the entity behind the account (Example: User, Bot, etc.). Possible values include: 'user', 'bot', 'skill' :type role: str or ~microsoft.agents.protocols.models.RoleTypes :param tenant_id: This conversation's tenant ID diff --git a/test_samples/bot_to_bot/bot_1/app.py b/test_samples/bot_to_bot/bot_1/app.py index 1c919fe3..525eb5aa 100644 --- a/test_samples/bot_to_bot/bot_1/app.py +++ b/test_samples/bot_to_bot/bot_1/app.py @@ -35,10 +35,12 @@ def get_default_connection(self) -> AccessTokenProviderBase: def get_token_provider( self, claims_identity: ClaimsIdentity, service_url: str ) -> AccessTokenProviderBase: + # This is the provider used for ABS return AUTH_PROVIDER def get_connection(self, connection_name: str) -> AccessTokenProviderBase: - # In this case we are using the same settings for both ABS and C + # In this case we are using the same settings for both ABS and Channel + # This is the provider used for Channel return AUTH_PROVIDER diff --git a/test_samples/bot_to_bot/bot_1/bot1.py b/test_samples/bot_to_bot/bot_1/bot1.py index 90c6edca..3bca8de5 100644 --- a/test_samples/bot_to_bot/bot_1/bot1.py +++ b/test_samples/bot_to_bot/bot_1/bot1.py @@ -32,7 +32,6 @@ class Bot1(ActivityHandler, ChannelApiHandlerProtocol): - ACTIVE_SKILL_PROPERTY_NAME = "Bot1.ActiveSkillProperty" _active_bot_client = False def __init__( @@ -52,15 +51,15 @@ def __init__( self._channel_host = channel_host self._conversation_id_factory = conversation_id_factory - target_skill_id = "EchoSkillBot" - self._target_skill = self._channel_host.channels.get(target_skill_id) + target_b2b_id = "EchoBot" + self._target_b2b = self._channel_host.channels.get(target_b2b_id) async def on_turn(self, turn_context: TurnContextProtocol): - # Forward all activities except EndOfConversation to the skill + # Forward all activities except EndOfConversation to the B2B connection if turn_context.activity.type != ActivityTypes.end_of_conversation: - # Try to get the active skill + # Try to get the active B2B connection if Bot1._active_bot_client: - await self._send_to_bot(turn_context, self._target_skill) + await self._send_to_bot(turn_context, self._target_b2b) return await super().on_turn(turn_context) @@ -74,16 +73,16 @@ async def on_message_activity(self, turn_context: TurnContextProtocol): Bot1._active_bot_client = True # send to bot - await self._send_to_bot(turn_context, self._target_skill) + await self._send_to_bot(turn_context, self._target_b2b) return await turn_context.send_activity('Say "agent" and I\'ll patch you through') async def on_end_of_conversation_activity(self, turn_context: TurnContextProtocol): - # Clear the active skill + # Clear the active B2B connection Bot1._active_bot_client = False - # Show status message, text and value returned by the skill + # Show status message, text and value returned by the B2B connection eoc_activity_message = f"Received {turn_context.activity.type}. Code: {turn_context.activity.code}." if turn_context.activity.text: eoc_activity_message += f" Text: {turn_context.activity.text}" @@ -202,7 +201,7 @@ async def on_upload_attachment( async def _send_to_bot( self, turn_context: TurnContextProtocol, target_channel: ChannelInfoProtocol ): - # Create a conversation ID to communicate with the skill + # Create a conversation ID to communicate with the B2B connection options = ConversationIdFactoryOptions( from_oauth_scope=turn_context.turn_state.get( ChannelAdapter.OAUTH_SCOPE_KEY @@ -219,7 +218,7 @@ async def _send_to_bot( # TODO: might need to close connection, tbd channel = self._channel_host.get_channel_from_channel_info(target_channel) - # Route activity to the skill + # Route activity to the B2B connection response = await channel.post_activity( target_channel.app_id, target_channel.resource_url, diff --git a/test_samples/bot_to_bot/bot_1/config.py b/test_samples/bot_to_bot/bot_1/config.py index 14ce00fa..a4daff4b 100644 --- a/test_samples/bot_to_bot/bot_1/config.py +++ b/test_samples/bot_to_bot/bot_1/config.py @@ -14,6 +14,7 @@ class DefaultConfig(MsalAuthConfiguration, ChannelsConfiguration): CLIENT_ID = "" CLIENT_SECRET = "" PORT = 3978 + SCOPES = ["https://api.botframework.com/.default"] # ChannelHost configuration @staticmethod @@ -21,14 +22,14 @@ def CHANNEL_HOST_CONFIGURATION(): return ChannelHostConfiguration( CHANNELS=[ ChannelInfo( - id="EchoSkillBot", - app_id="", - resource_url="http://localhost:3978/api/messages", + id="EchoBot", + app_id="", # Target bot's app_id + resource_url="http://localhost:3999/api/messages", token_provider="ChannelConnection", channel_factory="HttpBotClient", - endpoint="http://localhost:3978/api/botresponse/", + endpoint="http://localhost:3999/api/messages", ) ], HOST_ENDPOINT="http://localhost:3978/api/botresponse/", - HOST_APP_ID="", + HOST_APP_ID="", # usually the same as CLIENT_ID ) diff --git a/test_samples/bot_to_bot/bot_2/config.py b/test_samples/bot_to_bot/bot_2/config.py index 1fe1ea0c..e3ab5f8d 100644 --- a/test_samples/bot_to_bot/bot_2/config.py +++ b/test_samples/bot_to_bot/bot_2/config.py @@ -8,4 +8,4 @@ class DefaultConfig(MsalAuthConfiguration): TENANT_ID = "" CLIENT_ID = "" CLIENT_SECRET = "" - PORT = 3978 + PORT = 3999 From 3f260665f4b096da7e3ec41693c7ca9013ed1843 Mon Sep 17 00:00:00 2001 From: Axel Suarez Date: Wed, 26 Feb 2025 10:20:40 -0800 Subject: [PATCH 21/21] Updated formatting --- .../agents/hosting/aiohttp/channel_service_route_table.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/Hosting/microsoft-agents-hosting-aiohttp/microsoft/agents/hosting/aiohttp/channel_service_route_table.py b/libraries/Hosting/microsoft-agents-hosting-aiohttp/microsoft/agents/hosting/aiohttp/channel_service_route_table.py index 656fb9a7..bc4f9f60 100644 --- a/libraries/Hosting/microsoft-agents-hosting-aiohttp/microsoft/agents/hosting/aiohttp/channel_service_route_table.py +++ b/libraries/Hosting/microsoft-agents-hosting-aiohttp/microsoft/agents/hosting/aiohttp/channel_service_route_table.py @@ -27,7 +27,7 @@ async def deserialize_from_body( def get_serialized_response( - model_or_list: Union[AgentsModel, List[AgentsModel]] + model_or_list: Union[AgentsModel, List[AgentsModel]], ) -> Response: if isinstance(model_or_list, AgentsModel): json_obj = model_or_list.model_dump(