diff --git a/libraries/microsoft-agents-activity/microsoft_agents/activity/role_types.py b/libraries/microsoft-agents-activity/microsoft_agents/activity/role_types.py index 284e88ce..6daa4d24 100644 --- a/libraries/microsoft-agents-activity/microsoft_agents/activity/role_types.py +++ b/libraries/microsoft-agents-activity/microsoft_agents/activity/role_types.py @@ -10,3 +10,4 @@ class RoleTypes(str, Enum): skill = "skill" agentic_identity = "agenticAppInstance" agentic_user = "agenticUser" + connector_user = "connectoruser" diff --git a/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/app/oauth/__init__.py b/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/app/auth/__init__.py similarity index 100% rename from libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/app/oauth/__init__.py rename to libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/app/auth/__init__.py diff --git a/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/app/oauth/_handlers/__init__.py b/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/app/auth/_handlers/__init__.py similarity index 100% rename from libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/app/oauth/_handlers/__init__.py rename to libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/app/auth/_handlers/__init__.py diff --git a/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/app/oauth/_handlers/_authorization_handler.py b/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/app/auth/_handlers/_authorization_handler.py similarity index 100% rename from libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/app/oauth/_handlers/_authorization_handler.py rename to libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/app/auth/_handlers/_authorization_handler.py diff --git a/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/app/auth/_handlers/_connector_user_authorization.py b/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/app/auth/_handlers/_connector_user_authorization.py new file mode 100644 index 00000000..63e4f710 --- /dev/null +++ b/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/app/auth/_handlers/_connector_user_authorization.py @@ -0,0 +1,72 @@ +""" +Copyright (c) Microsoft Corporation. All rights reserved. +Licensed under the MIT License. +""" + +from abc import ABC +from typing import Optional +import logging + +from microsoft_agents.activity import TokenResponse + +from ....turn_context import TurnContext +from ....storage import Storage +from ....authorization import Connections +from ...._oauth import _FlowStateTag +from ..auth_handler import AuthHandler +from .._sign_in_response import _SignInResponse + +from ._authorization_handler import _AuthorizationHandler + +class _ConnectorUserAuthorizationHandler(_AuthorizationHandler): + """Authorization handler for connector user OAuth flow.""" + + # def get_obo_settings(self) -> dict: + # """Get On-Behalf-Of settings for the auth handler. + + # :return: The OBO settings dictionary. + # :rtype: dict + # """ + # return self._ + + async def _sign_in( + self, + context: TurnContext, + exchange_connection: Optional[str] = None, + exchange_scopes: Optional[list[str]] = None, + ) -> _SignInResponse: + token_response = await self.get_refreshed_token( + context, exchange_connection, exchange_scopes + ) + if token_response: + return _SignInResponse( + token_response=token_response, tag=_FlowStateTag.COMPLETE + ) + return _SignInResponse(tag=_FlowStateTag.FAILURE) + + + async def get_refreshed_token( + self, + context: TurnContext, + exchange_connection: Optional[str] = None, + exchange_scopes: Optional[list[str]] = None, + ) -> TokenResponse: + + token_response = self.create_token_response(context) + + async def _sign_out(self, context: TurnContext) -> None: + raise NotImplementedError + + def create_token_response(self, context: TurnContext) -> TokenResponse: + + if _ConnectorUserAuthorizationHandler._is_case_sensitive_claims_identity( + context.turn_state.get("claims_identity") + ): + token_response = TokenResponse(token=identity.security_token.unsafe_to_str()) + + try: + jwt_token = + + @staticmethod + def _is_case_sensitive_claims_identity(identity: ClaimsIdentity) -> bool: + return False \ No newline at end of file diff --git a/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/app/oauth/_handlers/_user_authorization.py b/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/app/auth/_handlers/_user_authorization.py similarity index 100% rename from libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/app/oauth/_handlers/_user_authorization.py rename to libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/app/auth/_handlers/_user_authorization.py diff --git a/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/app/oauth/_handlers/agentic_user_authorization.py b/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/app/auth/_handlers/agentic_user_authorization.py similarity index 100% rename from libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/app/oauth/_handlers/agentic_user_authorization.py rename to libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/app/auth/_handlers/agentic_user_authorization.py diff --git a/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/app/oauth/_sign_in_response.py b/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/app/auth/_sign_in_response.py similarity index 100% rename from libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/app/oauth/_sign_in_response.py rename to libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/app/auth/_sign_in_response.py diff --git a/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/app/oauth/_sign_in_state.py b/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/app/auth/_sign_in_state.py similarity index 100% rename from libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/app/oauth/_sign_in_state.py rename to libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/app/auth/_sign_in_state.py diff --git a/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/app/oauth/auth_handler.py b/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/app/auth/auth_handler.py similarity index 100% rename from libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/app/oauth/auth_handler.py rename to libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/app/auth/auth_handler.py diff --git a/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/app/oauth/authorization.py b/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/app/auth/authorization.py similarity index 100% rename from libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/app/oauth/authorization.py rename to libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/app/auth/authorization.py diff --git a/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/connector/client/__init__.py b/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/connector/client/__init__.py index c6446c9c..11f54659 100644 --- a/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/connector/client/__init__.py +++ b/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/connector/client/__init__.py @@ -1,4 +1,5 @@ from .connector_client import ConnectorClient +from .mcs_connector_client import MCSConnectorClient from .user_token_client import UserTokenClient -__all__ = ["ConnectorClient", "UserTokenClient"] +__all__ = ["ConnectorClient", "MCSConnectorClient", "UserTokenClient"] diff --git a/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/connector/client/mcs_connector_client.py b/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/connector/client/mcs_connector_client.py new file mode 100644 index 00000000..1dbacdb8 --- /dev/null +++ b/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/connector/client/mcs_connector_client.py @@ -0,0 +1,131 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +"""Connector Client for Microsoft Agents.""" + +import logging +from typing import Any, Optional +from aiohttp import ClientSession +from io import BytesIO + +from microsoft_agents.activity import ( + Activity, + ChannelAccount, + ConversationParameters, + ConversationResourceResponse, + ResourceResponse, + ConversationsResult, + PagedMembersResult, +) +from microsoft_agents.hosting.core.connector import ConnectorClientBase +from ..attachments_base import AttachmentsBase +from ..conversations_base import ConversationsBase +from .connector_client import AttachmentsOperations +from ..get_product_info import get_product_info + + +logger = logging.getLogger(__name__) + +class MCSConversationsOperations(ConversationsBase): + """Operations for managing conversations in MCS.""" + + def __init__(self, client: ClientSession): + self.client = client + + async def create_conversation(self, body: ConversationParameters) -> ConversationResourceResponse: + raise NotImplementedError() + + async def delete_activity(self, conversation_id: str, activity_id: str) -> None: + raise NotImplementedError() + + async def delete_conversation_member(self, conversation_id: str, member_id: str) -> None: + raise NotImplementedError() + + async def get_activity_members(self, conversation_id: str, activity_id: str) -> list[ChannelAccount]: + raise NotImplementedError() + + async def get_conversation_member(self, conversation_id: str, member_id: str) -> ChannelAccount: + raise NotImplementedError() + + async def get_conversation_members(self, conversation_id: str) -> list[ChannelAccount]: + raise NotImplementedError() + + async def get_conversations(self, continuation_token: Optional[str] = None) -> ConversationsResult: + raise NotImplementedError() + + async def send_conversation_history(self, conversation_id: str, transcript: Transcript) -> ResourceResponse: + raise NotImplementedError() + + async def update_activity(self, conversation_id: str, activity_id: str, activity: Activity) -> ResourceResponse: + raise NotImplementedError() + + async def upload_attachment(self, conversation_id: str, attachment_upload: AttachmentData) -> ResourceResponse: + raise NotImplementedError() + + async def get_conversation_paged_members( + self, + conversation_id: str, + page_size: Optional[int] = None, + continuation_token: Optional[str] = None, + ) -> PagedMembersResult: + raise NotImplementedError() + + async def send_to_conversation(self, conversation_id: str, body: Activity) -> ResourceResponse: + if not conversation_id: + raise ValueError("conversation_id cannot be None or empty.") + + async with self.client.post( + url="", + json=body.model_dump(by_alias=True, exclude_unset=True, mode="json"), + ) as response: + if response.status >= 300: + logger.error( + "Error sending to conversation: %s", + response.status, + stack_info=True, + ) + response.raise_for_status() + + data = await response.json() + return ResourceResponse.model_validate(data) + + async def reply_to_activity(self, conversation_id: str, body: Activity) -> ResourceResponse: + return await self.send_to_conversation(conversation_id, body) + + # Implement conversation-related methods as needed + +class MCSConnectorClient(ConnectorClientBase): + + def __init__(self, endpoint: str, *, session: ClientSession = None): + """ + Initialize a new instance of ConnectorClient. + + :param session: The aiohttp ClientSession to use for HTTP requests. + """ + if not endpoint.endswith("/"): + endpoint += "/" + + # Configure headers with JSON acceptance + headers = { + "Accept": "application/json", + "Content-Type": "application/json", + "User-Agent": get_product_info(), + } + # Create session with the base URL + session = session or ClientSession( + base_url=endpoint, + headers=headers, + ) + logger.debug( + "ConnectorClient initialized with endpoint: %s and headers: %s", + endpoint, + headers, + ) + + self.client = session + self._attachments = AttachmentOperations( + self.client + ) # Will implement if needed + self._conversations = MCSConversationsOperations( + self.client + ) # Will implement if needed \ No newline at end of file diff --git a/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/rest_channel_service_client_factory.py b/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/rest_channel_service_client_factory.py index 9e639480..7f661a99 100644 --- a/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/rest_channel_service_client_factory.py +++ b/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/rest_channel_service_client_factory.py @@ -101,6 +101,8 @@ async def create_connector_client( "RestChannelServiceClientFactory.create_connector_client: audience can't be None or Empty" ) + if context.activity.recipient and context.activity.recipient.role == RoleTypes.connector_user: + return MCSConnectorClient(turn_context.activity.service_url) if context.activity.is_agentic_request(): token = await self._get_agentic_token(context, service_url) else: