
    Ki:                        d Z ddl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	 ddl
mZmZmZ ddlmZ ddlmZmZmZ  ej        e          Z e            dz  Zd	Zd
eeef         fdZdeeef         d
dfdZded
efdZded
eedz  ef         fdZdeded
dfdZ ddededz  d
dfdZ!ded
efdZ" G d d          Z#e G d d                      Z$ G d de          Z%dS )zModal cloud execution environment using the native Modal SDK directly.

Uses ``Sandbox.create()`` + ``Sandbox.exec()`` instead of the older runtime
wrapper, while preserving Hermes' persistent snapshot behavior across sessions.
    N)	dataclass)Path)AnyDictOptional)get_hermes_home)BaseModalExecutionEnvironmentModalExecStartPreparedModalExeczmodal_snapshots.jsondirectreturnc                      t                                           r<	 t          j        t                                                     S # t
          $ r Y nw xY wi S )z#Load snapshot ID mapping from disk.)_SNAPSHOT_STOREexistsjsonloads	read_text	Exception     5/home/ubuntu/hermes-agent/tools/environments/modal.py_load_snapshotsr      s\     	:o7799::: 	 	 	D	Is   *A 
AAdatac                     t           j                            dd           t                               t	          j        | d                     dS )z$Persist snapshot ID mapping to disk.T)parentsexist_ok   )indentN)r   parentmkdir
write_textr   dumps)r   s    r   _save_snapshotsr#   '   sG       ===tz$q999:::::r   task_idc                     t            d|  S )N:)_DIRECT_SNAPSHOT_NAMESPACE)r$   s    r   _direct_snapshot_keyr(   -   s    (447444r   c                     t                      }t          |           }|                    |          }t          |t                    r|r|dfS |                    |           }t          |t                    r|r|dfS dS )zDReturn a snapshot id and whether it came from the legacy key format.FT)NF)r   r(   get
isinstancestr)r$   	snapshotsnamespaced_keysnapshot_idlegacy_snapshot_ids        r   _get_snapshot_restore_candidater1   1   s    !!I)'22N--//K+s## " "E!!"w//$c** (/A (!4'';r   r/   c                     t                      }||t          |           <   |                    | d           t          |           dS )z@Persist the direct Modal snapshot id under the direct namespace.N)r   r(   popr#   )r$   r/   r-   s      r   _store_direct_snapshotr4   A   sH    !!I/:I"7++,MM'4   Ir   c                     t                      }d}t          |           | fD ]:}|                    |          }||||k    r|                    |d           d};|rt	          |           dS dS )zGRemove direct Modal snapshot entries for a task, including legacy keys.FNT)r   r(   r*   r3   r#   )r$   r/   r-   updatedkeyvalues         r   _delete_direct_snapshotr9   I   s    !!IG$W--w7  c""=%;"6"6MM#t$$$G #	"""""# #r   
image_specc                     ddl }t          | t                    s| S |                     d          r|j                            |           S |j                            | dg          S )zEConvert registry references or snapshot ids into Modal image objects.r   Nzim-zRUN rm -rf /usr/local/lib/python*/site-packages/pip* 2>/dev/null; python -m ensurepip --upgrade --default-pip 2>/dev/null || true)setup_dockerfile_commands)modalr+   r,   
startswithImagefrom_idfrom_registry)r:   _modals     r   _resolve_modal_imagerC   Z   s    j#&& U## 0|##J///<%%N#
 &   r   c                   2    e Zd ZdZd Zd Zd Zd	dZd ZdS )
_AsyncWorkerzEBackground thread with its own event loop for async-safe Modal calls.c                 R    d | _         d | _        t          j                    | _        d S N)_loop_thread	threadingEvent_startedselfs    r   __init__z_AsyncWorker.__init__p   s#    :>
37!))r   c                     t          j        | j        d          | _        | j                                         | j                            d           d S )NTtargetdaemon   timeout)rJ   Thread	_run_looprI   startrL   waitrM   s    r   rY   z_AsyncWorker.startu   sN     't~dKKK2&&&&&r   c                     t          j                    | _        t          j        | j                   | j                                         | j                                         d S rG   )asyncionew_event_looprH   set_event_looprL   setrun_foreverrM   s    r   rX   z_AsyncWorker._run_loopz   sS    +--
tz***
     r   X  c                     | j         | j                                         rt          d          t          j        || j                   }|                    |          S )NzAsyncWorker loop is not runningrU   )rH   	is_closedRuntimeErrorr\   run_coroutine_threadsaferesult)rN   cororV   futures       r   run_coroutinez_AsyncWorker.run_coroutine   sT    :!5!5!7!7@AAA1$
CC}}W}---r   c                     | j         r=| j                                         r$| j                             | j         j                   | j        r| j                            d           d S d S )N
   rU   )rH   
is_runningcall_soon_threadsafestoprI   joinrM   s    r   rn   z_AsyncWorker.stop   sn    : 	=$*//11 	=J++DJO<<<< 	*Lb)))))	* 	*r   N)ra   )	__name__
__module____qualname____doc__rO   rY   rX   ri   rn   r   r   r   rE   rE   m   sj        OO* * *
' ' '
! ! !. . . .* * * * *r   rE   c                   >    e Zd ZU ej        ed<   eeef         ed<   dS )_DirectModalExecHandlethreadresult_holderN)	rp   rq   rr   rJ   rW   __annotations__r   r,   r   r   r   r   ru   ru      s5         S>!!!!!r   ru   c                        e Zd ZdZdZdZdZdZ	 	 	 	 	 ddedede	de
eeef                  dedef fdZdededefdZddZddZdedefdZdededz  fdZdeddfdZd Z xZS ) ModalEnvironmentz1Modal cloud execution via native Modal sandboxes.heredocg?z0[Command interrupted - Modal sandbox terminated]zModal execution error/root<   NTdefaultimagecwdrV   modal_sandbox_kwargspersistent_filesystemr$   c                    t                                          ||           || _        || _        || _        d | _        d | _        t                      | _        i | _	        t          |pi           d }d}| j        r<t          | j                  \  }}|r#t                              d|d d                    dd lg 	 ddlm}	m}
  |	            D ]e}                    j                            |d         |d         	                     t                              d
|d         |d                    f |
            }|D ]=}                    j                            |d         |d         	                     >|r(t                              dt+          |                     n2# t,          $ r%}t                              d|           Y d }~nd }~ww xY w| j                                         dt2          ffd}	 |p|}	 t5          |          }| j                             ||          d          \  | _        | _        |r7|r5t9          | j        |           t                              d| j                   n# t,          $ r}|s t                              d|d d         |           t=          | j        |           t5          |          }| j                             ||          d          \  | _        | _        Y d }~nd }~ww xY wn(# t,          $ r | j                                          w xY wt                              d| j                   d S )N)r   rV   Fz!Modal: restoring from snapshot %s   r   get_credential_file_mountsiter_skills_files	host_pathcontainer_path)remote_pathz#Modal: mounting credential %s -> %szModal: mounting %d skill filesz0Modal: could not load credential file mounts: %sr:   c                   K   j         j                            dd           d {V }t                    }r=t	          |                    dg                     }|                               ||d<    j        j        j        	 	 d
| |t          |                    dd                    d	| d {V }||fS )Nzhermes-agentT)create_if_missingmountssleepinfinityrV   i  )r   apprV   )r   r   )
Applookupaiodictlistr3   extendSandboxcreateint)r:   r   create_kwargsexisting_mountssandboxrB   cred_mountssandbox_kwargss        r   _create_sandboxz2ModalEnvironment.__init__.<locals>._create_sandbox   s     
)--nPT-UUUUUUUUC 00M :"&}'8'82'F'F"G"G&&{333*9h'5FN15 !M--i>>??          G <r   i,  rU   z1Modal: migrated legacy snapshot entry for task %szBModal: failed to restore snapshot %s, retrying with base image: %sz Modal: sandbox created (task=%s)) superrO   _persistent_task_id_base_image_sandbox_apprE   _worker_synced_filesr   r1   loggerinfor=   tools.credential_filesr   r   appendMountfrom_local_filelenr   debugrY   r   rC   ri   r4   warningr9   rn   )rN   r   r   rV   r   r   r$   restored_snapshot_idrestored_from_legacy_keyr   r   mount_entryskills_filesentryer   target_image_speceffective_imageexc
base_imagerB   r   r   	__class__s                       @@@r   rO   zModalEnvironment.__init__   sP    	S'2220 	#~~/128b99##(  	\=\> >: ": $ \?AUVYWYVYAZ[[[	P\\\\\\\\99;;  ""L00#K0$/0@$A 1     9, 01    -,..L%  ""L00k*$)*:$; 1       Q<c,>O>OPPP 	P 	P 	PLLKQOOOOOOOO	P 		 c 	  	  	  	  	  	  	  	 "$	 4 =
 #77H"I"I+/<+E+E#OO44 ,F , ,(	4=( ( ,D *4=:NOOOKKK  %    + X("-  
 (7KLLL1%88
+/<+E+E#OJ// ,F , ,(	4======,  	 	 	L	 	6FFFFFsQ   C,F1 1
G ;GG L! AJ :L! 
LA=LL! LL! !%Mr   r   r   c                     t          |          }	 |                                }|j        |j        f}n# t          $ r Y dS w xY w j                            |          |k    rdS 	 |                                }n# t          $ r Y dS w xY wddl	}|
                    |                              d          }t          t          |          j                  }	dt          j        |	           dt          j        |           dt          j        |            fd}
 j                             |
            d	
           | j        |<   dS )zGPush a single file into the sandbox if changed. Returns True if synced.Fr   Nasciiz	mkdir -p z	 && echo z | base64 -d > c                     K   j         j                            dd           d {V } | j                                         d {V  d S )Nbash-c)r   execr   rZ   )proccmdrN   s    r   _writez6ModalEnvironment._push_file_to_sandbox.<locals>._write.  s]      +//cBBBBBBBBD)--//!!!!!!!!!r      rU   T)r   statst_mtimest_sizeOSErrorr   r*   
read_bytesr   base64	b64encodedecoder,   r   shlexquoter   ri   )rN   r   r   hpr   file_keycontentr   b64container_dirr   r   s   `          @r   _push_file_to_sandboxz&ModalEnvironment._push_file_to_sandbox  s   )__	7799Dt|4HH 	 	 	55	 !!.11X==5	mmooGG 	 	 	55	 	w''..w77D00788SM22 S SK$$S S5:[5P5PS S 	
	" 	" 	" 	" 	" 	" 	""6688R"888-5>*ts!   "6 
AA(A= =
B
Bc                    	 ddl m}m}  |            D ]E}|                     |d         |d                   r!t                              d|d                    F |            D ]E}|                     |d         |d                   r!t                              d|d                    FdS # t          $ r&}t                              d|           Y d}~dS d}~ww xY w)	u   Push credential files and skill files into the running sandbox.

        Runs before each command. Uses mtime+size caching so only changed
        files are pushed (~13μs overhead in the no-op case).
        r   r   r   r   zModal: synced credential %szModal: synced skill file %szModal: file sync failed: %sN)r   r   r   r   r   r   r   )rN   r   r   r   r   s        r   _sync_fileszModalEnvironment._sync_files6  s'   	;\\\\\\\\3355 Y Y--eK.@%HXBYZZ YLL!>FV@WXXX**,, Y Y--eK.@%HXBYZZ YLL!>FV@WXXXY Y  	; 	; 	;LL6:::::::::	;s   B(B, ,
C6CCc                 .    |                                   d S rG   )r   rM   s    r   _before_executez ModalEnvironment._before_executeI  s    r   preparedc                      dt          j        j                   dj         d d d fd}t	          j        |d          }|                                 t          t          |                    S )	Nzcd z && )r8   errorc                      	 fd} j                              |             j        dz             d<   d S # t          $ r}|d<   Y d }~d S d }~ww xY w)Nc                  ,  K   j         j                            ddj                   d {V } | j        j                                         d {V }| j        j                                         d {V }| j                                         d {V }t          |t                    r|
                    dd          }t          |t                    r|
                    dd          }|}|r|r| d| n|}                    ||          S )Nr   r   rU   zutf-8replace)errors
)r   r   r   rV   stdoutreadstderrrZ   r+   bytesr   _result)processr   r   	exit_codeoutputfull_commandr   rN   s        r   _do_executezEModalEnvironment._start_modal_exec.<locals>._run.<locals>._do_executeR  sY     $(M$6$:$:$ ( 0	 %; % %      G $+>#6#:#:#<#<<<<<<<F#*>#6#:#:#<#<<<<<<<F&-l&6&6&8&8 8 8 8 8 8 8I!&%00 J!'wy!I!I!&%00 J!'wy!I!I#F M:@!LF!6!6f!6!6!6f<<	:::r   rT   rU   r8   r   )r   ri   rV   r   )r   r   r   r   rw   rN   s     r   _runz0ModalEnvironment._start_modal_exec.<locals>._runP  s    +; ; ; ; ; ; ;& *.)C)CKMM$,r1 *D * *g&&&  + + +)*g&&&&&&&+s   6; 
AAATrQ   )rv   rw   )handle)	r   r   r   commandrJ   rW   rY   r
   ru   )rN   r   r   tr   rw   s   ``  @@r   _start_modal_execz"ModalEnvironment._start_modal_execL  s    NU[66NNH<LNN"&66	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+8 D666				%;1Ta%b%b%bccccr   r   c                     |j                                         rd S |j        d         r#|                     d|j        d                    S |j        d         S )Nr   zModal execution error: r8   )rv   is_aliverw   _error_resultrN   r   s     r   _poll_modal_execz!ModalEnvironment._poll_modal_execp  sb    =!!## 	4( 	a%%&_@TU\@]&_&_```#G,,r   c                 v    | j                             | j        j                                        d           d S )Nr   rU   )r   ri   r   	terminater   r   s     r   _cancel_modal_execz#ModalEnvironment._cancel_modal_execw  sB    ""M#'')) 	# 	
 	
 	
 	
 	
r   c                      j         dS  j        r	  fd}	  j                             |            d          }n# t          $ r d}Y nw xY w|r>t           j        |           t                              d|dd          j                   n2# t          $ r%}t          	                    d|           Y d}~nd}~ww xY w	  j                             j         j
                                        d           n# t          $ r Y nw xY w j                                         d _         d _        dS #  j                                         d _         d _        w xY w)	z>Snapshot the filesystem (if persistent) then stop the sandbox.Nc                  ^   K   j         j                                         d {V } | j        S rG   )r   snapshot_filesystemr   	object_id)imgrN   s    r   	_snapshotz+ModalEnvironment.cleanup.<locals>._snapshot  s7       $ A E E G GGGGGGGC=(r   r}   rU   z/Modal: saved filesystem snapshot %s for task %sr   z%Modal: filesystem snapshot failed: %sr   )r   r   r   ri   r   r4   r   r   r   r   r   r   rn   r   )rN   r   r/   r   s   `   r   cleanupzModalEnvironment.cleanup}  s   = F 	KK) ) ) ) )'"&,"<"<YY[[RT"<"U"UKK  ' ' '"&KKK'  *4=+FFFKKI#CRC(  
  K K KFJJJJJJJJK
	L&&'++-- '      	 	 	D	 L DMDIII L DMDIsb   B $> B A
B AAB 
C B;;C 8C= <D6 =
D
D6 	D

D6 6)E)r|   r}   NTr~   )r   N)rp   rq   rr   rs   _stdin_mode_poll_interval_seconds_interrupt_output_unexpected_error_prefixr,   r   r   r   r   boolrO   r   r   r   r   r
   r   ru   r   r   r   r   __classcell__)r   s   @r   rz   rz      s       ;;K J6
 9=&* xG xGxG xG 	xG
 'tCH~6xG  $xG xG xG xG xG xG xGts C D    B; ; ; ;&   "d*; "d "d "d "d "dH-'= -$+ - - - -
)? 
D 
 
 
 
$ $ $ $ $ $ $r   rz   rG   )&rs   r\   r   loggingr   rJ   dataclassesr   pathlibr   typingr   r   r   hermes_constantsr   tools.environments.modal_commonr	   r
   r   	getLoggerrp   r   r   r'   r,   r   r#   r(   tupler  r1   r4   r9   rC   rE   ru   rz   r   r   r   <module>r     s            ! ! ! ! ! !       & & & & & & & & & & , , , , , ,          
	8	$	$!/##&<<% c3h    ;$sCx. ;T ; ; ; ;5# 5# 5 5 5 5S U3:t;K5L     C c d    # #S #sTz #T # # # #"S S    &* * * * * * * *@ " " " " " " " "
N N N N N4 N N N N Nr   