33import json
44import logging
55import typing
6- from json .decoder import JSONDecodeError
7-
8- import websockets
96import websockets .sync .connection as websockets_sync_connection
107from ...core .events import EventEmitterMixin , EventType
118from ...core .unchecked_base_model import construct_type
3936 from websockets import WebSocketClientProtocol # type: ignore
4037
4138_logger = logging .getLogger (__name__ )
39+
40+
41+ def _sanitize_numeric_types (obj : typing .Any ) -> typing .Any :
42+ """
43+ Recursively convert float values that are whole numbers to int.
44+
45+ Workaround for Fern-generated models that type integer API fields
46+ (like sample_rate) as float, causing JSON serialization to produce
47+ values like 44100.0 instead of 44100. The Deepgram API rejects
48+ float representations of integer fields.
49+
50+ See: https://github.com/deepgram/internal-api-specs/issues/205
51+ """
52+ if isinstance (obj , dict ):
53+ return {k : _sanitize_numeric_types (v ) for k , v in obj .items ()}
54+ elif isinstance (obj , list ):
55+ return [_sanitize_numeric_types (item ) for item in obj ]
56+ elif isinstance (obj , float ) and obj .is_integer ():
57+ return int (obj )
58+ return obj
59+
60+
4261V1SocketClientResponse = typing .Union [
4362 AgentV1ReceiveFunctionCallResponse ,
4463 AgentV1PromptUpdated ,
@@ -70,7 +89,7 @@ async def __aiter__(self):
7089 yield message
7190 else :
7291 try :
73- yield construct_type (V1SocketClientResponse , json .loads (message )) # type: ignore
92+ yield construct_type (type_ = V1SocketClientResponse , object_ = json .loads (message )) # type: ignore
7493 except Exception :
7594 _logger .warning (
7695 "Skipping unknown WebSocket message; update your SDK version to support new message types."
@@ -95,14 +114,14 @@ async def start_listening(self):
95114 else :
96115 json_data = json .loads (raw_message )
97116 try :
98- parsed = construct_type (V1SocketClientResponse , json_data ) # type: ignore
117+ parsed = construct_type (type_ = V1SocketClientResponse , object_ = json_data ) # type: ignore
99118 except Exception :
100119 _logger .warning (
101120 "Skipping unknown WebSocket message; update your SDK version to support new message types."
102121 )
103122 continue
104123 await self ._emit_async (EventType .MESSAGE , parsed )
105- except ( websockets . WebSocketException , JSONDecodeError ) as exc :
124+ except Exception as exc :
106125 await self ._emit_async (EventType .ERROR , exc )
107126 finally :
108127 await self ._emit_async (EventType .CLOSE , None )
@@ -142,12 +161,12 @@ async def send_function_call_response(self, message: AgentV1SendFunctionCallResp
142161 """
143162 await self ._send_model (message )
144163
145- async def send_keep_alive (self , message : AgentV1KeepAlive ) -> None :
164+ async def send_keep_alive (self , message : typing . Optional [ AgentV1KeepAlive ] = None ) -> None :
146165 """
147166 Send a message to the websocket connection.
148167 The message will be sent as a AgentV1KeepAlive.
149168 """
150- await self ._send_model (message )
169+ await self ._send_model (message or AgentV1KeepAlive ( type = "KeepAlive" ) )
151170
152171 async def send_update_prompt (self , message : AgentV1UpdatePrompt ) -> None :
153172 """
@@ -179,7 +198,7 @@ async def recv(self) -> V1SocketClientResponse:
179198 return data # type: ignore
180199 json_data = json .loads (data )
181200 try :
182- return construct_type (V1SocketClientResponse , json_data ) # type: ignore
201+ return construct_type (type_ = V1SocketClientResponse , object_ = json_data ) # type: ignore
183202 except Exception :
184203 _logger .warning ("Skipping unknown WebSocket message; update your SDK version to support new message types." )
185204 return json_data # type: ignore
@@ -196,7 +215,7 @@ async def _send_model(self, data: typing.Any) -> None:
196215 """
197216 Send a Pydantic model to the websocket connection.
198217 """
199- await self ._send (data .dict ())
218+ await self ._send (_sanitize_numeric_types ( data .dict () ))
200219
201220
202221class V1SocketClient (EventEmitterMixin ):
@@ -210,7 +229,7 @@ def __iter__(self):
210229 yield message
211230 else :
212231 try :
213- yield construct_type (V1SocketClientResponse , json .loads (message )) # type: ignore
232+ yield construct_type (type_ = V1SocketClientResponse , object_ = json .loads (message )) # type: ignore
214233 except Exception :
215234 _logger .warning (
216235 "Skipping unknown WebSocket message; update your SDK version to support new message types."
@@ -235,14 +254,14 @@ def start_listening(self):
235254 else :
236255 json_data = json .loads (raw_message )
237256 try :
238- parsed = construct_type (V1SocketClientResponse , json_data ) # type: ignore
257+ parsed = construct_type (type_ = V1SocketClientResponse , object_ = json_data ) # type: ignore
239258 except Exception :
240259 _logger .warning (
241260 "Skipping unknown WebSocket message; update your SDK version to support new message types."
242261 )
243262 continue
244263 self ._emit (EventType .MESSAGE , parsed )
245- except ( websockets . WebSocketException , JSONDecodeError ) as exc :
264+ except Exception as exc :
246265 self ._emit (EventType .ERROR , exc )
247266 finally :
248267 self ._emit (EventType .CLOSE , None )
@@ -282,12 +301,12 @@ def send_function_call_response(self, message: AgentV1SendFunctionCallResponse)
282301 """
283302 self ._send_model (message )
284303
285- def send_keep_alive (self , message : AgentV1KeepAlive ) -> None :
304+ def send_keep_alive (self , message : typing . Optional [ AgentV1KeepAlive ] = None ) -> None :
286305 """
287306 Send a message to the websocket connection.
288307 The message will be sent as a AgentV1KeepAlive.
289308 """
290- self ._send_model (message )
309+ self ._send_model (message or AgentV1KeepAlive ( type = "KeepAlive" ) )
291310
292311 def send_update_prompt (self , message : AgentV1UpdatePrompt ) -> None :
293312 """
@@ -319,7 +338,7 @@ def recv(self) -> V1SocketClientResponse:
319338 return data # type: ignore
320339 json_data = json .loads (data )
321340 try :
322- return construct_type (V1SocketClientResponse , json_data ) # type: ignore
341+ return construct_type (type_ = V1SocketClientResponse , object_ = json_data ) # type: ignore
323342 except Exception :
324343 _logger .warning ("Skipping unknown WebSocket message; update your SDK version to support new message types." )
325344 return json_data # type: ignore
@@ -336,4 +355,4 @@ def _send_model(self, data: typing.Any) -> None:
336355 """
337356 Send a Pydantic model to the websocket connection.
338357 """
339- self ._send (data .dict ())
358+ self ._send (_sanitize_numeric_types ( data .dict () ))
0 commit comments