Skip to content
Open
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
4 changes: 2 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -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",
Expand Down
105 changes: 78 additions & 27 deletions src/uipath/_cli/_chat/_bridge.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import asyncio
import logging
import os
import uuid
from typing import Any
from urllib.parse import urlparse

Expand All @@ -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

Expand Down Expand Up @@ -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:
Expand All @@ -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"
Expand All @@ -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:
Expand Down Expand Up @@ -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.
Expand Down
6 changes: 3 additions & 3 deletions src/uipath/_cli/_debug/_bridge.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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:
Expand All @@ -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:
Expand Down
4 changes: 2 additions & 2 deletions src/uipath/_cli/cli_debug.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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,
Expand Down
4 changes: 2 additions & 2 deletions src/uipath/_cli/cli_run.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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)
Expand Down
10 changes: 5 additions & 5 deletions uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading