Skip to content

Commit 81ef074

Browse files
authored
feat: support retrieving remote A2A agents (#1457)
1 parent 6628e0f commit 81ef074

File tree

8 files changed

+357
-8
lines changed

8 files changed

+357
-8
lines changed

packages/uipath-platform/pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "uipath-platform"
3-
version = "0.0.25"
3+
version = "0.0.26"
44
description = "HTTP client library for programmatic access to UiPath Platform"
55
readme = { file = "README.md", content-type = "text/markdown" }
66
requires-python = ">=3.11"

packages/uipath-platform/src/uipath/platform/_uipath.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
from .action_center import TasksService
99
from .agenthub._agenthub_service import AgentHubService
10+
from .agenthub._remote_a2a_service import RemoteA2aService
1011
from .chat import ConversationsService, UiPathLlmChatService, UiPathOpenAIService
1112
from .common import (
1213
ApiClient,
@@ -162,6 +163,10 @@ def guardrails(self) -> GuardrailsService:
162163
def agenthub(self) -> AgentHubService:
163164
return AgentHubService(self._config, self._execution_context, self.folders)
164165

166+
@property
167+
def remote_a2a(self) -> RemoteA2aService:
168+
return RemoteA2aService(self._config, self._execution_context, self.folders)
169+
165170
@property
166171
def orchestrator_setup(self) -> OrchestratorSetupService:
167172
return OrchestratorSetupService(self._config, self._execution_context)
Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
1-
"""UiPath AgentHub Models.
1+
"""UiPath AgentHub Models and Services.
22
3-
This module contains models related to UiPath AgentHub service.
3+
This module contains models and services related to UiPath AgentHub.
44
"""
55

6+
from uipath.platform.agenthub._remote_a2a_service import RemoteA2aService
67
from uipath.platform.agenthub.agenthub import LlmModel
8+
from uipath.platform.agenthub.remote_a2a import RemoteA2aAgent, RemoteA2aAgentFolder
79

8-
__all__ = ["LlmModel"]
10+
__all__ = ["LlmModel", "RemoteA2aAgent", "RemoteA2aAgentFolder", "RemoteA2aService"]
Lines changed: 283 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,283 @@
1+
"""Service for managing Remote A2A agents in UiPath AgentHub.
2+
3+
.. warning::
4+
This module is experimental and subject to change.
5+
The Remote A2A feature is in preview and its API may change in future releases.
6+
"""
7+
8+
import warnings
9+
from typing import Any, List
10+
11+
from ..common._base_service import BaseService
12+
from ..common._config import UiPathApiConfig
13+
from ..common._execution_context import UiPathExecutionContext
14+
from ..common._folder_context import FolderContext, header_folder
15+
from ..common._models import Endpoint, RequestSpec
16+
from ..orchestrator import FolderService
17+
from .remote_a2a import RemoteA2aAgent
18+
19+
20+
class RemoteA2aService(FolderContext, BaseService):
21+
"""Service for managing Remote A2A agents in UiPath AgentHub.
22+
23+
.. warning::
24+
This service is experimental and subject to change.
25+
"""
26+
27+
def __init__(
28+
self,
29+
config: UiPathApiConfig,
30+
execution_context: UiPathExecutionContext,
31+
folders_service: FolderService,
32+
) -> None:
33+
super().__init__(config=config, execution_context=execution_context)
34+
self._folders_service = folders_service
35+
36+
def list(
37+
self,
38+
*,
39+
top: int | None = None,
40+
skip: int | None = None,
41+
search: str | None = None,
42+
folder_path: str | None = None,
43+
) -> List[RemoteA2aAgent]:
44+
"""List Remote A2A agents.
45+
46+
.. warning::
47+
This method is experimental and subject to change.
48+
49+
When called without folder_path, returns all agents across
50+
the tenant that the user has access to. When called with a folder,
51+
returns only agents in that folder.
52+
53+
Args:
54+
top: Maximum number of agents to return.
55+
skip: Number of agents to skip (for pagination).
56+
search: Filter agents by name or slug.
57+
folder_path: Optional folder path to scope the query.
58+
59+
Returns:
60+
List[RemoteA2aAgent]: A list of Remote A2A agents.
61+
62+
Examples:
63+
```python
64+
from uipath import UiPath
65+
66+
client = UiPath()
67+
68+
# List all agents across tenant
69+
agents = client.remote_a2a.list()
70+
for agent in agents:
71+
print(f"{agent.name} - {agent.slug}")
72+
73+
# List with folder scope
74+
agents = client.remote_a2a.list(folder_path="MyFolder")
75+
```
76+
"""
77+
warnings.warn(
78+
"remote_a2a.list is experimental and subject to change.",
79+
stacklevel=2,
80+
)
81+
spec = self._list_spec(
82+
top=top,
83+
skip=skip,
84+
search=search,
85+
folder_path=folder_path,
86+
)
87+
response = self.request(
88+
spec.method,
89+
url=spec.endpoint,
90+
params=spec.params,
91+
headers=spec.headers,
92+
)
93+
data = response.json()
94+
return [RemoteA2aAgent.model_validate(agent) for agent in data.get("value", [])]
95+
96+
async def list_async(
97+
self,
98+
*,
99+
top: int | None = None,
100+
skip: int | None = None,
101+
search: str | None = None,
102+
folder_path: str | None = None,
103+
) -> List[RemoteA2aAgent]:
104+
"""Asynchronously list Remote A2A agents.
105+
106+
.. warning::
107+
This method is experimental and subject to change.
108+
109+
Args:
110+
top: Maximum number of agents to return.
111+
skip: Number of agents to skip (for pagination).
112+
search: Filter agents by name or slug.
113+
folder_path: Optional folder path to scope the query.
114+
115+
Returns:
116+
List[RemoteA2aAgent]: A list of Remote A2A agents.
117+
118+
Examples:
119+
```python
120+
import asyncio
121+
from uipath import UiPath
122+
123+
sdk = UiPath()
124+
125+
async def main():
126+
agents = await sdk.remote_a2a.list_async()
127+
for agent in agents:
128+
print(f"{agent.name} - {agent.slug}")
129+
130+
asyncio.run(main())
131+
```
132+
"""
133+
warnings.warn(
134+
"remote_a2a.list_async is experimental and subject to change.",
135+
stacklevel=2,
136+
)
137+
spec = self._list_spec(
138+
top=top,
139+
skip=skip,
140+
search=search,
141+
folder_path=folder_path,
142+
)
143+
response = await self.request_async(
144+
spec.method,
145+
url=spec.endpoint,
146+
params=spec.params,
147+
headers=spec.headers,
148+
)
149+
data = response.json()
150+
return [RemoteA2aAgent.model_validate(agent) for agent in data.get("value", [])]
151+
152+
def retrieve(
153+
self,
154+
slug: str,
155+
*,
156+
folder_path: str | None = None,
157+
) -> RemoteA2aAgent:
158+
"""Retrieve a specific Remote A2A agent by slug.
159+
160+
.. warning::
161+
This method is experimental and subject to change.
162+
163+
Args:
164+
slug: The unique slug identifier for the agent.
165+
folder_path: The folder path where the agent is located.
166+
167+
Returns:
168+
RemoteA2aAgent: The Remote A2A agent.
169+
170+
Examples:
171+
```python
172+
from uipath import UiPath
173+
174+
client = UiPath()
175+
176+
agent = client.remote_a2a.retrieve("weather", folder_path="MyFolder")
177+
print(f"Agent: {agent.name}, URL: {agent.a2a_url}")
178+
```
179+
"""
180+
warnings.warn(
181+
"remote_a2a.retrieve is experimental and subject to change.",
182+
stacklevel=2,
183+
)
184+
spec = self._retrieve_spec(slug=slug, folder_path=folder_path)
185+
response = self.request(
186+
spec.method,
187+
url=spec.endpoint,
188+
params=spec.params,
189+
headers=spec.headers,
190+
)
191+
return RemoteA2aAgent.model_validate(response.json())
192+
193+
async def retrieve_async(
194+
self,
195+
slug: str,
196+
*,
197+
folder_path: str | None = None,
198+
) -> RemoteA2aAgent:
199+
"""Asynchronously retrieve a specific Remote A2A agent by slug.
200+
201+
.. warning::
202+
This method is experimental and subject to change.
203+
204+
Args:
205+
slug: The unique slug identifier for the agent.
206+
folder_path: The folder path where the agent is located.
207+
208+
Returns:
209+
RemoteA2aAgent: The Remote A2A agent.
210+
211+
Examples:
212+
```python
213+
import asyncio
214+
from uipath import UiPath
215+
216+
sdk = UiPath()
217+
218+
async def main():
219+
agent = await sdk.remote_a2a.retrieve_async("weather", folder_path="MyFolder")
220+
print(f"Agent: {agent.name}, URL: {agent.a2a_url}")
221+
222+
asyncio.run(main())
223+
```
224+
"""
225+
warnings.warn(
226+
"remote_a2a.retrieve_async is experimental and subject to change.",
227+
stacklevel=2,
228+
)
229+
spec = self._retrieve_spec(slug=slug, folder_path=folder_path)
230+
response = await self.request_async(
231+
spec.method,
232+
url=spec.endpoint,
233+
params=spec.params,
234+
headers=spec.headers,
235+
)
236+
return RemoteA2aAgent.model_validate(response.json())
237+
238+
@property
239+
def custom_headers(self) -> dict[str, str]:
240+
return self.folder_headers
241+
242+
def _list_spec(
243+
self,
244+
*,
245+
top: int | None,
246+
skip: int | None,
247+
search: str | None,
248+
folder_path: str | None,
249+
) -> RequestSpec:
250+
headers = {}
251+
if folder_path is not None:
252+
folder_key = self._folders_service.retrieve_folder_key(folder_path)
253+
headers = header_folder(folder_key, None)
254+
255+
params: dict[str, Any] = {}
256+
if top is not None:
257+
params["top"] = top
258+
if skip is not None:
259+
params["skip"] = skip
260+
if search is not None:
261+
params["search"] = search
262+
263+
return RequestSpec(
264+
method="GET",
265+
endpoint=Endpoint("/agenthub_/api/remote-a2a-agents"),
266+
params=params,
267+
headers=headers,
268+
)
269+
270+
def _retrieve_spec(
271+
self,
272+
slug: str,
273+
*,
274+
folder_path: str | None,
275+
) -> RequestSpec:
276+
folder_key = self._folders_service.retrieve_folder_key(folder_path)
277+
return RequestSpec(
278+
method="GET",
279+
endpoint=Endpoint(f"/agenthub_/api/remote-a2a-agents/{slug}"),
280+
headers={
281+
**header_folder(folder_key, None),
282+
},
283+
)
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
"""Models for Remote A2A Agents in UiPath AgentHub.
2+
3+
.. warning::
4+
This module is experimental and subject to change.
5+
The Remote A2A feature is in preview and its API may change in future releases.
6+
"""
7+
8+
from datetime import datetime
9+
from typing import Any, Optional
10+
11+
from pydantic import BaseModel, ConfigDict, Field
12+
from pydantic.alias_generators import to_camel
13+
14+
15+
class RemoteA2aAgentFolder(BaseModel):
16+
"""Folder information for a Remote A2A agent."""
17+
18+
model_config = ConfigDict(
19+
alias_generator=to_camel,
20+
populate_by_name=True,
21+
use_enum_values=True,
22+
arbitrary_types_allowed=True,
23+
extra="allow",
24+
)
25+
26+
key: Optional[str] = None
27+
display_name: Optional[str] = None
28+
fully_qualified_name: Optional[str] = None
29+
30+
31+
class RemoteA2aAgent(BaseModel):
32+
"""Model representing a Remote A2A agent in UiPath AgentHub.
33+
34+
.. warning::
35+
This model is experimental and subject to change.
36+
"""
37+
38+
model_config = ConfigDict(
39+
alias_generator=to_camel,
40+
populate_by_name=True,
41+
use_enum_values=True,
42+
arbitrary_types_allowed=True,
43+
extra="allow",
44+
)
45+
46+
id: Optional[str] = None
47+
name: Optional[str] = None
48+
slug: Optional[str] = None
49+
description: Optional[str] = None
50+
agent_card_url: Optional[str] = None
51+
a2a_url: Optional[str] = Field(None, alias="a2aUrl")
52+
folder: Optional[RemoteA2aAgentFolder] = None
53+
headers: Optional[str] = None
54+
is_active: Optional[bool] = None
55+
cached_agent_card: Optional[Any] = None
56+
created_at: Optional[datetime] = None
57+
created_by: Optional[str] = None
58+
updated_at: Optional[datetime] = None
59+
updated_by: Optional[str] = None

packages/uipath-platform/uv.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)