Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/uipath/pyproject.toml
Original file line number Diff line number Diff line change
@@ -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"
Expand Down
24 changes: 23 additions & 1 deletion packages/uipath/src/uipath/agent/models/agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ class AgentResourceType(str, CaseInsensitiveEnum):
CONTEXT = "context"
ESCALATION = "escalation"
MCP = "mcp"
A2A = "a2a"
UNKNOWN = "unknown" # fallback branch discriminator


Expand Down Expand Up @@ -303,6 +304,7 @@ class BaseAgentResourceConfig(BaseCfg):
AgentResourceType.CONTEXT,
AgentResourceType.ESCALATION,
AgentResourceType.MCP,
AgentResourceType.A2A,
AgentResourceType.UNKNOWN,
] = Field(alias="$resourceType")

Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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"),
Expand Down Expand Up @@ -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",
Expand Down
214 changes: 214 additions & 0 deletions packages/uipath/tests/agent/models/test_agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from pydantic import TypeAdapter

from uipath.agent.models.agent import (
AgentA2aResourceConfig,
AgentBooleanOperator,
AgentBooleanRule,
AgentBuiltInValidatorGuardrail,
Expand Down Expand Up @@ -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)
2 changes: 1 addition & 1 deletion packages/uipath/uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading