Skip to content

Commit 6628e0f

Browse files
authored
feat: add a2a resource type (#1452)
1 parent fe3eea0 commit 6628e0f

File tree

4 files changed

+239
-3
lines changed

4 files changed

+239
-3
lines changed

packages/uipath/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"
3-
version = "2.10.15"
3+
version = "2.10.16"
44
description = "Python SDK and CLI for UiPath Platform, enabling programmatic interaction with automation services, process management, and deployment tools."
55
readme = { file = "README.md", content-type = "text/markdown" }
66
requires-python = ">=3.11"

packages/uipath/src/uipath/agent/models/agent.py

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@ class AgentResourceType(str, CaseInsensitiveEnum):
100100
CONTEXT = "context"
101101
ESCALATION = "escalation"
102102
MCP = "mcp"
103+
A2A = "a2a"
103104
UNKNOWN = "unknown" # fallback branch discriminator
104105

105106

@@ -303,6 +304,7 @@ class BaseAgentResourceConfig(BaseCfg):
303304
AgentResourceType.CONTEXT,
304305
AgentResourceType.ESCALATION,
305306
AgentResourceType.MCP,
307+
AgentResourceType.A2A,
306308
AgentResourceType.UNKNOWN,
307309
] = Field(alias="$resourceType")
308310

@@ -461,6 +463,25 @@ class AgentMcpResourceConfig(BaseAgentResourceConfig):
461463
)
462464

463465

466+
class AgentA2aResourceConfig(BaseAgentResourceConfig):
467+
"""Agent A2A resource configuration model."""
468+
469+
resource_type: Literal[AgentResourceType.A2A] = Field(
470+
alias="$resourceType", default=AgentResourceType.A2A, frozen=True
471+
)
472+
id: str
473+
slug: str = Field(..., alias="slug")
474+
agent_card_url: str = Field(default="", alias="agentCardUrl")
475+
is_active: bool = Field(default=True, alias="isActive")
476+
cached_agent_card: Optional[Dict[str, Any]] = Field(
477+
default=None, alias="cachedAgentCard"
478+
)
479+
created_at: Optional[str] = Field(default=None, alias="createdAt")
480+
created_by: Optional[str] = Field(default=None, alias="createdBy")
481+
updated_at: Optional[str] = Field(default=None, alias="updatedAt")
482+
updated_by: Optional[str] = Field(default=None, alias="updatedBy")
483+
484+
464485
_RECIPIENT_TYPE_NORMALIZED_MAP: Mapping[int | str, AgentEscalationRecipientType] = {
465486
1: AgentEscalationRecipientType.USER_ID,
466487
2: AgentEscalationRecipientType.GROUP_ID,
@@ -876,6 +897,7 @@ class AgentUnknownToolResourceConfig(BaseAgentToolResourceConfig):
876897
AgentContextResourceConfig,
877898
EscalationResourceConfig, # nested discrim on 'escalation_type'
878899
AgentMcpResourceConfig,
900+
AgentA2aResourceConfig,
879901
AgentUnknownResourceConfig, # when parent sets resource_type="Unknown"
880902
],
881903
Field(discriminator="resource_type"),
@@ -1221,7 +1243,7 @@ def _normalize_guardrails(v: Dict[str, Any]) -> None:
12211243

12221244
@staticmethod
12231245
def _normalize_resources(v: Dict[str, Any]) -> None:
1224-
KNOWN_RES = {"tool", "context", "escalation", "mcp"}
1246+
KNOWN_RES = {"tool", "context", "escalation", "mcp", "a2a"}
12251247
TOOL_MAP = {
12261248
"agent": "Agent",
12271249
"process": "Process",

packages/uipath/tests/agent/models/test_agent.py

Lines changed: 214 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from pydantic import TypeAdapter
55

66
from uipath.agent.models.agent import (
7+
AgentA2aResourceConfig,
78
AgentBooleanOperator,
89
AgentBooleanRule,
910
AgentBuiltInValidatorGuardrail,
@@ -3528,3 +3529,216 @@ def test_datafabric_entity_identifiers_empty(self):
35283529
}
35293530
parsed = AgentContextResourceConfig.model_validate(config)
35303531
assert parsed.datafabric_entity_identifiers == []
3532+
3533+
def test_a2a_resource(self):
3534+
"""Test that AgentDefinition can load A2A resources."""
3535+
3536+
json_data = {
3537+
"version": "1.0.0",
3538+
"id": "test-a2a-resource",
3539+
"name": "Agent with A2A Resource",
3540+
"metadata": {"isConversational": False, "storageVersion": "36.0.0"},
3541+
"messages": [
3542+
{"role": "System", "content": "You are an agentic assistant."}
3543+
],
3544+
"inputSchema": {"type": "object", "properties": {}},
3545+
"outputSchema": {"type": "object", "properties": {}},
3546+
"settings": {
3547+
"model": "gpt-4o-2024-11-20",
3548+
"maxTokens": 16384,
3549+
"temperature": 0,
3550+
"engine": "basic-v2",
3551+
},
3552+
"resources": [
3553+
{
3554+
"$resourceType": "a2a",
3555+
"id": "755e2f7d-5a3d-47f3-8e9d-7ff0bf226357",
3556+
"name": "Philosopher Agent",
3557+
"slug": "philosopher-agent",
3558+
"description": "A philosophical agent that answers questions with wisdom and philosopher quotes",
3559+
"agentCardUrl": "",
3560+
"isActive": True,
3561+
"cachedAgentCard": {
3562+
"name": "Philosopher Agent",
3563+
"description": "Philosopher Agent assistant",
3564+
"url": "https://philosopher-agent.example.com/a2a/5045dca3",
3565+
"supportedInterfaces": [
3566+
{
3567+
"url": "https://philosopher-agent.example.com/a2a/5045dca3",
3568+
"protocolBinding": "jsonrpc",
3569+
"protocolVersion": "1.0",
3570+
}
3571+
],
3572+
"capabilities": {
3573+
"streaming": True,
3574+
"pushNotifications": False,
3575+
"stateTransitionHistory": False,
3576+
},
3577+
"defaultInputModes": [
3578+
"application/json",
3579+
"text/plain",
3580+
],
3581+
"defaultOutputModes": [
3582+
"application/json",
3583+
"text/plain",
3584+
],
3585+
"skills": [
3586+
{
3587+
"id": "5045dca3-main",
3588+
"name": "Philosopher Agent Capabilities",
3589+
"description": "Philosopher Agent assistant",
3590+
"tags": ["assistant", "langgraph"],
3591+
"examples": [],
3592+
"inputModes": [
3593+
"application/json",
3594+
"text/plain",
3595+
],
3596+
"outputModes": [
3597+
"application/json",
3598+
"text/plain",
3599+
],
3600+
"metadata": {
3601+
"inputSchema": {
3602+
"required": ["messages"],
3603+
"properties": ["messages"],
3604+
"supportsA2A": True,
3605+
}
3606+
},
3607+
}
3608+
],
3609+
"version": "0.7.70",
3610+
},
3611+
"createdAt": "2026-03-15T10:12:47.9073065",
3612+
"createdBy": "f4bc4946-baed-4083-82b9-03d334bbacbe",
3613+
"updatedAt": None,
3614+
"updatedBy": None,
3615+
}
3616+
],
3617+
"features": [],
3618+
"guardrails": [],
3619+
}
3620+
3621+
config: AgentDefinition = TypeAdapter(AgentDefinition).validate_python(
3622+
json_data
3623+
)
3624+
3625+
# Validate A2A resource
3626+
a2a_resources = [
3627+
r for r in config.resources if r.resource_type == AgentResourceType.A2A
3628+
]
3629+
assert len(a2a_resources) == 1
3630+
a2a_resource = a2a_resources[0]
3631+
assert isinstance(a2a_resource, AgentA2aResourceConfig)
3632+
assert a2a_resource.name == "Philosopher Agent"
3633+
assert a2a_resource.slug == "philosopher-agent"
3634+
assert (
3635+
a2a_resource.description
3636+
== "A philosophical agent that answers questions with wisdom and philosopher quotes"
3637+
)
3638+
assert a2a_resource.is_active is True
3639+
assert a2a_resource.agent_card_url == ""
3640+
assert a2a_resource.id == "755e2f7d-5a3d-47f3-8e9d-7ff0bf226357"
3641+
assert a2a_resource.created_at == "2026-03-15T10:12:47.9073065"
3642+
assert a2a_resource.created_by == "f4bc4946-baed-4083-82b9-03d334bbacbe"
3643+
assert a2a_resource.updated_at is None
3644+
assert a2a_resource.updated_by is None
3645+
3646+
# Validate cached agent card is a plain dict
3647+
card = a2a_resource.cached_agent_card
3648+
assert isinstance(card, dict)
3649+
assert card["name"] == "Philosopher Agent"
3650+
assert card["url"] == "https://philosopher-agent.example.com/a2a/5045dca3"
3651+
assert card["version"] == "0.7.70"
3652+
assert len(card["supportedInterfaces"]) == 1
3653+
assert card["supportedInterfaces"][0]["protocolBinding"] == "jsonrpc"
3654+
assert card["capabilities"]["streaming"] is True
3655+
assert len(card["skills"]) == 1
3656+
assert card["skills"][0]["name"] == "Philosopher Agent Capabilities"
3657+
3658+
def test_a2a_resource_without_cached_card(self):
3659+
"""Test A2A resource with no cachedAgentCard."""
3660+
3661+
json_data = {
3662+
"version": "1.0.0",
3663+
"id": "test-a2a-no-card",
3664+
"name": "Agent with minimal A2A",
3665+
"metadata": {"isConversational": False, "storageVersion": "36.0.0"},
3666+
"messages": [{"role": "System", "content": "You are an assistant."}],
3667+
"inputSchema": {"type": "object", "properties": {}},
3668+
"outputSchema": {"type": "object", "properties": {}},
3669+
"settings": {
3670+
"model": "gpt-4o-2024-11-20",
3671+
"maxTokens": 16384,
3672+
"temperature": 0,
3673+
"engine": "basic-v2",
3674+
},
3675+
"resources": [
3676+
{
3677+
"$resourceType": "a2a",
3678+
"id": "abc-123",
3679+
"name": "Minimal A2A Agent",
3680+
"slug": "minimal-a2a",
3681+
"description": "A minimal A2A agent",
3682+
"isActive": False,
3683+
}
3684+
],
3685+
"features": [],
3686+
"guardrails": [],
3687+
}
3688+
3689+
config: AgentDefinition = TypeAdapter(AgentDefinition).validate_python(
3690+
json_data
3691+
)
3692+
3693+
a2a_resources = [
3694+
r for r in config.resources if r.resource_type == AgentResourceType.A2A
3695+
]
3696+
assert len(a2a_resources) == 1
3697+
a2a_resource = a2a_resources[0]
3698+
assert isinstance(a2a_resource, AgentA2aResourceConfig)
3699+
assert a2a_resource.name == "Minimal A2A Agent"
3700+
assert a2a_resource.slug == "minimal-a2a"
3701+
assert a2a_resource.is_active is False
3702+
assert a2a_resource.cached_agent_card is None
3703+
assert a2a_resource.agent_card_url == ""
3704+
assert a2a_resource.created_at is None
3705+
3706+
def test_a2a_resource_case_insensitive(self):
3707+
"""Test that A2A resource type is parsed case-insensitively."""
3708+
3709+
json_data = {
3710+
"version": "1.0.0",
3711+
"id": "test-a2a-case",
3712+
"name": "Agent A2A case test",
3713+
"metadata": {"isConversational": False, "storageVersion": "36.0.0"},
3714+
"messages": [{"role": "System", "content": "You are an assistant."}],
3715+
"inputSchema": {"type": "object", "properties": {}},
3716+
"outputSchema": {"type": "object", "properties": {}},
3717+
"settings": {
3718+
"model": "gpt-4o-2024-11-20",
3719+
"maxTokens": 16384,
3720+
"temperature": 0,
3721+
"engine": "basic-v2",
3722+
},
3723+
"resources": [
3724+
{
3725+
"$resourceType": "A2A",
3726+
"id": "case-test-id",
3727+
"name": "Case Test Agent",
3728+
"slug": "case-test",
3729+
"description": "Testing case insensitive parsing",
3730+
}
3731+
],
3732+
"features": [],
3733+
"guardrails": [],
3734+
}
3735+
3736+
config: AgentDefinition = TypeAdapter(AgentDefinition).validate_python(
3737+
json_data
3738+
)
3739+
3740+
a2a_resources = [
3741+
r for r in config.resources if r.resource_type == AgentResourceType.A2A
3742+
]
3743+
assert len(a2a_resources) == 1
3744+
assert isinstance(a2a_resources[0], AgentA2aResourceConfig)

packages/uipath/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)