
    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d0d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d1d"ed	e	d
e	fd#Zd2d$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!d3d*Z"d+ed
efd,Z#d+ed-ed
efd.Z$dS )4z*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-|                                                                 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      s`    }% % 7{{}}""$$66;;     namec                 J    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!    29T733UCCCCr   pathz
int | Nonec                     	 |                                  r+t          j        |                                 j                  ndS # t          $ r Y dS 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'   $   sV    48KKMMKt|DIIKK/000tK   tts   A A 
AAmodec                 \    |dS 	 t          j        | |           dS # t          $ r Y dS 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+   ,   sM     |
t   s    
++tmp_pathtargetc                     t          |          }t          j                            |          rt          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_replacer4   =   s[    $ VJ02z0J0JZ  ,,,PZIJs8}}i(((r      )indentdatar6   dump_kwargsc                   t          |           } | j                            dd           t          |           }t	          j        t          | j                  d| j         dd          \  }}	 t          j	        |dd	          5 }t          j        ||f|d
d| |                                 t          j        |                                           ddd           n# 1 swxY w Y   t          ||           }t!          ||           dS # t"          $ r( 	 t          j        |           n# t&          $ r Y n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)r6   ensure_asciiN)r   parentmkdirr'   tempfilemkstempr   stemr   fdopenjsondumpflushfsyncfilenor4   r+   BaseExceptionunlinkr&   )	r    r7   r6   r8   original_modefdr,   fr3   s	            r   atomic_json_writerY   U   s   ( ::DKdT222'--M#49  LB
Yr3111 		!QI "	 
    GGIIIHQXXZZ   		! 		! 		! 		! 		! 		! 		! 		! 		! 		! 		! 		! 		! 		! 		! #8T22	9m44444   	Ih 	 	 	D	sU   1D AC%D %C))D ,C)-#D 
ED21E2
D?<E>D??E)default_flow_style	sort_keysextra_contentrZ   r[   r\   c                   t          |           } | j                            dd           t          |           }t	          j        t          | j                  d| j         dd          \  }}	 t          j	        |dd	          5 }t          j        ||||
           |r|                    |           |                                 t          j        |                                           ddd           n# 1 swxY w Y   t!          ||           }	t#          |	|           dS # t$          $ r( 	 t          j        |           n# t(          $ r Y n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).
    Tr:   r=   r>   r?   r@   rD   rE   rF   )rZ   r[   N)r   rI   rJ   r'   rK   rL   r   rM   r   rN   yamlrP   writerQ   rR   rS   r4   r+   rT   rU   r&   )
r    r7   rZ   r[   r\   rV   rW   r,   rX   r3   s
             r   atomic_yaml_writer`      s   , ::DKdT222'--M#49  LB
Yr3111 	!QIdA2DPYZZZZ '&&&GGIIIHQXXZZ   	! 	! 	! 	! 	! 	! 	! 	! 	! 	! 	! 	! 	! 	! 	! #8T22	9m44444   	Ih 	 	 	D	sU   1D+ A*C>2D+ >DD+ D#D+ +
E6E
E
EEEEtextc                 t    	 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.
    )rO   loadsJSONDecodeError	TypeError
ValueError)ra   r   s     r   safe_json_loadsrg      sE    z$ )Z8   s    77keyc                     t          j        | d                                          }|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   r   intrf   re   )rh   r   raws      r   env_intrl      se    
)C


"
"
$
$C 3xx	"   s   < AAc                 J    t          t          j        | d          |          S )z*Read an environment variable as a boolean.r   r   r   )rh   r   s     r   env_boolrn      s!    29S"--w????r   )HTTPS_PROXY
HTTP_PROXY	ALL_PROXYhttps_proxy
http_proxy	all_proxy	proxy_urlc                     t          | pd                                          }|sdS |                                                    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)ru   	candidates     r   normalize_proxy_urlrz      ss     IO$$**,,I t##J// 989S__%5%56888r   c                      t           D ]=} t          j        | d          }t          |          }|r||k    r|t          j        | <   >dS )zARewrite supported proxy env vars to canonical URL forms in-place.r   N)_PROXY_ENV_KEYSr   r   rz   environ)rh   r   
normalizeds      r   normalize_proxy_env_varsr      sW     ) )	#r""(//
 	)*--(BJsO	) )r   base_urlc                     | pd                                 }|sdS t          d|v r|nd|           }|j        pd                                                    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   rk   parseds      r   base_url_hostnamer     sl     >r
 
 
"
"C rUc\\cczCzz::FO!r((**11#666r   domainc                     t          |           }|sdS |pd                                                                                    d          }|sdS ||k    p|                    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     s|     !**H ul!!##))++22377F uv@!2!23<!@!@@r   )F)r   )N)r   )r   N)%__doc__rO   loggingr   r#   rK   pathlibr   typingr   r   urllib.parser   r^   	getLogger__name__logger	frozensetr   r   r   r   r   r'   r+   r4   rj   rY   r`   rg   rl   rn   r|   rz   r   r   r    r   r   <module>r      s   0 0   				                 ! ! ! ! ! ! 		8	$	$ 55566 3  $    D D# D DT D D D D
d |    T  $    "U39- uS$Y7G C    8 	3 3 3
T	
3
3 	3
 3 
3 3 3 3t  % $1 1 1
T	
1
1 	1
 1 :1 
1 1 1 1n
 
# 
 
s 
 
 
 
   s 3    @ @# @ @ @ @ @ @3: #*    ) ) ) )7 7 7 7 7 7"AC A A A A A A A Ar   