
    
Bj1                     &   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	m
Z
 ddlmZ ddlZ ej                  e      Z eh d      Zd0de	d	ed
efdZd1ded	ed
efdZded
dfdZdeddd
dfdZde
eef   de
eef   d
efdZddde
eef   de	dede	d
df
dZddddde
eef   de	dedededz  d
dfd Zde
eef   d!ede	d
dfd"Zd2d#ed	e	d
e	fd$Zd3d%ed	ed
efd&Zd0d%ed	ed
efd'Z d(Z!d)edz  d
edz  fd*Z"d4d+Z#d,ed
efd-Z$d,ed.ed
efd/Z%y)5z*Shared utility functions for hermes-agent.    N)Path)AnyUnion)urlparse>   1onyestrueFvaluedefaultreturnc                     | |S t        | t              r| S t        | t              r$| j                         j	                         t
        v S t        |       S )zDCoerce bool-ish values using the project's shared truthy string set.)
isinstanceboolstrstriplowerTRUTHY_STRINGS)r   r   s     */home/ubuntu/.hermes/hermes-agent/utils.pyis_truthy_valuer      sI    }%%{{}""$66;    namec                 D    t        t        j                  | |      d      S )zBReturn True when an environment variable is set to a truthy value.Fr   r   osgetenv)r   r   s     r   env_var_enabledr      s    299T73UCCr   pathz
int | Nonec                     	 | j                         r-t        j                  | j                         j                        S dS # t        $ r Y yw xY w)zBCapture the permission bits of *path* if it exists, else ``None``.N)existsstatS_IMODEst_modeOSError)r   s    r   _preserve_file_moder&   $   sA    48KKMt||DIIK//0KtK s   <A A 	AAmodec                 V    |y	 t        j                  | |       y# t        $ r Y yw xY w)a  Re-apply *mode* to *path* after an atomic replace.

    ``tempfile.mkstemp`` creates files with 0o600 (owner-only).  After
    ``os.replace`` swaps the temp file into place the target inherits
    those restrictive permissions, breaking Docker / NAS volume mounts
    that rely on broader permissions set by the user.  Calling this
    right after ``os.replace`` restores the original permissions.
    N)r   chmodr%   )r   r'   s     r   _restore_file_moder*   ,   s1     |
t s    	((tmp_pathtargetc                     t        |      }t        j                  j                  |      rt        j                  j	                  |      n|}t        j
                  t        |       |       |S )u9  Atomically move *tmp_path* onto *target*, preserving symlinks.

    ``os.replace(tmp, target)`` atomically swaps ``tmp`` into place at
    ``target``.  When ``target`` is a symlink, the symlink itself is
    replaced with a regular file — silently detaching managed deployments
    that symlink ``config.yaml`` / ``SOUL.md`` / ``auth.json`` etc. from
    ``~/.hermes/`` to a git-tracked profile package or dotfiles repo
    (GitHub #16743).

    This helper resolves the symlink first so ``os.replace`` writes to
    the real file in-place while the symlink survives.  For non-symlink
    and non-existent paths the behavior is identical to a plain
    ``os.replace`` call.

    Returns the resolved real path used for the replace, so callers that
    need to re-apply permissions can target it instead of the symlink.
    )r   r   r   islinkrealpathreplace)r+   r,   
target_str	real_paths       r   atomic_replacer3   =   sK    $ VJ02z0J  ,PZIJJs8}i(r      )indentdatar5   dump_kwargsc                n   t        |       } | j                  j                  dd       t        |       }t	        j
                  t        | j                        d| j                   dd      \  }}	 t        j                  |dd	      5 }t        j                  ||f|d
d| |j                          t        j                  |j                                ddd       t        ||       }t!        ||       y# 1 sw Y   "xY w# t"        $ r' 	 t        j$                  |        # t&        $ r Y  w xY ww xY w)a  Write JSON data to a file atomically.

    Uses temp file + fsync + os.replace to ensure the target file is never
    left in a partially-written state. If the process crashes mid-write,
    the previous version of the file remains intact.

    Args:
        path: Target file path (will be created or overwritten).
        data: JSON-serializable data to write.
        indent: JSON indentation (default 2).
        **dump_kwargs: Additional keyword args forwarded to json.dump(), such
            as default=str for non-native types.
    Tparentsexist_ok._.tmpdirprefixsuffixwutf-8encodingF)r5   ensure_asciiN)r   parentmkdirr&   tempfilemkstempr   stemr   fdopenjsondumpflushfsyncfilenor3   r*   BaseExceptionunlinkr%   )	r   r6   r5   r7   original_modefdr+   fr2   s	            r   atomic_json_writerX   U   s(   ( :DKKdT2'-M##499+QLB
YYr31 		!QII "	
  GGIHHQXXZ 		! #8T2	9m4		! 		!  	IIh 	  		sI   1D 	AC8 D 8D=D 	D4D$#D4$	D0-D4/D00D4)default_flow_style	sort_keysextra_contentrY   rZ   r[   c                   t        |       } | j                  j                  dd       t        |       }t	        j
                  t        | j                        d| j                   dd      \  }}	 t        j                  |dd	      5 }t        j                  ||||
       |r|j                  |       |j                          t        j                  |j                                ddd       t!        ||       }	t#        |	|       y# 1 sw Y   "xY w# t$        $ r' 	 t        j&                  |        # t(        $ r Y  w xY ww xY w)an  Write YAML data to a file atomically.

    Uses temp file + fsync + os.replace to ensure the target file is never
    left in a partially-written state.  If the process crashes mid-write,
    the previous version of the file remains intact.

    Args:
        path: Target file path (will be created or overwritten).
        data: YAML-serializable data to write.
        default_flow_style: YAML flow style (default False).
        sort_keys: Whether to sort dict keys (default False).
        extra_content: Optional string to append after the YAML dump
            (e.g. commented-out sections for user reference).
    Tr9   r<   r=   r>   r?   rC   rD   rE   )rY   rZ   N)r   rH   rI   r&   rJ   rK   r   rL   r   rM   yamlrO   writerP   rQ   rR   r3   r*   rS   rT   r%   )
r   r6   rY   rZ   r[   rU   rV   r+   rW   r2   s
             r   atomic_yaml_writer_      s    , :DKKdT2'-M##499+QLB
YYr31 	!QIIdA2DPYZ&GGIHHQXXZ 	! #8T2	9m4	! 	!  	IIh 	  		sI   1D 	A D
) D 
DD 	E D65E6	E?EEEkey_pathc                 X   ddl m} ddlm} t	        |       } | j
                  j                  dd        |d      }d|_        d|_        d|_	        |j                  d	d
d	       | j                         r7| j                  dd      5 }|j                  |      xs  |       }ddd       n |       }t        |      s ||      }|}|j                  d      }	|	dd D ]-  }
|j!                  |
      }t        ||      s |       }|||
<   |}/ |||	d   <   t#        |       }t%        j&                  t)        | j
                        d| j*                   dd      \  }}	 t-        j.                  |dd      5 }|j1                  ||       |j3                          t-        j4                  |j7                                ddd       t9        ||       }t;        ||       y# 1 sw Y   5xY w# 1 sw Y   /xY w# t<        $ r' 	 t-        j>                  |        # t@        $ r Y  w xY ww xY w)a_  Update one dotted YAML key while preserving comments and readable text.

    This is intentionally narrower than :func:`atomic_yaml_write`: it is for
    user-edited config files where comments, ordering, quoting, and Unicode
    should survive a single setting mutation.  Writes still use the same temp
    file + fsync + atomic replace pattern.
    r   )YAML)CommentedMapTr9   rt)typFr4      )mappingsequenceoffsetrrD   rE   Nr<   r=   r>   r?   rC   )!ruamel.yamlrb   ruamel.yaml.commentsrc   r   rH   rI   preserve_quotesallow_unicoderY   r5   r!   openloadr   splitgetr&   rJ   rK   r   rL   r   rM   rO   rP   rQ   rR   r3   r*   rS   rT   r%   )r   r`   r   rb   rc   yaml_rtrW   configcurrentkeyskey
next_valuerU   rV   r+   r2   s                   r   atomic_roundtrip_yaml_updaterz      s    !1:DKKdT2tnG"G G!&GNN1qN3{{}YYsWY- 	7\\!_6F	7 	7 fl+f%G>>#DCRy [[%
*l3%J%GCL GDH'-M##499+QLB
YYr31 	!QLL#GGIHHQXXZ 	! #8T2	9m4=	7 	72	! 	!  	IIh 	  		sU   
G !G9 9AG-? G9  G*-G62G9 9	H)HH)	H%"H)$H%%H)textc                 z    	 t        j                  |       S # t         j                  t        t        f$ r |cY S w xY w)zParse JSON, returning *default* on any parse error.

    Replaces the ``try: json.loads(x) except (JSONDecodeError, TypeError)``
    pattern duplicated across display.py, anthropic_adapter.py,
    auxiliary_client.py, and others.
    )rN   loadsJSONDecodeError	TypeError
ValueError)r{   r   s     r   safe_json_loadsr     s7    zz$  )Z8 s     ::rx   c                     t        j                  | d      j                         }|s|S 	 t        |      S # t        t
        f$ r |cY S w xY w)z:Read an environment variable as an integer, with fallback. )r   r   r   intr   r   )rx   r   raws      r   env_intr     sJ    
))C

"
"
$C3x	" s   
5 A	A	c                 D    t        t        j                  | d      |      S )z*Read an environment variable as a boolean.r   r   r   )rx   r   s     r   env_boolr     s    299S"-w??r   )HTTPS_PROXY
HTTP_PROXY	ALL_PROXYhttps_proxy
http_proxy	all_proxy	proxy_urlc                     t        | xs d      j                         }|sy|j                         j                  d      rd|t	        d      d  S |S )zNormalize proxy URLs for httpx/aiohttp compatibility.

    WSL/Clash-style environments often export SOCKS proxies as
    ``socks://127.0.0.1:PORT``. httpx rejects that alias and expects the
    explicit ``socks5://`` scheme instead.
    r   Nzsocks://z	socks5://)r   r   r   
startswithlen)r   	candidates     r   normalize_proxy_urlr   +  sV     IO$**,I##J/9S_%56788r   c                      t         D ]?  } t        j                  | d      }t        |      }|s'||k7  s-|t        j                  | <   A y)zARewrite supported proxy env vars to canonical URL forms in-place.r   N)_PROXY_ENV_KEYSr   r   r   environ)rx   r   
normalizeds      r   normalize_proxy_env_varsr   :  sB     )		#r"(/
*-(BJJsO	)r   base_urlc                     | xs dj                         }|syt        d|v r|nd|       }|j                  xs dj                         j	                  d      S )a  Return the lowercased hostname for a base URL, or ``""`` if absent.

    Use exact-hostname comparisons against known provider hosts
    (``api.openai.com``, ``api.x.ai``, ``api.anthropic.com``) instead of
    substring matches on the raw URL. Substring checks treat attacker- or
    proxy-controlled paths/hosts like ``https://api.openai.com.example/v1``
    or ``https://proxy.test/api.openai.com/v1`` as native endpoints, which
    leads to wrong api_mode / auth routing.
    r   z://z//r<   )r   r   hostnamer   rstrip)r   r   parseds      r   base_url_hostnamer   F  sW     >r
 
 
"CUc\cC5z:FOO!r((*11#66r   domainc                     t        |       }|sy|xs dj                         j                         j                  d      }|sy||k(  xs |j	                  d|z         S )ac  Return True when the base URL's hostname is ``domain`` or a subdomain.

    Safer counterpart to ``domain in base_url``, which is the substring
    false-positive class documented on ``base_url_hostname``. Accepts bare
    hosts, full URLs, and URLs with paths.

        base_url_host_matches("https://api.moonshot.ai/v1", "moonshot.ai") == True
        base_url_host_matches("https://moonshot.ai", "moonshot.ai")        == True
        base_url_host_matches("https://evil.com/moonshot.ai/v1", "moonshot.ai") == False
        base_url_host_matches("https://moonshot.ai.evil/v1", "moonshot.ai")     == False
    Fr   r<   )r   r   r   r   endswith)r   r   r   s      r   base_url_host_matchesr   W  s_     !*Hl!!#))+2237Fv@!2!23<!@@r   )F)r   )N)r   )r   N)&__doc__rN   loggingr   r"   rJ   pathlibr   typingr   r   urllib.parser   r]   	getLogger__name__logger	frozensetr   r   r   r   r   r&   r*   r3   r   rX   r_   rz   r   r   r   r   r   r   r   r    r   r   <module>r      sH   0   	     ! 			8	$ 563  $ D# D DT D
d | T  $ "U39- uS$Y7G C 8 	3
T	
3
3 	3
 3 
3t  % $1
T	
1
1 	1
 1 :1 
1h=
T	
== = 
	=F
# 
 
s 
  s 3 @# @ @ @3: #* )7 7 7"AC A A Ar   