
    `gj1                        d Z ddlmZ ddlZddlZddlZddlZddlmZ ddl	m
Z
mZmZ dZd Z G d d	      Z G d
 d      Zy)a"  OpenAI Realtime API WebSocket client + file-queue speaker.

This module is the "output" side of the v2 voice bridge: it takes text,
sends it to the OpenAI Realtime API, receives audio deltas back, and
appends the PCM bytes to a file. A separate consumer (the audio
bridge) streams that file into Chrome's fake microphone.

Designed for simplicity: a single synchronous WebSocket connection per
speaker, per session. The ``websockets`` package is imported lazily so
that importing this module never fails just because the optional dep
is missing.
    )annotationsN)Path)AnyCallableOptionalz wss://api.openai.com/v1/realtimec                 N    	 ddl m}  | S # t        $ r}t        d      |d}~ww xY w)z=Import ``websockets.sync.client.connect`` or raise with hint.r   )connectzXwebsockets package is required for OpenAI Realtime; install with: pip install websocketsN)websockets.sync.clientr	   ImportErrorRuntimeError)_connectexcs     ]/root/.hermes/venv/lib/python3.12/site-packages/plugins/google_meet/realtime/openai_client.py_require_websocketsr      s:    > O  3
 	s   
 	$$c                  r    e Zd ZdZ	 	 	 	 	 d
	 	 	 	 	 	 	 	 	 	 	 	 	 ddZddZddZdddZddZddZ	ddd	Z
y)RealtimeSessionaj  Minimal sync client for the OpenAI Realtime WebSocket API.

    Usage:
        sess = RealtimeSession(api_key=..., audio_sink_path=Path("out.pcm"))
        sess.connect()
        sess.speak("Hello team.")
        sess.close()

    Thread safety: ``speak`` and ``cancel_response`` may be called from
    different threads; a lock serializes WebSocket writes.
    Nc                    dd l }|| _        || _        || _        || _        |rt        |      nd | _        || _        d | _        |j                         | _
        d | _        d| _        d | _        y )Nr   )	threadingapi_keymodelvoiceinstructionsr   audio_sink_pathsample_rate_wsLock
_send_lock_last_response_idaudio_bytes_outlast_audio_out_at)selfr   r   r   r   r   r   
_threadings           r   __init__zRealtimeSession.__init__4   sj     	'

(8GtO4T&$//+04$%26    c           	     $   t               }t         d| j                   }dd| j                   fdg}	  |||      | _        | j                  d| j                  | j                  dd	gd
d
dd       y# t
        $ r  |||      | _        Y Kw xY w)z8Open WS and send session.update with voice+instructions.z?model=AuthorizationzBearer )zOpenAI-Betazrealtime=v1)additional_headers)extra_headerszsession.updateaudiotextpcm16)r   r   
modalitiesoutput_audio_formatinput_audio_format)typesessionN)	r   REALTIME_URLr   r   r   	TypeError
_send_jsonr   r   )r!   r	   urlheaderss       r   r	   zRealtimeSession.connectM   s    %'gdjj\2~67*
	;sw?DH 	(!ZZ$($5$5#*F"3+2*1		
  	;s':DH	;s   A4 4BBc                    | j                   #	 | j                   j                          d | _         y y # t        $ r Y w xY wN)r   close	Exceptionr!   s    r   r8   zRealtimeSession.closej   sA    88  DH    s   1 	==c                   | j                   t        d      t        j                         }| j	                  dddd|dgdd	       | j	                  d
ddgid       d}d}| j
                  =| j
                  j                  j                  dd       t        | j
                  d      }	 	 |t        j                         |z
  z
  }|dk  rt        d| d      | j                  |      }|ng	 t        |t        t        t        f      rt        j                   |      n|}t        |t&              s|j)                  d      }	|	dk(  r|j)                  d      xs |j)                  d      xs d}
|
r|	 t+        j,                  |
      }|r|j/                  |       |j1                          |t3        |      z  }| xj4                  t3        |      z  c_        t        j                         | _        n`|	dk(  r.|j)                  d      xs i j)                  d      }|r5|| _        n-|	dv rn*|	dk(  r#|j)                  d      xs |}t        d|       ||j;                          	 t        j                         |z
  d z  }d||d!S # t"        t$        f$ r Y w xY w# t$        t"        f$ r d}Y (w xY w# ||j;                          w w xY w)"zSend ``text`` and accumulate the audio response.

        Audio deltas are base64-decoded and appended to
        ``audio_sink_path`` (opened 'ab' and closed per call, so a
        separate streaming reader can consume whatever is there).
        Nz.RealtimeSession.connect() must be called firstzconversation.item.createmessageuser
input_text)r/   r*   )r/   rolecontent)r/   itemzresponse.creater,   r)   )r/   responser   Tparentsexist_okabz*realtime response did not complete within stimeoutr/   zresponse.audio.deltadelta r$   zresponse.createdrB   id>   response.doneresponse.cancelledresponse.completederrorzrealtime error: g     @@okbytes_writtenduration_ms)r   r   time	monotonicr3   r   parentmkdiropenTimeoutError_recv
isinstancestrbytes	bytearrayjsonloadsr2   
ValueErrordictgetbase64	b64decodewriteflushlenr   r    r   r8   )r!   r*   rI   startrS   sink_fp	remainingrawframeftypeb64chunkriderrrT   s                  r   speakzRealtimeSession.speakt   s    88OPP 2%")5t DE		
 	))G95	
 +  ''--dT-J4//6G-	 #t~~'7%'?@	>&DWIQO  jjj3;/9#UI?V/WDJJsO]`E "%.		&)22))G,H		'0BHbCw2($*$4$4S$9E !#MM%0#MMO)SZ7M 00CJ>059YY[D200 99Z06B;;DAC14.[[g%))G,5C&)9#'?@@M V "~~'%/69*&
 	
G ":.  !+I6 ($'E(* " #sW   -AJ: 32J %AJ: 9J" CJ: JJ: JJ: "J73J: 6J77J: :Kc                d    | j                   y	 | j                  ddi       y# t        $ r Y yw xY w)a?  Interrupt the in-flight response (barge-in).

        Sends ``response.cancel`` on the current WebSocket so the model
        stops generating audio immediately. Safe to call at any time;
        returns True if a cancel was actually sent, False when there's
        nothing to cancel or the socket isn't open.
        Fr/   zresponse.cancelT)r   r3   r9   r:   s    r   cancel_responsezRealtimeSession.cancel_response   s=     88	OOV%678 		s   # 	//c                    | j                   J | j                  5  | j                   j                  t        j                  |             d d d        y # 1 sw Y   y xY wr7   )r   r   sendr`   dumps)r!   payloads     r   r3   zRealtimeSession._send_json   sG    xx###__ 	/HHMM$**W-.	/ 	/ 	/s   /AAc                    | j                   J 	 || j                   j                         S | j                   j                  |      S # t        $ r | j                   j                         cY S w xY w)NrH   )r   recvr2   )r!   rI   s     r   r[   zRealtimeSession._recv   s`    xx###	#xx}}&88===11 	#88==?"	#s   A A #A.-A.)zgpt-realtimealloyrK   Ni]  )r   r]   r   r]   r   r]   r   r]   r   Optional[Path]r   intreturnNone)r   r   )g      >@)r*   r]   rI   floatr   rc   )r   bool)rz   rc   r   r   r7   )rI   zOptional[float])__name__
__module____qualname____doc__r#   r	   r8   rt   rv   r3   r[    r$   r   r   r   '   s    
 $*. 77 7 	7
 7 (7 7 
72
:W
v /
#r$   r   c                  X    e Zd ZdZ	 d	 	 	 	 	 	 	 d	dZd
dZddZddZ	 d	 	 	 	 	 ddZy)RealtimeSpeakera[  File-based JSONL queue wrapper around :class:`RealtimeSession`.

    Each line in ``queue_path`` is a JSON object of the form
    ``{"id": "<uuid>", "text": "..."}``. Processed lines are appended
    to ``processed_path`` (if set) and then removed from the queue;
    if ``processed_path`` is ``None``, processed lines are simply
    dropped.
    Nc                f    || _         t        |      | _        |rt        |      | _        y d | _        y r7   )r0   r   
queue_pathprocessed_path)r!   r0   r   r   s       r   r#   zRealtimeSpeaker.__init__   s-     z*6Dd>2$r$   c                   | j                   j                         sg S g }| j                   j                         j                         D ]q  }|j	                         }|s	 t        j                  |      }t        |t              s=d|vr t        t        j                               |d<   |j                  |       s |S # t        $ r Y w xY w)NrL   )r   exists	read_text
splitlinesstripr`   ra   rb   r\   rc   r]   uuiduuid4append)r!   outlineentrys       r   _read_queuezRealtimeSpeaker._read_queue  s    %%'IOO--/::< 	D::<D

4( eT*5 !$**,/dJJu	 
  s   B==	C	C	c                    |s| j                   j                  d       y | j                   j                  dj                  d |D              dz          y )NrK   
c              3  F   K   | ]  }t        j                  |        y wr7   )r`   ry   ).0es     r   	<genexpr>z1RealtimeSpeaker._rewrite_queue.<locals>.<genexpr>  s     7djjm7s   !)r   
write_textjoin)r!   rl   s     r   _rewrite_queuezRealtimeSpeaker._rewrite_queue  sE     OO&&r*""II7Y77$>	
r$   c                ^   | j                   y | j                   j                  j                  dd       |j                  d      |j                  dd      |d}t	        | j                   dd	      5 }|j                  t        j                  |      d
z          d d d        y # 1 sw Y   y xY w)NTrC   rL   r*   rK   )rL   r*   resultazutf-8)encodingr   )r   rW   rX   rd   rY   rg   r`   ry   )r!   r   r   recordfps        r   _append_processedz!RealtimeSpeaker._append_processed"  s    &""(((E		$621FRXY$%%sW= 	0HHTZZ'$./	0 	0 	0s   2(B##B,c           	         |       s!| j                         }|st        j                  |       0|d   }|j                  d      xs dj	                         }|r	 | j
                  j                  |      }ndddd}| j                  ||       | j                         }|r;|d   j                  d	      |j                  d	      k(  r| j                  |d
d         nC| j                  |D 	cg c](  }	|	j                  d	      |j                  d	      k7  s'|	* c}	        |       s y y # t        $ r}dt        |      d}Y d }~d }~ww xY wc c}	w )Nr   r*   rK   F)rR   rP   Tg        rQ   rL      )r   rU   sleeprd   r   r0   rt   r9   r]   r   r   )
r!   stop_fnpoll_intervalentriesheadr*   r   r   latestr   s
             r   run_until_stoppedz!RealtimeSpeaker.run_until_stopped,  s0   
 )&&(G

=) 1:DHHV$*113D>!\\//5F !%qM""40 %%'F&)---$?##F12J/ ## &H1!%%+$*GQH3 )) ! >$)CH=F> Is$   D+ ,(EE+	E4EEr7   )r0   r   r   r   r   r~   r   r   )r   
list[dict])rl   r   r   r   )r   rc   r   rc   r   r   )g      ?)r   zCallable[[], bool]r   r   r   r   )	r   r   r   r   r#   r   r   r   r   r   r$   r   r   r      sq     *.	O O O '	O
 
O&
0  # #    
	 r$   r   )r   
__future__r   re   r`   rU   r   pathlibr   typingr   r   r   r1   r   r   r   r   r$   r   <module>r      sI    #      * * 2	E# E#P] ]r$   