
    il)                        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d.de	d	ed
efdZd/d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d0d!ed	e	d
e	fd"Zd1d#ed	ed
efd$Zd.d#ed	ed
efd%Zd&Z d'edz  d
edz  fd(Z!d2d)Z"d*ed
efd+Z#d*ed,ed
efd-Z$y)3z*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/longshao/.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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_loadsrf      s7    zz$  )Z8 s     ::keyc                     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   intre   rd   )rg   r   raws      r   env_intrl      sJ    
))C

"
"
$C3x	" s   
5 A	A	c                 D    t        t        j                  | d      |      S )z*Read an environment variable as a boolean.ri   r   r   )rg   r   s     r   env_boolrn      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.
    ri   Nzsocks://z	socks5://)r   r   r   
startswithlen)ru   	candidates     r   normalize_proxy_urlrz      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.ri   N)_PROXY_ENV_KEYSr   r   rz   environ)rg   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.
    ri   z://z//r<   )r   r   hostnamer   rstrip)r   rk   parseds      r   base_url_hostnamer     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
    Fri   r<   )r   r   r   r   endswith)r   r   r   s      r   base_url_host_matchesr     s_     !*Hl!!#))+2237Fv@!2!23<!@@r   )F)ri   )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   rj   rX   r_   rf   rl   rn   r|   rz   r   r   r    r   r   <module>r      s   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n
# 
 
s 
  s 3 @# @ @ @3: #* )7 7 7"AC A A Ar   