o
    j{                     @  s   U 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 edZi Zded	< eed
dZd*ddZd+ddZd,ddZd-ddZd.dd Zd/d"d#Zd0d(d)ZdS )1aF  
Resolve MCube voice/LLM/STT settings from livekitvoicebot_cluster:

- Per-business table `{business_id}_bots` (when present)
- Shared override rows in `business_id_bots` (JSON `config`)

Matches Django `cluster_bot_mcube_config` merge rules so outbound, WS bridge, and
ai_worker stay consistent without calling the HTTP API.
    )annotationsN)Any   )_parse_mysql_cluster_url_runtime_mcube_overrideszmcube.cluster_bot_runtimez3dict[tuple[int, int], tuple[float, dict[str, str]]]_CACHECLUSTER_BOT_RUNTIME_CACHE_TTL_S30nstrreturnc                 C  s(   dt | tdtdtd  dS )N``   )r   replacechr)r
    r   ^/var/www/html/livekitdocker/backend/agent_runtime/src/mcube_integration/cluster_bot_runtime.py_q_ident   s   (r   rawr   dict[str, Any]c                 C  s   | d u ri S t | tr| S t | ttfr| jddd} t | tr@|  r@zt| }t |tr2|W S i W S  t	y?   i  Y S w i S )Nutf-8r   errors)

isinstancedictbytes	bytearraydecoder   stripjsonloads	Exception)r   pr   r   r   _json_load_maybe   s   

r#   datac                 C  s   |  d}t|ttfr|jddd}t|trE| rEzt|}t|t	r8t| dp3| dp3d W S W dS  t
yD   Y dS w dS )Nvoicer   r   r   voice_idvoiceId )getr   r   r   r   r   r   r   r    r   r!   )r$   	voice_raw	voice_objr   r   r   _voice_id_from_row/   s   


"r,   bidintbot_pkbot_namec                 C  sZ  i }t |}|p	d }z|  }|d dd | pg D }|s.|W  d    W S d}	d|v r7d}	nd|v r=d}	|||pB|g}
|d	td
 dtd dtd dtd d|	 d|
 | }|r|dd urt|d}W d    W |S W d    W |S W d    W |S 1 sw   Y  W |S  t	y   t
d|| Y |S w )Nr(   z
                SELECT COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS
                WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'business_id_bots'
                c                 S  s   h | ]}|d  qS )COLUMN_NAMEr   ).0rr   r   r   	<setcomp>I   s    z3_fetch_business_id_bots_override.<locals>.<setcomp>
updated_atz ORDER BY `updated_at` DESCidz ORDER BY `id` DESCz$
                SELECT config FROM business_id_botsz
                WHERE business_idz = %s
                  AND (namez	 = %s OR z = %s)
                z)
                LIMIT 1
                configz?cluster_bot_runtime: business_id_bots read failed bid=%s bot=%s)r   r   cursorexecutefetchallr   fetchoner)   r#   r!   log	exception)connr-   r/   r0   override_cfgname_bot
name_labelcurcols	order_sqlparamsr3   r   r   r    _fetch_business_id_bots_override=   sb   

!!!!rI   c              	   C  s   | d}i }z>|   /}|dt| dtd d|g | }|r2t|}W d    W |S W d    W |S 1 s>w   Y  W |S  tyX   tjd||dd Y |S w )	N_botszSELECT * FROM z WHERE bot_idz = %s LIMIT 1z=cluster_bot_runtime: no row or missing table %s for bot_id=%sT)exc_info)r;   r<   r   r>   r   r!   r?   debug)rA   r-   r/   tabler$   rE   rowr   r   r   _fetch_dynamic_bot_rowf   s&   

$
rP   rB   c           	      C  s   t | }| D ]\}}|||< qt|dp|dpd }|dp1|dp1|dp1d}|d u r8d}t||d< |dpN|dpN|d	pNd}|rYt| |d< t|d
pi|dpit|pid }|rs||d
< |S )Nr0   r9   r(   system_promptMCUBE_SYSTEM_PROMPTpromptfirst_messageMCUBE_FIRST_MESSAGEmessage_outboundtts_voice_idMCUBE_TTS_VOICE_ID)r   itemsr   r)   r   r,   )	r$   rB   flatkvr0   rQ   firstvidr   r   r   _flat_for_overridest   s6   
 

(r_   r8   
int | NonerK   dict[str, str]c                 C  s  | du s|du r
i S z
t | }t |}W n ty   i  Y S w t }t||f}|r9||d  tk r9|d S tt	dd}|sEi S zddl
}ddlm} W n tya   td i  Y S w dd	 | D }	||	d
< z
|jdi |	}
W n ty   td i  Y S w zrzOt|
||}t|dp|dpd }t|
|||}t||}t|}|rtd||t|  ||ft||f< |W W z|
  W S  ty   Y S w  ty   td|| i  Y W z|
  W S  ty   Y S w w z|
  W w  ty   Y w w )z
    MCube runtime string overrides from cluster `{bid}_bots` + `business_id_bots`.
    Empty when DB unavailable or keys missing.
    Nr   r   DATABASE_CLUSTER_URLr(   )
DictCursorz*cluster_bot_runtime: pymysql not installedc                 S  s   i | ]\}}|d kr||qS )cursorclassr   )r2   r[   r\   r   r   r   
<dictcomp>   s    z:get_runtime_overrides_from_cluster_bot.<locals>.<dictcomp>rd   z&cluster_bot_runtime: DB connect failedr0   r9   z-cluster_bot_runtime: bid=%s bot_id=%s keys=%sz2cluster_bot_runtime: merge failed bid=%s bot_id=%sr   )r.   r!   time	monotonicr   r)   _CACHE_TTL_Sr   osgetenvpymysqlpymysql.cursorsrc   ImportErrorr?   warningrY   connectr@   rP   r   r   rI   r_   r   infolistkeysclose)r8   rK   r-   bpidnowhitrH   rk   rc   conn_kwrA   r$   r0   rB   rZ   outr   r   r   &get_runtime_overrides_from_cluster_bot   sv   

 
ry   )r
   r   r   r   )r   r   r   r   )r$   r   r   r   )r-   r.   r/   r.   r0   r   r   r   )r-   r.   r/   r.   r   r   )r$   r   rB   r   r   r   )r8   r`   rK   r`   r   ra   )__doc__
__future__r   r   loggingri   rf   typingr   business_id_agentsr   r   	getLoggerr?   r   __annotations__floatrj   rh   r   r#   r,   rI   rP   r_   ry   r   r   r   r   <module>   s$    






)
