Skip to content

Commit 09dae2a

Browse files
committed
fix: stream debug run in console
1 parent 22e6296 commit 09dae2a

File tree

4 files changed

+119
-3
lines changed

4 files changed

+119
-3
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"
3-
version = "2.2.22"
3+
version = "2.2.23"
44
description = "Python SDK and CLI for UiPath Platform, enabling programmatic interaction with automation services, process management, and deployment tools."
55
readme = { file = "README.md", content-type = "text/markdown" }
66
requires-python = ">=3.11"
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,18 +7,126 @@
77
from urllib.parse import urlparse
88

99
import socketio # type: ignore[import-untyped]
10+
from rich.console import Console
11+
from rich.live import Live
12+
from rich.markdown import Markdown
13+
from rich.panel import Panel
1014
from socketio import AsyncClient
1115
from uipath.core.chat import (
1216
UiPathConversationEvent,
1317
UiPathConversationExchangeEndEvent,
1418
UiPathConversationExchangeEvent,
19+
UiPathConversationMessageEvent,
20+
UiPathConversationMessageStartEvent,
1521
)
1622
from uipath.runtime.chat import UiPathChatProtocol
1723
from uipath.runtime.context import UiPathRuntimeContext
1824

1925
logger = logging.getLogger(__name__)
2026

2127

28+
class ConsoleChatBridge(UiPathChatProtocol):
29+
"""Console implementation of UiPathChatProtocol that streams message content."""
30+
31+
def __init__(self):
32+
self.console = Console(force_terminal=True)
33+
self._connected = False
34+
35+
self._live: Live | None = None
36+
self._buffer: list[str] = []
37+
self._current_role: str | None = None
38+
39+
async def connect(self) -> None:
40+
self._connected = True
41+
self.console.print(
42+
Panel("[bold green]Console Chat Started[/bold green]", expand=False)
43+
)
44+
45+
async def disconnect(self) -> None:
46+
if self._live:
47+
self._live.stop()
48+
self.console.print(
49+
Panel("[bold red]Console Chat Ended[/bold red]", expand=False)
50+
)
51+
self._connected = False
52+
53+
async def emit_message_event(self, event: UiPathConversationMessageEvent) -> None:
54+
"""Handles *all* message-level events."""
55+
if not self._connected:
56+
return
57+
58+
if event.start:
59+
await self._on_message_start(event.start)
60+
61+
if event.content_part:
62+
cp = event.content_part
63+
64+
if cp.start:
65+
await self._on_content_part_start(cp.start.mime_type)
66+
67+
if cp.chunk:
68+
await self._on_content_part_chunk(cp.chunk.data or "")
69+
70+
if cp.end:
71+
await self._on_content_part_end()
72+
73+
if event.end:
74+
await self._on_message_end()
75+
76+
async def _on_message_start(self, start: UiPathConversationMessageStartEvent):
77+
self._current_role = start.role or "assistant"
78+
self.console.print(
79+
Panel(
80+
"[bold blue]User[/bold blue]"
81+
if self._current_role == "user"
82+
else "[bold green]Assistant[/bold green]",
83+
expand=False,
84+
)
85+
)
86+
87+
async def _on_message_end(self):
88+
if self._live:
89+
self._live.stop()
90+
self._live = None
91+
self._buffer = []
92+
93+
async def _on_content_part_start(self, mime_type: str):
94+
"""Prepare to stream a content part of a message."""
95+
self._buffer = []
96+
97+
if not self._live:
98+
self._live = Live(auto_refresh=False)
99+
self._live.start()
100+
101+
async def _on_content_part_chunk(self, text: str):
102+
"""Stream one chunk of assistant text."""
103+
if not self._live:
104+
# Should never happen, but safe guard
105+
self._live = Live(auto_refresh=False)
106+
self._live.start()
107+
108+
self._buffer.append(text)
109+
110+
rendered = Markdown("".join(self._buffer))
111+
self._live.update(
112+
Panel(rendered, title="[green]Assistant (streaming)"),
113+
)
114+
self._live.refresh()
115+
116+
async def _on_content_part_end(self):
117+
"""Finalize streaming for this part (but message may continue)."""
118+
if self._live:
119+
self._live.stop()
120+
self._live = None
121+
122+
# Print the final assembled text once more
123+
final_text = "".join(self._buffer)
124+
self.console.print(
125+
Panel(Markdown(final_text), expand=False, border_style="green")
126+
)
127+
self._buffer = []
128+
129+
22130
class WebSocketChatBridge:
23131
"""WebSocket-based chat bridge for streaming conversational events to CAS.
24132

src/uipath/_cli/cli_run.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,13 @@
1010
UiPathRuntimeResult,
1111
UiPathStreamOptions,
1212
)
13+
from uipath.runtime.chat import UiPathChatProtocol
1314
from uipath.runtime.context import UiPathRuntimeContext
1415
from uipath.runtime.debug import UiPathDebugBridgeProtocol
1516
from uipath.runtime.errors import UiPathRuntimeError
16-
from uipath.runtime.events import UiPathRuntimeStateEvent
17+
from uipath.runtime.events import UiPathRuntimeMessageEvent, UiPathRuntimeStateEvent
1718

19+
from uipath._cli._chat._bridge import ConsoleChatBridge
1820
from uipath._cli._debug._bridge import ConsoleDebugBridge
1921
from uipath._cli._utils._common import read_resource_overwrites_from_file
2022
from uipath._cli._utils._debug import setup_debugging
@@ -122,14 +124,20 @@ async def debug_runtime(
122124
ctx: UiPathRuntimeContext, runtime: UiPathRuntimeProtocol
123125
) -> UiPathRuntimeResult | None:
124126
debug_bridge: UiPathDebugBridgeProtocol = ConsoleDebugBridge()
127+
chat_bridge: UiPathChatProtocol = ConsoleChatBridge()
128+
125129
await debug_bridge.emit_execution_started()
130+
await chat_bridge.connect()
126131
options = UiPathStreamOptions(resume=resume)
127132
async for event in runtime.stream(ctx.get_input(), options=options):
128133
if isinstance(event, UiPathRuntimeResult):
129134
await debug_bridge.emit_execution_completed(event)
135+
await chat_bridge.disconnect()
130136
ctx.result = event
131137
elif isinstance(event, UiPathRuntimeStateEvent):
132138
await debug_bridge.emit_state_update(event)
139+
elif isinstance(event, UiPathRuntimeMessageEvent):
140+
await chat_bridge.emit_message_event(event)
133141
return ctx.result
134142

135143
async def execute() -> None:

uv.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)