11"""Chat bridge implementations for conversational agents."""
22
33import asyncio
4+ import datetime
45import json
56import logging
67import os
8+ import time
79import uuid
10+ from datetime import timezone , datetime
811from typing import Any
912from urllib .parse import urlparse
1013
1316 UiPathConversationExchangeEndEvent ,
1417 UiPathConversationExchangeEvent ,
1518 UiPathConversationInterruptEvent ,
16- UiPathConversationInterruptStartEvent ,
19+ UiPathConversationInterruptEndEvent ,
1720 UiPathConversationMessageEvent ,
21+ UiPathConversationToolCallConfirmationInterruptStart ,
22+ UiPathConversationToolCallConfirmationValue
1823)
1924from uipath .runtime import UiPathRuntimeResult
2025from uipath .runtime .chat import UiPathChatProtocol
@@ -56,6 +61,11 @@ def __init__(
5661 self ._client : Any | None = None
5762 self ._connected_event = asyncio .Event ()
5863
64+ # Interrupt handling
65+ self ._interrupt_id : str | None = None
66+ self ._interrupt_end_event = asyncio .Event ()
67+ self ._interrupt_end_data : UiPathConversationInterruptEndEvent | None = None
68+
5969 # Set CAS_WEBSOCKET_DISABLED when using the debugger to prevent websocket errors from
6070 # interrupting the debugging session. Events will be logged instead of being sent.
6171 self ._websocket_disabled = os .environ .get ("CAS_WEBSOCKET_DISABLED" ) == "true"
@@ -242,7 +252,10 @@ async def emit_exchange_end_event(self) -> None:
242252 async def emit_interrupt_event (self , runtime_result : UiPathRuntimeResult ):
243253 if self ._client and self ._connected_event .is_set ():
244254 try :
255+ # Clear previous interrupt state and generate new interrupt_id
245256 self ._interrupt_id = str (uuid .uuid4 ())
257+ self ._interrupt_end_event .clear ()
258+ self ._interrupt_end_data = None
246259
247260 interrupt_event = UiPathConversationEvent (
248261 conversation_id = self .conversation_id ,
@@ -252,9 +265,9 @@ async def emit_interrupt_event(self, runtime_result: UiPathRuntimeResult):
252265 message_id = self ._current_message_id ,
253266 interrupt = UiPathConversationInterruptEvent (
254267 interrupt_id = self ._interrupt_id ,
255- start = UiPathConversationInterruptStartEvent (
256- type = "coded-agent-interrupt " ,
257- value = runtime_result .output ,
268+ start = UiPathConversationToolCallConfirmationInterruptStart (
269+ type = "uipath_cas_tool_call_confirmation " ,
270+ value = UiPathConversationToolCallConfirmationValue ( ** runtime_result .triggers [ 0 ]. api_resume . request ) ,
258271 ),
259272 ),
260273 ),
@@ -270,15 +283,27 @@ async def emit_interrupt_event(self, runtime_result: UiPathRuntimeResult):
270283 else :
271284 await self ._client .emit ("ConversationEvent" , event_data )
272285 except Exception as e :
273- logger .warning (f"Error sending interrupt event: { e } " )
286+ logger .error (f"Error sending interrupt event: { e } " , exc_info = True )
274287
275288 async def wait_for_resume (self ) -> dict [str , Any ]:
276289 """Wait for the interrupt_end event to be received.
277290
278291 Returns:
279292 Resume data from the interrupt end event
280293 """
281- return {}
294+ # Wait for the end interrupt event (with timeout)
295+ await asyncio .wait_for (self ._interrupt_end_event .wait (), timeout = 15 * 10 )
296+
297+ if self ._interrupt_end_data is None :
298+ logger .error ("Interrupt end event was signaled but data is None" )
299+ return {"confirmed" : False }
300+
301+ # Extract approved status from the end event value
302+ approved = False
303+ if hasattr (self ._interrupt_end_data , 'value' ) and hasattr (self ._interrupt_end_data .value , 'approved' ):
304+ approved = self ._interrupt_end_data .value .approved
305+
306+ return {"confirmed" : approved }
282307
283308 @property
284309 def is_connected (self ) -> bool :
@@ -310,6 +335,25 @@ async def _handle_conversation_event(
310335 error_event = event .get ("conversationError" )
311336 if error_event :
312337 logger .error (f"Conversation error: { json .dumps (error_event )} " )
338+ return
339+
340+ # Check for endInterrupt events
341+ try :
342+ parsed_event = UiPathConversationEvent (** event )
343+ if (
344+ parsed_event .exchange
345+ and parsed_event .exchange .message
346+ and parsed_event .exchange .message .interrupt
347+ and parsed_event .exchange .message .interrupt .end
348+ ):
349+ interrupt = parsed_event .exchange .message .interrupt
350+ # Check if this is the interrupt we're waiting for
351+ if interrupt .interrupt_id == self ._interrupt_id :
352+ logger .info (f"Received endInterrupt for interrupt_id: { self ._interrupt_id } " )
353+ self ._interrupt_end_data = interrupt .end
354+ self ._interrupt_end_event .set ()
355+ except Exception as e :
356+ logger .warning (f"Error parsing conversation event: { e } " )
313357
314358 async def _cleanup_client (self ) -> None :
315359 """Clean up client resources."""
0 commit comments