3939 RealtimeModelAudioInterruptedEvent ,
4040 RealtimeModelErrorEvent ,
4141 RealtimeModelEvent ,
42+ RealtimeModelExceptionEvent ,
4243 RealtimeModelInputAudioTranscriptionCompletedEvent ,
4344 RealtimeModelItemDeletedEvent ,
4445 RealtimeModelItemUpdatedEvent ,
@@ -130,48 +131,84 @@ async def _listen_for_messages(self):
130131
131132 try :
132133 async for message in self ._websocket :
133- parsed = json .loads (message )
134- await self ._handle_ws_event (parsed )
134+ try :
135+ parsed = json .loads (message )
136+ await self ._handle_ws_event (parsed )
137+ except json .JSONDecodeError as e :
138+ await self ._emit_event (
139+ RealtimeModelExceptionEvent (
140+ exception = e , context = "Failed to parse WebSocket message as JSON"
141+ )
142+ )
143+ except Exception as e :
144+ await self ._emit_event (
145+ RealtimeModelExceptionEvent (
146+ exception = e , context = "Error handling WebSocket event"
147+ )
148+ )
135149
136- except websockets .exceptions .ConnectionClosed :
137- # TODO connection closed handling (event, cleanup)
138- logger .warning ("WebSocket connection closed" )
150+ except websockets .exceptions .ConnectionClosedOK :
151+ # Normal connection closure - no exception event needed
152+ logger .info ("WebSocket connection closed normally" )
153+ except websockets .exceptions .ConnectionClosed as e :
154+ await self ._emit_event (
155+ RealtimeModelExceptionEvent (
156+ exception = e , context = "WebSocket connection closed unexpectedly"
157+ )
158+ )
139159 except Exception as e :
140- logger .error (f"WebSocket error: { e } " )
160+ await self ._emit_event (
161+ RealtimeModelExceptionEvent (
162+ exception = e , context = "WebSocket error in message listener"
163+ )
164+ )
141165
142166 async def send_event (self , event : RealtimeClientMessage ) -> None :
143167 """Send an event to the model."""
144168 assert self ._websocket is not None , "Not connected"
145- converted_event = {
146- "type" : event ["type" ],
147- }
148169
149- converted_event .update (event .get ("other_data" , {}))
170+ try :
171+ converted_event = {
172+ "type" : event ["type" ],
173+ }
150174
151- await self ._websocket .send (json .dumps (converted_event ))
175+ converted_event .update (event .get ("other_data" , {}))
176+
177+ await self ._websocket .send (json .dumps (converted_event ))
178+ except Exception as e :
179+ await self ._emit_event (
180+ RealtimeModelExceptionEvent (
181+ exception = e , context = f"Failed to send event: { event .get ('type' , 'unknown' )} "
182+ )
183+ )
152184
153185 async def send_message (
154186 self , message : RealtimeUserInput , other_event_data : dict [str , Any ] | None = None
155187 ) -> None :
156188 """Send a message to the model."""
157- message = (
158- message
159- if isinstance (message , dict )
160- else {
161- "type" : "message" ,
162- "role" : "user" ,
163- "content" : [{"type" : "input_text" , "text" : message }],
189+ try :
190+ message = (
191+ message
192+ if isinstance (message , dict )
193+ else {
194+ "type" : "message" ,
195+ "role" : "user" ,
196+ "content" : [{"type" : "input_text" , "text" : message }],
197+ }
198+ )
199+ other_data = {
200+ "item" : message ,
164201 }
165- )
166- other_data = {
167- "item" : message ,
168- }
169- if other_event_data :
170- other_data .update (other_event_data )
202+ if other_event_data :
203+ other_data .update (other_event_data )
171204
172- await self .send_event ({"type" : "conversation.item.create" , "other_data" : other_data })
205+ await self .send_event ({"type" : "conversation.item.create" , "other_data" : other_data })
173206
174- await self .send_event ({"type" : "response.create" })
207+ await self .send_event ({"type" : "response.create" })
208+ except Exception as e :
209+ await self ._emit_event (
210+ RealtimeModelExceptionEvent (exception = e , context = "Failed to send message" )
211+ )
175212
176213 async def send_audio (self , audio : bytes , * , commit : bool = False ) -> None :
177214 """Send a raw audio chunk to the model.
@@ -182,17 +219,23 @@ async def send_audio(self, audio: bytes, *, commit: bool = False) -> None:
182219 detection, this can be used to indicate the turn is completed.
183220 """
184221 assert self ._websocket is not None , "Not connected"
185- base64_audio = base64 .b64encode (audio ).decode ("utf-8" )
186- await self .send_event (
187- {
188- "type" : "input_audio_buffer.append" ,
189- "other_data" : {
190- "audio" : base64_audio ,
191- },
192- }
193- )
194- if commit :
195- await self .send_event ({"type" : "input_audio_buffer.commit" })
222+
223+ try :
224+ base64_audio = base64 .b64encode (audio ).decode ("utf-8" )
225+ await self .send_event (
226+ {
227+ "type" : "input_audio_buffer.append" ,
228+ "other_data" : {
229+ "audio" : base64_audio ,
230+ },
231+ }
232+ )
233+ if commit :
234+ await self .send_event ({"type" : "input_audio_buffer.commit" })
235+ except Exception as e :
236+ await self ._emit_event (
237+ RealtimeModelExceptionEvent (exception = e , context = "Failed to send audio" )
238+ )
196239
197240 async def send_tool_output (
198241 self , tool_call : RealtimeModelToolCallEvent , output : str , start_response : bool
@@ -342,8 +385,13 @@ async def _handle_ws_event(self, event: dict[str, Any]):
342385 OpenAIRealtimeServerEvent
343386 ).validate_python (event )
344387 except Exception as e :
345- logger .error (f"Invalid event: { event } - { e } " )
346- # await self._emit_event(RealtimeModelErrorEvent(error=f"Invalid event: {event} - {e}"))
388+ event_type = event .get ("type" , "unknown" ) if isinstance (event , dict ) else "unknown"
389+ await self ._emit_event (
390+ RealtimeModelExceptionEvent (
391+ exception = e ,
392+ context = f"Failed to validate server event: { event_type } " ,
393+ )
394+ )
347395 return
348396
349397 if parsed .type == "response.audio.delta" :
0 commit comments