From 736243431d9014276f5a424d05895dcfdf8fed86 Mon Sep 17 00:00:00 2001 From: Radu Mihai Gheorghe Date: Mon, 16 Mar 2026 19:55:53 +0200 Subject: [PATCH] feat: add a2a resource type --- packages/uipath/pyproject.toml | 2 +- .../uipath/src/uipath/agent/models/agent.py | 24 +- .../uipath/tests/agent/models/test_agent.py | 214 ++++++++++++++++++ packages/uipath/uv.lock | 2 +- 4 files changed, 239 insertions(+), 3 deletions(-) diff --git a/packages/uipath/pyproject.toml b/packages/uipath/pyproject.toml index 1c630606a..d141a81de 100644 --- a/packages/uipath/pyproject.toml +++ b/packages/uipath/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "uipath" -version = "2.10.15" +version = "2.10.16" description = "Python SDK and CLI for UiPath Platform, enabling programmatic interaction with automation services, process management, and deployment tools." readme = { file = "README.md", content-type = "text/markdown" } requires-python = ">=3.11" diff --git a/packages/uipath/src/uipath/agent/models/agent.py b/packages/uipath/src/uipath/agent/models/agent.py index c2c09533c..1ab47987f 100644 --- a/packages/uipath/src/uipath/agent/models/agent.py +++ b/packages/uipath/src/uipath/agent/models/agent.py @@ -100,6 +100,7 @@ class AgentResourceType(str, CaseInsensitiveEnum): CONTEXT = "context" ESCALATION = "escalation" MCP = "mcp" + A2A = "a2a" UNKNOWN = "unknown" # fallback branch discriminator @@ -303,6 +304,7 @@ class BaseAgentResourceConfig(BaseCfg): AgentResourceType.CONTEXT, AgentResourceType.ESCALATION, AgentResourceType.MCP, + AgentResourceType.A2A, AgentResourceType.UNKNOWN, ] = Field(alias="$resourceType") @@ -461,6 +463,25 @@ class AgentMcpResourceConfig(BaseAgentResourceConfig): ) +class AgentA2aResourceConfig(BaseAgentResourceConfig): + """Agent A2A resource configuration model.""" + + resource_type: Literal[AgentResourceType.A2A] = Field( + alias="$resourceType", default=AgentResourceType.A2A, frozen=True + ) + id: str + slug: str = Field(..., alias="slug") + agent_card_url: str = Field(default="", alias="agentCardUrl") + is_active: bool = Field(default=True, alias="isActive") + cached_agent_card: Optional[Dict[str, Any]] = Field( + default=None, alias="cachedAgentCard" + ) + created_at: Optional[str] = Field(default=None, alias="createdAt") + created_by: Optional[str] = Field(default=None, alias="createdBy") + updated_at: Optional[str] = Field(default=None, alias="updatedAt") + updated_by: Optional[str] = Field(default=None, alias="updatedBy") + + _RECIPIENT_TYPE_NORMALIZED_MAP: Mapping[int | str, AgentEscalationRecipientType] = { 1: AgentEscalationRecipientType.USER_ID, 2: AgentEscalationRecipientType.GROUP_ID, @@ -876,6 +897,7 @@ class AgentUnknownToolResourceConfig(BaseAgentToolResourceConfig): AgentContextResourceConfig, EscalationResourceConfig, # nested discrim on 'escalation_type' AgentMcpResourceConfig, + AgentA2aResourceConfig, AgentUnknownResourceConfig, # when parent sets resource_type="Unknown" ], Field(discriminator="resource_type"), @@ -1221,7 +1243,7 @@ def _normalize_guardrails(v: Dict[str, Any]) -> None: @staticmethod def _normalize_resources(v: Dict[str, Any]) -> None: - KNOWN_RES = {"tool", "context", "escalation", "mcp"} + KNOWN_RES = {"tool", "context", "escalation", "mcp", "a2a"} TOOL_MAP = { "agent": "Agent", "process": "Process", diff --git a/packages/uipath/tests/agent/models/test_agent.py b/packages/uipath/tests/agent/models/test_agent.py index 8a984f710..98b42e084 100644 --- a/packages/uipath/tests/agent/models/test_agent.py +++ b/packages/uipath/tests/agent/models/test_agent.py @@ -4,6 +4,7 @@ from pydantic import TypeAdapter from uipath.agent.models.agent import ( + AgentA2aResourceConfig, AgentBooleanOperator, AgentBooleanRule, AgentBuiltInValidatorGuardrail, @@ -3528,3 +3529,216 @@ def test_datafabric_entity_identifiers_empty(self): } parsed = AgentContextResourceConfig.model_validate(config) assert parsed.datafabric_entity_identifiers == [] + + def test_a2a_resource(self): + """Test that AgentDefinition can load A2A resources.""" + + json_data = { + "version": "1.0.0", + "id": "test-a2a-resource", + "name": "Agent with A2A Resource", + "metadata": {"isConversational": False, "storageVersion": "36.0.0"}, + "messages": [ + {"role": "System", "content": "You are an agentic assistant."} + ], + "inputSchema": {"type": "object", "properties": {}}, + "outputSchema": {"type": "object", "properties": {}}, + "settings": { + "model": "gpt-4o-2024-11-20", + "maxTokens": 16384, + "temperature": 0, + "engine": "basic-v2", + }, + "resources": [ + { + "$resourceType": "a2a", + "id": "755e2f7d-5a3d-47f3-8e9d-7ff0bf226357", + "name": "Philosopher Agent", + "slug": "philosopher-agent", + "description": "A philosophical agent that answers questions with wisdom and philosopher quotes", + "agentCardUrl": "", + "isActive": True, + "cachedAgentCard": { + "name": "Philosopher Agent", + "description": "Philosopher Agent assistant", + "url": "https://philosopher-agent.example.com/a2a/5045dca3", + "supportedInterfaces": [ + { + "url": "https://philosopher-agent.example.com/a2a/5045dca3", + "protocolBinding": "jsonrpc", + "protocolVersion": "1.0", + } + ], + "capabilities": { + "streaming": True, + "pushNotifications": False, + "stateTransitionHistory": False, + }, + "defaultInputModes": [ + "application/json", + "text/plain", + ], + "defaultOutputModes": [ + "application/json", + "text/plain", + ], + "skills": [ + { + "id": "5045dca3-main", + "name": "Philosopher Agent Capabilities", + "description": "Philosopher Agent assistant", + "tags": ["assistant", "langgraph"], + "examples": [], + "inputModes": [ + "application/json", + "text/plain", + ], + "outputModes": [ + "application/json", + "text/plain", + ], + "metadata": { + "inputSchema": { + "required": ["messages"], + "properties": ["messages"], + "supportsA2A": True, + } + }, + } + ], + "version": "0.7.70", + }, + "createdAt": "2026-03-15T10:12:47.9073065", + "createdBy": "f4bc4946-baed-4083-82b9-03d334bbacbe", + "updatedAt": None, + "updatedBy": None, + } + ], + "features": [], + "guardrails": [], + } + + config: AgentDefinition = TypeAdapter(AgentDefinition).validate_python( + json_data + ) + + # Validate A2A resource + a2a_resources = [ + r for r in config.resources if r.resource_type == AgentResourceType.A2A + ] + assert len(a2a_resources) == 1 + a2a_resource = a2a_resources[0] + assert isinstance(a2a_resource, AgentA2aResourceConfig) + assert a2a_resource.name == "Philosopher Agent" + assert a2a_resource.slug == "philosopher-agent" + assert ( + a2a_resource.description + == "A philosophical agent that answers questions with wisdom and philosopher quotes" + ) + assert a2a_resource.is_active is True + assert a2a_resource.agent_card_url == "" + assert a2a_resource.id == "755e2f7d-5a3d-47f3-8e9d-7ff0bf226357" + assert a2a_resource.created_at == "2026-03-15T10:12:47.9073065" + assert a2a_resource.created_by == "f4bc4946-baed-4083-82b9-03d334bbacbe" + assert a2a_resource.updated_at is None + assert a2a_resource.updated_by is None + + # Validate cached agent card is a plain dict + card = a2a_resource.cached_agent_card + assert isinstance(card, dict) + assert card["name"] == "Philosopher Agent" + assert card["url"] == "https://philosopher-agent.example.com/a2a/5045dca3" + assert card["version"] == "0.7.70" + assert len(card["supportedInterfaces"]) == 1 + assert card["supportedInterfaces"][0]["protocolBinding"] == "jsonrpc" + assert card["capabilities"]["streaming"] is True + assert len(card["skills"]) == 1 + assert card["skills"][0]["name"] == "Philosopher Agent Capabilities" + + def test_a2a_resource_without_cached_card(self): + """Test A2A resource with no cachedAgentCard.""" + + json_data = { + "version": "1.0.0", + "id": "test-a2a-no-card", + "name": "Agent with minimal A2A", + "metadata": {"isConversational": False, "storageVersion": "36.0.0"}, + "messages": [{"role": "System", "content": "You are an assistant."}], + "inputSchema": {"type": "object", "properties": {}}, + "outputSchema": {"type": "object", "properties": {}}, + "settings": { + "model": "gpt-4o-2024-11-20", + "maxTokens": 16384, + "temperature": 0, + "engine": "basic-v2", + }, + "resources": [ + { + "$resourceType": "a2a", + "id": "abc-123", + "name": "Minimal A2A Agent", + "slug": "minimal-a2a", + "description": "A minimal A2A agent", + "isActive": False, + } + ], + "features": [], + "guardrails": [], + } + + config: AgentDefinition = TypeAdapter(AgentDefinition).validate_python( + json_data + ) + + a2a_resources = [ + r for r in config.resources if r.resource_type == AgentResourceType.A2A + ] + assert len(a2a_resources) == 1 + a2a_resource = a2a_resources[0] + assert isinstance(a2a_resource, AgentA2aResourceConfig) + assert a2a_resource.name == "Minimal A2A Agent" + assert a2a_resource.slug == "minimal-a2a" + assert a2a_resource.is_active is False + assert a2a_resource.cached_agent_card is None + assert a2a_resource.agent_card_url == "" + assert a2a_resource.created_at is None + + def test_a2a_resource_case_insensitive(self): + """Test that A2A resource type is parsed case-insensitively.""" + + json_data = { + "version": "1.0.0", + "id": "test-a2a-case", + "name": "Agent A2A case test", + "metadata": {"isConversational": False, "storageVersion": "36.0.0"}, + "messages": [{"role": "System", "content": "You are an assistant."}], + "inputSchema": {"type": "object", "properties": {}}, + "outputSchema": {"type": "object", "properties": {}}, + "settings": { + "model": "gpt-4o-2024-11-20", + "maxTokens": 16384, + "temperature": 0, + "engine": "basic-v2", + }, + "resources": [ + { + "$resourceType": "A2A", + "id": "case-test-id", + "name": "Case Test Agent", + "slug": "case-test", + "description": "Testing case insensitive parsing", + } + ], + "features": [], + "guardrails": [], + } + + config: AgentDefinition = TypeAdapter(AgentDefinition).validate_python( + json_data + ) + + a2a_resources = [ + r for r in config.resources if r.resource_type == AgentResourceType.A2A + ] + assert len(a2a_resources) == 1 + assert isinstance(a2a_resources[0], AgentA2aResourceConfig) diff --git a/packages/uipath/uv.lock b/packages/uipath/uv.lock index 654e6fd31..30b9106ab 100644 --- a/packages/uipath/uv.lock +++ b/packages/uipath/uv.lock @@ -2540,7 +2540,7 @@ wheels = [ [[package]] name = "uipath" -version = "2.10.15" +version = "2.10.16" source = { editable = "." } dependencies = [ { name = "applicationinsights" },