Skip to content

Commit 059ebed

Browse files
committed
fix: add execution options
1 parent b18ff9f commit 059ebed

File tree

7 files changed

+108
-130
lines changed

7 files changed

+108
-130
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-runtime"
3-
version = "0.0.3"
3+
version = "0.0.4"
44
description = "UiPath Runtime abstractions"
55
readme = { file = "README.md", content-type = "text/markdown" }
66
requires-python = ">=3.11"

src/uipath/runtime/__init__.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
"""UiPath Runtime Package."""
22

3-
from uipath.runtime.base import UiPathBaseRuntime, UiPathStreamNotSupportedError
3+
from uipath.runtime.base import (
4+
UiPathBaseRuntime,
5+
UiPathExecuteOptions,
6+
UiPathExecutionRuntime,
7+
UiPathStreamNotSupportedError,
8+
UiPathStreamOptions,
9+
)
410
from uipath.runtime.context import UiPathRuntimeContext
511
from uipath.runtime.events import UiPathRuntimeEvent
612
from uipath.runtime.factory import UiPathRuntimeFactory
@@ -14,8 +20,11 @@
1420
)
1521

1622
__all__ = [
23+
"UiPathExecuteOptions",
24+
"UiPathStreamOptions",
1725
"UiPathRuntimeContext",
1826
"UiPathBaseRuntime",
27+
"UiPathExecutionRuntime",
1928
"UiPathRuntimeFactory",
2029
"UiPathRuntimeResult",
2130
"UiPathRuntimeStatus",

src/uipath/runtime/base.py

Lines changed: 33 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,13 @@
66
Any,
77
AsyncGenerator,
88
Generic,
9+
List,
10+
Literal,
911
Optional,
1012
TypeVar,
1113
)
1214

15+
from pydantic import BaseModel, Field
1316
from typing_extensions import override
1417
from uipath.core import UiPathTraceManager
1518

@@ -33,6 +36,27 @@ class UiPathStreamNotSupportedError(NotImplementedError):
3336
pass
3437

3538

39+
class UiPathExecuteOptions(BaseModel):
40+
"""Execution-time options controlling runtime behavior."""
41+
42+
resume: bool = Field(
43+
default=False,
44+
description="Indicates whether to resume a suspended execution.",
45+
)
46+
breakpoints: Optional[List[str] | Literal["*"]] = Field(
47+
default=None,
48+
description="List of nodes or '*' to break on all steps.",
49+
)
50+
51+
model_config = {"arbitrary_types_allowed": True, "extra": "allow"}
52+
53+
54+
class UiPathStreamOptions(UiPathExecuteOptions):
55+
"""Streaming-specific execution options."""
56+
57+
pass
58+
59+
3660
class UiPathBaseRuntime(ABC):
3761
"""Base runtime class implementing the async context manager protocol.
3862
@@ -51,13 +75,14 @@ async def get_schema(self) -> UiPathRuntimeSchema:
5175
raise NotImplementedError()
5276

5377
@abstractmethod
54-
async def execute(self, input: dict[str, Any]) -> UiPathRuntimeResult:
78+
async def execute(
79+
self, input: dict[str, Any], options: Optional[UiPathExecuteOptions] = None
80+
) -> UiPathRuntimeResult:
5581
"""Produce the agent output."""
5682
raise NotImplementedError()
5783

5884
async def stream(
59-
self,
60-
input: dict[str, Any],
85+
self, input: dict[str, Any], options: Optional[UiPathStreamOptions] = None
6186
) -> AsyncGenerator[UiPathRuntimeEvent, None]:
6287
"""Stream execution events in real-time.
6388
@@ -126,8 +151,7 @@ def __init__(
126151
self.log_handler = UiPathRuntimeExecutionLogHandler(execution_id)
127152

128153
async def execute(
129-
self,
130-
input: dict[str, Any],
154+
self, input: dict[str, Any], options: Optional[UiPathExecuteOptions] = None
131155
) -> UiPathRuntimeResult:
132156
"""Execute runtime with context."""
133157
if self.log_handler:
@@ -141,18 +165,17 @@ async def execute(
141165
with self.trace_manager.start_execution_span(
142166
self.root_span, execution_id=self.execution_id
143167
):
144-
return await self.delegate.execute(input)
168+
return await self.delegate.execute(input, options=options)
145169
else:
146-
return await self.delegate.execute(input)
170+
return await self.delegate.execute(input, options=options)
147171
finally:
148172
self.trace_manager.flush_spans()
149173
if self.log_handler:
150174
log_interceptor.teardown()
151175

152176
@override
153177
async def stream(
154-
self,
155-
input: dict[str, Any],
178+
self, input: dict[str, Any], options: Optional[UiPathStreamOptions] = None
156179
) -> AsyncGenerator[UiPathRuntimeEvent, None]:
157180
"""Stream runtime execution with context.
158181
@@ -176,7 +199,7 @@ async def stream(
176199
with self.trace_manager.start_execution_span(
177200
self.root_span, execution_id=self.execution_id
178201
):
179-
async for event in self.delegate.stream(input):
202+
async for event in self.delegate.stream(input, options=options):
180203
yield event
181204
finally:
182205
self.trace_manager.flush_spans()

src/uipath/runtime/context.py

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,6 @@
66
from functools import cached_property
77
from typing import (
88
Any,
9-
List,
10-
Literal,
119
Optional,
1210
TypeVar,
1311
)
@@ -35,7 +33,6 @@ class UiPathRuntimeContext(BaseModel):
3533

3634
entrypoint: Optional[str] = None
3735
input: Optional[Any] = None
38-
resume: bool = False
3936
job_id: Optional[str] = None
4037
trace_context: Optional[UiPathTraceContext] = None
4138
config_path: str = "uipath.json"
@@ -47,7 +44,6 @@ class UiPathRuntimeContext(BaseModel):
4744
trace_file: Optional[str] = None
4845
logs_file: Optional[str] = "execution.log"
4946
logs_min_level: Optional[str] = "INFO"
50-
breakpoints: Optional[List[str] | Literal["*"]] = None
5147
result: Optional[UiPathRuntimeResult] = None
5248

5349
model_config = {"arbitrary_types_allowed": True, "extra": "allow"}
@@ -111,9 +107,7 @@ def __exit__(self, exc_type, exc_val, exc_tb):
111107
"""
112108
try:
113109
if self.result is None:
114-
execution_result = UiPathRuntimeResult()
115-
else:
116-
execution_result = self.result
110+
self.result = UiPathRuntimeResult()
117111

118112
if exc_type:
119113
# Create error info from exception
@@ -128,10 +122,10 @@ def __exit__(self, exc_type, exc_val, exc_tb):
128122
category=UiPathErrorCategory.UNKNOWN,
129123
)
130124

131-
execution_result.status = UiPathRuntimeStatus.FAULTED
132-
execution_result.error = error_info
125+
self.result.status = UiPathRuntimeStatus.FAULTED
126+
self.result.error = error_info
133127

134-
content = execution_result.to_dict()
128+
content = self.result.to_dict()
135129

136130
# Always write output file at runtime, except for inner runtimes
137131
# Inner runtimes have execution_id

src/uipath/runtime/debug/runtime.py

Lines changed: 23 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,16 @@
11
"""Debug runtime implementation."""
22

33
import logging
4-
from typing import Any, Generic, TypeVar
4+
from typing import Any, Generic, Optional, TypeVar
55

66
from uipath.runtime import (
77
UiPathBaseRuntime,
88
UiPathBreakpointResult,
9-
UiPathRuntimeContext,
9+
UiPathExecuteOptions,
1010
UiPathRuntimeResult,
1111
UiPathRuntimeStatus,
1212
UiPathStreamNotSupportedError,
13+
UiPathStreamOptions,
1314
)
1415
from uipath.runtime.debug import UiPathDebugBridge, UiPathDebugQuitError
1516
from uipath.runtime.events import (
@@ -26,53 +27,48 @@ class UiPathDebugRuntime(UiPathBaseRuntime, Generic[T]):
2627

2728
def __init__(
2829
self,
29-
context: UiPathRuntimeContext,
3030
delegate: T,
3131
debug_bridge: UiPathDebugBridge,
3232
):
3333
"""Initialize the UiPathDebugRuntime."""
34-
super().__init__(context)
35-
self.context: UiPathRuntimeContext = context
34+
super().__init__()
3635
self.delegate: T = delegate
3736
self.debug_bridge: UiPathDebugBridge = debug_bridge
3837

39-
async def execute(self, input: dict[str, Any]) -> UiPathRuntimeResult:
38+
async def execute(
39+
self, input: dict[str, Any], options: Optional[UiPathExecuteOptions] = None
40+
) -> UiPathRuntimeResult:
4041
"""Execute the workflow with debug support."""
4142
try:
4243
await self.debug_bridge.connect()
4344

4445
await self.debug_bridge.emit_execution_started()
4546

4647
result: UiPathRuntimeResult
48+
4749
# Try to stream events from inner runtime
4850
try:
49-
result = await self._stream_and_debug(self.delegate, input)
51+
result = await self._stream_and_debug(input, options=options)
5052
except UiPathStreamNotSupportedError:
5153
# Fallback to regular execute if streaming not supported
5254
logger.debug(
5355
f"Runtime {self.delegate.__class__.__name__} does not support "
5456
"streaming, falling back to execute()"
5557
)
56-
result = await self.delegate.execute(input)
58+
result = await self.delegate.execute(input, options=options)
5759

5860
await self.debug_bridge.emit_execution_completed(result)
5961

60-
self.context.result = result
61-
6262
return result
6363

6464
except Exception as e:
65-
# Emit execution error
66-
self.context.result = UiPathRuntimeResult(
67-
status=UiPathRuntimeStatus.FAULTED,
68-
)
6965
await self.debug_bridge.emit_execution_error(
7066
error=str(e),
7167
)
7268
raise
7369

7470
async def _stream_and_debug(
75-
self, inner_runtime: T, input: dict[str, Any]
71+
self, input: dict[str, Any], options: Optional[UiPathExecuteOptions] = None
7672
) -> UiPathRuntimeResult:
7773
"""Stream events from inner runtime and handle debug interactions."""
7874
final_result: UiPathRuntimeResult
@@ -81,13 +77,17 @@ async def _stream_and_debug(
8177
# Starting in paused state - wait for breakpoints and resume
8278
await self.debug_bridge.wait_for_resume()
8379

80+
debug_options = UiPathStreamOptions(
81+
resume=options.resume if options else False,
82+
breakpoints=options.breakpoints if options else None,
83+
)
84+
8485
# Keep streaming until execution completes (not just paused at breakpoint)
8586
while not execution_completed:
8687
# Update breakpoints from debug bridge
87-
if inner_runtime.context is not None:
88-
inner_runtime.context.breakpoints = self.debug_bridge.get_breakpoints()
88+
debug_options.breakpoints = self.debug_bridge.get_breakpoints()
8989
# Stream events from inner runtime
90-
async for event in inner_runtime.stream(input):
90+
async for event in self.delegate.stream(input, options=debug_options):
9191
# Handle final result
9292
if isinstance(event, UiPathRuntimeResult):
9393
final_result = event
@@ -99,8 +99,8 @@ async def _stream_and_debug(
9999
await self.debug_bridge.emit_breakpoint_hit(event)
100100
await self.debug_bridge.wait_for_resume()
101101

102-
if inner_runtime.context is not None:
103-
inner_runtime.context.resume = True
102+
# Tell inner runtime we're resuming
103+
debug_options.resume = True
104104

105105
except UiPathDebugQuitError:
106106
final_result = UiPathRuntimeResult(
@@ -122,9 +122,6 @@ async def _stream_and_debug(
122122
async def cleanup(self) -> None:
123123
"""Cleanup runtime resources."""
124124
try:
125-
await self.delegate.cleanup()
126-
finally:
127-
try:
128-
await self.debug_bridge.disconnect()
129-
except Exception as e:
130-
logger.warning(f"Error disconnecting debug bridge: {e}")
125+
await self.debug_bridge.disconnect()
126+
except Exception as e:
127+
logger.warning(f"Error disconnecting debug bridge: {e}")

0 commit comments

Comments
 (0)