From 35712e3697f19ead5553df3862be6d0540ea540c Mon Sep 17 00:00:00 2001 From: GabrielVasilescu04 Date: Thu, 18 Dec 2025 11:18:52 +0200 Subject: [PATCH] feat: update runtime version --- pyproject.toml | 4 +- src/uipath/_cli/_chat/_bridge.py | 105 ++++++++++++++++++++++-------- src/uipath/_cli/_debug/_bridge.py | 6 +- src/uipath/_cli/cli_debug.py | 4 +- src/uipath/_cli/cli_run.py | 4 +- uv.lock | 10 +-- 6 files changed, 92 insertions(+), 41 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 3848e9371..936d32f03 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,11 +1,11 @@ [project] name = "uipath" -version = "2.2.38" +version = "2.3.0" 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-runtime>=0.2.7, <0.3.0", + "uipath-runtime>=0.3.0, <0.4.0", "uipath-core>=0.1.4, <0.2.0", "click>=8.3.1", "httpx>=0.28.1", diff --git a/src/uipath/_cli/_chat/_bridge.py b/src/uipath/_cli/_chat/_bridge.py index 55251318b..2dfb95c58 100644 --- a/src/uipath/_cli/_chat/_bridge.py +++ b/src/uipath/_cli/_chat/_bridge.py @@ -3,6 +3,7 @@ import asyncio import logging import os +import uuid from typing import Any from urllib.parse import urlparse @@ -12,7 +13,11 @@ UiPathConversationEvent, UiPathConversationExchangeEndEvent, UiPathConversationExchangeEvent, + UiPathConversationInterruptEvent, + UiPathConversationInterruptStartEvent, + UiPathConversationMessageEvent, ) +from uipath.runtime import UiPathRuntimeResult from uipath.runtime.chat import UiPathChatProtocol from uipath.runtime.context import UiPathRuntimeContext @@ -82,7 +87,6 @@ async def connect(self, timeout: float = 10.0) -> None: self._client.on("disconnect", self._handle_disconnect) self._client.on("connect_error", self._handle_connect_error) - # Clear connection event self._connected_event.clear() try: @@ -98,11 +102,8 @@ async def connect(self, timeout: float = 10.0) -> None: timeout=timeout, ) - # Wait for connection confirmation await asyncio.wait_for(self._connected_event.wait(), timeout=timeout) - logger.info("WebSocket connection established successfully") - except asyncio.TimeoutError as e: error_message = ( f"Failed to connect to WebSocket server within {timeout}s timeout" @@ -127,34 +128,16 @@ async def disconnect(self) -> None: logger.warning("WebSocket client not connected") return - # Send exchange end event using stored IDs - if self._client and self._connected_event.is_set(): - try: - end_event = UiPathConversationEvent( - conversation_id=self.conversation_id, - exchange=UiPathConversationExchangeEvent( - exchange_id=self.exchange_id, - end=UiPathConversationExchangeEndEvent(), - ), - ) - event_data = end_event.model_dump( - mode="json", exclude_none=True, by_alias=True - ) - await self._client.emit("ConversationEvent", event_data) - logger.info("Exchange end event sent") - except Exception as e: - logger.warning(f"Error sending exchange end event: {e}") - try: - logger.info("Disconnecting from WebSocket server") await self._client.disconnect() - logger.info("WebSocket disconnected successfully") except Exception as e: logger.error(f"Error during WebSocket disconnect: {e}") finally: await self._cleanup_client() - async def emit_message_event(self, message_event: Any) -> None: + async def emit_message_event( + self, message_event: UiPathConversationMessageEvent + ) -> None: """Wrap and send a message event to the WebSocket server. Args: @@ -183,14 +166,82 @@ async def emit_message_event(self, message_event: Any) -> None: mode="json", exclude_none=True, by_alias=True ) - logger.debug("Sending conversation event to WebSocket") await self._client.emit("ConversationEvent", event_data) - logger.debug("Conversation event sent successfully") + + # Store the current message ID, used for emitting interrupt events. + self._current_message_id = message_event.message_id + + except Exception as e: + logger.error(f"Error sending conversation event to WebSocket: {e}") + raise RuntimeError(f"Failed to send conversation event: {e}") from e + + async def emit_exchange_end_event(self) -> None: + """Send an exchange end event. + + Raises: + RuntimeError: If client is not connected + """ + if self._client is None: + raise RuntimeError("WebSocket client not connected. Call connect() first.") + + if not self._connected_event.is_set(): + raise RuntimeError("WebSocket client not in connected state") + + try: + exchange_end_event = UiPathConversationEvent( + conversation_id=self.conversation_id, + exchange=UiPathConversationExchangeEvent( + exchange_id=self.exchange_id, + end=UiPathConversationExchangeEndEvent(), + ), + ) + + event_data = exchange_end_event.model_dump( + mode="json", exclude_none=True, by_alias=True + ) + + await self._client.emit("ConversationEvent", event_data) except Exception as e: 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): + if self._client and self._connected_event.is_set(): + try: + self._interrupt_id = str(uuid.uuid4()) + + interrupt_event = UiPathConversationEvent( + conversation_id=self.conversation_id, + exchange=UiPathConversationExchangeEvent( + exchange_id=self.exchange_id, + message=UiPathConversationMessageEvent( + message_id=self._current_message_id, + interrupt=UiPathConversationInterruptEvent( + interrupt_id=self._interrupt_id, + start=UiPathConversationInterruptStartEvent( + type="coded-agent-interrupt", + value=runtime_result.output, + ), + ), + ), + ), + ) + event_data = interrupt_event.model_dump( + mode="json", exclude_none=True, by_alias=True + ) + await self._client.emit("ConversationEvent", event_data) + except Exception as e: + logger.warning(f"Error sending interrupt event: {e}") + + async def wait_for_resume(self) -> dict[str, Any]: + """Wait for the interrupt_end event to be received. + + Returns: + Resume data from the interrupt end event + """ + return {} + @property def is_connected(self) -> bool: """Check if the WebSocket is currently connected. diff --git a/src/uipath/_cli/_debug/_bridge.py b/src/uipath/_cli/_debug/_bridge.py index e1b783da0..0ea307d5c 100644 --- a/src/uipath/_cli/_debug/_bridge.py +++ b/src/uipath/_cli/_debug/_bridge.py @@ -18,7 +18,7 @@ UiPathRuntimeResult, UiPathRuntimeStatus, ) -from uipath.runtime.debug import UiPathDebugBridgeProtocol, UiPathDebugQuitError +from uipath.runtime.debug import UiPathDebugProtocol, UiPathDebugQuitError from uipath.runtime.events import UiPathRuntimeStateEvent from uipath.runtime.resumable import UiPathResumeTriggerType @@ -846,7 +846,7 @@ async def _handle_error(self, error: Any) -> None: logger.error(f"SignalR error: {error}") -def get_remote_debug_bridge(context: UiPathRuntimeContext) -> UiPathDebugBridgeProtocol: +def get_remote_debug_bridge(context: UiPathRuntimeContext) -> UiPathDebugProtocol: """Factory to get SignalR debug bridge for remote debugging.""" uipath_url = os.environ.get("UIPATH_URL") if not uipath_url or not context.job_id: @@ -869,7 +869,7 @@ def get_remote_debug_bridge(context: UiPathRuntimeContext) -> UiPathDebugBridgeP def get_debug_bridge( context: UiPathRuntimeContext, verbose: bool = True -) -> UiPathDebugBridgeProtocol: +) -> UiPathDebugProtocol: """Factory to get appropriate debug bridge based on context. Args: diff --git a/src/uipath/_cli/cli_debug.py b/src/uipath/_cli/cli_debug.py index 17d1839f4..bf7ba7a31 100644 --- a/src/uipath/_cli/cli_debug.py +++ b/src/uipath/_cli/cli_debug.py @@ -10,7 +10,7 @@ UiPathRuntimeProtocol, ) from uipath.runtime.chat import UiPathChatProtocol, UiPathChatRuntime -from uipath.runtime.debug import UiPathDebugBridgeProtocol, UiPathDebugRuntime +from uipath.runtime.debug import UiPathDebugProtocol, UiPathDebugRuntime from uipath._cli._chat._bridge import get_chat_bridge from uipath._cli._debug._bridge import get_debug_bridge @@ -136,7 +136,7 @@ async def execute_debug_runtime(): delegate=runtime, chat_bridge=chat_bridge ) - debug_bridge: UiPathDebugBridgeProtocol = get_debug_bridge(ctx) + debug_bridge: UiPathDebugProtocol = get_debug_bridge(ctx) debug_runtime = UiPathDebugRuntime( delegate=chat_runtime or runtime, diff --git a/src/uipath/_cli/cli_run.py b/src/uipath/_cli/cli_run.py index 38c3d1d13..aadca9ffe 100644 --- a/src/uipath/_cli/cli_run.py +++ b/src/uipath/_cli/cli_run.py @@ -12,7 +12,7 @@ ) from uipath.runtime.chat import UiPathChatProtocol, UiPathChatRuntime from uipath.runtime.context import UiPathRuntimeContext -from uipath.runtime.debug import UiPathDebugBridgeProtocol +from uipath.runtime.debug import UiPathDebugProtocol from uipath.runtime.errors import UiPathRuntimeError from uipath.runtime.events import UiPathRuntimeStateEvent @@ -123,7 +123,7 @@ async def execute_runtime( async def debug_runtime( ctx: UiPathRuntimeContext, runtime: UiPathRuntimeProtocol ) -> UiPathRuntimeResult | None: - debug_bridge: UiPathDebugBridgeProtocol = ConsoleDebugBridge() + debug_bridge: UiPathDebugProtocol = ConsoleDebugBridge() await debug_bridge.emit_execution_started() options = UiPathStreamOptions(resume=resume) diff --git a/uv.lock b/uv.lock index eb8c2c97f..d04c4efbb 100644 --- a/uv.lock +++ b/uv.lock @@ -2477,7 +2477,7 @@ wheels = [ [[package]] name = "uipath" -version = "2.2.38" +version = "2.3.0" source = { editable = "." } dependencies = [ { name = "click" }, @@ -2542,7 +2542,7 @@ requires-dist = [ { name = "tenacity", specifier = ">=9.0.0" }, { name = "truststore", specifier = ">=0.10.1" }, { name = "uipath-core", specifier = ">=0.1.4,<0.2.0" }, - { name = "uipath-runtime", specifier = ">=0.2.7,<0.3.0" }, + { name = "uipath-runtime", specifier = ">=0.3.0,<0.4.0" }, ] [package.metadata.requires-dev] @@ -2588,14 +2588,14 @@ wheels = [ [[package]] name = "uipath-runtime" -version = "0.2.7" +version = "0.3.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "uipath-core" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/d8/90/03a8f57d64c4b25ebf2e364f2012bf97ef625a7cef9a6e2f68dfdce29188/uipath_runtime-0.2.7.tar.gz", hash = "sha256:2718a98db995a70b92f5eaa84fb315e8edd324ec406be2face978ffeaa062223", size = 95944, upload-time = "2025-12-11T11:29:25.94Z" } +sdist = { url = "https://files.pythonhosted.org/packages/3e/43/da9d5fef33d6a2423bc856a97a777efc1e0050db3ce649c44cd4a86f6b2c/uipath_runtime-0.3.0.tar.gz", hash = "sha256:0c68b583a56a84bdf75c911bdb9bfb9503836471be80e761a5ecf6cf4d8ead75", size = 97202, upload-time = "2025-12-18T09:01:53.557Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d3/1e/b884a6b80c3985391c2c4155498bf577839c6a85f038fcc8d019d95ed94e/uipath_runtime-0.2.7-py3-none-any.whl", hash = "sha256:c2e0176a0aebcd70d9ba8c323f14b587841fc15872176cebcd31ff61bd9e9e0d", size = 36954, upload-time = "2025-12-11T11:29:21.781Z" }, + { url = "https://files.pythonhosted.org/packages/1c/63/71f6f11478eaec4a016fa190d282634dc017bf54f69dd301e820bb74a157/uipath_runtime-0.3.0-py3-none-any.whl", hash = "sha256:1b3d13f9d7af41b691e9d29392f6e632df3221b5312adc0579c4d81cb5543dce", size = 37673, upload-time = "2025-12-18T09:01:49.983Z" }, ] [[package]]