Skip to content

Commit a349a7d

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

File tree

7 files changed

+96
-129
lines changed

7 files changed

+96
-129
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: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
"""UiPath Runtime Package."""
22

3-
from uipath.runtime.base import UiPathBaseRuntime, UiPathStreamNotSupportedError
3+
from uipath.runtime.base import (
4+
UiPathBaseRuntime,
5+
UiPathExecutionOptions,
6+
UiPathExecutionRuntime,
7+
UiPathStreamNotSupportedError,
8+
)
49
from uipath.runtime.context import UiPathRuntimeContext
510
from uipath.runtime.events import UiPathRuntimeEvent
611
from uipath.runtime.factory import UiPathRuntimeFactory
@@ -16,6 +21,8 @@
1621
__all__ = [
1722
"UiPathRuntimeContext",
1823
"UiPathBaseRuntime",
24+
"UiPathExecutionOptions",
25+
"UiPathExecutionRuntime",
1926
"UiPathRuntimeFactory",
2027
"UiPathRuntimeResult",
2128
"UiPathRuntimeStatus",

src/uipath/runtime/base.py

Lines changed: 27 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,21 @@ class UiPathStreamNotSupportedError(NotImplementedError):
3336
pass
3437

3538

39+
class UiPathExecutionOptions(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+
3654
class UiPathBaseRuntime(ABC):
3755
"""Base runtime class implementing the async context manager protocol.
3856
@@ -51,13 +69,14 @@ async def get_schema(self) -> UiPathRuntimeSchema:
5169
raise NotImplementedError()
5270

5371
@abstractmethod
54-
async def execute(self, input: dict[str, Any]) -> UiPathRuntimeResult:
72+
async def execute(
73+
self, input: dict[str, Any], options: Optional[UiPathExecutionOptions] = None
74+
) -> UiPathRuntimeResult:
5575
"""Produce the agent output."""
5676
raise NotImplementedError()
5777

5878
async def stream(
59-
self,
60-
input: dict[str, Any],
79+
self, input: dict[str, Any], options: Optional[UiPathExecutionOptions] = None
6180
) -> AsyncGenerator[UiPathRuntimeEvent, None]:
6281
"""Stream execution events in real-time.
6382
@@ -126,8 +145,7 @@ def __init__(
126145
self.log_handler = UiPathRuntimeExecutionLogHandler(execution_id)
127146

128147
async def execute(
129-
self,
130-
input: dict[str, Any],
148+
self, input: dict[str, Any], options: Optional[UiPathExecutionOptions] = None
131149
) -> UiPathRuntimeResult:
132150
"""Execute runtime with context."""
133151
if self.log_handler:
@@ -141,18 +159,17 @@ async def execute(
141159
with self.trace_manager.start_execution_span(
142160
self.root_span, execution_id=self.execution_id
143161
):
144-
return await self.delegate.execute(input)
162+
return await self.delegate.execute(input, options=options)
145163
else:
146-
return await self.delegate.execute(input)
164+
return await self.delegate.execute(input, options=options)
147165
finally:
148166
self.trace_manager.flush_spans()
149167
if self.log_handler:
150168
log_interceptor.teardown()
151169

152170
@override
153171
async def stream(
154-
self,
155-
input: dict[str, Any],
172+
self, input: dict[str, Any], options: Optional[UiPathExecutionOptions] = None
156173
) -> AsyncGenerator[UiPathRuntimeEvent, None]:
157174
"""Stream runtime execution with context.
158175
@@ -176,7 +193,7 @@ async def stream(
176193
with self.trace_manager.start_execution_span(
177194
self.root_span, execution_id=self.execution_id
178195
):
179-
async for event in self.delegate.stream(input):
196+
async for event in self.delegate.stream(input, options=options):
180197
yield event
181198
finally:
182199
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: 22 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
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+
UiPathExecutionOptions,
1010
UiPathRuntimeResult,
1111
UiPathRuntimeStatus,
1212
UiPathStreamNotSupportedError,
@@ -26,53 +26,48 @@ class UiPathDebugRuntime(UiPathBaseRuntime, Generic[T]):
2626

2727
def __init__(
2828
self,
29-
context: UiPathRuntimeContext,
3029
delegate: T,
3130
debug_bridge: UiPathDebugBridge,
3231
):
3332
"""Initialize the UiPathDebugRuntime."""
34-
super().__init__(context)
35-
self.context: UiPathRuntimeContext = context
33+
super().__init__()
3634
self.delegate: T = delegate
3735
self.debug_bridge: UiPathDebugBridge = debug_bridge
3836

39-
async def execute(self, input: dict[str, Any]) -> UiPathRuntimeResult:
37+
async def execute(
38+
self, input: dict[str, Any], options: Optional[UiPathExecutionOptions] = None
39+
) -> UiPathRuntimeResult:
4040
"""Execute the workflow with debug support."""
4141
try:
4242
await self.debug_bridge.connect()
4343

4444
await self.debug_bridge.emit_execution_started()
4545

4646
result: UiPathRuntimeResult
47+
4748
# Try to stream events from inner runtime
4849
try:
49-
result = await self._stream_and_debug(self.delegate, input)
50+
result = await self._stream_and_debug(input, options=options)
5051
except UiPathStreamNotSupportedError:
5152
# Fallback to regular execute if streaming not supported
5253
logger.debug(
5354
f"Runtime {self.delegate.__class__.__name__} does not support "
5455
"streaming, falling back to execute()"
5556
)
56-
result = await self.delegate.execute(input)
57+
result = await self.delegate.execute(input, options=options)
5758

5859
await self.debug_bridge.emit_execution_completed(result)
5960

60-
self.context.result = result
61-
6261
return result
6362

6463
except Exception as e:
65-
# Emit execution error
66-
self.context.result = UiPathRuntimeResult(
67-
status=UiPathRuntimeStatus.FAULTED,
68-
)
6964
await self.debug_bridge.emit_execution_error(
7065
error=str(e),
7166
)
7267
raise
7368

7469
async def _stream_and_debug(
75-
self, inner_runtime: T, input: dict[str, Any]
70+
self, input: dict[str, Any], options: Optional[UiPathExecutionOptions] = None
7671
) -> UiPathRuntimeResult:
7772
"""Stream events from inner runtime and handle debug interactions."""
7873
final_result: UiPathRuntimeResult
@@ -81,13 +76,17 @@ async def _stream_and_debug(
8176
# Starting in paused state - wait for breakpoints and resume
8277
await self.debug_bridge.wait_for_resume()
8378

79+
debug_options = UiPathExecutionOptions(
80+
resume=options.resume if options else False,
81+
breakpoints=options.breakpoints if options else None,
82+
)
83+
8484
# Keep streaming until execution completes (not just paused at breakpoint)
8585
while not execution_completed:
8686
# Update breakpoints from debug bridge
87-
if inner_runtime.context is not None:
88-
inner_runtime.context.breakpoints = self.debug_bridge.get_breakpoints()
87+
debug_options.breakpoints = self.debug_bridge.get_breakpoints()
8988
# Stream events from inner runtime
90-
async for event in inner_runtime.stream(input):
89+
async for event in self.delegate.stream(input, options=debug_options):
9190
# Handle final result
9291
if isinstance(event, UiPathRuntimeResult):
9392
final_result = event
@@ -99,8 +98,8 @@ async def _stream_and_debug(
9998
await self.debug_bridge.emit_breakpoint_hit(event)
10099
await self.debug_bridge.wait_for_resume()
101100

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

105104
except UiPathDebugQuitError:
106105
final_result = UiPathRuntimeResult(
@@ -122,9 +121,6 @@ async def _stream_and_debug(
122121
async def cleanup(self) -> None:
123122
"""Cleanup runtime resources."""
124123
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}")
124+
await self.debug_bridge.disconnect()
125+
except Exception as e:
126+
logger.warning(f"Error disconnecting debug bridge: {e}")

0 commit comments

Comments
 (0)