11"""Chat bridge implementations for conversational agents."""
22
33import asyncio
4+ import json
45import logging
56import os
67import uuid
@@ -55,6 +56,10 @@ def __init__(
5556 self ._client : Any | None = None
5657 self ._connected_event = asyncio .Event ()
5758
59+ # Set CAS_WEBSOCKET_DISABLED when using the debugger to prevent websocket errors from
60+ # interrupting the debugging session. Events will be logged instead of being sent.
61+ self ._websocket_disabled = os .environ .get ("CAS_WEBSOCKET_DISABLED" ) == "true"
62+
5863 async def connect (self , timeout : float = 10.0 ) -> None :
5964 """Establish WebSocket connection to the server.
6065
@@ -87,37 +92,43 @@ async def connect(self, timeout: float = 10.0) -> None:
8792 self ._client .on ("connect" , self ._handle_connect )
8893 self ._client .on ("disconnect" , self ._handle_disconnect )
8994 self ._client .on ("connect_error" , self ._handle_connect_error )
95+ self ._client .on ("ConversationEvent" , self ._handle_conversation_event )
9096
9197 self ._connected_event .clear ()
9298
93- try :
94- # Attempt to connect with timeout
95- await asyncio .wait_for (
96- self ._client .connect (
97- url = self .websocket_url ,
98- socketio_path = self .websocket_path ,
99- headers = self .headers ,
100- auth = self .auth ,
101- transports = ["websocket" ],
102- ),
103- timeout = timeout ,
99+ if self ._websocket_disabled :
100+ logger .warning (
101+ "SocketIOChatBridge is in debug mode. Not connecting websocket."
104102 )
103+ else :
104+ try :
105+ # Attempt to connect with timeout
106+ await asyncio .wait_for (
107+ self ._client .connect (
108+ url = self .websocket_url ,
109+ socketio_path = self .websocket_path ,
110+ headers = self .headers ,
111+ auth = self .auth ,
112+ transports = ["websocket" ],
113+ ),
114+ timeout = timeout ,
115+ )
105116
106- await asyncio .wait_for (self ._connected_event .wait (), timeout = timeout )
117+ await asyncio .wait_for (self ._connected_event .wait (), timeout = timeout )
107118
108- except asyncio .TimeoutError as e :
109- error_message = (
110- f"Failed to connect to WebSocket server within { timeout } s timeout"
111- )
112- logger .error (error_message )
113- await self ._cleanup_client ()
114- raise RuntimeError (error_message ) from e
119+ except asyncio .TimeoutError as e :
120+ error_message = (
121+ f"Failed to connect to WebSocket server within { timeout } s timeout"
122+ )
123+ logger .error (error_message )
124+ await self ._cleanup_client ()
125+ raise RuntimeError (error_message ) from e
115126
116- except Exception as e :
117- error_message = f"Failed to connect to WebSocket server: { e } "
118- logger .error (error_message )
119- await self ._cleanup_client ()
120- raise RuntimeError (error_message ) from e
127+ except Exception as e :
128+ error_message = f"Failed to connect to WebSocket server: { e } "
129+ logger .error (error_message )
130+ await self ._cleanup_client ()
131+ raise RuntimeError (error_message ) from e
121132
122133 async def disconnect (self ) -> None :
123134 """Close the WebSocket connection gracefully.
@@ -150,7 +161,7 @@ async def emit_message_event(
150161 if self ._client is None :
151162 raise RuntimeError ("WebSocket client not connected. Call connect() first." )
152163
153- if not self ._connected_event .is_set ():
164+ if not self ._connected_event .is_set () and not self . _websocket_disabled :
154165 raise RuntimeError ("WebSocket client not in connected state" )
155166
156167 try :
@@ -167,7 +178,12 @@ async def emit_message_event(
167178 mode = "json" , exclude_none = True , by_alias = True
168179 )
169180
170- await self ._client .emit ("ConversationEvent" , event_data )
181+ if self ._websocket_disabled :
182+ logger .info (
183+ f"SocketIOChatBridge is in debug mode. Not sending event: { json .dumps (event_data )} "
184+ )
185+ else :
186+ await self ._client .emit ("ConversationEvent" , event_data )
171187
172188 # Store the current message ID, used for emitting interrupt events.
173189 self ._current_message_id = message_event .message_id
@@ -185,7 +201,7 @@ async def emit_exchange_end_event(self) -> None:
185201 if self ._client is None :
186202 raise RuntimeError ("WebSocket client not connected. Call connect() first." )
187203
188- if not self ._connected_event .is_set ():
204+ if not self ._connected_event .is_set () and not self . _websocket_disabled :
189205 raise RuntimeError ("WebSocket client not in connected state" )
190206
191207 try :
@@ -201,7 +217,12 @@ async def emit_exchange_end_event(self) -> None:
201217 mode = "json" , exclude_none = True , by_alias = True
202218 )
203219
204- await self ._client .emit ("ConversationEvent" , event_data )
220+ if self ._websocket_disabled :
221+ logger .info (
222+ f"SocketIOChatBridge is in debug mode. Not sending event: { json .dumps (event_data )} "
223+ )
224+ else :
225+ await self ._client .emit ("ConversationEvent" , event_data )
205226
206227 except Exception as e :
207228 logger .error (f"Error sending conversation event to WebSocket: { e } " )
@@ -231,7 +252,12 @@ async def emit_interrupt_event(self, runtime_result: UiPathRuntimeResult):
231252 event_data = interrupt_event .model_dump (
232253 mode = "json" , exclude_none = True , by_alias = True
233254 )
234- await self ._client .emit ("ConversationEvent" , event_data )
255+ if self ._websocket_disabled :
256+ logger .info (
257+ f"SocketIOChatBridge is in debug mode. Not sending event: { json .dumps (event_data )} "
258+ )
259+ else :
260+ await self ._client .emit ("ConversationEvent" , event_data )
235261 except Exception as e :
236262 logger .warning (f"Error sending interrupt event: { e } " )
237263
@@ -266,6 +292,14 @@ async def _handle_connect_error(self, data: Any) -> None:
266292 """Handle connection error event."""
267293 logger .error (f"WebSocket connection error: { data } " )
268294
295+ async def _handle_conversation_event (
296+ self , event : dict [str , Any ], _sid : str
297+ ) -> None :
298+ """Handle received ConversationEvent events."""
299+ error_event = event .get ("conversationError" )
300+ if error_event :
301+ logger .error (f"Conversation error: { json .dumps (error_event )} " )
302+
269303 async def _cleanup_client (self ) -> None :
270304 """Clean up client resources."""
271305 self ._connected_event .clear ()
@@ -316,6 +350,13 @@ def get_chat_bridge(
316350 websocket_url = f"wss://{ host } ?conversationId={ context .conversation_id } "
317351 websocket_path = "autopilotforeveryone_/websocket_/socket.io"
318352
353+ if os .environ .get ("CAS_WEBSOCKET_HOST" ):
354+ websocket_url = f"ws://{ os .environ .get ('CAS_WEBSOCKET_HOST' )} ?conversationId={ context .conversation_id } "
355+ websocket_path = "/socket.io"
356+ logger .warning (
357+ f"CAS_WEBSOCKET_HOST is set. Using websocket_url '{ websocket_url } { websocket_path } '."
358+ )
359+
319360 # Build headers from context
320361 headers = {
321362 "Authorization" : f"Bearer { os .environ .get ('UIPATH_ACCESS_TOKEN' , '' )} " ,
0 commit comments