o
    PL
j1                     @   s  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eZeh dZd@de	d	ed
efddZdAded	ed
efddZded
dfddZdeddd
dfddZde
eef de
eef d
efddZddde
eef de	ded e	d
df
d!d"Zdddd#de
eef de	d$ed%ed&edB d
dfd'd(Zde
eef d)ede	d
dfd*d+ZdBd,ed	e	d
e	fd-d.ZdCd/ed	ed
efd0d1Zd@d/ed	ed
efd2d3Z d4Z!d5edB d
edB fd6d7Z"dDd8d9Z#d:ed
efd;d<Z$d:ed=ed
efd>d?Z%dS )Ez*Shared utility functions for hermes-agent.    N)Path)AnyUnion)urlparse>   1onyestrueFvaluedefaultreturnc                 C   s<   | du r|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.N)
isinstanceboolstrstriplowerTRUTHY_STRINGS)r
   r    r   )/home/kuhnn/.hermes/hermes-agent/utils.pyis_truthy_value   s   

r    namec                 C   s   t t| |ddS )zBReturn True when an environment variable is set to a truthy value.Fr   r   osgetenv)r   r   r   r   r   env_var_enabled      r   pathz
int | Nonec                 C   s6   z|   rt|  jW S dW S  ty   Y dS w )zBCapture the permission bits of *path* if it exists, else ``None``.N)existsstatS_IMODEst_modeOSError)r   r   r   r   _preserve_file_mode$   s
    r$   modec                 C   s4   |du rdS z	t | | W dS  ty   Y dS 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%   r   r   r   _restore_file_mode,   s   	r'   tmp_pathtargetc                 C   s8   t |}tj|rtj|n|}t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_pathr   r   r   atomic_replace=   s   r/      )indentdatar1   dump_kwargsc          	      K   s   t | } | jjddd t| }tjt| jd| j ddd\}}z=tj	|ddd	 }t
j||f|d
d| |  t|  W d   n1 sNw   Y  t|| }t|| W dS  tyw   zt| W   tyv   Y  w 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)r1   ensure_asciiN)r   parentmkdirr$   tempfilemkstempr   stemr   fdopenjsondumpflushfsyncfilenor/   r'   BaseExceptionunlinkr#   )	r   r2   r1   r3   original_modefdr(   fr.   r   r   r   atomic_json_writeU   sB   

rS   )default_flow_style	sort_keysextra_contentrT   rU   rV   c          
      C   s   t | } | jjddd t| }tjt| jd| j ddd\}}z@tj	|ddd	#}t
j||||d
 |r<|| |  t|  W d   n1 sQw   Y  t|| }	t|	| W dS  tyz   zt| W   tyy   Y  w 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).
    Tr4   r7   r8   r9   r:   r>   r?   r@   )rT   rU   N)r   rC   rD   r$   rE   rF   r   rG   r   rH   yamlrJ   writerK   rL   rM   r/   r'   rN   rO   r#   )
r   r2   rT   rU   rV   rP   rQ   r(   rR   r.   r   r   r   atomic_yaml_write   s6   


rY   key_pathc                 C   s  ddl m} ddlm} t| } | jjddd |dd}d|_d|_d|_	|j
d	d
d	d |  rR| jddd}||pA| }W d   n1 sLw   Y  n| }t||s^||}|}|d}	|	dd D ]}
||
}t||s~| }|||
< |}qk|||	d < t| }tjt| jd| j ddd\}}z6tj|ddd}||| |  t|  W d   n1 sw   Y  t|| }t|| W dS  ty   zt| W   t y   Y  w 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)CommentedMapTr4   rt)typFr0      )mappingsequenceoffsetrr?   r@   Nr7   r8   r9   r:   r>   )!ruamel.yamlr[   ruamel.yaml.commentsr\   r   rC   rD   preserve_quotesallow_unicoderT   r1   r   openloadr   splitgetr$   rE   rF   r   rG   r   rH   rJ   rK   rL   rM   r/   r'   rN   rO   r#   )r   rZ   r
   r[   r\   yaml_rtrR   configcurrentkeyskey
next_valuerP   rQ   r(   r.   r   r   r   atomic_roundtrip_yaml_update   sb   






rs   textc              
   C   s,   zt | W S  t jttfy   | Y S 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.
    )rI   loadsJSONDecodeError	TypeError
ValueError)rt   r   r   r   r   safe_json_loads  s
   ry   rq   c              	   C   s>   t | d }|s|S zt|W S  ttfy   | Y S w )z:Read an environment variable as an integer, with fallback.r   )r   r   r   intrx   rw   )rq   r   rawr   r   r   env_int  s   
r|   c                 C   s   t t| d|dS )z*Read an environment variable as a boolean.r   r   r   )rq   r   r   r   r   env_bool  r   r}   )HTTPS_PROXY
HTTP_PROXY	ALL_PROXYhttps_proxy
http_proxy	all_proxy	proxy_urlc                 C   s@   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)r   	candidater   r   r   normalize_proxy_url+  s   r   c                  C   s8   t D ]} t| d}t|}|r||kr|tj| < qdS )zARewrite supported proxy env vars to canonical URL forms in-place.r   N)_PROXY_ENV_KEYSr   r   r   environ)rq   r
   
normalizedr   r   r   normalize_proxy_env_vars:  s   
r   base_urlc                 C   sB   | 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//r7   )r   r   hostnamer   rstrip)r   r{   parsedr   r   r   base_url_hostnameF  s
   
r   domainc                 C   sD   t | }|sdS |pd  d}|sdS ||kp!|d| 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   r7   )r   r   r   r   endswith)r   r   r   r   r   r   base_url_host_matchesW  s   r   )F)r   )N)r   )r   N)&__doc__rI   loggingr   r    rE   pathlibr   typingr   r   urllib.parser   rW   	getLogger__name__logger	frozensetr   r   r   r   r   r$   r'   r/   rz   rS   rY   rs   ry   r|   r}   r   r   r   r   r   r   r   r   r   <module>   s|    
&

:

4

C
