33import asyncio
44import logging
55import os
6+ import uuid
67from typing import Any
78from urllib .parse import urlparse
89
1213 UiPathConversationEvent ,
1314 UiPathConversationExchangeEndEvent ,
1415 UiPathConversationExchangeEvent ,
16+ UiPathConversationInterruptEvent ,
17+ UiPathConversationInterruptStartEvent ,
18+ UiPathConversationMessageEvent ,
1519)
20+ from uipath .runtime import UiPathRuntimeResult
1621from uipath .runtime .chat import UiPathChatProtocol
1722from uipath .runtime .context import UiPathRuntimeContext
1823
@@ -82,7 +87,6 @@ async def connect(self, timeout: float = 10.0) -> None:
8287 self ._client .on ("disconnect" , self ._handle_disconnect )
8388 self ._client .on ("connect_error" , self ._handle_connect_error )
8489
85- # Clear connection event
8690 self ._connected_event .clear ()
8791
8892 try :
@@ -98,11 +102,8 @@ async def connect(self, timeout: float = 10.0) -> None:
98102 timeout = timeout ,
99103 )
100104
101- # Wait for connection confirmation
102105 await asyncio .wait_for (self ._connected_event .wait (), timeout = timeout )
103106
104- logger .info ("WebSocket connection established successfully" )
105-
106107 except asyncio .TimeoutError as e :
107108 error_message = (
108109 f"Failed to connect to WebSocket server within { timeout } s timeout"
@@ -127,34 +128,16 @@ async def disconnect(self) -> None:
127128 logger .warning ("WebSocket client not connected" )
128129 return
129130
130- # Send exchange end event using stored IDs
131- if self ._client and self ._connected_event .is_set ():
132- try :
133- end_event = UiPathConversationEvent (
134- conversation_id = self .conversation_id ,
135- exchange = UiPathConversationExchangeEvent (
136- exchange_id = self .exchange_id ,
137- end = UiPathConversationExchangeEndEvent (),
138- ),
139- )
140- event_data = end_event .model_dump (
141- mode = "json" , exclude_none = True , by_alias = True
142- )
143- await self ._client .emit ("ConversationEvent" , event_data )
144- logger .info ("Exchange end event sent" )
145- except Exception as e :
146- logger .warning (f"Error sending exchange end event: { e } " )
147-
148131 try :
149- logger .info ("Disconnecting from WebSocket server" )
150132 await self ._client .disconnect ()
151- logger .info ("WebSocket disconnected successfully" )
152133 except Exception as e :
153134 logger .error (f"Error during WebSocket disconnect: { e } " )
154135 finally :
155136 await self ._cleanup_client ()
156137
157- async def emit_message_event (self , message_event : Any ) -> None :
138+ async def emit_message_event (
139+ self , message_event : UiPathConversationMessageEvent
140+ ) -> None :
158141 """Wrap and send a message event to the WebSocket server.
159142
160143 Args:
@@ -183,14 +166,82 @@ async def emit_message_event(self, message_event: Any) -> None:
183166 mode = "json" , exclude_none = True , by_alias = True
184167 )
185168
186- logger .debug ("Sending conversation event to WebSocket" )
187169 await self ._client .emit ("ConversationEvent" , event_data )
188- logger .debug ("Conversation event sent successfully" )
170+
171+ # Store the current message ID, used for emitting interrupt events.
172+ self ._current_message_id = message_event .message_id
173+
174+ except Exception as e :
175+ logger .error (f"Error sending conversation event to WebSocket: { e } " )
176+ raise RuntimeError (f"Failed to send conversation event: { e } " ) from e
177+
178+ async def emit_exchange_end_event (self ) -> None :
179+ """Send an exchange end event.
180+
181+ Raises:
182+ RuntimeError: If client is not connected
183+ """
184+ if self ._client is None :
185+ raise RuntimeError ("WebSocket client not connected. Call connect() first." )
186+
187+ if not self ._connected_event .is_set ():
188+ raise RuntimeError ("WebSocket client not in connected state" )
189+
190+ try :
191+ exchange_end_event = UiPathConversationEvent (
192+ conversation_id = self .conversation_id ,
193+ exchange = UiPathConversationExchangeEvent (
194+ exchange_id = self .exchange_id ,
195+ end = UiPathConversationExchangeEndEvent (),
196+ ),
197+ )
198+
199+ event_data = exchange_end_event .model_dump (
200+ mode = "json" , exclude_none = True , by_alias = True
201+ )
202+
203+ await self ._client .emit ("ConversationEvent" , event_data )
189204
190205 except Exception as e :
191206 logger .error (f"Error sending conversation event to WebSocket: { e } " )
192207 raise RuntimeError (f"Failed to send conversation event: { e } " ) from e
193208
209+ async def emit_interrupt_event (self , runtime_result : UiPathRuntimeResult ):
210+ if self ._client and self ._connected_event .is_set ():
211+ try :
212+ self ._interrupt_id = str (uuid .uuid4 ())
213+
214+ interrupt_event = UiPathConversationEvent (
215+ conversation_id = self .conversation_id ,
216+ exchange = UiPathConversationExchangeEvent (
217+ exchange_id = self .exchange_id ,
218+ message = UiPathConversationMessageEvent (
219+ message_id = self ._current_message_id ,
220+ interrupt = UiPathConversationInterruptEvent (
221+ interrupt_id = self ._interrupt_id ,
222+ start = UiPathConversationInterruptStartEvent (
223+ type = "coded-agent-interrupt" ,
224+ value = runtime_result .output ,
225+ ),
226+ ),
227+ ),
228+ ),
229+ )
230+ event_data = interrupt_event .model_dump (
231+ mode = "json" , exclude_none = True , by_alias = True
232+ )
233+ await self ._client .emit ("ConversationEvent" , event_data )
234+ except Exception as e :
235+ logger .warning (f"Error sending interrupt event: { e } " )
236+
237+ async def wait_for_resume (self ) -> dict [str , Any ]:
238+ """Wait for the interrupt_end event to be received.
239+
240+ Returns:
241+ Resume data from the interrupt end event
242+ """
243+ return {}
244+
194245 @property
195246 def is_connected (self ) -> bool :
196247 """Check if the WebSocket is currently connected.
0 commit comments