Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
[project]
name = "uipath"
version = "2.8.5"
version = "2.8.6"
description = "Python SDK and CLI for UiPath Platform, enabling programmatic interaction with automation services, process management, and deployment tools."
readme = { file = "README.md", content-type = "text/markdown" }
requires-python = ">=3.11"
dependencies = [
"uipath-core>=0.2.3, <0.3.0",
"uipath-runtime>=0.6.3, <0.7.0",
"uipath-core>=0.3.0, <0.4.0",
"uipath-runtime>=0.7.0, <0.8.0",
"click>=8.3.1",
"httpx>=0.28.1",
"pyjwt>=2.10.1",
Expand Down
65 changes: 55 additions & 10 deletions src/uipath/_cli/_chat/_bridge.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,13 @@
UiPathConversationEvent,
UiPathConversationExchangeEndEvent,
UiPathConversationExchangeEvent,
UiPathConversationInterruptEndEvent,
UiPathConversationInterruptEvent,
UiPathConversationInterruptStartEvent,
UiPathConversationMessageEvent,
UiPathConversationToolCallConfirmationInterruptStartEvent,
UiPathConversationToolCallConfirmationValue,
)
from uipath.runtime import UiPathRuntimeResult
from uipath.runtime import UiPathResumeTrigger
from uipath.runtime.chat import UiPathChatProtocol
from uipath.runtime.context import UiPathRuntimeContext

Expand Down Expand Up @@ -56,6 +58,11 @@ def __init__(
self._client: Any | None = None
self._connected_event = asyncio.Event()

# Interrupt state for HITL round-trip
self._interrupt_end_event = asyncio.Event()
self._interrupt_end_value: UiPathConversationInterruptEndEvent | None = None
self._current_message_id: str | None = None

# Set CAS_WEBSOCKET_DISABLED when using the debugger to prevent websocket errors from
# interrupting the debugging session. Events will be logged instead of being sent.
self._websocket_disabled = os.environ.get("CAS_WEBSOCKET_DISABLED") == "true"
Expand Down Expand Up @@ -239,11 +246,24 @@ async def emit_exchange_end_event(self) -> None:
logger.error(f"Error sending conversation event to WebSocket: {e}")
raise RuntimeError(f"Failed to send conversation event: {e}") from e

async def emit_interrupt_event(self, runtime_result: UiPathRuntimeResult):
async def emit_interrupt_event(self, resume_trigger: UiPathResumeTrigger):
if self._client and self._connected_event.is_set():
try:
# Clear previous interrupt state and generate new interrupt_id
self._interrupt_id = str(uuid.uuid4())

# Ensure we have a valid message_id
if self._current_message_id is None:
raise RuntimeError(
"Cannot emit interrupt event: no current message_id set"
)

# Ensure api_resume is not None
if resume_trigger.api_resume is None:
raise RuntimeError(
"Cannot emit interrupt event: api_resume is None"
)

interrupt_event = UiPathConversationEvent(
conversation_id=self.conversation_id,
exchange=UiPathConversationExchangeEvent(
Expand All @@ -252,14 +272,17 @@ async def emit_interrupt_event(self, runtime_result: UiPathRuntimeResult):
message_id=self._current_message_id,
interrupt=UiPathConversationInterruptEvent(
interrupt_id=self._interrupt_id,
start=UiPathConversationInterruptStartEvent(
type="coded-agent-interrupt",
value=runtime_result.output,
start=UiPathConversationToolCallConfirmationInterruptStartEvent(
type="uipath_cas_tool_call_confirmation",
value=UiPathConversationToolCallConfirmationValue(
**resume_trigger.api_resume.request
),
),
),
),
),
)

event_data = interrupt_event.model_dump(
mode="json", exclude_none=True, by_alias=True
)
Expand All @@ -278,6 +301,13 @@ async def wait_for_resume(self) -> dict[str, Any]:
Returns:
Resume data from the interrupt end event
"""
self._interrupt_end_event.clear()
self._interrupt_end_value = None

await self._interrupt_end_event.wait()

if self._interrupt_end_value:
return self._interrupt_end_value.model_dump(mode="python", by_alias=False)
return {}

@property
Expand Down Expand Up @@ -306,10 +336,25 @@ async def _handle_connect_error(self, data: Any) -> None:
async def _handle_conversation_event(
self, event: dict[str, Any], _sid: str
) -> None:
"""Handle received ConversationEvent events."""
error_event = event.get("conversationError")
if error_event:
logger.error(f"Conversation error: {json.dumps(error_event)}")

try:
parsed_event = UiPathConversationEvent(**event)
if (
parsed_event.exchange
and parsed_event.exchange.message
and parsed_event.exchange.message.interrupt
and parsed_event.exchange.message.interrupt.end
):
interrupt = parsed_event.exchange.message.interrupt

if interrupt.interrupt_id == self._interrupt_id:
logger.info(
f"Received endInterrupt for interrupt_id: {self._interrupt_id}"
)
self._interrupt_end_value = interrupt.end
self._interrupt_end_event.set()
except Exception as e:
logger.warning(f"Error parsing conversation event: {e}")

async def _cleanup_client(self) -> None:
"""Clean up client resources."""
Expand Down
9 changes: 4 additions & 5 deletions src/uipath/eval/_helpers/evaluators_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -299,17 +299,16 @@ def tool_calls_args_score(
tool_counters[call.name] += 1

# Check arguments based on mode
# The linter highlights a few problems here due to using lambdas, but they're safe to ignore
# Breaking this down into proper functions would unnecessarily make the code more complex
if subset:
# Subset mode: safely check if all expected args exist and match
# Capture 'call' as a default argument to bind the loop variable
args_check = ( # noqa: E731
lambda k, v: k in call.args # noqa: B023
and call.args[k] == v # noqa: B023
lambda k, v, call=call: k in call.args and call.args[k] == v
)
else:
# Exact mode: direct access (may raise KeyError)
args_check = lambda k, v: call.args[k] == v # noqa: E731, B023
# Capture 'call' as a default argument to bind the loop variable
args_check = lambda k, v, call=call: call.args[k] == v # noqa: E731

try:
args_match = all(
Expand Down
Loading
Loading