
    qi                        d Z ddlZddlZddlmZ ddlmZ ddlmZm	Z	m
Z
mZ ddlmZ ddlmZ ddlmZ dd	lmZmZ dd
lmZmZmZmZmZmZmZmZmZmZ ddl m!Z!  G d de"e      Z# G d de"e      Z$ G d de"e      Z% G d de!      Z&y)u  Genesys AudioHook Serializer for Pipecat.

This module provides a serializer for integrating Pipecat pipelines with
Genesys Cloud Contact Center via the AudioHook protocol.

Features:
- Bidirectional audio streaming (PCMU μ-law at 8kHz)
- Automatic protocol handshake handling (open/opened, close/closed, ping/pong)
- Input/output variables for Architect flow integration
- DTMF event support
- Barge-in (interruption) events
- Pause/resume support for hold scenarios (optional)

Protocol Reference:
- https://developer.genesys.cloud/devapps/audiohook

Audio Format:
- PCMU (μ-law) at 8kHz sample rate (preferred)
- L16 (16-bit linear PCM) at 8kHz also supported
- Mono (external channel) or Stereo (external on left, internal on right)
    N)	timedelta)Enum)AnyDictListOptional)logger)KeypadEntry)SOXRStreamAudioResampler)pcm_to_ulawulaw_to_pcm)
AudioRawFrameCancelFrameEndFrameFrameInputAudioRawFrameInputDTMFFrameInterruptionFrameOutputTransportMessageFrame!OutputTransportMessageUrgentFrame
StartFrame)FrameSerializerc                   @    e Zd ZdZdZdZdZdZdZdZ	dZ
d	Zd
ZdZdZdZy)AudioHookMessageTypez!AudioHook protocol message types.openopenedcloseclosedpauseresumedpingpongupdateeventerror
disconnectN)__name__
__module____qualname____doc__OPENOPENEDCLOSECLOSEDPAUSERESUMEDPINGPONGUPDATEEVENTERROR
DISCONNECT     M/opt/pipecat/venv/lib/python3.12/site-packages/pipecat/serializers/genesys.pyr   r   1   sA    +DFEFEGDDFEEJr8   r   c                       e Zd ZdZdZdZdZy)AudioHookChannelz&AudioHook audio channel configuration.externalinternalbothN)r'   r(   r)   r*   EXTERNALINTERNALBOTHr7   r8   r9   r;   r;   B   s    0HHDr8   r;   c                       e Zd ZdZdZdZy)AudioHookMediaFormatzSupported audio formats.PCMUL16N)r'   r(   r)   r*   rD   rE   r7   r8   r9   rC   rC   J   s    "D
Cr8   rC   c                        e Zd ZdZdZ G d dej                        Z	 d:dee   f fdZe	de
fd	       Ze	dee
   fd
       Ze	defd       Ze	defd       Ze	deee
ef      fd       Ze	deee
ef      fd       Ze	deee
ef      fd       Zdee
ef   ddfdZdefdZdede
fdZde
defdZdefdZ	 	 d;dedeee
ef      dedee
ef   fdZ	 	 	 d<dedeee
      dee
   dee
ef   fd Z 	 d:d!eee
ef      dee
ef   fd"Z!dee
ef   fd#Z"dee
ef   fd$Z#dee
ef   fd%Z$	 	 	 	 d=d&e
d'e
d!eee
ef      d(ee
   dee
ef   f
d)Z%	 d>d*ed+e
d,edee
ef   fd-Z&de'de
e(z  dz  fd.Z)d/e
e(z  de'dz  fd0Z*d/e(de'dz  fd1Z+d+ee
ef   de'dz  fd2Z,d+ee
ef   de'dz  fd3Z-d+ee
ef   de'dz  fd4Z.d+ee
ef   de'dz  fd5Z/d+ee
ef   de'dz  fd6Z0d+ee
ef   de'dz  fd7Z1d+ee
ef   de'dz  fd8Z2d+ee
ef   de'dz  fd9Z3 xZ4S )?GenesysAudioHookSerializera  Serializer for Genesys AudioHook WebSocket protocol.

    This serializer handles converting between Pipecat frames and Genesys
    AudioHook protocol messages. It supports:

    - Bidirectional audio streaming (PCMU at 8kHz)
    - Automatic protocol handshake (open/opened, close/closed, ping/pong)
    - Session lifecycle management with pause/resume support
    - Custom input/output variables for Architect flow integration
    - DTMF event handling
    - Barge-in events for interruption support

    The AudioHook protocol uses:
    - Text WebSocket frames for JSON control messages
    - Binary WebSocket frames for audio data

    Example usage:
        ```python
        serializer = GenesysAudioHookSerializer(
            params=GenesysAudioHookSerializer.InputParams(
                channel=AudioHookChannel.EXTERNAL,
                supported_languages=["en-US", "es-ES"],
                selected_language="en-US",
            )
        )

        # Use with FastAPI WebSocket transport
        transport = FastAPIWebsocketTransport(
            websocket=websocket,
            params=FastAPIWebsocketParams(
                audio_in_enabled=True,
                audio_out_enabled=True,
                serializer=serializer,
                audio_out_fixed_packet_size=1600,  # Important: prevents 429 rate limiting from Genesys
            ),
        )

        # Access call information after connection
        participant = serializer.participant  # ani, dnis, etc.
        input_vars = serializer.input_variables  # Custom vars from Architect

        # Set output variables to return to Architect
        serializer.set_output_variables({"intent": "billing", "resolved": True})
        ```

    Attributes:
        PROTOCOL_VERSION: The AudioHook protocol version (currently "2").
    2c                       e Zd ZU dZdZeed<   dZee   ed<   e	j                  Ze	ed<   ej                  Zeed<   dZeed	<   d
Zeed<   dZeee      ed<   dZee   ed<   d
Zeed<   y)&GenesysAudioHookSerializer.InputParamsaQ  Configuration parameters for GenesysAudioHookSerializer.

        Attributes:
            genesys_sample_rate: Sample rate used by Genesys (default: 8000 Hz).
            sample_rate: Optional override for pipeline input sample rate.
            channel: Which audio channels to process (external, internal, both).
            media_format: Audio format (PCMU or L16).
            process_external: Whether to process external (customer) audio.
            process_internal: Whether to process internal (agent) audio.
            supported_languages: List of language codes the bot supports (e.g., ["en-US", "es-ES"]).
            selected_language: Default language code to use.
            start_paused: Whether to start the session in paused state.
            ignore_rtvi_messages: Inherited from base FrameSerializer, defaults to True.
        i@  genesys_sample_rateNsample_ratechannelmedia_formatTprocess_externalFprocess_internalsupported_languagesselected_languagestart_paused)r'   r(   r)   r*   rK   int__annotations__rL   r   r;   r?   rM   rC   rD   rN   rO   boolrP   rQ   r   strrR   rS   r7   r8   r9   InputParamsrJ      s    	 $(S'%)Xc])$4$=$=!=-A-F-F*F!%$%!&$&37Xd3i07+/8C=/"d"r8   rX   Nparamsc                    t        |   |xs t        j                         fi | | j                  j
                  | _        d| _        t        t        j                               | _        t               | _        t               | _        d| _        d| _        d| _        d| _        t'        d      | _        d| _        d| _        d| _        d| _        d| _        d| _        | j7                  d       | j7                  d       | j7                  d       | j7                  d       | j7                  d       | j7                  d	       | j7                  d
       y)zInitialize the GenesysAudioHookSerializer.

        Args:
            params: Configuration parameters.
            **kwargs: Additional arguments passed to BaseObject (e.g., name).
        r   FNon_openon_closeon_pingon_pause	on_updateon_erroron_dtmf)super__init__rG   rX   _paramsrK   _genesys_sample_rate_sample_raterW   uuiduuid4_session_idr   _input_resampler_output_resampler_client_seq_server_seq_is_open
_is_pausedr   	_position_conversation_id_participant_custom_config_media_info_input_variables_output_variables_register_event_handler)selfrY   kwargs	__class__s      r9   rc   z#GenesysAudioHookSerializer.__init__   s,    	K#=#I#I#KVvV$(LL$D$D!tzz|, !9 :!9!; "1 046:8<;?:>;? 	$$Y/$$Z0$$Y/$$Z0$$[1$$Z0$$Y/r8   returnc                     | j                   S )zAGet the Genesys AudioHook session ID generated by the serializer.)ri   rx   s    r9   
session_idz%GenesysAudioHookSerializer.session_id   s     r8   c                     | j                   S )z Get the Genesys conversation ID.)rq   r}   s    r9   conversation_idz*GenesysAudioHookSerializer.conversation_id        $$$r8   c                     | j                   S )z'Check if the AudioHook session is open.)rn   r}   s    r9   is_openz"GenesysAudioHookSerializer.is_open   s     }}r8   c                     | j                   S )z#Check if audio streaming is paused.)ro   r}   s    r9   	is_pausedz$GenesysAudioHookSerializer.is_paused   s     r8   c                     | j                   S )z=Get participant info (ani, dnis, etc.) from the open message.)rr   r}   s    r9   participantz&GenesysAudioHookSerializer.participant   s        r8   c                     | j                   S )z1Get custom input variables from the open message.)ru   r}   s    r9   input_variablesz*GenesysAudioHookSerializer.input_variables   r   r8   c                     | j                   S )z4Get custom output variables to send back to Genesys.)rv   r}   s    r9   output_variablesz+GenesysAudioHookSerializer.output_variables   s     %%%r8   	variablesc                 B    || _         t        j                  d|        y)a  Set custom output variables to send back to Genesys on close.

        These variables will be included in the 'closed' response when Genesys
        closes the connection, making them available in the Architect flow.

        Args:
            variables: Dictionary of custom variables to send to Genesys.

        Example:
            ```python
            # During the conversation, collect data and set it
            serializer.set_output_variables({
                "intent": "billing_inquiry",
                "customer_verified": True,
                "summary": "Customer asked about their bill",
                "transfer_to": "billing_queue"
            })
            ```
        zOutput variables set: N)rv   r	   debug)rx   r   s     r9   set_output_variablesz/GenesysAudioHookSerializer.set_output_variables   s     ( "+-i[9:r8   framec                    K   | j                   j                  xs |j                  | _        t	        j
                  d| j                          yw)zSets up the serializer with pipeline configuration.

        Args:
            frame: The StartFrame containing pipeline configuration.
        z2GenesysAudioHookSerializer setup with sample_rate=N)rd   rL   audio_in_sample_raterf   r	   r   )rx   r   s     r9   setupz GenesysAudioHookSerializer.setup  s@      !LL44R8R8RI$J[J[I\]^s   AApositionc                 0    |j                         }d|ddS )zFormat a timedelta as ISO 8601 duration string.

        Args:
            position: The timedelta to format.

        Returns:
            ISO 8601 duration string (e.g., "PT1.5S").
        PTz.3fS)total_seconds)rx   r   r   s      r9   _format_positionz+GenesysAudioHookSerializer._format_position  s$     !..0M#&a((r8   position_strc                     |j                  d      r,|j                  d      r	 t        |dd       }t        |      S t        d      S # t        $ r Y t        d      S w xY w)zParse an ISO 8601 duration string to timedelta.

        Args:
            position_str: ISO 8601 duration string (e.g., "PT1.5S").

        Returns:
            Corresponding timedelta.
        r   r      )secondsr   )
startswithendswithfloatr   
ValueError)rx   r   r   s      r9   _parse_positionz*GenesysAudioHookSerializer._parse_position  sj     ""4(\-B-B3-GQr 23 11 |  |s   A	 		AAc                 D    | xj                   dz  c_         | j                   S )z$Get the next server sequence number.   )rm   r}   s    r9   _next_server_seqz+GenesysAudioHookSerializer._next_server_seq.  s    Ar8   msg_type
parametersinclude_positionc                     | j                         }| j                  |j                  || j                  | j                  d}|r| j                  | j                        |d<   |r||d<   |S )a  Create a protocol message with common fields.

        Based on the Genesys AudioHook protocol, responses include:
        - seq: Server's sequence number (incremented per message)
        - clientseq: Echo of the client's last sequence number

        Args:
            msg_type: The message type.
            parameters: Optional parameters object.
            include_position: Whether to include position field.

        Returns:
            The message dictionary.
        )versiontypeseq	clientseqidr   r   )r   PROTOCOL_VERSIONvaluerl   ri   r   rp   )rx   r   r   r   r   msgs         r9   _create_messagez*GenesysAudioHookSerializer._create_message3  sm    ( ##%,,NN))""
 "33DNNCC
O *C
r8   rS   rQ   rR   c                 $   g }| j                   j                  t        j                  k(  rdg}nV| j                   j                  t        j                  k(  rdg}n+| j                   j                  t        j
                  k(  rddg}|d| j                   j                  j                  || j                  dgd}|r||d<   |r||d<   | j                  t        j                  |d	      }d
| _        t        j                  d| j                          |S )a  Create an 'opened' response message for the client.

        This should be sent in response to an 'open' message from Genesys.

        Args:
            start_paused: Whether to start the session paused.
            supported_languages: List of supported language codes.
            selected_language: The selected language code.

        Returns:
            Dictionary of the opened response message.
        r<   r=   audio)r   formatchannelsrate)startPausedmediasupportedLanguagesselectedLanguageF)r   r   TzAudioHook session opened: )rd   rM   r;   r?   r@   rA   rN   r   re   r   r   r,   rn   r	   r   ri   )rx   rS   rQ   rR   r   r   r   s          r9   create_opened_responsez1GenesysAudioHookSerializer.create_opened_responseX  s   & !<<#3#<#<<"|H\\!!%5%>%>>"|H\\!!%5%:%::"J/H ( $"ll77== ( 55	


 /BJ+,->J)*"" ''!" # 
 1$2B2B1CDE
r8   r   c                     d}|rd|i}| j                  t        j                  |      }d| _        t	        j
                  d| j                          |S )a  Create a 'closed' response message.

        This should be sent in response to a 'close' message from Genesys.

        Args:
            output_variables: Optional custom variables to pass back to Genesys.
                These will be available in the Architect flow after the AudioHook
                action completes.

        Returns:
            Dictionary of the closed response message.

        Example:
            ```python
            # Pass custom data back to Genesys
            serializer.create_closed_response(
                output_variables={
                    "intent": "billing_inquiry",
                    "customer_verified": True,
                    "summary": "Customer asked about their bill"
                }
            )
            ```
        NoutputVariablesr   FzAudioHook session closed: )r   r   r.   rn   r	   r   ri   )rx   r   r   r   s       r9   create_closed_responsez1GenesysAudioHookSerializer.create_closed_response  sa    8 04
+-=>J"" ''! # 

 1$2B2B1CDE
r8   c                 D    | j                  t        j                        }|S )zCreate a 'pong' response message.

        This should be sent in response to a 'ping' message from Genesys.

        Returns:
            Dictionary of the pong response message.
        )r   r   r2   rx   r   s     r9   create_pong_responsez/GenesysAudioHookSerializer.create_pong_response  s      ""#7#<#<=
r8   c                     | j                  t        j                        }d| _        t	        j
                  d| j                          |S )zCreate a 'resumed' response message.

        This should be sent in response to a 'pause' message when ready to resume.

        Returns:
            Dictionary of the resumed response message.
        FzAudioHook session resumed: )r   r   r0   ro   r	   r   ri   r   s     r9   create_resumed_responsez2GenesysAudioHookSerializer.create_resumed_response  sB     ""#7#?#?@243C3C2DEF
r8   c                 ~    | j                  t        j                  ddi dgi      }t        j                  d       |S )a  Create a barge-in event message.

        This notifies Genesys Cloud that the user has interrupted the bot's
        audio output. Genesys will stop any queued audio playback.

        Returns:
            Dictionary of the barge-in event message.
        entitiesbarge_in)r   datar   u#   🔇 Barge-in event sent to Genesys)r   r   r4   r	   r   r   s     r9   create_barge_in_eventz0GenesysAudioHookSerializer.create_barge_in_event  sF     "" &&"j"%E$FG # 

 	:;
r8   reasonactioninfoc                     d|i}d|i}|r|j                  |       ||d<   |r||d<   | j                  t        j                  |      }t	        j
                  d| d|        |S )a  Create a 'disconnect' message to initiate session termination.

        Args:
            reason: Disconnect reason (e.g., "completed", "error").
            action: Action to take ("transfer" to agent, "finished" if completed).
            output_variables: Custom output variables to pass back to Genesys.
            info: Optional additional information.

        Returns:
            Dictionary of the disconnect message.
        r   r   r   r   r   zAudioHook disconnect: reason=z	, action=)r#   r   r   r6   r	   r   )rx   r   r   r   r   r   out_varsr   s           r9   create_disconnect_messagez4GenesysAudioHookSerializer.create_disconnect_message  s    $ '/%7
 f%OO,-(0
$%!%Jv"" ++! # 

 	4VHIfXNO
r8   codemessage	retryablec                     |||d}| j                  t        j                  |      }t        j                  d| d|        |S )zCreate an 'error' message.

        Args:
            code: Error code.
            message: Error message.
            retryable: Whether the operation can be retried.

        Returns:
            Dictionary of the error message.
        )r   r   r   r   zAudioHook error:  - )r   r   r5   r	   r%   )rx   r   r   r   r   r   s         r9   create_error_messagez/GenesysAudioHookSerializer.create_error_message  sX    " "

 "" &&! # 

 	(c';<
r8   c                 h  K   t        |t        t        f      r0t        j                  | j                  | j                  d            S t        |t              r| j                  r| j                  ry|j                  }| j                  j                  t        j                  k(  r5t        ||j                   | j"                  | j$                         d{   }nt'        j(                  d       y|t+        |      dk(  ryt-        |      S t        |t.              r#t        j                  | j1                               S t        |t2        t4        f      rZ| j7                  |      ryt        |j8                  t:              r-d|j8                  v rt        j                  |j8                        S yy7 ۭw)a\  Serializes a Pipecat frame to Genesys AudioHook format.

        Handles conversion of various frame types to AudioHook messages:
        - AudioRawFrame -> Binary PCMU audio data (resampled to 8kHz)
        - EndFrame/CancelFrame -> Disconnect message (JSON)
        - InterruptionFrame -> Barge-in event (JSON)
        - OutputTransportMessageFrame -> Pass-through JSON

        Args:
            frame: The Pipecat frame to serialize.

        Returns:
            Serialized data as string (JSON) or bytes (audio), or None if
            the frame type is not handled or session is not open.
        	completed)r   r   N$L16 format not yet fully implementedr   r   )
isinstancer   r   jsondumpsr   r   r   rn   ro   r   rd   rN   rC   rD   r   rL   re   rk   r	   warninglenbytesr   r   r   r   should_ignore_framer   dict)rx   r   r   serialized_datas       r9   	serializez$GenesysAudioHookSerializer.serialize+  sh      eh45::..%)%:%:; /   }-==DOO;;D ||((,@,E,EE(3%%--**	) # EF&#o*>!*C))01::d88:;; ;=^_`''. %--.93Mzz%--00  A#s   CF2F0CF2r   c                 2  K   t        |t              r| j                  |       d{   S 	 t        j                  |      }| j                  |       d{   S 7 3# t        j
                  $ r"}t        j                  d|        Y d}~yd}~ww xY w7 >w)a  Deserializes Genesys AudioHook data to Pipecat frames.

        Handles:
        - Binary data -> InputAudioRawFrame (converted from PCMU to PCM)
        - JSON 'open' -> OutputTransportMessageUrgentFrame with 'opened' response
        - JSON 'close' -> OutputTransportMessageUrgentFrame with 'closed' response
        - JSON 'ping' -> OutputTransportMessageUrgentFrame with 'pong' response
        - JSON 'pause' -> Sets is_paused=True, returns None
        - JSON 'dtmf' -> InputDTMFFrame
        - JSON 'update' -> Updates participant info, returns None
        - JSON 'error' -> Logs error, returns None

        Protocol responses (opened, closed, pong) are returned as urgent frames
        to be sent immediately through the transport.

        Args:
            data: The raw WebSocket data from Genesys (binary audio or JSON text).

        Returns:
            A Pipecat frame to process, or None if handled internally.
        Nz#Failed to parse AudioHook message: )	r   r   _deserialize_audior   loadsJSONDecodeErrorr	   r%   _handle_control_message)rx   r   r   es       r9   deserializez&GenesysAudioHookSerializer.deserializel  s     . dE"00666	jj&G
 11'::: 7
 ## 	LL>qcBC	 ;sD   %BABA BBBB0BBBBc           	        K   | j                   r| j                  ry}t              }| j                  j                  t
        j                  k(  r[t              dkD  rMt        fdt        dt              d      D              }t        j                  d| dt        |       d       | j                  j                  t        j                  k(  r5t        || j                  | j                   | j"                         d{   }nt        j$                  d       y|t        |      dk(  ryd	}t'        ||| j                   
      }|S 7 Hw)zDeserialize binary audio data to an InputAudioRawFrame.

        Args:
            data: Raw audio bytes (PCMU or L16).

        Returns:
            InputAudioRawFrame with PCM audio at pipeline sample rate.
        Nr   c              3   (   K   | ]	  }|     y wNr7   ).0ir   s     r9   	<genexpr>z@GenesysAudioHookSerializer._deserialize_audio.<locals>.<genexpr>  s     G1tAwGs   r   u   🔊 Stereo audio: u    bytes → z bytes (external channel)r   r   )r   num_channelsrL   )rn   ro   r   rd   rM   r;   rA   r   ranger	   r   rN   rC   rD   r   re   rf   rj   r   r   )rx   r   
audio_dataoriginal_lendeserialized_datar   audio_frames    `     r9   r   z-GenesysAudioHookSerializer._deserialize_audio  s-     }}
4y <<#3#8#88SY] GaTA0FGGJLL%l^;s:>OOhi <<$$(<(A(AA&1))!!%%	' ! NNAB$,=(>!(C (#%))
 /!s   C?EEA	Ec                   K   |j                  dd      }|j                  dd      | _        d|v r| j                  |d         | _        |t        j
                  j                  k(  r| j                  |       d{   S |t        j                  j                  k(  r| j                  |       d{   S |t        j                  j                  k(  r| j                  |       d{   S |t        j                  j                  k(  r| j                  |       d{   S |t        j                  j                  k(  r| j                  |       d{   S |t        j                   j                  k(  r| j#                  |       d{   S |dk(  r| j%                  |       d{   S |dk(  rt'        j(                  d	       y|d
k(  rt'        j(                  d       yt'        j*                  d|        y7 7 M7 7 7 7 ~7 bw)zHandle a JSON control message from Genesys.

        Args:
            message: Parsed JSON message.

        Returns:
            Frame if the message should be passed to the pipeline, None otherwise.
        r    r   r   r   Ndtmfplayback_startedzPlayback started (from Genesys)playback_completedz!Playback completed (from Genesys)z Unknown AudioHook message type: )getrl   r   rp   r   r+   r   _handle_openr-   _handle_closer1   _handle_pingr/   _handle_pauser3   _handle_updater5   _handle_error_handle_dtmfr	   r   r   )rx   r   r   s      r9   r   z2GenesysAudioHookSerializer._handle_control_message  s     ;;vr*";;ua0  !11'*2EFDN+00666**7333-33999++G444-22888**7333-33999++G444-44:::,,W555-33999++G444**7333++LL:;--LL<=NN=hZHI; 4 5 4 5 6 5 4s   A8H:G:;5H0G=15H&H '5HH5HH5HH	H&H	'AH=H HHHH	Hc           
        K   |j                  dt        t        j                                     | _        |j                  di       }|j                  d      | _        |j                  d      | _        |j                  d      | _        |j                  d      | _        |j                  d      | _	        | j                  }|r1t        |t              r t        |      dkD  r|d   }|j                  d	g       }t        j                  d
|j                  d       d| d|j                  d              t        |t              rd|v r9d|v r5t        j                   | j"                  _        t        j                  d       nqd|v r5t        j&                  | j"                  _        t        j                  d       n8d|v r4t        j(                  | j"                  _        t        j                  d       | j                  r| j                  j                  dd      nd}t        j*                  d| j                   d| j
                   d|        | j-                  d|       d{    t/        | j1                  | j"                  j2                  | j"                  j4                  | j"                  j6                              S 7 ^w)a  Handle an 'open' message from Genesys.

        This initializes the session with metadata from Genesys Cloud and
        automatically responds with an 'opened' message using the configured
        InputParams (supported_languages, selected_language, start_paused).

        Extracts and stores:
        - session_id: The AudioHook session identifier
        - conversation_id: The Genesys conversation ID
        - participant: Caller info (ani, dnis, etc.)
        - input_variables: Custom variables from Architect flow
        - media_info: Audio configuration from Genesys

        Args:
            message: The open message from Genesys.

        Returns:
            OutputTransportMessageUrgentFrame with the 'opened' response.
        r   r   conversationIdr   customConfigr   inputVariablesr   r   u"   📡 Genesys audio config: format=r   z, channels=z, rate=r   r<   r=   u-   📡 Stereo mode: extracting external channelu    📡 Mono mode: external channelu    📡 Mono mode: internal channelaniunknownz AudioHook open request: session=z, conversation=z, ani=r[   N)rS   rQ   rR   r   )r   rW   rg   rh   ri   rq   rr   rs   rt   ru   r   listr   r	   r   r;   rA   rd   rM   r?   r@   r   _call_event_handlerr   r   rS   rQ   rR   )rx   r   rY   
media_listaudio_mediar   r	  s          r9   r   z'GenesysAudioHookSerializer._handle_open  s^    ( #;;tS->?\2. &

+; <"JJ}5$jj8!::g. &

+; < %%
*Z63z?Q;N*4Q-K"z26HLL4[__X5N4O{[cZddklwl{l{  }C  mD  lE  F (D))jH.D+;+@+@DLL(LL!PQ8++;+D+DDLL(LL!CD8++;+D+DDLL(LL!CD :>9J9Jd##E95PY.t/?/?.@ A 112&?	

 &&y':::0//!\\66$(LL$D$D"&,,"@"@ 0 
 	
 	;s   I$K&K'AKc                 >  K   |j                  di       }|j                  dd      }t        j                  d|        d| _        t        j                  d       | j	                  d|       d{    t        | j                  | j                  	      
      S 7 *w)a  Handle a 'close' message from Genesys.

        Automatically responds with a 'closed' message. If output_variables
        were set via set_output_variables(), they will be included in the
        response and made available in the Architect flow.

        Args:
            message: The close message from Genesys.

        Returns:
            OutputTransportMessageUrgentFrame with the closed response
            (includes outputVariables if set).
        r   r   r
  u$   🔴 Genesys closed the connection: Fz%Sending closed response to Genesys...r\   N)r   r  )r   r	   r   rn   r  r   r   rv   rx   r   rY   r   s       r9   r   z(GenesysAudioHookSerializer._handle_close6  s      \2.Hi0:6(CD;=&&z7;;; 1//AWAW/X
 	
	 	<s   A.B0B1+Bc                    K   t        j                  d       | j                  d|       d{    t        | j	                               S 7 w)a  Handle a 'ping' message from Genesys.

        Automatically responds with a 'pong' message to maintain the connection.

        Args:
            message: The ping message from Genesys.

        Returns:
            OutputTransportMessageUrgentFrame with pong response.
        z#Sending pong response to Genesys...r]   Nr  )r	   r   r  r   r   )rx   r   s     r9   r   z'GenesysAudioHookSerializer._handle_pingU  sF      	9;&&y'::: 19R9R9TUU 	;s   +AAAc                    K   |j                  di       }|j                  dd      }t        j                  d|        d| _        | j	                  d|       d{    y7 w)a#  Handle a 'pause' message from Genesys.

        This is used when audio streaming is temporarily suspended
        (e.g., during hold).

        Args:
            message: The pause message.

        Returns:
            None (response should be sent via create_resumed_response()).
        r   r   r
  z AudioHook pause request: reason=Tr^   N)r   r	   r   ro   r  r  s       r9   r  z(GenesysAudioHookSerializer._handle_pauseg  sc      \2.Hi06vh?@&&z7;;;  	<s   AA#A!A#c                    K   |j                  di       }d|v r
|d   | _        t        j                  d|        | j	                  d|       d{    y7 w)zHandle an 'update' message from Genesys.

        Updates may include changes to participants or configuration.

        Args:
            message: The update message.

        Returns:
            None.
        r   r   zAudioHook update received: r_   N)r   rr   r	   r   r  )rx   r   rY   s      r9   r  z)GenesysAudioHookSerializer._handle_update  s_      \2.F" &} 5D26(;<&&{G<<< 	=s   AAAAc                    K   |j                  di       }|j                  dd      }|j                  dd      }t        j                  d| d|        | j                  d|       d	{    y	7 w)
zHandle an 'error' message from Genesys.

        Args:
            message: The error message.

        Returns:
            None.
        r   r   r   r   zUnknown errorzAudioHook error from Genesys: r   r`   N)r   r	   r%   r  )rx   r   rY   r   	error_msgs        r9   r  z(GenesysAudioHookSerializer._handle_error  sp      \2.zz&!$JJy/:	5dV3ykJK&&z7;;; 	<s   A'A1)A/*A1c                 b  K   |j                  di       }|j                  dd      }|st        j                  d       yt        j                  d|        | j	                  d|       d{    	 t        t        |            S 7 # t        $ r t        j                  d|        Y yw xY ww)	a  Handle a 'dtmf' message from Genesys.

        DTMF (Dual-Tone Multi-Frequency) events are sent when the user
        presses keys on their phone keypad.

        Args:
            message: The DTMF message.

        Returns:
            InputDTMFFrame with the pressed digit.
        r   digitr   z#DTMF message received without digitNzDTMF received: ra   zInvalid DTMF digit: )r   r	   r   r   r  r   r
   r   )rx   r   rY   r  s       r9   r  z'GenesysAudioHookSerializer._handle_dtmf  s      \2.

7B'NN@AoeW-.&&y':::	!+e"455 	;  	NN1%9:	s6   A*B/,B-B/2B B/!B,)B/+B,,B/r   )NT)FNN)r   transferNN)F)5r'   r(   r)   r*   r   r   rX   r   rc   propertyrW   r~   r   rV   r   r   r   r   r   r   r   r   r   r   r   r   r   rT   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r  r  r  r  __classcell__)rz   s   @r9   rG   rG   Q   sh   /b #o11 #8 )-,0%,0\  C     %# % %    4   !Xd38n5 ! ! %$sCx.!9 % % &(4S>": & &;d38n ; ;._ _
) 
)s 
)C I $ #   04!%	#&# T#s(^,# 	#
 
c3h#N #37+/	77 &d3i07 $C=	7
 
c3h7v 6:)"4S>2) 
c3h)V	d38n 	c3h tCH~ ( " 59"## # #4S>2	#
 sm# 
c3h#R  	  	
 
c3h<?U ?sU{T/A ?B!;cEk !;edl !;F2U 2ut| 2h.T#s(^ .PT .`A
$sCx. A
UT\ A
F
4S> 
edl 
>V$sCx. VUT\ V$4S> edl 0DcN ut| ,4S> edl &$sCx. UT\ r8   rG   )'r*   r   rg   datetimer   enumr   typingr   r   r   r   logurur	   pipecat.audio.dtmf.typesr
   .pipecat.audio.resamplers.soxr_stream_resamplerr   pipecat.audio.utilsr   r   pipecat.frames.framesr   r   r   r   r   r   r   r   r   r   #pipecat.serializers.base_serializerr   rW   r   r;   rC   rG   r7   r8   r9   <module>r%     sw   ,     , ,  0 S 8   @3 "sD 3 s sr8   