Skip to content

Commit a4b0bd7

Browse files
committed
Hide breakpoint logic from runtimes
1 parent 585f221 commit a4b0bd7

File tree

3 files changed

+48
-36
lines changed

3 files changed

+48
-36
lines changed

src/uipath/runtime/debug/runtime.py

Lines changed: 40 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
"""Debug runtime implementation."""
22

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

66
from uipath.runtime import (
77
UiPathBaseRuntime,
88
UiPathBreakpointResult,
99
UiPathRuntimeContext,
10+
UiPathRuntimeEvent,
1011
UiPathRuntimeResult,
1112
UiPathRuntimeStatus,
1213
UiPathStreamNotSupportedError,
@@ -46,7 +47,7 @@ async def execute(self, input: dict[str, Any]) -> UiPathRuntimeResult:
4647
result: UiPathRuntimeResult
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)
5051
except UiPathStreamNotSupportedError:
5152
# Fallback to regular execute if streaming not supported
5253
logger.debug(
@@ -71,9 +72,38 @@ async def execute(self, input: dict[str, Any]) -> UiPathRuntimeResult:
7172
)
7273
raise
7374

74-
async def _stream_and_debug(
75-
self, inner_runtime: T, input: dict[str, Any]
76-
) -> UiPathRuntimeResult:
75+
async def stream(
76+
self,
77+
input: dict[str, Any],
78+
) -> AsyncGenerator[UiPathRuntimeEvent, None]:
79+
"""Stream events from inner runtime and handle debug interactions."""
80+
async for event in self.delegate.stream(input):
81+
if isinstance(event, UiPathRuntimeStateEvent):
82+
# Move these to constructor and remove from context
83+
if self.context:
84+
breakpoints = self.context.breakpoints
85+
else:
86+
breakpoints = None
87+
hit_breakpoint = False
88+
89+
if breakpoints == "*":
90+
hit_breakpoint = True
91+
elif isinstance(breakpoints, list) and event.node_name in breakpoints:
92+
hit_breakpoint = True
93+
94+
if hit_breakpoint:
95+
yield UiPathBreakpointResult(
96+
breakpoint_node=event.node_name,
97+
breakpoint_type="before",
98+
next_nodes=event.next_nodes,
99+
current_state={"node": event.node_name},
100+
)
101+
else:
102+
yield event
103+
else:
104+
yield event
105+
106+
async def _stream_and_debug(self, input: dict[str, Any]) -> UiPathRuntimeResult:
77107
"""Stream events from inner runtime and handle debug interactions."""
78108
final_result: UiPathRuntimeResult
79109
execution_completed = False
@@ -84,10 +114,10 @@ async def _stream_and_debug(
84114
# Keep streaming until execution completes (not just paused at breakpoint)
85115
while not execution_completed:
86116
# Update breakpoints from debug bridge
87-
if inner_runtime.context is not None:
88-
inner_runtime.context.breakpoints = self.debug_bridge.get_breakpoints()
117+
if self.delegate.context is not None:
118+
self.delegate.context.breakpoints = self.debug_bridge.get_breakpoints()
89119
# Stream events from inner runtime
90-
async for event in inner_runtime.stream(input):
120+
async for event in self.stream(input):
91121
# Handle final result
92122
if isinstance(event, UiPathRuntimeResult):
93123
final_result = event
@@ -99,8 +129,8 @@ async def _stream_and_debug(
99129
await self.debug_bridge.emit_breakpoint_hit(event)
100130
await self.debug_bridge.wait_for_resume()
101131

102-
if inner_runtime.context is not None:
103-
inner_runtime.context.resume = True
132+
if self.delegate.context is not None:
133+
self.delegate.context.resume = True
104134

105135
except UiPathDebugQuitError:
106136
final_result = UiPathRuntimeResult(

src/uipath/runtime/events/state.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
"""Events related to agent messages and state updates."""
22

3-
from typing import Any, Dict, Optional
3+
from typing import Any, Dict, List
44

55
from pydantic import Field
66

@@ -62,8 +62,12 @@ class UiPathRuntimeStateEvent(UiPathRuntimeEvent):
6262
"""
6363

6464
payload: Dict[str, Any] = Field(description="Framework-specific state update")
65-
node_name: Optional[str] = Field(
66-
default=None, description="Name of the node/agent that caused this update"
65+
node_name: str = Field(
66+
..., description="Name of the node/agent that caused this update"
67+
)
68+
next_nodes: List[str] = Field(
69+
default_factory=list,
70+
description="List of node names that the agent should follow",
6771
)
6872
event_type: UiPathRuntimeEventType = Field(
6973
default=UiPathRuntimeEventType.RUNTIME_STATE, frozen=True

tests/test_debugger.py

Lines changed: 1 addition & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99

1010
from uipath.runtime import (
1111
UiPathBaseRuntime,
12-
UiPathBreakpointResult,
1312
UiPathRuntimeContext,
1413
UiPathRuntimeResult,
1514
UiPathRuntimeStatus,
@@ -90,29 +89,8 @@ async def stream(
9089
yield UiPathRuntimeStateEvent(
9190
node_name=node,
9291
payload={"index": idx, "node": node},
92+
next_nodes=self.node_sequence[idx + 1 : idx + 2], # at most one
9393
)
94-
95-
# 2) Check for breakpoints on this node
96-
if self.context:
97-
breakpoints = self.context.breakpoints
98-
else:
99-
breakpoints = None
100-
hit_breakpoint = False
101-
102-
if breakpoints == "*":
103-
hit_breakpoint = True
104-
elif isinstance(breakpoints, list) and node in breakpoints:
105-
hit_breakpoint = True
106-
107-
if hit_breakpoint:
108-
next_nodes = self.node_sequence[idx + 1 : idx + 2] # at most one
109-
yield UiPathBreakpointResult(
110-
breakpoint_node=node,
111-
breakpoint_type="before",
112-
next_nodes=next_nodes,
113-
current_state={"node": node, "index": idx},
114-
)
115-
11694
# 3) Final result at the end of streaming
11795
yield UiPathRuntimeResult(
11896
status=UiPathRuntimeStatus.SUCCESSFUL,

0 commit comments

Comments
 (0)