o
    ›fjëÀ  ã                   @   s  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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 d dlmZmZmZ d dl Z dede!fdd„Z"dede#de#fdd„Z$dede%fdd„Z&de'de'fdd„Z(de'de'fdd„Z)dSde'de
ee*ef  de'fdd „Z+d!Z,d"e'd#e'dee*ef fd$d%„Z-dede*fd&d'„Z.d(ede%fd)d*„Z/d+e'd,e'de*fd-d.„Z0dede!fd/d0„Z1de'de'fd1d2„Z2d#e'de'de
e' fd3d4„Z3d#e'd"e'd5e'de'de*f
d6d7„Z4dede!fd8d9„Z5d,e'd:e6de'ddfd;d<„Z7d#e'd,e'd:e6de'ddf
d=d>„Z8d#e'd:e6de'd"e'ddf
d?d@„Z9de'de'fdAdB„Z:	dSde'dCe#d:e6de
ee*ef  de'f
dDdE„Z;de'd:e6de'fdFdG„Z<de'de'fdHdI„Z=dJe#dKe	e# dee#e	e' f fdLdM„Z>dJe#dNe	e' ddfdOdP„Z?G dQdR„ dRej@ƒZAdS )Té    N)Úuuid4)Údefaultdict)ÚAnyÚDictÚListÚOptional)Úconnections)ÚstatusÚviewsets)Úaction)ÚResponseé   )ÚAgentConfig)ÚAgentConfigCreateSerializerÚAgentConfigSerializer)Ú_q_identÚensure_business_cluster_tables)Úlist_cloud_agent_deploymentsÚprepare_workdir_for_botÚrun_lk_agentÚvÚreturnc                 C   ó   | dv S )N)r   TÚ1ÚtrueÚTrue© ©r   r   r   ú6/var/www/html/livekitdocker/backend/apps/agents/api.pyÚ_truthy_col   ó   r   Údefaultc              	   C   s&   zt | ƒW S  ttfy   | Y S w ©N)ÚintÚ	TypeErrorÚ
ValueError)r   r!   r   r   r   Ú	_safe_int   s
   
ÿr&   Úvalc                 C   ó`   | d u rg S t | tƒr| S t | tƒr.zt | ¡}t |tƒr |W S g W S  ty-   g  Y S w g S r"   ©Ú
isinstanceÚlistÚstrÚjsonÚloadsÚ	Exception)r'   Úparsedr   r   r   Ú_parse_transfer_destinations   ó   


ÿr1   Úrowc              
   C   st  t | ƒ}| d¡}t|tƒr!z	t |¡|d< W n	 ty    Y nw | d¡}t|tƒr>z	t |¡|d< W n	 ty=   Y nw t| d¡t ƒsnt| d¡ƒ| d¡pTdpTd| d¡p\dp\dt| d¡dƒt| d	¡ƒd
œ|d< t| d¡t ƒs—t| d¡ƒ| d¡p„dp„d| d¡pŒdpŒdt| d¡ƒdœ|d< t| d¡t ƒsÀt| d¡ƒ| d¡p­dp­d| d¡pµdpµdt| d¡ƒdœ|d< t| d¡t ƒsét| d¡ƒ| d¡pÖdpÖd| d¡pÞdpÞdt| d¡ƒdœ|d< | d¡}t|t ƒröd}n|durt|ƒ 	¡ nd}t
| d¡ƒ}|std|ii ƒ}t| d¡t ƒsNt| d¡ƒ| d¡p-dp-d| d¡p7dp7d| d¡pAd pAd ||t| d!¡ƒd"œ|d< | d#¡}t|tƒrmzt |¡}W n tyl   i }Y nw |du rui }nt|t ƒs}i }| d$¡}|durt|ƒ 	¡ |d%< n
| d%¡sšd|d%< ||d#< | d&¡}	t|	t ƒs¸|	}
|
du r²d'}
d(|
i|d&< |S ))zÍ
    Cluster rows store features as flat columns; clients expect nested objects matching POST shape.
    Build `end_conversation`, `skip_turn`, etc. without putting them back into `platform_settings`.
    ÚvoiceÚplatform_settingsÚend_conversationÚend_conversation_descriptionÚ Úend_conversation_promptÚend_conversation_timeouté
   Ú&end_conversation_disable_interruptions)ÚenabledÚdescriptionÚpromptÚtimeoutÚdisable_interruptionsÚ	skip_turnÚskip_turn_descriptionÚskip_turn_promptÚskip_turn_disable_interruptions)r=   r>   r?   rA   Úvoicemail_detectionÚvoicemail_descriptionÚvoicemail_detection_promptÚ)voicemail_detection_disable_interruptionsÚdetect_languageÚdetect_language_descriptionÚdetect_language_promptÚ%detect_language_disable_interruptionsÚtransfer_to_numberNÚtransfer_destinationsÚdestinationsÚtransfer_enabledÚtransfer_number_descriptionÚtransfer_to_number_promptÚtransfer_destination_typeÚphoneÚ(transfer_to_number_disable_interruptions)r=   r>   r?   Údestination_typeÚphone_numberrP   rA   Úadvanced_settingsÚfiller_keywordsÚkeywordsÚconcurrent_callséÿÿÿÿÚlimit)ÚdictÚgetr*   r,   r-   r.   r/   r   r&   Ústripr1   Ú_transfer_phone_for_column)r3   ÚoutÚ	voice_rawÚpsÚ	phone_colÚ	phone_strrP   ÚadvÚfkÚraw_ccÚlimr   r   r   Ú_enrich_cluster_bot_response,   s    

ÿ

ÿ
û	
ü
ü
ü


ù

ÿ




rl   c                 C   sN   t t| ƒƒ}| d¡}|dur||d< | d¡p| d¡}|dur%||d< |S )zKAlign cluster `{bid}_bots` rows with AgentConfig-shaped API (`id`, `name`).Úbot_idNÚidÚbot_nameÚname)rl   r_   r`   )r3   rc   Úbidro   r   r   r   Ú_normalize_cluster_bot—   s   
rr   Úrequest_dataÚ	validatedc                 C   sj   | r|   d¡nd p|r|pi   d¡nd }t|tƒr,zt |¡}W n ty+   i }Y nw t|tƒs3i }|S )NÚvoice_config©r`   r*   r,   r-   r.   r/   r_   )rs   rt   Ú	voice_cfgr   r   r   Ú_voice_config_from_request£   s   ÿ
ÿ
rx   )Útext_normalisation_typeÚoptimize_streaming_latencyÚstreaming_latency_optimizationÚpronunciation_dictionary_pathÚ	stabilityÚspeedÚ
similarityrw   Úbsc                 C   s€   t | d¡pi ƒ}| pi  ¡ D ]\}}|||< qi }tD ]!}||vr#q| |¡}|du r-qt|tƒr9| ¡ dkr9q|||< q|S )zZVoice tab controls stored under `platform_settings.voice` (subset of full `voice` column).r4   Nr8   )r_   r`   ÚitemsÚVOICE_TAB_PLATFORM_KEYSr*   r,   ra   )rw   r€   ÚmergedÚkr   rc   r   r   r   Ú)_voice_tab_settings_for_platform_settings¼   s   


r…   c                 C   sX   | d u rdS t | ƒ ¡  ¡  dd¡}|dv rdS |dv rdS |dv r$d	S |d
v r*dS |S )Nr8   ú Ú_)rU   rX   rU   )Úphone_dynamic_variableÚphone_number_dynamicÚphone_as_dynamic_variablerˆ   )Úsip_urir‹   )Úsip_uri_dynamic_variableÚsip_uri_dynamicrŒ   )r,   Úlowerra   Úreplace)r'   Úsr   r   r   Ú$_normalize_transfer_destination_typeÎ   s   r‘   Úrawc                 C   r(   r"   r)   )r’   r0   r   r   r   Ú_parse_destinations_listÝ   r2   r“   ÚtnÚfeaturesc           	      C   s.  t |  d¡p| d¡pdƒ ¡ }|r|dd… S t|  d¡ƒ}|s(t| d¡ƒ}|D ]5}t|tƒs2q*t| d¡p<| d	¡ƒ}|d
krCq*| d¡pN| d¡pNd}t |ƒ ¡ }|r_|dd…   S q*|r•|d }t|tƒr•t| d¡pu| d	¡ƒ}|d
kr•| d¡p†| d¡p†d}t |ƒ ¡ }|r•|dd… S dS )z¦
    Resolve `transfer_to_number` VARCHAR from explicit fields or first phone-type destination row.
    UI label "Phone Number" maps to destination type `phone`.
    rX   Útransfer_phone_numberr8   Né@   rP   rO   rT   ÚtyperU   r   )r,   r`   ra   r“   r*   r_   r‘   )	r”   r•   rU   rP   ÚdestÚdtÚpre   Úfirstr   r   r   rb   ë   sF   ÿþ
ÿÿ
ÿrb   c                 C   s&   | d u p| dkpt | tƒo|  ¡ dkS ©Nr8   )r*   r,   ra   r   r   r   r   Ú	_blankish  s   &rž   c                 C   sj   |   d¡}|d u ri S t|tƒr,zt |¡}t|tƒr|W S i W S  ty+   i  Y S w t|tƒr3|S i S )NÚbot_settings)r`   r*   r,   r-   r.   r_   r/   )rs   r’   r0   r   r   r   Ú_parse_bot_settings  s   


ÿr    c                 C   sd   |  d¡}|d u r| r|   d¡}t|tƒr'zt |¡}W n
 ty&   Y d S w t|tƒr0|r0|S d S )NÚconversation_behaviorrv   )r€   rs   r’   r   r   r   Ú_parse_conversation_behavior'  s   


ÿr¢   Úvdc                 C   s$   t || ƒ}i }|r||d< t |¡S )zç
    `platform_settings` keeps a compact `voice` object for Voice tab fields only.

    Full merged voice (incl. voice_id) stays in the `voice` column; language in
    `default_language`; features/prompts in dedicated columns.
    r4   )r…   r-   Údumps)r€   rw   r£   rs   Ú	voice_tabÚpayloadr   r   r   Ú%_build_platform_settings_column_value5  s
   
	
r§   c                 C   r   )N)Tr   r   r   r   r   r   r   r   r   Ú_truthyE  r    r¨   Ú
table_colsc              	   C   s8  | sdS d}|D ]\}}|| vs||vrqt |  |¡ƒrdnd||< qd|v r8d| v r8t |  d¡ƒr4dnd|d< d}|D ]&\}}||vsH|| vrIq<|  |¡}|dur^t|ƒ ¡ dkr^t|ƒnd||< q<d	}	|	D ]&\}}||vss|| vrtqg|  |¡}|dur‰t|ƒ ¡ dkr‰t|ƒnd||< qgd
|v r´d
| v r´ztdt|  d
¡ƒƒ|d
< W n ttfy³   d|d
< Y nw d|v rÙd| v rÙ|  d¡}|durÕt|ƒ ¡ dkrÕt|ƒdd… nd|d< d|v rød| v rø|  d¡}
|
durøt|
tƒsôt	 
|
¡n|
|d< d}|D ]\}}||vs
|| vrqüt |  |¡ƒrdnd||< qüdS )zEMap `platform_settings.features` into dedicated `{bid}_bots` columns.N))rB   rB   )rJ   rJ   )r6   r6   )rF   rF   r   r   rQ   rN   ))rD   rD   )rL   rL   )r9   r9   )rS   rS   )rH   rH   r8   ))rC   rC   )rK   rK   )r7   r7   )rR   rR   )rG   rG   r:   rT   é    rO   ))rE   rE   )rM   rM   )r<   r<   )rV   rV   )rI   rI   )r¨   r`   r,   ra   Úmaxr#   r$   r%   r*   r-   r¤   )r•   r©   r3   Ú	bool_keysÚfeat_keyÚcolÚ	long_textri   r   Ú
short_textÚtdÚinterrupt_colsr   r   r   Ú_apply_features_to_columnsI  sP   
*
*ÿ
0
ýr³   c              	   C   sâ  |   d¡pi }d|vr(d|v r(|  d¡}|dur$t|ƒ ¡ dkr$t|ƒnd|d< d|vrId|v rI|  d¡}|durEt|ƒ ¡ dkrEt|ƒnd|d< d|vrod|v roztd	t|  d
¡ƒƒ|d< W n ttfyn   d|d< Y nw d|vr„d|v r„t|  d¡ƒr€dnd	|d< |   d¡pŠi }d|vr¬d|v r¬|  d¡}|dur¨t|ƒ ¡ dkr¨t|ƒnd|d< d|vrÍd|v rÍ|  d¡}|durÉt|ƒ ¡ dkrÉt|ƒnd|d< d|vròd|v rò|  d¡}|durît|ƒ ¡ dkrît|ƒdd… nd|d< d|vrd|v r|  d¡}|durt|tƒst	 
|¡n||d< d|vr-d|v r-t|  d¡ƒr)dnd	|d< |   d¡p4i }d|vrZd|v rZ|  d¡}|durVt|ƒ ¡ dkrVt|ƒnd|d< d|vrd|v r|  d¡}|dur{t|ƒ ¡ dkr{t|ƒnd|d< d|vr—d|v r—t|  d¡ƒr“dnd	|d< |   d¡pži }	d|vrÄd|v rÄ|	  d¡}|durÀt|ƒ ¡ dkrÀt|ƒnd|d< d|vréd|v ré|	  d¡}|duråt|ƒ ¡ dkråt|ƒnd|d< d|vrd|v rt|	  d¡ƒrýdnd	|d< |   d¡pi }
d |vr.d |v r.|
  d¡}|dur*t|ƒ ¡ dkr*t|ƒnd|d < d!|vrSd!|v rS|
  d¡}|durOt|ƒ ¡ dkrOt|ƒnd|d!< d"|vrmd"|v rot|
  d¡ƒrgdnd	|d"< dS dS dS )#zª
    Fill prompt/description/timeout/interrupt columns from top-level `bot_settings` sections
    when those keys were not supplied via `platform_settings.features`.
    r6   r9   r?   Nr8   r7   r>   r:   r   r@   r<   rA   r   rN   rS   rR   rT   rW   rª   rO   rP   rV   rF   rH   rG   rI   rB   rD   rC   rE   rJ   rL   rK   rM   )r`   r,   ra   r«   r#   r$   r%   r¨   r*   r-   r¤   )r€   r•   r©   r3   Úecr   r”   r±   ÚvmÚstÚdlr   r   r   Ú'_apply_flat_bot_settings_detail_columns’  s€   
(
(ÿ
(
(
0




,
,


,
,
,
,

 ýr¸   c              	   C   sf  i }| r|   d¡}t|tƒr|  d¡}t|tƒr|}t|||ƒ t| |||ƒ | s7d|v r5d|vr5d|d< dS |   d¡p=i }d|v rQd|vrQ|  d¡rMdnd|d< |   d	¡pWi }d	|v rkd	|vrk|  d¡rgdnd|d	< |   d
¡pqi }	d
|v r…d
|vr…|	  d¡rdnd|d
< |   d¡p‹i }
d|v rŸd|vrŸ|
  d¡r›dnd|d< |   d¡p¥i }d|v r¹d|vr¹|  d¡rµdnd|d< d|v rÊt||ƒ}|rÆ|nd|d< |   d¡pÐi }|  d¡}d|v r|du sç|dksç|dkrìd|d< nztdtdt|ƒƒƒ|d< W n t	t
fy
   d|d< Y nw |   d¡pi }|  d¡}d|v r8|dur4t|ƒ ¡ dkr4t|ƒ ¡ |d< nd|d< d|v rui }|  d¡durfzt|  d¡ƒ|d< W n t	t
fye   |  d¡|d< Y nw |rqt |¡|d< nd|d< t|   d¡p}i ƒ}|  d¡rŽ| d|  d¡¡ | ¡ D ]\}}| ||¡ q’d|v r¯|r±t |¡|d< dS dS dS )zODedicated columns for features + toggles; platform_settings JSON stays minimal.r5   r•   rJ   r   Nr6   r=   r   rB   rF   rN   rQ   r\   r^   r8   r]   i,  rY   r[   rZ   Úturn_timeoutr4   Úvoice_id)r`   r*   r_   r³   r¸   rb   r«   Úminr#   r$   r%   r,   ra   r-   r¤   Ú
setdefaultr   )r€   r©   r3   rw   r•   re   Úrawfr´   r¶   rµ   r·   r”   rU   Úccrk   rh   r[   ÚrestÚvor„   r   r   r   r   Ú"_apply_bot_settings_to_cluster_rowâ  s„   







ÿ


ÿÿrÁ   c              
   C   s   t | ƒ}d|v r1|d }t|ƒrd|d< nt|ƒ ¡ }zt|ƒ|d< W n ty0   ||d< Y nw dD ]+}||vr:q3|| }t|ƒrGd||< q3zt|ƒ||< W q3 ttfy^   d||< Y q3w d|v r~| d¡}t|ƒrrd|d< |S t|ƒ ¡ dd… |d< |S )zk
    Legacy `{bid}_bots` tables often use INT for mcube_gid; empty strings from JSON must not be sent.
    Ú	mcube_gidr   )r\   NÚagent_idé€   )r_   rž   r,   ra   r#   r%   r$   r`   )r3   rc   r   r   r®   r   r   r   Ú%_sanitize_cluster_bots_row_for_insert6  s:   
ÿ
ÿ
ÿrÅ   rq   c                    sà  |pi }t | |ƒ}t| ƒ}|| d¡p|  d¡pd ¡ pd|  d¡p&|  d¡| d¡p1|  d¡p1d ¡ p5d| d¡d urA| d¡n|  d¡pGd| d¡d urS| d¡n|  d¡pYdt|||| ƒddd	œ	}t|| ƒ}|d urxd
ˆ v rxt |¡|d
< | d¡d ur„| d¡n|  d¡| d¡d ur”| d¡n|  d¡| d¡d ur¤| d¡n|  d¡| d¡d ur´| d¡n|  d¡| d¡d urÄ| d¡n|  d¡| d¡d urÔ| d¡n|  d¡| d¡d urä| d¡n|  d¡t |  d¡põ| d¡põg ¡t |  d¡p| d¡pg ¡|d dœ
}	|	 ¡ D ]\}
}|
ˆ v r"|d ur"|||
< qdˆ v rZ| d¡d ur6| d¡n|  d¡}|d urVt	|ƒ ¡ dkrVt	|ƒ ¡ d d… |d< nd |d< t
|ˆ ||ƒ ‡ fdd„| ¡ D ƒ}t|ƒS )Nrp   r8   úUnnamed BotrÃ   ÚagentIdÚ	llm_modelÚsystem_promptr   )	Úbusiness_idro   rÃ   Ú
agent_namerÈ   r?   r5   Ú
voice_nameÚ	is_activer¡   Ústt_providerÚtts_providerÚllm_providerÚmcube_exenumberrÂ   Úfirst_message_inboundÚfirst_message_outboundÚ
guardrailsÚsystem_toolsr5   )
rÎ   rÏ   rÐ   rÑ   rÂ   Úmessage_inboundÚmessage_outboundrÔ   rÕ   r5   Údefault_languagerª   c                    s   i | ]\}}|ˆ v r||“qS r   r   )Ú.0r„   r   ©r©   r   r   Ú
<dictcomp>–  ó    z*_build_cluster_bot_row.<locals>.<dictcomp>)rx   r    r`   ra   r§   r¢   r-   r¤   r   r,   rÁ   rÅ   )rs   rq   r©   rt   r£   rw   r€   r3   ÚcbÚoptional_mapr„   r   ÚdlangÚfilteredr   rÚ   r   Ú_build_cluster_bot_rowZ  s`   
$
ÿó
     
ÿ
ÿ ò€

ÿýrá   c                 C   s„  ddddddddd	d
ddddœ}i }|  ¡ D ]z\}}|| v r||v r| | }|dkr5|dv r0dnd||< q|dkrLt|ƒsGt|ƒ ¡ dd… nd||< q|dkrct|ƒs^t|ƒ ¡ dd… nd||< q|d
krŒt|ƒrpd||< qt|ƒ ¡ }zt|ƒ||< W q ty‹   |||< Y qw |||< qt| ƒ}d| v rt| ƒ}	i }
t|	||
|ƒ |
  ¡ D ]\}}||v r·|||< q«t	|	| ƒ}|durÌd|v rÌt
 |¡|d< d|  d¡i}d|v ràt|	||| ƒ|d< d|v rÿd| v rÿ|  d¡}|dvrût|ƒ ¡ dd… nd|d< |S d| v sd| v r@d|v rti |d|  d¡i| ƒ|d< d|v r@d| v r@|  d¡}|dvr<t|ƒ ¡ dd… nd|d< |S )z1Map JSON/API fields to cluster columns for PATCH.ro   rÃ   rË   r?   rÈ   rÎ   rÏ   rÐ   rÑ   rÂ   rÖ   r×   rÍ   )rp   rÃ   rË   rÉ   rÈ   rÎ   rÏ   rÐ   rÑ   rÂ   rÒ   rÓ   rÍ   )Tr   r   r   r   r   NrÄ   éÿ   rŸ   r¡   rØ   r5   r   rª   ru   )r   rž   r,   ra   r#   r%   rx   r    rÁ   r¢   r-   r¤   r`   r§   )rs   r©   ÚmappingÚupdatesÚapi_keyr®   r'   r   rw   r€   Útmpr„   r   rÝ   Úvd_patchr·   r   r   r   Ú_cluster_update_valuesš  s‚   ó&&
ÿ€
€
ÿ
$
÷
ÿ
&rè   c                 C   sT   t | ƒ}| d¡p
d|d< | d¡pd|d< | d¡}|r(t|ƒ d¡r(||d< |S )zPMatch `DocumentViewSet` cluster rows so `doc_name` appears as `name` for the UI.Údoc_namer8   rp   Údoc_urlÚ	file_path)zhttp://zhttps://Úweb_url)r_   r`   r,   Ú
startswith)r3   rc   Údur   r   r   Ú_shape_cluster_kb_documentà  s   
rï   rÊ   Úbot_idsc              
   C   sj  |si S t | ƒ}t|ƒ |› d}g }tƒ }|D ]"}zt |ƒ}W n ttfy+   Y qw ||vr:| |¡ | |¡ q|s?i S d dgt|ƒ ¡}dt	|ƒ› d|› d}	t
d  ¡ }
|
 |	|¡ dd	„ |
jD ƒ}|
 ¡ }W d
  ƒ n1 sww   Y  ttƒ}|D ].}ttt||ƒƒƒ}| d¡}|d
u r—q‚zt |ƒ}W n ttfy¨   Y q‚w ||  |¡ q‚t|ƒS )zSLoad `{business_id}_knowledgebase` rows grouped by `bot_id` (newest first per bot).Ú_knowledgebaseú, ú%súSELECT * FROM z WHERE bot_id IN (z) ORDER BY bot_id ASC, id DESCÚclusterc                 S   ó   g | ]}|d  ‘qS ©r   r   ©rÙ   Úcr   r   r   Ú
<listcomp>  ó    z2_knowledge_documents_by_bot_id.<locals>.<listcomp>Nrm   )r#   r   Úsetr$   r%   ÚaddÚappendÚjoinÚlenr   r   ÚcursorÚexecuter>   Úfetchallr   r+   rï   r_   Úzipr`   )rÊ   rð   rq   ÚtableÚuniqÚseenÚxÚixÚplaceholdersÚsqlÚcurÚcolsÚraw_rowsÚby_botÚrÚdÚb_idr   r   r   Ú_knowledge_documents_by_bot_idë  sP   
ÿ

€ÿ
ý
ÿr  Úbotsc              
   C   sà   g }|D ])}|  d¡}|du r|  d¡}|dur-z	| t|ƒ¡ W q ttfy,   Y qw qt| |ƒ}|D ]8}|  d¡}|du rE|  d¡}z|durNt|ƒnd}W n ttfy^   d}Y nw |duri|  |g ¡ng |d< q5dS )zXMutate bot dicts in place: set `knowledge_documents` from cluster `{bid}_knowledgebase`.rm   Nrn   Úknowledge_documents)r`   rþ   r#   r$   r%   r  )rÊ   r  ÚidsÚbr’   ÚgroupedÚikr   r   r   Ú_attach_knowledge_documents  s0   

ÿý


ÿør  c                       s”   e Zd ZdZej ¡  d¡Zdd„ Z	‡ fdd„Z
‡ fdd„Zd	d
„ Zdd„ Zdef‡ fdd„Z‡ fdd„Zdd„ Zeddgddddd„ƒZ‡  ZS )Ú
BotViewSetz¯
    Bot CRUD. With `business_id`, stores rows only in cluster DB `{business_id}_bots`.
    Without `business_id`, uses legacy master `AgentConfig` (agents_agent_config).
    rp   c                 C   s   | j dkrtS tS )NÚcreate)r   r   r   )Úselfr   r   r   Úget_serializer_class5  s   
zBotViewSet.get_serializer_classc           
   
      sþ   |j  d¡}|rrzKt|ƒ}t|ƒ |› d}td  ¡ +}| dt|ƒ› d¡ dd„ |jD ƒ‰ ‡ fdd„| 	¡ D ƒ}t
||ƒ W d	  ƒ n1 sJw   Y  t|ƒW S  tyq }	 ztd
t|	ƒdœtjdW  Y d	}	~	S d	}	~	ww tƒ j|g|¢R i |¤ŽS )z•
        If business_id is provided, read bots from cluster `{business_id}_bots`.
        Otherwise fall back to master DB AgentConfig list.
        rÊ   Ú_botsrõ   rô   z ORDER BY bot_id DESCc                 S   rö   r÷   r   rø   r   r   r   rú   G  rû   z#BotViewSet.list.<locals>.<listcomp>c                    s   g | ]}t ttˆ |ƒƒƒ‘qS r   )rr   r_   r  )rÙ   r  ©r  r   r   rú   H  rÜ   Nz!Failed to load bots from cluster.©ÚmessageÚerror©r	   )Úquery_paramsr`   r#   r   r   r  r  r   r>   r  r  r   r/   r,   r	   ÚHTTP_500_INTERNAL_SERVER_ERRORÚsuperr+   )
r  ÚrequestÚargsÚkwargsrÊ   rq   r  r  ÚrowsÚe©Ú	__class__r   r   r+   :  s*   
ü
þ€ÿzBotViewSet.listc              
      sD  |j  d¡}| d¡}|r•|d ur•zet|ƒ}t|ƒ |› d}td  ¡ H}| dt|ƒ› d|g¡ | ¡ }	|	sJt	ddit
jd	W  d   ƒ W S d
d„ |jD ƒ}
ttt|
|	ƒƒƒ}t||gƒ t	|ƒW  d   ƒ W S 1 spw   Y  W n ty” } zt	dt|ƒdœt
jd	W  Y d }~S d }~ww tƒ j|g|¢R i |¤ŽS )NrÊ   Úpkr  rõ   rô   ú WHERE bot_id = %sÚdetailú
Not found.r$  c                 S   rö   r÷   r   rø   r   r   r   rú   _  rû   z'BotViewSet.retrieve.<locals>.<listcomp>z Failed to load bot from cluster.r!  )r%  r`   r#   r   r   r  r  r   Úfetchoner   r	   ÚHTTP_404_NOT_FOUNDr>   rr   r_   r  r  r/   r,   r&  r'  Úretrieve)r  r(  r)  r*  Úbid_rawr/  rq   r  r  r3   r  r¦   r,  r-  r   r   r5  R  s4   

ü&ø	þ€ÿzBotViewSet.retrievec                 O   ó   | j |g|¢R ddi|¤ŽS )NÚpartialT©Ú_update_cluster_or_master©r  r(  r)  r*  r   r   r   Úpartial_updatej  ó   zBotViewSet.partial_updatec                 O   r7  )Nr8  Fr9  r;  r   r   r   Úupdatem  r=  zBotViewSet.updater8  c              
      s\  |j  d¡p|j d¡}| d¡}|r|d urzØt|ƒ}t|ƒ |› d}td  ¡ »}	|	 dt|ƒ› d¡ dd„ |	j	D ƒ}
t
|j |
ƒ}|s|	 dt|ƒ› d	|g¡ |	 ¡ }|smtd
ditjdW  d   ƒ W S dd„ |	j	D ƒ}ttt||ƒƒƒ}t||gƒ t|ƒW  d   ƒ W S d dd„ |D ƒ¡}t| ¡ ƒ}| |¡ |	 dt|ƒ› d|› d	|¡ |	 dt|ƒ› d	|g¡ |	 ¡ }dd„ |	j	D ƒ}ttt||ƒƒƒ}t||gƒ t|ƒW  d   ƒ W S 1 sëw   Y  W n ty } ztdt|ƒdœtjdW  Y d }~S d }~ww |r!tƒ j|g|¢R i |¤ŽS tƒ j|g|¢R i |¤ŽS )NrÊ   r/  r  rõ   rô   ú LIMIT 0c                 S   ó   h | ]}|d  ’qS r÷   r   rø   r   r   r   Ú	<setcomp>z  rû   z7BotViewSet._update_cluster_or_master.<locals>.<setcomp>r0  r1  r2  r$  c                 S   rö   r÷   r   rø   r   r   r   rú     rû   z8BotViewSet._update_cluster_or_master.<locals>.<listcomp>rò   c                 s   s    | ]
}t |ƒ› d V  qdS )z = %sN©r   )rÙ   r„   r   r   r   Ú	<genexpr>…  s   € z7BotViewSet._update_cluster_or_master.<locals>.<genexpr>zUPDATE z SET c                 S   rö   r÷   r   rø   r   r   r   rú   Ž  rû   z Failed to update bot in cluster.r!  )Údatar`   r%  r#   r   r   r  r  r   r>   rè   r3  r   r	   r4  rr   r_   r  r  rÿ   r+   Úvaluesrþ   r/   r,   r&  r'  r<  r>  )r  r(  r8  r)  r*  r6  r/  rq   r  r  r©   rä   r3   r  r¦   Ú	set_partsÚvalsr,  r-  r   r   r:  p  s\   

ø	ô
þ&çþ€ÿz$BotViewSet._update_cluster_or_masterc           
   
      sô   |j  d¡p|j d¡}| d¡}|rm|d urmz7t|ƒ}t|ƒ |› d}td  ¡ }| dt|ƒ› d|g¡ W d   ƒ n1 sCw   Y  t	t
jdW S  tyl }	 zt	dt|	ƒd	œt
jdW  Y d }	~	S d }	~	ww tƒ j|g|¢R i |¤ŽS )
NrÊ   r/  r  rõ   zDELETE FROM r0  r$  z"Failed to delete bot from cluster.r!  )r%  r`   rD  r#   r   r   r  r  r   r   r	   ÚHTTP_204_NO_CONTENTr/   r,   r&  r'  Údestroy)
r  r(  r)  r*  r6  r/  rq   r  r  r,  r-  r   r   rI  ›  s&   

ÿþ€ÿzBotViewSet.destroyc              
      sÄ  | j |jd}|jdd |j d¡}|dvrÈzt|ƒ}W n ttfy3   tddgitj	d Y S w t
|ƒ |› d}td	  ¡ }| d
t|ƒ› d¡ dd„ |jD ƒ}	W d  ƒ n1 saw   Y  tt dd¡ƒ ¡ dv }
d}d}t|j d¡p„|j d¡p„dƒ ¡ p‰d}|
røzRt|ƒ}t d¡}t d¡pœd}td|||r«t|ƒ ¡ r«|ndtt dd¡ƒdd}| d¡}|sÝt| d¡pÅdƒ}d| ¡ v rÝtƒ }|rÖ|d ndpÜt d ¡}W n ty÷ } zd!t|ƒd"œ}W Y d}~nd}~ww t|jƒ}|r||d< ||d#< t|||	|jƒ‰ ˆ std$d%itjdS tˆ  ¡ ƒ}d& d'gt |ƒ ¡}d& d(d)„ |D ƒ¡}d*t|ƒ› d+|› d,|› d-}td	  ¡ 1}| |‡ fd.d/„|D ƒ¡ |j!}| d
t|ƒ› d0|g¡ | "¡ }d1d/„ |jD ƒ}W d  ƒ n	1 sw   Y  t#tt$||ƒƒƒ}t%||gƒ |dur»t&| d2¡ƒ| d3¡| d4¡| d¡| d5¡| d6¡| d¡d7œ|d8< |  '|¡}t|tj(|d9S | )¡ }t*|d:|id;j}|  '|¡}t|tj(|d9S )<z®
        With business_id: insert only into cluster `{business_id}_bots` (livekitvoicebot_cluster).
        Without business_id: legacy create on master AgentConfig.
        )rD  T)Úraise_exceptionrÊ   r   zInvalid business_id.r$  r  rõ   rô   r?  c                 S   r@  r÷   r   rø   r   r   r   rA  Ä  rû   z$BotViewSet.create.<locals>.<setcomp>NÚ!LIVEKIT_AUTO_DEPLOY_ON_BOT_CREATEÚfalse)r   r   ÚyesÚonrp   r8   rÆ   ÚLIVEKIT_AGENT_SECRETS_FILEÚLIVEKIT_AGENT_REGIONzus-eastr  ÚLIVEKIT_AGENT_CREATE_TIMEOUT_SÚ1800)ÚregionÚsecrets_fileÚ	timeout_sÚsilentrÃ   Ústderrz maximum number of agents reachedr   ÚLIVEKIT_FALLBACK_AGENT_IDF)Úokr"  rË   r1  z+Cluster bots table has no writable columns.rò   ró   c                 s   s    | ]}t |ƒV  qd S r"   rB  rø   r   r   r   rC  û  s   € z$BotViewSet.create.<locals>.<genexpr>zINSERT INTO z (z
) VALUES (ú)c                    s   g | ]}ˆ | ‘qS r   r   rø   ©r3   r   r   rú   ÿ  rû   z%BotViewSet.create.<locals>.<listcomp>r0  c                 S   rö   r÷   r   rø   r   r   r   rú     rû   rY  Ú	exit_codeÚcommandr"  Ústdout)rY  r\  r]  rÃ   r"  r^  rW  Úlivekit_deploy)r	   Úheadersr(  )Úcontext)+Úget_serializerrD  Úis_validr`   r#   r$   r%   r   r	   ÚHTTP_400_BAD_REQUESTr   r   r  r  r   r>   r,   ÚosÚgetenvrŽ   Úvalidated_datara   r   r   r   r/   r_   rá   r&  r+   Úkeysrÿ   r   Ú	lastrowidr3  rr   r  r  ÚboolÚget_success_headersÚHTTP_201_CREATEDÚsaver   )r  r(  r)  r*  Ú
serializerÚbusiness_id_rawrq   r  r  r©   Úauto_deployÚdeploy_infoÚlivekit_agent_idro   ÚworkdirrT  rS  rW  Úexistingr,  Úrequest_for_rowr  r
  Úcol_sqlr  Úbot_pkÚfetchedÚout_colsr¦   r`  Úbotr   r[  r   r  ­  s°   

þÿ
þ$þ
ú
ÿ€€ÿ
þû

ù
	
zBotViewSet.createTÚpostzinitiate-call)r1  ÚmethodsÚurl_pathNc              
   C   st  |j pi }t| d¡p| d¡p| d¡pdƒ ¡ }|s&tdddœtjdS | d	¡p0|j d	¡}z|d
vr:t|ƒnd}W n t	yH   d}Y nw d}z|durYt
jj|d ¡ nd}W n t	yg   d}Y nw t| d¡pu| d¡putƒ ƒ}tt dd¡pdƒ d¡}	tt dd¡pŒdƒ d¡}
tt dd¡p™dƒ}tt dd¡p£dƒ ¡ }t| d¡p®dƒ ¡ }|s¼|	r¼|	› |› }t| d¡pÃdƒ ¡ }|s×|
r×|
› | d¡› d|› }t| d¡pÞdƒ ¡ }|sòtt|ddƒpìdƒ ¡ pñd}|||dœ}|rþ||d< |r||d< |dur!zt|ƒ|d< W n t	y    ||d< Y nw |dur*||d	< |r`t|ddƒr=t|jƒ ¡ |d< t|ddƒrPt|jƒ ¡ pMd |d!< t|d"dƒr`t|jƒ ¡ |d#< t d$d%¡ d¡}tt d&d'¡ƒ}t|ƒ}| dd¡}| d	¡}|}|dur±|dur±z|› dt|ƒ› dt|ƒ› }| d	d¡ W n- t	y°   ||d< Y n w |durÐz|› dt|ƒ› }W n t	yÏ   ||d< Y nw z*tj|d(}|j||d)}|jrç| ¡ ni }W d  ƒ n	1 sôw   Y  W n  t	y } ztdd*t|ƒd+œtjdW  Y d}~S d}~ww |jd,vr.tdd-|j|d.œtjdS td/d0||d1œtj dS )2zË
        Click-to-call entrypoint from the UI.

        Expected body: { "customer_number": "...", "bot_id": "<pk>" }
        Also accepts aliases used by other integrations: `to`, `custnumber`.
        Úcustomer_numberÚ
custnumberÚtor8   Fzcustomer_number is required)Úsuccessr"  r$  rÊ   r   N)rn   Úcall_idÚrefidÚMCUBE_PUBLIC_BASE_URLú/ÚMCUBE_PUBLIC_WS_URL_BASEÚMCUBE_WEBHOOK_PATHz/webhooks/mcubeÚMCUBE_WS_PATH_PREFIXz/wsÚcallback_urlÚrefurlrË   rp   r!   )r€  rË   r‚  rm   rÑ   Ú	exenumberrÂ   r   ÚgidÚmcube_http_authorizationÚHTTP_AUTHORIZATIONÚMCUBE_OUTBOUND_CALL_URLz0https://app3.syntheon.in/api/mcube/outbound-callÚMCUBE_OUTBOUND_CALL_TIMEOUT_SÚ20)r@   )r-   z$Failed to call outbound-call service)r  r"  r#  )éÈ   éÉ   z'Outbound-call service returned an error)r  r"  Úhttp_statusÚresponseTzCall initiated)r  r"  r‚  r•  )!rD  r,   r`   ra   r   r	   rd  r%  r#   r/   r   ÚobjectsÚfilterrœ   r   re  rf  ÚrstripÚgetattrrÑ   rÂ   r  Úfloatr_   ÚpopÚhttpxÚClientr{  Úcontentr-   ÚHTTP_502_BAD_GATEWAYÚstatus_codeÚHTTP_200_OK)r  r(  r/  rD  r~  rÊ   Úbusiness_id_intrz  r‚  Úpublic_baseÚpublic_ws_baseÚwebhook_pathÚws_path_prefixr‰  rŠ  rË   r¦   Ú
mcube_baserU  Ú	post_bodyÚpath_botÚpath_bidÚ	mcube_urlÚclientÚrespÚ	resp_jsonr,  r   r   r   Úinitiate_call  sÖ   
(ÿþÿ"ÿý
ÿ
þý
ÿ
ÿþ€þ€ÿüù
þzBotViewSet.initiate_callr"   )Ú__name__Ú
__module__Ú__qualname__Ú__doc__r   r–  ÚallÚorder_byÚquerysetr  r+   r5  r<  r>  rj  r:  rI  r  r   r¯  Ú__classcell__r   r   r-  r   r  -  s    +nr  r"   )Br-   re  Úuuidr   Úcollectionsr   Útypingr   r   r   r   Ú	django.dbr   Úrest_frameworkr	   r
   Úrest_framework.decoratorsr   Úrest_framework.responser   Úmodelsr   Úserializersr   r   Úapps.cluster.dynamic_tablesr   r   Úapps.livekit.clir   r   r   rœ  rj  r   r#   r&   r+   r1   r_   rl   rr   r,   rx   r‚   r…   r‘   r“   rb   rž   r    r¢   r§   r¨   rü   r³   r¸   rÁ   rÅ   rá   rè   rï   r  r  ÚModelViewSetr  r   r   r   r   Ú<module>   sŽ    k$+ÿÿÿÿ
þIPÿÿÿÿ
þT%ÿÿÿÿÿ
þ@F&*