microsoft-agents-copilotstudio-client 1.9.0.dev5__tar.gz → 1.9.0.dev10__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. {microsoft_agents_copilotstudio_client-1.9.0.dev5/microsoft_agents_copilotstudio_client.egg-info → microsoft_agents_copilotstudio_client-1.9.0.dev10}/PKG-INFO +24 -2
  2. microsoft_agents_copilotstudio_client-1.9.0.dev10/VERSION.txt +1 -0
  3. microsoft_agents_copilotstudio_client-1.9.0.dev10/microsoft_agents/copilotstudio/client/_conversation_endpoint_discovery.py +106 -0
  4. {microsoft_agents_copilotstudio_client-1.9.0.dev5 → microsoft_agents_copilotstudio_client-1.9.0.dev10}/microsoft_agents/copilotstudio/client/copilot_client.py +100 -16
  5. {microsoft_agents_copilotstudio_client-1.9.0.dev5 → microsoft_agents_copilotstudio_client-1.9.0.dev10}/microsoft_agents/copilotstudio/client/errors/error_resources.py +25 -0
  6. {microsoft_agents_copilotstudio_client-1.9.0.dev5 → microsoft_agents_copilotstudio_client-1.9.0.dev10}/microsoft_agents/copilotstudio/client/power_platform_environment.py +1 -0
  7. microsoft_agents_copilotstudio_client-1.9.0.dev5/readme.md → microsoft_agents_copilotstudio_client-1.9.0.dev10/microsoft_agents_copilotstudio_client.egg-info/PKG-INFO +44 -1
  8. {microsoft_agents_copilotstudio_client-1.9.0.dev5 → microsoft_agents_copilotstudio_client-1.9.0.dev10}/microsoft_agents_copilotstudio_client.egg-info/SOURCES.txt +1 -0
  9. microsoft_agents_copilotstudio_client-1.9.0.dev10/microsoft_agents_copilotstudio_client.egg-info/requires.txt +1 -0
  10. microsoft_agents_copilotstudio_client-1.9.0.dev5/PKG-INFO → microsoft_agents_copilotstudio_client-1.9.0.dev10/readme.md +23 -22
  11. microsoft_agents_copilotstudio_client-1.9.0.dev5/VERSION.txt +0 -1
  12. microsoft_agents_copilotstudio_client-1.9.0.dev5/microsoft_agents_copilotstudio_client.egg-info/requires.txt +0 -1
  13. {microsoft_agents_copilotstudio_client-1.9.0.dev5 → microsoft_agents_copilotstudio_client-1.9.0.dev10}/LICENSE +0 -0
  14. {microsoft_agents_copilotstudio_client-1.9.0.dev5 → microsoft_agents_copilotstudio_client-1.9.0.dev10}/MANIFEST.in +0 -0
  15. {microsoft_agents_copilotstudio_client-1.9.0.dev5 → microsoft_agents_copilotstudio_client-1.9.0.dev10}/microsoft_agents/copilotstudio/client/__init__.py +0 -0
  16. {microsoft_agents_copilotstudio_client-1.9.0.dev5 → microsoft_agents_copilotstudio_client-1.9.0.dev10}/microsoft_agents/copilotstudio/client/agent_type.py +0 -0
  17. {microsoft_agents_copilotstudio_client-1.9.0.dev5 → microsoft_agents_copilotstudio_client-1.9.0.dev10}/microsoft_agents/copilotstudio/client/connection_settings.py +0 -0
  18. {microsoft_agents_copilotstudio_client-1.9.0.dev5 → microsoft_agents_copilotstudio_client-1.9.0.dev10}/microsoft_agents/copilotstudio/client/copilot_client_protocol.py +0 -0
  19. {microsoft_agents_copilotstudio_client-1.9.0.dev5 → microsoft_agents_copilotstudio_client-1.9.0.dev10}/microsoft_agents/copilotstudio/client/direct_to_engine_connection_settings_protocol.py +0 -0
  20. {microsoft_agents_copilotstudio_client-1.9.0.dev5 → microsoft_agents_copilotstudio_client-1.9.0.dev10}/microsoft_agents/copilotstudio/client/errors/__init__.py +0 -0
  21. {microsoft_agents_copilotstudio_client-1.9.0.dev5 → microsoft_agents_copilotstudio_client-1.9.0.dev10}/microsoft_agents/copilotstudio/client/execute_turn_request.py +0 -0
  22. {microsoft_agents_copilotstudio_client-1.9.0.dev5 → microsoft_agents_copilotstudio_client-1.9.0.dev10}/microsoft_agents/copilotstudio/client/power_platform_cloud.py +0 -0
  23. {microsoft_agents_copilotstudio_client-1.9.0.dev5 → microsoft_agents_copilotstudio_client-1.9.0.dev10}/microsoft_agents/copilotstudio/client/py.typed +0 -0
  24. {microsoft_agents_copilotstudio_client-1.9.0.dev5 → microsoft_agents_copilotstudio_client-1.9.0.dev10}/microsoft_agents/copilotstudio/client/start_request.py +0 -0
  25. {microsoft_agents_copilotstudio_client-1.9.0.dev5 → microsoft_agents_copilotstudio_client-1.9.0.dev10}/microsoft_agents/copilotstudio/client/subscribe_event.py +0 -0
  26. {microsoft_agents_copilotstudio_client-1.9.0.dev5 → microsoft_agents_copilotstudio_client-1.9.0.dev10}/microsoft_agents/copilotstudio/client/subscribe_request.py +0 -0
  27. {microsoft_agents_copilotstudio_client-1.9.0.dev5 → microsoft_agents_copilotstudio_client-1.9.0.dev10}/microsoft_agents/copilotstudio/client/subscribe_response.py +0 -0
  28. {microsoft_agents_copilotstudio_client-1.9.0.dev5 → microsoft_agents_copilotstudio_client-1.9.0.dev10}/microsoft_agents/copilotstudio/client/user_agent_helper.py +0 -0
  29. {microsoft_agents_copilotstudio_client-1.9.0.dev5 → microsoft_agents_copilotstudio_client-1.9.0.dev10}/microsoft_agents_copilotstudio_client.egg-info/dependency_links.txt +0 -0
  30. {microsoft_agents_copilotstudio_client-1.9.0.dev5 → microsoft_agents_copilotstudio_client-1.9.0.dev10}/microsoft_agents_copilotstudio_client.egg-info/top_level.txt +0 -0
  31. {microsoft_agents_copilotstudio_client-1.9.0.dev5 → microsoft_agents_copilotstudio_client-1.9.0.dev10}/pyproject.toml +0 -0
  32. {microsoft_agents_copilotstudio_client-1.9.0.dev5 → microsoft_agents_copilotstudio_client-1.9.0.dev10}/setup.cfg +0 -0
  33. {microsoft_agents_copilotstudio_client-1.9.0.dev5 → microsoft_agents_copilotstudio_client-1.9.0.dev10}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: microsoft-agents-copilotstudio-client
3
- Version: 1.9.0.dev5
3
+ Version: 1.9.0.dev10
4
4
  Summary: A client library for Microsoft Agents
5
5
  Author: Microsoft Corporation
6
6
  License-Expression: MIT
@@ -15,7 +15,7 @@ Classifier: Operating System :: OS Independent
15
15
  Requires-Python: >=3.10
16
16
  Description-Content-Type: text/markdown
17
17
  License-File: LICENSE
18
- Requires-Dist: microsoft-agents-hosting-core==1.9.0.dev5
18
+ Requires-Dist: microsoft-agents-hosting-core==1.9.0.dev10
19
19
  Dynamic: license-file
20
20
  Dynamic: requires-dist
21
21
 
@@ -230,6 +230,28 @@ def create_client():
230
230
  return copilot_client
231
231
  ```
232
232
 
233
+ #### Conversation Endpoint Discovery
234
+
235
+ For published agents without a `direct_connect_url`, the client first sends an
236
+ authenticated `GET` to
237
+ `https://<environment-host>/copilotstudio/agents/<escaped-schema-name>/conversation-endpoints`.
238
+ The host is selected from the configured environment and cloud. Discovery uses the
239
+ client's bearer token and the configured `client_session_settings`.
240
+
241
+ The client validates all advertised endpoints before storing them and uses each
242
+ operation URL as returned, preserving its query string and escaping conversation
243
+ IDs when substituting `{conversationId}`. `createConversation`, `executeTurn`, and
244
+ `continueTurn` are required; `subscribe` is optional and subscription fails
245
+ explicitly when the agent does not advertise it. The client has no continue-turn
246
+ operation.
247
+
248
+ Successful discovery is reused for the lifetime of the client, with concurrent
249
+ calls coordinated to avoid duplicate initialization. Failures and cancellation
250
+ allow a later call to retry. The discovered create URL is written to
251
+ `settings.direct_connect_url`; clearing that value triggers discovery again.
252
+ Assigning a different explicit URL switches back to legacy direct-URL routing.
253
+ Explicit direct URLs and prebuilt agents bypass discovery entirely.
254
+
233
255
  #### DirectConnect URL Mode (Simplified Setup)
234
256
 
235
257
  For simplified setup, you can use a DirectConnect URL instead of environment-based configuration:
@@ -0,0 +1,106 @@
1
+ # Copyright (c) Microsoft Corporation. All rights reserved.
2
+ # Licensed under the MIT License.
3
+
4
+ from dataclasses import dataclass
5
+ from typing import Literal, Optional
6
+ from urllib.parse import quote, urlsplit, urlunsplit
7
+
8
+ from .connection_settings import ConnectionSettings
9
+ from .errors import copilot_studio_errors
10
+ from .power_platform_environment import PowerPlatformEnvironment
11
+
12
+ ConversationOperation = Literal["createConversation", "executeTurn", "subscribe"]
13
+ _CONVERSATION_ID = "{conversationId}"
14
+
15
+
16
+ def get_discovery_url(settings: ConnectionSettings) -> str:
17
+ url = urlsplit(PowerPlatformEnvironment.get_copilot_studio_connection_url(settings))
18
+ return urlunsplit(
19
+ (
20
+ url.scheme,
21
+ url.netloc,
22
+ f"/copilotstudio/agents/{quote(settings.agent_identifier, safe='')}/conversation-endpoints",
23
+ "",
24
+ "",
25
+ )
26
+ )
27
+
28
+
29
+ def _validate_url(
30
+ value: object, discovery_url: str, requires_conversation_id: bool
31
+ ) -> str:
32
+ error = str(copilot_studio_errors.InvalidDiscoveryOperationUrl)
33
+ if not isinstance(value, str) or not value.strip():
34
+ raise ValueError(error)
35
+
36
+ remainder = value.replace(_CONVERSATION_ID, "")
37
+ if (
38
+ requires_conversation_id != (_CONVERSATION_ID in value)
39
+ or "{" in remainder
40
+ or "}" in remainder
41
+ or "\\" in value
42
+ or any(character.isspace() or ord(character) < 32 for character in value)
43
+ ):
44
+ raise ValueError(error)
45
+
46
+ try:
47
+ discovery = urlsplit(discovery_url)
48
+ url = urlsplit(value.replace(_CONVERSATION_ID, "conversation"))
49
+ host = discovery.hostname or ""
50
+ suffix_index = host.find(".environment.")
51
+ suffix = host[suffix_index:] if suffix_index >= 0 else "." + host
52
+ valid = (
53
+ url.scheme == "https"
54
+ and (url.port if url.port is not None else 443)
55
+ == (discovery.port if discovery.port is not None else 443)
56
+ and bool(url.hostname)
57
+ and url.hostname.endswith(suffix)
58
+ and url.username is None
59
+ and url.password is None
60
+ and not url.fragment
61
+ )
62
+ except ValueError as exc:
63
+ raise ValueError(error) from exc
64
+ if not valid:
65
+ raise ValueError(error)
66
+ return value
67
+
68
+
69
+ @dataclass(frozen=True)
70
+ class ConversationEndpoints:
71
+ create_conversation: str
72
+ execute_turn: str
73
+ continue_turn: str
74
+ subscribe: Optional[str]
75
+
76
+ @classmethod
77
+ def from_response(cls, data: object, discovery_url: str) -> "ConversationEndpoints":
78
+ if not isinstance(data, dict):
79
+ raise ValueError(str(copilot_studio_errors.InvalidDiscoveryResponse))
80
+
81
+ return cls(
82
+ create_conversation=_validate_url(
83
+ data.get("createConversation"), discovery_url, False
84
+ ),
85
+ execute_turn=_validate_url(data.get("executeTurn"), discovery_url, True),
86
+ continue_turn=_validate_url(data.get("continueTurn"), discovery_url, True),
87
+ subscribe=(
88
+ _validate_url(data["subscribe"], discovery_url, True)
89
+ if data.get("subscribe") is not None
90
+ else None
91
+ ),
92
+ )
93
+
94
+ def get_url(
95
+ self, operation: ConversationOperation, conversation_id: Optional[str] = None
96
+ ) -> str:
97
+ if operation == "createConversation":
98
+ return self.create_conversation
99
+ if not conversation_id:
100
+ raise ValueError(str(copilot_studio_errors.DiscoveryConversationIdRequired))
101
+ url = self.execute_turn if operation == "executeTurn" else self.subscribe
102
+ if url is None:
103
+ raise NotImplementedError(
104
+ str(copilot_studio_errors.DiscoverySubscribeNotSupported)
105
+ )
106
+ return url.replace(_CONVERSATION_ID, quote(conversation_id, safe=""))
@@ -1,13 +1,23 @@
1
1
  # Copyright (c) Microsoft Corporation. All rights reserved.
2
2
  # Licensed under the MIT License.
3
3
 
4
- import aiohttp
4
+ import asyncio
5
+ import json
5
6
  import logging
6
7
  from typing import AsyncIterable, Optional
7
8
 
9
+ import aiohttp
10
+
8
11
  from microsoft_agents.activity import Activity, ActivityTypes, ConversationAccount
9
12
 
13
+ from ._conversation_endpoint_discovery import (
14
+ ConversationEndpoints,
15
+ ConversationOperation,
16
+ get_discovery_url,
17
+ )
18
+ from .agent_type import AgentType
10
19
  from .connection_settings import ConnectionSettings
20
+ from .errors import copilot_studio_errors
11
21
  from .execute_turn_request import ExecuteTurnRequest
12
22
  from .power_platform_environment import PowerPlatformEnvironment
13
23
  from .start_request import StartRequest
@@ -34,6 +44,91 @@ class CopilotClient:
34
44
  self._logger = logging.getLogger(__name__)
35
45
  self.conversation_id = ""
36
46
  self._island_experimental_url = ""
47
+ self._discovery_lock = asyncio.Lock()
48
+ self._discovered_direct_connect_url: Optional[str] = None
49
+ self._discovered_endpoints: Optional[ConversationEndpoints] = None
50
+
51
+ async def _resolve_connection_url(
52
+ self, conversation_id: Optional[str] = None, subscribe: bool = False
53
+ ) -> str:
54
+ if (
55
+ self.settings.copilot_agent_type != AgentType.PREBUILT
56
+ and not self.settings.direct_connect_url
57
+ ):
58
+ async with self._discovery_lock:
59
+ if (
60
+ self.settings.copilot_agent_type != AgentType.PREBUILT
61
+ and not self.settings.direct_connect_url
62
+ ):
63
+ endpoints = await self._discover_endpoints()
64
+ if (
65
+ self.settings.copilot_agent_type != AgentType.PREBUILT
66
+ and not self.settings.direct_connect_url
67
+ ):
68
+ self._discovered_endpoints = endpoints
69
+ self._discovered_direct_connect_url = (
70
+ endpoints.create_conversation
71
+ )
72
+ self.settings.direct_connect_url = endpoints.create_conversation
73
+
74
+ if (
75
+ self.settings.copilot_agent_type != AgentType.PREBUILT
76
+ and self._discovered_endpoints is not None
77
+ and self.settings.direct_connect_url == self._discovered_direct_connect_url
78
+ ):
79
+ operation: ConversationOperation = (
80
+ "subscribe"
81
+ if subscribe
82
+ else "executeTurn" if conversation_id else "createConversation"
83
+ )
84
+ return self._discovered_endpoints.get_url(operation, conversation_id)
85
+ return PowerPlatformEnvironment.get_copilot_studio_connection_url(
86
+ settings=self.settings,
87
+ conversation_id=conversation_id,
88
+ create_subscribe_link=subscribe,
89
+ )
90
+
91
+ async def _discover_endpoints(self) -> ConversationEndpoints:
92
+ if not self._token or not self._token.strip():
93
+ raise ValueError(str(copilot_studio_errors.DiscoveryAccessTokenRequired))
94
+
95
+ url = get_discovery_url(self.settings)
96
+ headers = {
97
+ "Authorization": f"Bearer {self._token}",
98
+ "Accept": self.APPLICATION_JSON_TYPE,
99
+ "User-Agent": UserAgentHelper.get_user_agent_header(),
100
+ }
101
+ if self.settings.enable_diagnostics:
102
+ self._logger.debug(">>> SEND TO %s", url)
103
+
104
+ async with aiohttp.ClientSession(
105
+ **self.settings.client_session_settings
106
+ ) as session:
107
+ async with session.get(
108
+ url, headers=headers, allow_redirects=False
109
+ ) as response:
110
+ if response.status != 200:
111
+ self._logger.error(
112
+ "Error discovering conversation endpoints: %s", response.status
113
+ )
114
+ raise aiohttp.ClientResponseError(
115
+ response.request_info,
116
+ response.history,
117
+ status=response.status,
118
+ message="Error discovering conversation endpoints",
119
+ headers=response.headers,
120
+ )
121
+ try:
122
+ data = await response.json()
123
+ except (
124
+ json.JSONDecodeError,
125
+ UnicodeDecodeError,
126
+ aiohttp.ContentTypeError,
127
+ ) as exc:
128
+ raise ValueError(
129
+ str(copilot_studio_errors.InvalidDiscoveryResponse)
130
+ ) from exc
131
+ return ConversationEndpoints.from_response(data, url)
37
132
 
38
133
  async def post_request(
39
134
  self, url: str, data: dict, headers: dict
@@ -112,9 +207,7 @@ class CopilotClient:
112
207
  :return: An asynchronous iterable of Activity objects received in the response.
113
208
  """
114
209
 
115
- url = PowerPlatformEnvironment.get_copilot_studio_connection_url(
116
- settings=self.settings
117
- )
210
+ url = await self._resolve_connection_url()
118
211
  data = {"emitStartConversationEvent": emit_start_conversation_event}
119
212
  headers = {
120
213
  "Content-Type": self.APPLICATION_JSON_TYPE,
@@ -164,9 +257,7 @@ class CopilotClient:
164
257
  activity.conversation.id or self._current_conversation_id
165
258
  )
166
259
 
167
- url = PowerPlatformEnvironment.get_copilot_studio_connection_url(
168
- settings=self.settings, conversation_id=local_conversation_id
169
- )
260
+ url = await self._resolve_connection_url(local_conversation_id)
170
261
  data = ExecuteTurnRequest(activity=activity).model_dump(
171
262
  mode="json", by_alias=True, exclude_unset=True
172
263
  )
@@ -188,9 +279,7 @@ class CopilotClient:
188
279
  :return: An asynchronous iterable of Activity objects received in the response.
189
280
  """
190
281
 
191
- url = PowerPlatformEnvironment.get_copilot_studio_connection_url(
192
- settings=self.settings
193
- )
282
+ url = await self._resolve_connection_url()
194
283
  data = start_request.model_dump(mode="json", by_alias=True, exclude_unset=True)
195
284
  headers = {
196
285
  "Content-Type": self.APPLICATION_JSON_TYPE,
@@ -249,12 +338,7 @@ class CopilotClient:
249
338
  if not conversation_id:
250
339
  raise ValueError("CopilotClient.subscribe: conversation_id cannot be None")
251
340
 
252
- # Build the subscribe URL using the environment helper to ensure correct path and query handling
253
- url = PowerPlatformEnvironment.get_copilot_studio_connection_url(
254
- settings=self.settings,
255
- conversation_id=conversation_id,
256
- create_subscribe_link=True,
257
- )
341
+ url = await self._resolve_connection_url(conversation_id, subscribe=True)
258
342
  headers = {
259
343
  "Content-Type": self.APPLICATION_JSON_TYPE,
260
344
  "Authorization": f"Bearer {self._token}",
@@ -52,6 +52,31 @@ class CopilotStudioErrorResources:
52
52
  -65006,
53
53
  )
54
54
 
55
+ InvalidDiscoveryResponse = ErrorMessage(
56
+ "Conversation endpoint discovery returned an invalid JSON object.",
57
+ -65007,
58
+ )
59
+
60
+ InvalidDiscoveryOperationUrl = ErrorMessage(
61
+ "Conversation endpoint discovery returned an invalid operation URL.",
62
+ -65008,
63
+ )
64
+
65
+ DiscoveryConversationIdRequired = ErrorMessage(
66
+ "A conversation ID is required for this operation.",
67
+ -65009,
68
+ )
69
+
70
+ DiscoverySubscribeNotSupported = ErrorMessage(
71
+ "The agent does not advertise a subscribe endpoint.",
72
+ -65010,
73
+ )
74
+
75
+ DiscoveryAccessTokenRequired = ErrorMessage(
76
+ "A non-empty access token is required for conversation endpoint discovery.",
77
+ -65011,
78
+ )
79
+
55
80
  def __init__(self):
56
81
  """Initialize CopilotStudioErrorResources."""
57
82
  pass
@@ -322,6 +322,7 @@ class PowerPlatformEnvironment:
322
322
  if cloud == PowerPlatformCloud.OTHER and not cloud_base_address:
323
323
  raise ValueError(str(copilot_studio_errors.CloudBaseAddressRequired))
324
324
  cloud_base_address = cloud_base_address or "api.unknown.powerplatform.com"
325
+ cloud_base_address = urlparse(cloud_base_address).netloc or cloud_base_address
325
326
  normalized_resource_id = environment_id.lower().replace("-", "")
326
327
  id_suffix_length = PowerPlatformEnvironment.get_id_suffix_length(cloud)
327
328
  hex_prefix = normalized_resource_id[:-id_suffix_length]
@@ -1,3 +1,24 @@
1
+ Metadata-Version: 2.4
2
+ Name: microsoft-agents-copilotstudio-client
3
+ Version: 1.9.0.dev10
4
+ Summary: A client library for Microsoft Agents
5
+ Author: Microsoft Corporation
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/microsoft/Agents
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: Programming Language :: Python :: 3.10
10
+ Classifier: Programming Language :: Python :: 3.11
11
+ Classifier: Programming Language :: Python :: 3.12
12
+ Classifier: Programming Language :: Python :: 3.13
13
+ Classifier: Programming Language :: Python :: 3.14
14
+ Classifier: Operating System :: OS Independent
15
+ Requires-Python: >=3.10
16
+ Description-Content-Type: text/markdown
17
+ License-File: LICENSE
18
+ Requires-Dist: microsoft-agents-hosting-core==1.9.0.dev10
19
+ Dynamic: license-file
20
+ Dynamic: requires-dist
21
+
1
22
  # Microsoft Agents Copilot Studio Client
2
23
 
3
24
  [![PyPI version](https://img.shields.io/pypi/v/microsoft-agents-copilotstudio-client)](https://pypi.org/project/microsoft-agents-copilotstudio-client/)
@@ -209,6 +230,28 @@ def create_client():
209
230
  return copilot_client
210
231
  ```
211
232
 
233
+ #### Conversation Endpoint Discovery
234
+
235
+ For published agents without a `direct_connect_url`, the client first sends an
236
+ authenticated `GET` to
237
+ `https://<environment-host>/copilotstudio/agents/<escaped-schema-name>/conversation-endpoints`.
238
+ The host is selected from the configured environment and cloud. Discovery uses the
239
+ client's bearer token and the configured `client_session_settings`.
240
+
241
+ The client validates all advertised endpoints before storing them and uses each
242
+ operation URL as returned, preserving its query string and escaping conversation
243
+ IDs when substituting `{conversationId}`. `createConversation`, `executeTurn`, and
244
+ `continueTurn` are required; `subscribe` is optional and subscription fails
245
+ explicitly when the agent does not advertise it. The client has no continue-turn
246
+ operation.
247
+
248
+ Successful discovery is reused for the lifetime of the client, with concurrent
249
+ calls coordinated to avoid duplicate initialization. Failures and cancellation
250
+ allow a later call to retry. The discovered create URL is written to
251
+ `settings.direct_connect_url`; clearing that value triggers discovery again.
252
+ Assigning a different explicit URL switches back to legacy direct-URL routing.
253
+ Explicit direct URLs and prebuilt agents bypass discovery entirely.
254
+
212
255
  #### DirectConnect URL Mode (Simplified Setup)
213
256
 
214
257
  For simplified setup, you can use a DirectConnect URL instead of environment-based configuration:
@@ -555,4 +598,4 @@ audience = PowerPlatformEnvironment.get_token_audience(
555
598
  |Semantic Kernel Integration|A weather agent built with Semantic Kernel|[semantic-kernel-multiturn](https://github.com/microsoft/Agents/blob/main/samples/python/semantic-kernel-multiturn/README.md)|
556
599
  |Streaming Agent|Streams OpenAI responses|[azure-ai-streaming](https://github.com/microsoft/Agents/blob/main/samples/python/azureai-streaming/README.md)|
557
600
  |Copilot Studio Client|Console app to consume a Copilot Studio Agent|[copilotstudio-client](https://github.com/microsoft/Agents/blob/main/samples/python/copilotstudio-client/README.md)|
558
- |Cards Agent|Agent that uses rich cards to enhance conversation design |[cards](https://github.com/microsoft/Agents/blob/main/samples/python/cards/README.md)|
601
+ |Cards Agent|Agent that uses rich cards to enhance conversation design |[cards](https://github.com/microsoft/Agents/blob/main/samples/python/cards/README.md)|
@@ -5,6 +5,7 @@ pyproject.toml
5
5
  readme.md
6
6
  setup.py
7
7
  microsoft_agents/copilotstudio/client/__init__.py
8
+ microsoft_agents/copilotstudio/client/_conversation_endpoint_discovery.py
8
9
  microsoft_agents/copilotstudio/client/agent_type.py
9
10
  microsoft_agents/copilotstudio/client/connection_settings.py
10
11
  microsoft_agents/copilotstudio/client/copilot_client.py
@@ -1,24 +1,3 @@
1
- Metadata-Version: 2.4
2
- Name: microsoft-agents-copilotstudio-client
3
- Version: 1.9.0.dev5
4
- Summary: A client library for Microsoft Agents
5
- Author: Microsoft Corporation
6
- License-Expression: MIT
7
- Project-URL: Homepage, https://github.com/microsoft/Agents
8
- Classifier: Programming Language :: Python :: 3
9
- Classifier: Programming Language :: Python :: 3.10
10
- Classifier: Programming Language :: Python :: 3.11
11
- Classifier: Programming Language :: Python :: 3.12
12
- Classifier: Programming Language :: Python :: 3.13
13
- Classifier: Programming Language :: Python :: 3.14
14
- Classifier: Operating System :: OS Independent
15
- Requires-Python: >=3.10
16
- Description-Content-Type: text/markdown
17
- License-File: LICENSE
18
- Requires-Dist: microsoft-agents-hosting-core==1.9.0.dev5
19
- Dynamic: license-file
20
- Dynamic: requires-dist
21
-
22
1
  # Microsoft Agents Copilot Studio Client
23
2
 
24
3
  [![PyPI version](https://img.shields.io/pypi/v/microsoft-agents-copilotstudio-client)](https://pypi.org/project/microsoft-agents-copilotstudio-client/)
@@ -230,6 +209,28 @@ def create_client():
230
209
  return copilot_client
231
210
  ```
232
211
 
212
+ #### Conversation Endpoint Discovery
213
+
214
+ For published agents without a `direct_connect_url`, the client first sends an
215
+ authenticated `GET` to
216
+ `https://<environment-host>/copilotstudio/agents/<escaped-schema-name>/conversation-endpoints`.
217
+ The host is selected from the configured environment and cloud. Discovery uses the
218
+ client's bearer token and the configured `client_session_settings`.
219
+
220
+ The client validates all advertised endpoints before storing them and uses each
221
+ operation URL as returned, preserving its query string and escaping conversation
222
+ IDs when substituting `{conversationId}`. `createConversation`, `executeTurn`, and
223
+ `continueTurn` are required; `subscribe` is optional and subscription fails
224
+ explicitly when the agent does not advertise it. The client has no continue-turn
225
+ operation.
226
+
227
+ Successful discovery is reused for the lifetime of the client, with concurrent
228
+ calls coordinated to avoid duplicate initialization. Failures and cancellation
229
+ allow a later call to retry. The discovered create URL is written to
230
+ `settings.direct_connect_url`; clearing that value triggers discovery again.
231
+ Assigning a different explicit URL switches back to legacy direct-URL routing.
232
+ Explicit direct URLs and prebuilt agents bypass discovery entirely.
233
+
233
234
  #### DirectConnect URL Mode (Simplified Setup)
234
235
 
235
236
  For simplified setup, you can use a DirectConnect URL instead of environment-based configuration:
@@ -576,4 +577,4 @@ audience = PowerPlatformEnvironment.get_token_audience(
576
577
  |Semantic Kernel Integration|A weather agent built with Semantic Kernel|[semantic-kernel-multiturn](https://github.com/microsoft/Agents/blob/main/samples/python/semantic-kernel-multiturn/README.md)|
577
578
  |Streaming Agent|Streams OpenAI responses|[azure-ai-streaming](https://github.com/microsoft/Agents/blob/main/samples/python/azureai-streaming/README.md)|
578
579
  |Copilot Studio Client|Console app to consume a Copilot Studio Agent|[copilotstudio-client](https://github.com/microsoft/Agents/blob/main/samples/python/copilotstudio-client/README.md)|
579
- |Cards Agent|Agent that uses rich cards to enhance conversation design |[cards](https://github.com/microsoft/Agents/blob/main/samples/python/cards/README.md)|
580
+ |Cards Agent|Agent that uses rich cards to enhance conversation design |[cards](https://github.com/microsoft/Agents/blob/main/samples/python/cards/README.md)|
@@ -1 +0,0 @@
1
- 1.9.0.dev5
@@ -1 +0,0 @@
1
- microsoft-agents-hosting-core==1.9.0.dev5