Skip to content

Commit 6c1f50e

Browse files
authored
feat: coded functions (#1254)
1 parent ce7a8a1 commit 6c1f50e

File tree

7 files changed

+72
-21
lines changed

7 files changed

+72
-21
lines changed

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.9.15"
3+
version = "2.10.0"
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"

src/uipath/_cli/cli_init.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -369,7 +369,7 @@ async def initialize() -> list[UiPathRuntimeSchema]:
369369

370370
if not entrypoints:
371371
console.warning(
372-
'No function entrypoints found. Add them to `uipath.json` under "functions": {"my_function": "src/main.py:main"}'
372+
'No entrypoints found. Add them to `uipath.json` under "functions" or "agents": {"my_function": "src/main.py:main"}'
373373
)
374374

375375
# Gather schemas from all discovered runtimes

src/uipath/_cli/models/uipath_json_schema.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,12 @@ class UiPathJsonConfig(BaseModelWithDefaultConfig):
8989
"Each key is an entrypoint name, and each value is a path in format 'file_path:function_name'",
9090
)
9191

92+
agents: dict[str, str] = Field(
93+
default_factory=dict,
94+
description="Entrypoint definitions for agent scripts. "
95+
"Each key is an entrypoint name, and each value is a path in format 'file_path:agent_name'",
96+
)
97+
9298
def to_json_string(self, indent: int = 2) -> str:
9399
"""Export to JSON string with proper formatting."""
94100
return self.model_dump_json(
@@ -110,6 +116,7 @@ def create_default(cls) -> "UiPathJsonConfig":
110116
include_uv_lock=True,
111117
),
112118
functions={},
119+
agents={},
113120
)
114121

115122
@classmethod

src/uipath/functions/factory.py

Lines changed: 20 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -45,22 +45,18 @@ def _load_config(self) -> dict[str, Any]:
4545
return self._config
4646

4747
def discover_entrypoints(self) -> list[str]:
48-
"""Discover all function entrypoints from uipath.json."""
48+
"""Discover all entrypoints (functions and agents) from uipath.json."""
4949
config = self._load_config()
50-
return list(config.get("functions", {}).keys())
50+
functions = list(config.get("functions", {}).keys())
51+
agents = list(config.get("agents", {}).keys())
52+
return functions + agents
5153

5254
async def get_storage(self) -> UiPathRuntimeStorageProtocol | None:
5355
"""Get storage protocol if any (placeholder for protocol compliance)."""
5456
return None
5557

5658
async def get_settings(self) -> UiPathRuntimeFactorySettings | None:
57-
"""Get factory settings for coded functions.
58-
59-
Coded functions don't need span filtering - all spans are relevant
60-
since developers have full control over instrumentation.
61-
62-
Low-code agents (LangGraph) need filtering due to framework overhead.
63-
"""
59+
"""Get factory settings for coded functions."""
6460
return None
6561

6662
async def new_runtime(
@@ -77,15 +73,22 @@ def _create_runtime(self, entrypoint: str) -> UiPathRuntimeProtocol:
7773
"""Create runtime instance from entrypoint specification."""
7874
config = self._load_config()
7975
functions = config.get("functions", {})
80-
81-
if entrypoint not in functions:
76+
agents = config.get("agents", {})
77+
78+
# Check both functions and agents
79+
if entrypoint in functions:
80+
func_spec = functions[entrypoint]
81+
entrypoint_type = "function"
82+
elif entrypoint in agents:
83+
func_spec = agents[entrypoint]
84+
entrypoint_type = "agent"
85+
else:
86+
available = list(functions.keys()) + list(agents.keys())
8287
raise ValueError(
8388
f"Entrypoint '{entrypoint}' not found in uipath.json. "
84-
f"Available: {', '.join(functions.keys())}"
89+
f"Available: {', '.join(available)}"
8590
)
8691

87-
func_spec = functions[entrypoint]
88-
8992
if ":" not in func_spec:
9093
raise ValueError(
9194
f"Invalid function specification: '{func_spec}'. "
@@ -98,7 +101,9 @@ def _create_runtime(self, entrypoint: str) -> UiPathRuntimeProtocol:
98101
if not full_path.exists():
99102
raise ValueError(f"File not found: {full_path}")
100103

101-
inner = UiPathFunctionsRuntime(str(full_path), function_name, entrypoint)
104+
inner = UiPathFunctionsRuntime(
105+
str(full_path), function_name, entrypoint, entrypoint_type
106+
)
102107
return UiPathDebugFunctionsRuntime(
103108
delegate=inner,
104109
entrypoint_path=str(full_path),

src/uipath/functions/runtime.py

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,11 +39,25 @@
3939
class UiPathFunctionsRuntime:
4040
"""Runtime wrapper for a single Python function with full script executor compatibility."""
4141

42-
def __init__(self, file_path: str, function_name: str, entrypoint_name: str):
43-
"""Initialize the function runtime."""
42+
def __init__(
43+
self,
44+
file_path: str,
45+
function_name: str,
46+
entrypoint_name: str,
47+
entrypoint_type: str = "function",
48+
):
49+
"""Initialize the function runtime.
50+
51+
Args:
52+
file_path: Path to the Python file containing the function
53+
function_name: Name of the function to execute
54+
entrypoint_name: Name of the entrypoint
55+
entrypoint_type: Type of entrypoint - 'function' or 'agent'
56+
"""
4457
self.file_path = Path(file_path)
4558
self.function_name = function_name
4659
self.entrypoint_name = entrypoint_name
60+
self.entrypoint_type = entrypoint_type
4761
self._function: Callable[..., Any] | None = None
4862
self._module: ModuleType | None = None
4963

@@ -240,7 +254,7 @@ async def get_schema(self) -> UiPathRuntimeSchema:
240254
return UiPathRuntimeSchema(
241255
filePath=self.entrypoint_name,
242256
uniqueId=str(uuid.uuid4()),
243-
type="agent",
257+
type=self.entrypoint_type,
244258
input=input_schema,
245259
output=output_schema,
246260
graph=graph,

tests/functions/test_unwrap_decorated.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,3 +71,28 @@ async def test_execute_unwraps_decorated_function(decorated_module):
7171
assert isinstance(result.output, dict)
7272
assert result.output["total_nodes"] == 1
7373
assert result.output["max_value"] == 42
74+
75+
76+
@pytest.mark.asyncio
77+
async def test_schema_type_defaults_to_function(decorated_module):
78+
"""Schema type should be 'function' by default."""
79+
runtime = UiPathFunctionsRuntime(str(decorated_module), "main", "decorated")
80+
schema = await runtime.get_schema()
81+
82+
assert schema.type == "function"
83+
84+
85+
@pytest.mark.asyncio
86+
async def test_schema_type_reflects_entrypoint_type(decorated_module):
87+
"""Schema type should reflect the entrypoint_type passed to the runtime."""
88+
runtime_fn = UiPathFunctionsRuntime(
89+
str(decorated_module), "main", "decorated", entrypoint_type="function"
90+
)
91+
schema_fn = await runtime_fn.get_schema()
92+
assert schema_fn.type == "function"
93+
94+
runtime_agent = UiPathFunctionsRuntime(
95+
str(decorated_module), "main", "decorated", entrypoint_type="agent"
96+
)
97+
schema_agent = await runtime_agent.get_schema()
98+
assert schema_agent.type == "agent"

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)