-
Notifications
You must be signed in to change notification settings - Fork 56
Adding configurability for CopilotClient ClientSession creation #277
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,61 @@ | ||
| import pytest | ||
|
|
||
| from aiohttp.web import Request, Response, Application | ||
|
|
||
| from microsoft_agents.activity import Activity | ||
|
|
||
| from microsoft_agents.copilotstudio.client import ( | ||
| CopilotClient, | ||
| ConnectionSettings, | ||
| PowerPlatformEnvironment | ||
| ) | ||
|
|
||
| from microsoft_agents.testing.integration.core import ( | ||
| AiohttpRunner | ||
| ) | ||
|
|
||
| def mock_mcs_handler(activity: Activity) -> Awaitable[[Request], Response]: | ||
| """Creates a mock handler for MCS endpoint returning the given activity.""" | ||
| async def handler(request: Request) -> Response: | ||
| activity_data = activity.model_dump_json(exclude_unset=True) | ||
| return Response( | ||
| body=activity_data | ||
| ) | ||
| return handler | ||
|
|
||
| def mock_mcs_endpoint( | ||
| mocker, | ||
| activity: Activity, | ||
| path: str, | ||
| port: int | ||
| ) -> AiohttpRunner: | ||
| """Mock MCS responses for testing.""" | ||
|
|
||
| PowerPlatformEnvironment.get_copilot_studio_connection_url = mocker.MagicMock( | ||
| return_value=f"http://localhost:{port}{path}" | ||
| ) | ||
|
|
||
| app = Application() | ||
| app.router.add_post(path, mock_mcs_handler(activity)) | ||
|
|
||
| return AiohttpRunner(app, port=port) | ||
|
|
||
|
|
||
| @pytest.mark.asyncio | ||
| async def test_start_conversation_and_ask_question(mocker): | ||
|
|
||
| activity = Activity( | ||
| type="message" | ||
| ) | ||
|
|
||
| runner = mock_mcs_endpoint(mocker, activity, "/mcs-endpoint", port=8081) | ||
|
|
||
| await with runner: | ||
| settings = ConnectionSettings("environment-id", "agent-id") | ||
| client = CopilotClient(settings=settings, token="test-token") | ||
|
|
||
| async for conv_activity in client.start_conversation(): | ||
| assert conv_activity.type == "message" | ||
|
|
||
| async for question_activity in client.ask_question("Hello!"): | ||
| assert question_activity.type == "message" |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -18,6 +18,7 @@ def __init__( | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| cloud: Optional[PowerPlatformCloud], | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| copilot_agent_type: Optional[AgentType], | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| custom_power_platform_cloud: Optional[str], | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| client_session_defaults: Optional[dict] = None, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) -> None: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) -> None: | |
| ) -> None: | |
| """ | |
| Initializes a new instance of ConnectionSettings. | |
| Args: | |
| environment_id (str): The ID of the Power Platform environment. | |
| agent_identifier (str): The identifier for the agent. | |
| cloud (Optional[PowerPlatformCloud]): The Power Platform cloud to use. Defaults to PROD if not specified. | |
| copilot_agent_type (Optional[AgentType]): The type of Copilot agent. Defaults to PUBLISHED if not specified. | |
| custom_power_platform_cloud (Optional[str]): Custom cloud endpoint, if applicable. | |
| client_session_defaults (Optional[dict]): Optional dictionary of default values for the client session. | |
| Supported keys may include configuration options such as authentication tokens, | |
| user preferences, or other session-specific settings. The exact keys depend on the | |
| client implementation. | |
| Example: | |
| client_session_defaults = { | |
| "auth_token": "your_token_here", | |
| "locale": "en-US", | |
| "timeout": 30 | |
| } | |
| settings = ConnectionSettings( | |
| environment_id="env123", | |
| agent_identifier="agent456", | |
| cloud=PowerPlatformCloud.PROD, | |
| copilot_agent_type=AgentType.PUBLISHED, | |
| custom_power_platform_cloud=None, | |
| client_session_defaults=client_session_defaults | |
| ) | |
| """ |
Copilot
AI
Dec 3, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The client_session_defaults attribute should be added to the DirectToEngineConnectionSettingsProtocol to maintain protocol conformance. Since ConnectionSettings implements this protocol, all attributes should be declared in the protocol interface.
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -28,7 +28,9 @@ def __init__( | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| async def post_request( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| self, url: str, data: dict, headers: dict | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) -> AsyncIterable[Activity]: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| async with aiohttp.ClientSession() as session: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| async with aiohttp.ClientSession( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| **self.settings.client_session_defaults | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) as session: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| async with session.post(url, json=data, headers=headers) as response: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+31
to
34
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| async with aiohttp.ClientSession( | |
| **self.settings.client_session_defaults | |
| ) as session: | |
| async with session.post(url, json=data, headers=headers) as response: | |
| try: | |
| async with aiohttp.ClientSession( | |
| **self.settings.client_session_defaults | |
| ) as session: | |
| async with session.post(url, json=data, headers=headers) as response: | |
| if response.status != 200: | |
| # self.logger(f"Error sending request: {response.status}") | |
| raise aiohttp.ClientError( | |
| f"Error sending request: {response.status}" | |
| ) | |
| # Set conversation ID from response header when status is 200 | |
| conversation_id_header = response.headers.get("x-ms-conversationid") | |
| if conversation_id_header: | |
| self._current_conversation_id = conversation_id_header | |
| event_type = None | |
| async for line in response.content: | |
| if line.startswith(b"event:"): | |
| event_type = line[6:].decode("utf-8").strip() | |
| if line.startswith(b"data:") and event_type == "activity": | |
| activity_data = line[5:].decode("utf-8").strip() | |
| activity = Activity.model_validate_json(activity_data) | |
| if activity.type == ActivityTypes.message: | |
| self._current_conversation_id = activity.conversation.id | |
| yield activity | |
| except TypeError as e: | |
| raise ValueError( | |
| f"Invalid parameters provided to aiohttp.ClientSession: {e}. " | |
| f"Check self.settings.client_session_defaults for invalid keys/values." | |
| ) from e |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The type hint
dictis too generic. Consider usingDict[str, Any]from thetypingmodule for better type safety, or even better, define the expected structure more explicitly if the aiohttp.ClientSession constructor parameters are known.