Skip to content

Commit 4e26bd0

Browse files
feat(agent): add QuickForm escalation channel models
Introduce a discriminated-union hierarchy for escalation channels (BaseEscalationChannelProperties, AgentActionCenterEscalationChannel, AgentQuickFormEscalationChannel) so QuickForm escalations carry their FormLib schema instead of an Action Center app reference. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 4a1489b commit 4e26bd0

4 files changed

Lines changed: 132 additions & 38 deletions

File tree

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.78"
3+
version = "2.10.79"
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: 73 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,14 @@ class AgentEscalationRecipientType(str, CaseInsensitiveEnum):
142142
ARGUMENT_GROUP_NAME = "ArgumentGroupName"
143143

144144

145+
class AgentEscalationChannelType(str, CaseInsensitiveEnum):
146+
"""Agent escalation channel type enumeration."""
147+
148+
ACTION_CENTER = "actionCenter"
149+
ACTION_CENTER_QUICK_FORM = "actionCenterQuickForm"
150+
UNKNOWN = "unknown" # fallback branch discriminator
151+
152+
145153
class AgentContextRetrievalMode(str, CaseInsensitiveEnum):
146154
"""Agent context retrieval mode enumeration."""
147155

@@ -691,13 +699,9 @@ def _resolve_task_title(v: Any) -> Any:
691699
return v
692700

693701

694-
class AgentEscalationChannelProperties(BaseResourceProperties):
695-
"""Agent escalation channel properties model."""
702+
class BaseEscalationChannelProperties(BaseResourceProperties):
703+
"""Fields shared by every escalation channel's properties."""
696704

697-
app_name: str | None = Field(default=None, alias="appName")
698-
app_version: int = Field(..., alias="appVersion")
699-
folder_name: Optional[str] = Field(None, alias="folderName")
700-
resource_key: str | None = Field(default=None, alias="resourceKey")
701705
is_actionable_message_enabled: Optional[bool] = Field(
702706
None, alias="isActionableMessageEnabled"
703707
)
@@ -706,29 +710,44 @@ class AgentEscalationChannelProperties(BaseResourceProperties):
706710
)
707711

708712

709-
class AgentEscalationChannel(BaseCfg):
710-
"""Agent escalation channel model."""
713+
class AgentEscalationChannelProperties(BaseEscalationChannelProperties):
714+
"""Action Center app-task channel properties (channel type ``actionCenter``)."""
715+
716+
app_name: str | None = Field(default=None, alias="appName")
717+
app_version: int = Field(..., alias="appVersion")
718+
folder_name: Optional[str] = Field(None, alias="folderName")
719+
resource_key: str | None = Field(default=None, alias="resourceKey")
720+
721+
722+
class AgentQuickFormChannelProperties(BaseEscalationChannelProperties):
723+
"""Quick Form channel properties (channel type ``actionCenterQuickForm``)."""
724+
725+
schema: Dict[str, Any] = Field(...) # type: ignore[assignment]
726+
727+
@property
728+
def schema_id(self) -> str | None:
729+
"""Return the schema id nested inside schema."""
730+
return self.schema.get("schemaId")
731+
732+
733+
class BaseAgentEscalationChannel(BaseCfg):
734+
"""Fields shared by every escalation channel variant."""
711735

712736
id: Optional[str] = Field(None, alias="id")
713737
name: str = Field(..., alias="name")
714-
type: str = Field(alias="type")
715738
description: str = Field(..., alias="description")
716739
input_schema: Dict[str, Any] = Field(..., alias="inputSchema")
717740
output_schema: Dict[str, Any] = Field(EMPTY_SCHEMA, alias="outputSchema")
718741
argument_properties: Dict[str, AgentToolArgumentProperties] = Field(
719742
{}, alias="argumentProperties"
720743
)
721744
outcome_mapping: Optional[Dict[str, str]] = Field(None, alias="outcomeMapping")
722-
properties: AgentEscalationChannelProperties = Field(..., alias="properties")
723745
recipients: List[AgentEscalationRecipient] = Field(..., alias="recipients")
724746
task_title: Optional[Union[str, TaskTitle]] = Field(
725747
default="Escalation Task", alias="taskTitle"
726748
)
727749
priority: Optional[str] = None
728750
labels: List[str] = Field(default_factory=list)
729-
# schema_body avoids shadowing pydantic.BaseModel.schema(); JSON alias stays "schema".
730-
schema_id: Optional[str] = Field(None, alias="schemaId")
731-
schema_body: Optional[Dict[str, Any]] = Field(None, alias="schema")
732751

733752
@model_validator(mode="before")
734753
@classmethod
@@ -737,6 +756,46 @@ def _apply_task_title_resolution(cls, v: Any) -> Any:
737756
return _resolve_task_title(v)
738757

739758

759+
class AgentActionCenterEscalationChannel(BaseAgentEscalationChannel):
760+
"""Action Center app-task escalation channel."""
761+
762+
type: Literal[AgentEscalationChannelType.ACTION_CENTER] = Field(
763+
default=AgentEscalationChannelType.ACTION_CENTER, alias="type"
764+
)
765+
properties: AgentEscalationChannelProperties = Field(..., alias="properties")
766+
767+
768+
class AgentQuickFormEscalationChannel(BaseAgentEscalationChannel):
769+
"""Quick Form escalation channel; FormLib schema lives in ``properties.schema``."""
770+
771+
type: Literal[AgentEscalationChannelType.ACTION_CENTER_QUICK_FORM] = Field(
772+
default=AgentEscalationChannelType.ACTION_CENTER_QUICK_FORM, alias="type"
773+
)
774+
properties: AgentQuickFormChannelProperties = Field(..., alias="properties")
775+
776+
777+
class AgentUnknownEscalationChannel(BaseAgentEscalationChannel):
778+
"""Fallback for unknown or future escalation channel types."""
779+
780+
type: Literal[AgentEscalationChannelType.UNKNOWN] = Field(
781+
default=AgentEscalationChannelType.UNKNOWN, alias="type"
782+
)
783+
properties: BaseEscalationChannelProperties = Field(
784+
default_factory=BaseEscalationChannelProperties, alias="properties"
785+
)
786+
787+
788+
AgentEscalationChannel = Annotated[
789+
Union[
790+
AgentActionCenterEscalationChannel,
791+
AgentQuickFormEscalationChannel,
792+
AgentUnknownEscalationChannel,
793+
],
794+
Field(discriminator="type"),
795+
_case_insensitive_enum_validator("type", AgentEscalationChannelType),
796+
]
797+
798+
740799
class AgentEscalationResourceConfig(BaseAgentResourceConfig):
741800
"""Agent escalation resource configuration model."""
742801

@@ -772,23 +831,6 @@ class AgentIxpVsEscalationResourceConfig(BaseAgentResourceConfig):
772831
)
773832

774833

775-
class AgentQuickFormEscalationResourceConfig(BaseAgentResourceConfig):
776-
"""Quick Form Agent escalation resource configuration model (escalationType=2).
777-
778-
Quick Form escalations render a schema-first HITL task in Action Center via FormLib.
779-
The schema (and its key) live on the channel (see AgentEscalationChannel.schema_id /
780-
schema) and are sent inline to Orchestrator's GenericTasks/CreateTask endpoint.
781-
"""
782-
783-
id: Optional[str] = Field(None, alias="id")
784-
resource_type: Literal[AgentResourceType.ESCALATION] = Field(
785-
alias="$resourceType", default=AgentResourceType.ESCALATION, frozen=True
786-
)
787-
channels: List[AgentEscalationChannel] = Field(alias="channels")
788-
is_agent_memory_enabled: bool = Field(default=False, alias="isAgentMemoryEnabled")
789-
escalation_type: Literal[2] = Field(default=2, alias="escalationType")
790-
791-
792834
class BaseAgentToolResourceConfig(BaseAgentResourceConfig):
793835
"""Base agent tool resource configuration model."""
794836

@@ -994,11 +1036,11 @@ class AgentUnknownToolResourceConfig(BaseAgentToolResourceConfig):
9941036
Field(discriminator="type"),
9951037
]
9961038

1039+
9971040
EscalationResourceConfig = Annotated[
9981041
Union[
9991042
Annotated[AgentEscalationResourceConfig, Tag(0)],
10001043
Annotated[AgentIxpVsEscalationResourceConfig, Tag(1)],
1001-
Annotated[AgentQuickFormEscalationResourceConfig, Tag(2)],
10021044
],
10031045
Discriminator(lambda v: v.get("escalation_type") or v.get("escalationType") or 0),
10041046
]

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

Lines changed: 56 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
from uipath.agent.models.agent import (
77
AgentA2aResourceConfig,
8+
AgentActionCenterEscalationChannel,
89
AgentBooleanOperator,
910
AgentBooleanRule,
1011
AgentBuiltInValidatorGuardrail,
@@ -14,7 +15,6 @@
1415
AgentContextType,
1516
AgentCustomGuardrail,
1617
AgentDefinition,
17-
AgentEscalationChannel,
1818
AgentEscalationRecipient,
1919
AgentEscalationRecipientType,
2020
AgentEscalationResourceConfig,
@@ -36,6 +36,7 @@
3636
AgentNumberOperator,
3737
AgentNumberRule,
3838
AgentProcessToolResourceConfig,
39+
AgentQuickFormChannelProperties,
3940
AgentResourceType,
4041
AgentToolArgumentPropertiesVariant,
4142
AgentToolType,
@@ -2607,11 +2608,62 @@ def test_agent_with_ixp_vs_escalation(self):
26072608
assert len(channel.recipients) == 0
26082609

26092610
# Validate channel properties
2611+
assert isinstance(channel, AgentActionCenterEscalationChannel)
26102612
assert channel.properties.app_name is None
26112613
assert channel.properties.app_version == 1
26122614
assert channel.properties.folder_name is None
26132615
assert channel.properties.resource_key is None
26142616

2617+
def test_quick_form_channel_properties_derive_schema_id_from_body(self):
2618+
"""schema_id reads the schemaId nested inside the schema body."""
2619+
2620+
props = AgentQuickFormChannelProperties.model_validate(
2621+
{
2622+
"schema": {
2623+
"schemaId": "e74ebb74-80ba-47b9-a370-532a1ba4c41e",
2624+
"fields": [],
2625+
"outcomes": [],
2626+
},
2627+
}
2628+
)
2629+
assert props.schema_id == "e74ebb74-80ba-47b9-a370-532a1ba4c41e"
2630+
2631+
def test_quick_form_channel_properties_schema_id_none_when_absent(self):
2632+
"""schema_id is None when the schema body carries no schemaId."""
2633+
2634+
props = AgentQuickFormChannelProperties.model_validate(
2635+
{"schema": {"fields": [], "outcomes": []}}
2636+
)
2637+
assert props.schema_id is None
2638+
2639+
def test_quick_form_channel_properties_require_schema(self):
2640+
with pytest.raises(ValidationError):
2641+
AgentQuickFormChannelProperties.model_validate(
2642+
{"isActionableMessageEnabled": False}
2643+
)
2644+
2645+
def test_quick_form_channel_requires_schema(self):
2646+
"""A quick-form channel without a schema fails to parse."""
2647+
with pytest.raises(ValidationError):
2648+
TypeAdapter(AgentEscalationResourceConfig).validate_python(
2649+
{
2650+
"$resourceType": "escalation",
2651+
"name": "Escalation",
2652+
"description": "",
2653+
"channels": [
2654+
{
2655+
"name": "c",
2656+
"description": "",
2657+
"inputSchema": {"type": "object", "properties": {}},
2658+
"type": "actionCenterQuickForm",
2659+
"recipients": [],
2660+
"properties": {"isActionableMessageEnabled": False},
2661+
}
2662+
],
2663+
"isAgentMemoryEnabled": False,
2664+
}
2665+
)
2666+
26152667
def test_task_title_text_builder_type(self):
26162668
"""Test TextBuilderTaskTitle with tokens."""
26172669
from uipath.agent.models.agent import (
@@ -2664,7 +2716,7 @@ def test_escalation_channel_uses_task_title_v2_when_present(self):
26642716
},
26652717
}
26662718

2667-
channel = AgentEscalationChannel(**channel_data) # type: ignore[arg-type]
2719+
channel = AgentActionCenterEscalationChannel(**channel_data) # type: ignore[arg-type]
26682720

26692721
assert isinstance(channel.task_title, dict) or hasattr(
26702722
channel.task_title, "tokens"
@@ -2688,7 +2740,7 @@ def test_escalation_channel_uses_legacy_task_title(self):
26882740
"taskTitle": "Legacy Task Title",
26892741
}
26902742

2691-
channel = AgentEscalationChannel(**channel_data) # type: ignore[arg-type]
2743+
channel = AgentActionCenterEscalationChannel(**channel_data) # type: ignore[arg-type]
26922744

26932745
assert channel.task_title == "Legacy Task Title"
26942746

@@ -2709,7 +2761,7 @@ def test_escalation_channel_defaults_to_escalation_task(self):
27092761
"recipients": [],
27102762
}
27112763

2712-
channel = AgentEscalationChannel(**channel_data) # type: ignore[arg-type]
2764+
channel = AgentActionCenterEscalationChannel(**channel_data) # type: ignore[arg-type]
27132765

27142766
assert channel.task_title == "Escalation Task"
27152767

packages/uipath/uv.lock

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)