o
    roj)                     @  s  d Z ddlm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
 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mZ dd
lmZ ddlmZmZ ddlmZ ddlmZm Z m!Z!m"Z"m#Z# ddl$m%Z%m&Z& ddl'm(Z( ddl)m*Z*m+Z+ ddl,m-Z-m.Z. ddl/m0Z0m1Z1m2Z2 e3e4Z5e
ege	d f Z6e
eege	d f Z7e0dddZ8dZ9dZ:dZ;dZ<dZ=dZ>dZ?dZ@G dd dZAG dd  d ZBdS )!u   Orquestación conversacional: RTP → STT → LLM → TTS → RTP.

Los proveedores STT/TTS/LLM se inyectan por factory; cambiar cloud↔local
no requiere tocar este archivo.
    )annotationsN)deque)	AwaitableCallable)Any)BotActionTriggerfind_triggerload_bot_action_triggersstrip_phrase_from_speech)utc_now)MetricsTrackerNullCallMetricsTracker)	CallState)Settingsget_settings)create_language_model)ChatMessageConversationContextLanguageModelLlmFatalErrorLlmQuotaExceeded)AmbienceLoopget_ambience_loop)MediaManager)SAMPLES_PER_PACKET
RtpSession)create_speech_to_textcreate_text_to_speech)AudioFormatSpeechToTextTextToSpeechmulawi@  )encodingsample_rate_hziX           u@   Disculpá, no pude escucharte bien. ¿Podés repetir, por favor?uj   Disculpá, en este momento no puedo continuar con la atención automática. Por favor intentá más tarde.u<  INICIO_LLAMADA: Generá el saludo inicial de esta llamada telefónica usando los datos de la póliza ya precargados. Sé breve (1-2 oraciones), saludá por el nombre del cliente si está disponible, mencioná que sos de Qualia seguros y preguntá en qué podés ayudar. No inventes datos que no estén en la póliza.c                   @  sN  e Zd ZdZdddddddlddZdd Zedmd!d"Zdnd#d$Zdnd%d&Z	dod(d)Z
dmd*d+Zdnd,d-Zdpd0d1Zdnd2d3Zdqd5d6Zdrd9d:Zdsd<d=Zdtd>d?Zdnd@dAZdndBdCZdndDdEZdndFdGZdHdIdudLdMZdvdQdRZdHdSdwdVdWZdxdZd[Zdnd\d]Zdyd^d_ZdzdadbZdndcddZdndedfZdndgdhZ d{djdkZ!dS )|BotConversationSessionuD   Una conversación activa por llamada (turno a turno con VAD simple).N)	on_update	on_actionaction_triggersmetricspoliza_taskcallr   rtpr   sttr   ttsr    llmr   settingsr   r(   NotifyFn | Noner)   ActionFn | Noner*   list[BotActionTrigger] | Noner+   MetricsTracker | Noner,   asyncio.Task | NonereturnNonec       	         C  s   || _ || _|| _|| _|| _|| _|| _|| _|| _|	pg | _	|
p$t
 | _d | _t | _g | _d| _d| _d| _d| _d| _d | _d| _d | _d | _d S )NFr   )r-   r.   _poliza_taskr/   r0   r1   r2   r(   r)   r*   r   r+   _taskasyncioEvent_stopped_history	_speaking
_llm_turns_stt_errors_llm_errors_llm_disabled_warmup_task_action_fired	_ambience_ambience_task)selfr-   r.   r/   r0   r1   r2   r(   r)   r*   r+   r,    rJ   >/var/www/html/bot-asterisk-ari/backend/services/bot_session.py__init__L   s.   


zBotConversationSession.__init__c                 C  s   | j | jjS N)r+   getr-   call_idrI   rJ   rJ   rK   _metrics_sessions   s   z'BotConversationSession._metrics_sessionboolc                 C  s   | j d uo
| j   S rM   )r;   donerP   rJ   rJ   rK   runningv   s   zBotConversationSession.runningc                 C  s6   | j rd S | j  tj|  d| jj d| _d S )Nzbot-name)	rT   r>   clearr<   create_task_runr-   rO   r;   rP   rJ   rJ   rK   startz   s   
zBotConversationSession.startc                   s   | j   |  I d H  | j}d | _|r/| s/|  z|I d H  W n
 tjy.   Y nw | j}d | _|rS| sU|  z|I d H  W d S  tjyR   Y d S w d S d S rM   )	r>   set_stop_ambiencerE   rS   cancelr<   CancelledErrorr;   )rI   warmuptaskrJ   rJ   rK   stop   s,   
zBotConversationSession.stopdict | Nonec              
     s  | j rdS | js| jjsdS z| jjt| j|  t| jjdI dH }W nH tyB } zt	
d| jj| W Y d}~dS d}~w ty] } zt	
d| jj| W Y d}~dS d}~w tyn   t	d| jj Y dS w |r|| j_t	d| jj| |  I dH  |S )zKExtrae resultado estructurado (json_schema) antes de liberar la cadena LLM.N)context
transcriptz2Bot call=%s: extract resultado omitido (cuota): %su)   Bot call=%s: extract resultado falló: %sz/Bot call=%s: extract resultado error inesperadozBot call=%s: resultado=%s)rD   r?   r-   rd   r1   extract_call_resultlist_conversation_contextr   loggerwarningrO   r   	Exception	exceptioncall_resultinfo_notify)rI   resultexcrJ   rJ   rK   finalize_resultado   sV   
z)BotConversationSession.finalize_resultadoc                 C  s"   | j   o| jjdvo| jj S )N)endedfailed)r>   is_setr-   statusr.   	is_closedrP   rJ   rJ   rK   _call_alive   s
   
z"BotConversationSession._call_alivec                   s"   | j r|  | jI d H  d S d S rM   )r(   r-   rP   rJ   rJ   rK   rn      s   zBotConversationSession._notifystatestrc                 C  s$   || j _| j jdv rd| j _d S d S )N)ringingansweredtalking)r-   agent_stateru   )rI   rx   rJ   rJ   rK   _set_agent_state   s   z'BotConversationSession._set_agent_statec           	        s  | j j}td|| jj| jj| jj| j j zِz| j	ddI d H }| 
 s;W W |  I d H  td|| j d S |si| jj}td||| jj d| j _|  I d H  W W |  I d H  td|| j d S |  I d H  |   |  I d H  | js| 
 sW W |  I d H  td|| j d S | 
 r| jrn	| jtkrtd|t n| d |  I d H  |  I d H }| 
 r| jrn|sq| d	 |  I d H  | |I d H }| 
 r| jrn|d u r|  jd
7  _| jtkrtd|| j d| j _|  I d H  n| t I d H  | ! I d H  q|s&t"d| qt#t$|d }| % }|d ur<|&| d| _td|| | j j'(d|  | j)(t*d|d |  I d H  | +|I d H }| 
 rr| jrsn5|swq| j j'(d|  | j)(t*d|d | ,|I d H  | j-s| 
 sn| ! I d H  | 
 sW n$ t.j/y     t0y   t1d| d| j _|  I d H  Y nw W |  I d H  td|| j d S W |  I d H  td|| j d S |  I d H  td|| j w )Nu>   Bot conversación iniciada call=%s stt=%s tts=%s llm=%s doc=%sg       @timeoutu-   Bot conversación finalizada call=%s turns=%du   Bot call=%s: sin destino RTP hacia Asterisk. EXTERNAL_MEDIA_ADVERTISE_HOST=%s puerto=%s — debe ser la IP de ESTE servidor alcanzable desde Asterisk (UDP abierto). Revisá también UNICASTRTP_LOCAL_* del canal externalMedia.errorz.Bot call=%s: tope de %d turnos LLM; deteniendo	listeningthinking   z/Bot call=%s: %d errores STT seguidos; abortandou   STT vacío call=%sr   zBot STT call=%s: %rzuser: userrolecontentbot: 	assistantu   Bot conversación error call=%s)2r-   rO   rh   rm   r/   provider_namer0   r1   document_id_wait_for_rtprw   r\   rA   r2   external_media_advertise_hostr   r.   
local_portr}   rn   _await_poliza_if_pending_start_ambience_speak_openingrD   MAX_LLM_TURNSri   r~   _capture_utterance_recognize_userrB   MAX_STT_ERRORS_speakSTT_ERROR_PROMPT_post_speak_cooldowndebugintlenrQ   record_user_turnrd   appendr?   r   
_reply_llm_speak_and_maybe_actrF   r<   r^   rj   rk   )	rI   rO   rtp_ok	advertise	utterance	user_textuser_audio_msmsessbot_textrJ   rJ   rK   rY      s  e
VP







GzBotConversationSession._runr   c                   s   t || j}|}|r|jst||jp|}| r"| |I dH  |r*| js*| js,dS d| _t	
d| jj|j|j|jp=d tj| |d| jjdd  d dS )uC   TTS del turno; si hay frase trigger, agenda la acción tras hablar.NTu/   Bot call=%s: trigger %r → action=%s target=%s-zbot-action-   rU   )r   r*   speak_phraser
   phrasestripr   rF   r)   rh   rm   r-   rO   actiontargetr<   rX   _dispatch_action)rI   r   triggerspeechrJ   rJ   rK   r   K  s*   

z+BotConversationSession._speak_and_maybe_actr   r   c                   sV   | j sd S z|  | j|I d H  W d S  ty*   td| jj|j d| _Y d S w )Nu   Bot call=%s: falló acción %sF)r)   r-   rj   rh   rk   rO   r   rF   )rI   r   rJ   rJ   rK   r   f  s   z'BotConversationSession._dispatch_actionr   c                 C  s    t | jj| jj| jj| jjdS )N)rO   r   caller_numberpoliza)r   r-   rO   r   numberpoliza_datarP   rJ   rJ   rK   rg   s  s   z,BotConversationSession._conversation_contextc                 C  s<   | j jpi }	 |dpd}|rd| dS | jjpd S )uF   Saludo TTS sin LLM: usa nombre CRM si hay póliza, si no BOT_GREETING.nombre zHola u:   , soy Mariana de Qualia seguros. ¿En qué puedo ayudarte?)r-   r   rN   r2   bot_greetingr   )rI   datar   rJ   rJ   rK   _build_immediate_greeting{  s   
z0BotConversationSession._build_immediate_greetingc                   sv    j s jd ur
d S  jjpd  dkrd S d fdd}tj| d jj	d d  d	 _t
d
 jj	 d S )Nr   ollamar8   r9   c                     s>   z j  I d H  W d S  ty   td jj Y d S w )Nu   Bot call=%s: warmup LLM falló)r1   r_   rj   rh   rk   r-   rO   rJ   rP   rJ   rK   rY     s   
z6BotConversationSession._start_llm_warmup.<locals>._runzllm-warmup-r   rU   z0Bot call=%s: warmup Ollama en paralelo al saludor8   r9   )rD   rE   r2   llm_providerr   lowerr<   rX   r-   rO   rh   rm   )rI   rY   rJ   rP   rK   _start_llm_warmup  s   	z(BotConversationSession._start_llm_warmupc                   sB   | j }|d u s| rd S z|I d H  W d S  ty    Y d S w rM   )rE   rS   rj   rI   r`   rJ   rJ   rK   _await_warmup  s   z$BotConversationSession._await_warmupc                   sv   | j }|du s| rdS ztjt|ddI dH  W dS  tjy1   td| jj	 Y dS  t
y:   Y dS w )zDEspera breve a CRM antes del saludo (carga en paralelo al RTP wait).Ng      @r   u4   Bot call=%s: CRM aún pendiente; saludo con fallback)r:   rS   r<   wait_forshieldTimeoutErrorrh   rm   r-   rO   rj   r   rJ   rJ   rK   r     s    
z/BotConversationSession._await_poliza_if_pendingc                   sv  | j jpd  }|dkrt| jjrt| jsttd| jj	| jjp!i 
d | d |  I dH  | jtddI dH }| jsC|  sEdS |rl| jjd	|  | jtd
|d | |I dH  |  I dH  dS td| jj	 | jr}|dkr}dS |   |  }|rtd| jj	|dd  | jjd	|  | jtd
|d | |I dH  |  I dH  dS dS )zSaludo inicial.

        Por defecto (BOT_OPENING_MODE=immediate): TTS ya con nombre CRM /
        BOT_GREETING, y warmup de Ollama en paralelo.
        Con mode=llm: espera el saludo generado por el modelo (comportamiento viejo).
        	immediater1   u3   Bot call=%s: saludo inicial vía LLM con póliza %sr   r   NFrecord_userr   r   r   u:   Bot call=%s: LLM no devolvió saludo; uso saludo inmediatoz Bot call=%s: saludo inmediato %rx   )r2   bot_opening_moder   r   r-   r   rD   rh   rm   rO   rN   r~   rn   r   OPENING_PROMPTrw   rd   r   r?   r   r   r   ri   r   r   )rI   moder   greetingrJ   rJ   rK   r     sV   


z%BotConversationSession._speak_openingT)speakreasonr   c                  sp   | j rd S d| _ d| j_td| jj| |  I d H  |r4|  r6| t	I d H  | 
 I d H  d S d S d S )NTr   u%   Bot call=%s: LLM deshabilitado — %s)rD   r-   r}   rh   r   rO   rn   rw   r   QUOTA_ERROR_PROMPTr   )rI   r   r   rJ   rJ   rK   _disable_llm  s   z#BotConversationSession._disable_llmr   bytes
str | Nonec                   s   t   }z| jj|tdI dH }W n/ tyC   td| j	j
 |  }|dur@t   | d }|j|t|d d Y dS w t   | d }|  }|dura|j|t|d d |jped S )zBSTT del turno. ``None`` = error del proveedor; ``""`` = sin texto.audio_formatNu9   Bot STT falló call=%s — se omite LLM/OpenAI este turno     @@g     @@)audio_secondsr   )r<   get_running_looptimer/   	recognize	TELEPHONYrj   rh   rk   r-   rO   rQ   
record_sttr   textr   )rI   r   t0ro   r   
latency_msrJ   rJ   rK   r     s8   

z&BotConversationSession._recognize_userr   r   r   c          
   
     s  | j rdS |r		 t| jt d }t fdd|D s'|td dg }|  jd7  _td| j	j
| jt|t| j	j |  I dH  t  }z| jj||  d	I dH }W nd tyz } z| d
| I dH  W Y d}~dS d}~w ty } z| d| I dH  W Y d}~dS d}~w ty   |  jd7  _td| j	j
 | jtkr| | j dI dH  Y dS w t  | d }|  }|dur|j|t|ddpdt|ddpdd |jpd  }	|	rd| _|	S )u*   Solo se invoca con transcripción válida.r   Nc                 3  s$    | ]}|j d ko|j kV  qdS )r   Nr   ).0mr   rJ   rK   	<genexpr>%  s    
z4BotConversationSession._reply_llm.<locals>.<genexpr>r   r   r   z4Bot LLM request call=%s turn=%d history=%d poliza=%s)rc   zcuota OpenAI: zerror fatal LLM: u   Bot LLM falló call=%sz fallos LLM consecutivosr   prompt_tokensr   completion_tokens)r   r   )!rD   rf   r?   MAX_HISTORY_MESSAGESanyr   rA   rh   rm   r-   rO   r   rR   r   r   r<   r   r   r1   replyrg   r   r   r   rj   rC   rk   MAX_LLM_ERRORSrQ   
record_llmgetattrr   r   )
rI   r   r   history_for_llmr   r   rp   r   r   r   rJ   r   rK   r     sp   




z!BotConversationSession._reply_llmr   floatc                   s   | j jr(| j jtd|dI d H }|rtd| jj dS td| jj dS t	 
 | }|  rg|t	 
  }|dkrJtd| jj dS | j jtd	|dI d H }|rctd| jj dS |  s4dS )
Ng      ?r   z,Bot call=%s: RTP recibido (listo para audio)um   Bot call=%s: sin RTP entrante aún (habitual en softphone con silence suppression); se usa UNICASTRTP_LOCAL_*Tr   z.Bot call=%s: timeout esperando RTP de AsteriskF      ?)r.   
has_remoterecv_ulaw_frameminrh   rm   r-   rO   r<   r   r   rw   ri   )rI   r   framedeadline	remainingrJ   rJ   rK   r   V  s@   
z$BotConversationSession._wait_for_rtpc                   sd   t   td  }|  r0|t    }|dkrdS | jjtd|dI dH  |  sdS dS )zADescarta frames entrantes para no transcribir eco del propio TTS.r   r   g?r   N)r<   r   r   POST_SPEAK_COOLDOWN_MSrw   r.   r   r   )rI   r   r   rJ   rJ   rK   r   y  s   z+BotConversationSession._post_speak_cooldownc                   s4  t dt| jjd }t dt| jjd }t |t| jjd }| jj}t dt| jjd }t|dkr8|ndd}t	 }d}d}	d}
d}d}t
  }d}|  r| jjddI dH }|du r|s|
s|dkrt
  | d	krd
}td| jj |
r|	|krntqQ|d7 }| jr|  qQt|}t|d}||k}|
s|dkr|| |rd
}
d}d}	|r|d| n|| |  n|d7 }qQ|r|d7 }d}	|| n|	d7 }	|| |	|kr||krn||	 |krn|  sU|  }|dur|dkr||d  ||krt|S dS )u   VAD por energía: silencia → fin de turno.

        Mantiene un pre-roll de frames previos al umbral para no cortar
        el ataque de la primera palabra (común en STT telefónico).
        r   r$   r   )maxlenFr   r   Ng      @Tu   Bot call=%s: 5s escuchando sin RTP del puente — Asterisk no está recibiendo audio del caller (NAT típico en datos móviles: falta external_media_address/local_net en el transport PJSIP, rtp_symmetric=yes, o UDP RTP abierto en firewall)r&       {Gz?) maxr   r2   bot_silence_msbot_min_speech_msbot_max_utterance_msbot_vad_energybot_preroll_msr   	bytearrayr<   r   r   rw   r.   r   rh   ri   r-   rO   r@   rW   r   ulaw_to_pcmaudiooprmsr   extendjoinrQ   add_silence_secondsr   )rI   silence_framesmin_speech_frames
max_framesenergy_thresholdpreroll_framesprerollbufspeech_seensilence_seenstartedframes_seenno_rtp_warnedlisten_startedpre_speech_silence_framesr   pcmr  	is_speechr   rJ   rJ   rK   r     s   




>z)BotConversationSession._capture_utterancer   c                   s   |   sd S | d |  I d H  t  }| jj|tdI d H }t  | d }| 	 }|d urC|j
|t|p=dt d |rI|   sKd S t  }d| _z| |I d H  W d| _nd| _w tt  | d }|d ur}|| d S d S )	Nspeakingr   r   r   )
charactersaudio_ready_atTFi  )rw   r~   rn   r<   r   r   r0   
synthesizer   rQ   
record_ttsr   r   r@   
_play_ulawr   record_bot_turn)rI   r   r   audiosynth_msr   play_startedplay_msrJ   rJ   rK   r     s4   

zBotConversationSession._speakc                 C  s   | j jsd S | jd urd S | j | j j}|s$td| jj| j j d S zt	|| j j
d| _W n tyB   td| jj| Y d S w tj|  d| jjd d  d| _td| jj|j| j j
| jj d S )Nz(Bot call=%s: ambience no encontrado (%s))gainz*Bot call=%s: no se pudo cargar ambience %sz	ambience-r   rU   z7Bot call=%s: ambience ON file=%s gain=%.2f (%.1fs loop))r2   bot_ambience_enabledrH   resolve_prompt_pathbot_ambience_filerh   ri   r-   rO   r   bot_ambience_gainrG   rj   rk   r<   rX   _ambience_looprm   rV   
duration_s)rI   pathrJ   rJ   rK   r     sF   
z&BotConversationSession._start_ambiencec                   sX   | j }d | _ d | _|r(| s*|  z|I d H  W d S  tjy'   Y d S w d S d S rM   )rH   rG   rS   r]   r<   r^   r   rJ   rJ   rK   r\   )  s   z%BotConversationSession._stop_ambiencec                   s   |   rB| js| jdu rtdI dH  qz| j| j  W n ty3   t	
d| jj Y dS w tdI dH  |   sdS dS )uC   Envía ambiente en loop mientras no hay TTS (el TTS mezcla encima).Nr   z$Bot call=%s: error enviando ambience)rw   r@   rG   r<   sleepr.   	send_ulaw
next_framerj   rh   rk   r-   rO   rP   rJ   rJ   rK   r%  4  s    z%BotConversationSession._ambience_loopulawc                   sl   t dt|tD ]*}|  s d S |||t  }| jd ur%| j|}| j| t	dI d H  q	d S )Nr   r   )
ranger   r   rw   rG   mix_with_ttsr.   r)  r<   r(  )rI   r+  offsetchunkrJ   rJ   rK   r  D  s   
z!BotConversationSession._play_ulaw)r-   r   r.   r   r/   r   r0   r    r1   r   r2   r   r(   r3   r)   r4   r*   r5   r+   r6   r,   r7   r8   r9   )r8   rR   r   )r8   rb   )rx   ry   r8   r9   )r   ry   r8   r9   )r   r   r8   r9   )r8   r   )r8   ry   )r   ry   r   rR   r8   r9   )r   r   r8   r   )r   ry   r   rR   r8   ry   )r   r   r8   rR   )r8   r   )r   ry   r8   r9   )r+  r   r8   r9   )"__name__
__module____qualname____doc__rL   rQ   propertyrT   rZ   ra   rq   rw   rn   r~   rY   r   r   rg   r   r   r   r   r   r   r   r   r   r   r   r   r   r\   r%  r  rJ   rJ   rJ   rK   r'   I   sL    '



*



{





	
3

:
#

e

$
r'   c                   @  s   e Zd ZdZ				d1d2ddZd3ddZd4ddZd5ddZd6ddZddd7d!d"Z	d8d#d$Z
d9d'd(Zd:d*d+Zd8d,d-Zd;d/d0ZdS )<BotSessionManagerz'Administra sesiones de bot por call_id.Nmedia_managerr   r2   Settings | Noner(   r3   r)   r4   r+   r6   r8   r9   c                 C  sR   || _ |pt | _|| _|| _|pt | _i | _d | _d | _	d | _
t| j| _d S rM   )r6  r   r2   r(   r)   r   r+   	_sessions_stt_tts_llmr	   _action_triggers)rI   r6  r2   r(   r)   r+   rJ   rJ   rK   rL   R  s   zBotSessionManager.__init__fnc                 C  
   || _ d S rM   )r(   rI   r=  rJ   rJ   rK   set_on_updatee     
zBotSessionManager.set_on_updatec                 C  r>  rM   )r)   r?  rJ   rJ   rK   set_on_actionh  rA  zBotSessionManager.set_on_actionr   c                 C  r>  rM   )r+   )rI   r+   rJ   rJ   rK   set_metricsk  rA  zBotSessionManager.set_metrics0tuple[SpeechToText, TextToSpeech, LanguageModel]c                 C  sR   | j d u rt| j| _ | jd u rt| j| _| jd u r!t| j| _| j | j| jfS rM   )r9  r   r2   r:  r   r;  r   rP   rJ   rJ   rK   _ensure_providersn  s   


z#BotSessionManager._ensure_providers)r,   r-   r   r,   r7   c                  s   | j jstd|j d S |j| jv r| j|j jrd S | j|j}|d u r1t	d|j d| 
 \}}}| jj|j|j|j|jt|dd d t|||||| j | j| j| j| j|d}|| j|j< |  d S )Nz-Bot deshabilitado (BOT_ENABLED=false) call=%su   Sin sesión RTP para bot call=z1; attach_bot_external_media debe ejecutarse antesmodel)r/   r0   r1   	llm_model)	r/   r0   r1   r2   r(   r)   r*   r+   r,   )r2   bot_enabledrh   rm   rO   r8  rT   r6  get_rtp_sessionRuntimeErrorrE  r+   set_providersr   r   r'   r(   r)   r<  rZ   )rI   r-   r,   r.   r/   r0   r1   sessionrJ   rJ   rK   start_for_callw  sD   
z BotSessionManager.start_for_callc                   sT   | j jsdS | j jpd  dkrdS |  \}}}td | I dH  dS )z0Precarga el LLM (Ollama) al arrancar el backend.Nr   r   z%Warmup LLM al startup provider=ollama)	r2   rH  r   r   r   rE  rh   rm   r_   )rI   _r1   rJ   rJ   rK   
warmup_llm  s   
zBotSessionManager.warmup_llmrO   ry   c                   s   | j |d }|r*z	| I d H  W n ty"   td| Y nw | I d H  | jd urIt| jdrKz	| j	| W d S  tyH   Y d S w d S d S )Nz)Bot call=%s: fallo al finalizar resultadodrop_thread)
r8  poprq   rj   rh   rk   ra   r;  hasattrrP  rI   rO   rL  rJ   rJ   rK   stop_for_call  s$   zBotSessionManager.stop_for_callrR   c                 C  s   | j |}|d uo|jS rM   )r8  rN   rT   rS  rJ   rJ   rK   
is_running  s   zBotSessionManager.is_runningc                   s   t | jD ]
}| |I d H  q| j| j| jfD ](}|d urAz	| I d H  W q ty@ } zt	d| W Y d }~qd }~ww qd  | _ | _| _d S )NzCierre provider: %s)
rf   r8  rT  r9  r:  r;  closerj   rh   r   )rI   rO   providerrp   rJ   rJ   rK   rV    s   zBotSessionManager.closedict[str, Any]c                 C  s0   | j j| j j| j j| j jdd | j D dS )Nc                 S  s   g | ]	\}}|j r|qS rJ   )rT   )r   cidsrJ   rJ   rK   
<listcomp>  s
    
z,BotSessionManager.status.<locals>.<listcomp>)enabledstt_providertts_providerr   active_sessions)r2   rH  r]  r^  r   r8  itemsrP   rJ   rJ   rK   ru     s   zBotSessionManager.status)NNNN)r6  r   r2   r7  r(   r3   r)   r4   r+   r6   r8   r9   )r=  r3   r8   r9   )r=  r4   r8   r9   )r+   r   r8   r9   )r8   rD  )r-   r   r,   r7   r8   r9   r   )rO   ry   r8   r9   )rO   ry   r8   rR   )r8   rX  )r0  r1  r2  r3  rL   r@  rB  rC  rE  rM  rO  rT  rU  rV  ru   rJ   rJ   rJ   rK   r5  O  s$    




+



r5  )Cr3  
__future__r   r<   r   loggingcollectionsr   collections.abcr   r   typingr   bot_actionsr   r   r	   r
   call_metrics.sessionr   call_metrics.trackerr   r   calls.modelsr   configr   r   llm.factoryr   llm.protocolsr   r   r   r   r   media.ambiancer   r   media.managerr   media.rtp_sessionr   r   voice.factoryr   r   voice.protocolsr   r   r    	getLoggerr0  rh   NotifyFnActionFnr   r   r   r   r   r   r   r   r   r'   r5  rJ   rJ   rJ   rK   <module>   sV    
	      