
    sj:                        d Z ddlmZ ddlZddlZddlZddlZddlmZm	Z	m
Z
 dZdZd*d	Zd+d,dZd-dZd.dZd/dZd0dZd1dZd/dZd2dZd3dZd4d Zd-d!Zd5d6d#Zd$dd$d%d7d)ZdS )8z0Shared helpers for direct xAI HTTP integrations.    )annotationsN)AnyDictOptionali ' i  returnboolc                    	 ddl m}   | dd          pd                                rdS nE# t          $ r8 t          j                            dd                                          rY dS Y nw xY w	 ddlm}  |            dz  }|	                                sdS t          j        |                    d	
                    }t          |t                    r|                    d          nd}t          |t                    r|                    d          nd}t          |t                    r|                    d          nd}t          |t                    r|                    d          nd}t          |pd                                          rdS t          |t                    r|                    d          nd}t          |t                    r|                    d          nd}	t          |	t                     rU|	D ]R}
t          |
t                    st          |
                    dd          pd                                          r dS SdS # t"          $ r Y dS w xY w)u  Cheap probe — return True when xAI credentials are *likely* usable.

    Deliberately avoids :func:`resolve_xai_http_credentials` so callers in
    hot-paint paths (``hermes tools`` repaint, tool-registration scans,
    ``WebSearchProvider.is_available()``) don't incur disk locks or — in
    the OAuth path — a network token refresh. The ABC contract on
    :meth:`agent.web_search_provider.WebSearchProvider.is_available`
    explicitly forbids network calls for exactly this reason.

    Resolution order, fast-to-slow:

    1. ``XAI_API_KEY`` env var (cheapest; covers explicit-key users).
    2. ``~/.hermes/auth.json`` has a non-empty ``providers.xai-oauth.tokens.access_token``
       (single file read, no expiry check, no refresh).
    3. ``credential_pool.xai-oauth`` has any entry with a non-empty
       ``access_token`` (covers multi-account ``hermes auth add xai-oauth``
       grants that are pool-only / ``manual:device_code``).

    Returns False on any exception so a corrupted auth store can't block
    other availability scans. Truthful refresh + expiry handling happens
    in ``search()`` (or whichever caller actually makes the request).
    r   )
get_secretXAI_API_KEY Tget_hermes_homez	auth.jsonFz	utf-8-sigencoding	providersN	xai-oauthtokensaccess_tokencredential_pool)agent.secret_scoper
   stripImportErrorosenvirongethermes_constantsr   existsjsonloads	read_text
isinstancedictstrlist	Exception)r
   r   	auth_pathstorer   	xai_stater   r   r   entriesentrys              2/home/agent/.hermes/hermes-agent/tools/xai_http.pyhas_xai_credentialsr,      s   .111111
 J}b))/R6688 	4		    :>>-,,2244 	44	 	444444#O%%3	!! 	5
9...DDEE.8.E.EOEIIk***4	2<Y2M2MWIMM+...SW	,6y$,G,GQx(((T5?5M5MWvzz.111SW|!r""((** 	4 ;EUD:Q:Q[%))$5666W[ /400O,,, 	
 gt$$ 	      !%.. uyy44:;;AACC  44 u   uus5   + <A-,A-1'I C;I B?I I 
I)(I)namer#   c                    	 ddl m} n0# t          $ r# t          j                            | |          cY S w xY w ||           }||n|S )zRead ``name`` from ``~/.hermes/.env`` first, then ``os.environ``.

    Wraps :func:`hermes_cli.config.get_env_value` so tests can patch
    ``tools.xai_http.get_env_value`` to inject dotenv-only secrets into the
    xAI credential resolver.
    r   )get_env_value)hermes_cli.configr/   r   r   r   r   )r-   default_hermes_get_env_valuevalues       r+   r/   r/   O   st    -LLLLLLL - - -z~~dG,,,,,- "!$''E%5572s   	 *66c                 @    	 ddl m}  n# t          $ r d} Y nw xY wd|  S )z>Return a stable Hermes-specific User-Agent for xAI HTTP calls.r   __version__unknownzHermes-Agent/)
hermes_clir6   r%   r5   s    r+   hermes_xai_user_agentr9   _   sN     *******       (;(((s   	 Dict[str, str]c                 "    dt                      iS )u5  Default headers for OpenAI-SDK and raw HTTP clients talking to xAI.

    Replaces the OpenAI Python SDK's identifying ``User-Agent: OpenAI/Python …``
    so chat/completions and Responses traffic is attributed as Hermes Agent,
    matching the direct HTTP integrations (search, TTS, STT, image, video).
    z
User-Agent)r9        r+   hermes_xai_default_headersr>   h   s     /1122r=   section_nameDict[str, Any]c                    	 ddl m}  |            }t          |t                    r|                    |           nd}t          |t                    r|ni S # t
          $ r i cY S w xY w)z=Return a top-level Hermes config section as a dict, or empty.r   )load_configN)r0   rB   r!   r"   r   r%   )r?   rB   cfgsections       r+   _load_config_sectionrE   r   s    111111kmm+5c4+@+@J#'','''d$Wd33;ww;   			s   AA A&%A&r3   r   r1   c                    t          | t                    r| S t          | t                    r2|                                                                 }|dv rdS |dv rdS |S )N>   1onyestrueenabledT>   0noofffalsedisabledF)r!   r   r#   r   lower)r3   r1   
normalizeds      r+   _coerce_boolrS   ~   sk    % % [[]]((**
>>>4@@@5Nr=   Optional[int]c                   | dS t          | t                    rZ|                                                                 }|dv rdS |dv rdS 	 t	          |          } n# t
          $ r
 t          cY S w xY wt          | t          t          f          r,t	          |           }|dk    rdS t          |t                    S t          S )zNormalize an xAI storage TTL.

    Returns:
        int seconds for an expiring file,
        None for permanent storage (omit expires_after on the wire).
    N>   r   r1   >   rL   nonenullneverforever	permanentr   )
r!   r#   r   rQ   int
ValueError&SAFE_XAI_STORAGE_EXPIRES_AFTER_SECONDSfloatmin%MAX_XAI_STORAGE_EXPIRES_AFTER_SECONDS)r3   rR   secondss      r+   _coerce_expires_afterrb      s     }t% 	:[[]]((**
((4OOO4	:
OOEE 	: 	: 	:9999	:%#u&& Ce**a<<47ABBB11s   A A10A1c                   t          |           }t          |t                    r|                    d          nd}t          |t                    r|                    d          nd}t          |t                    r|ni }t	          |                    d          d          }t	          |                    d          d          }t          |                    d                    }|||dS )	a  Read storage settings for xAI Imagine under image_gen/video_gen config.

    Supported config shape:

        image_gen:
          xai:
            storage:
              enabled: true
              public_url: true
              expires_after: null     # omit for permanent public URLs

    The same shape is accepted under ``video_gen.xai.storage``. Storage is on
    by default so xAI returns permanent public URLs instead of short-lived CDN URLs.
    xaiNstoragerK   T
public_urlexpires_after)rK   rf   rg   )rE   r!   r"   r   rS   rb   )r?   rD   xai_sectionre   rK   rf   rg   s          r+   read_xai_imagine_storage_configri      s     #<00G(27D(A(AK'++e$$$tK,6{D,I,ISkooi(((tG#GT22:ggG7;;y11488Ggkk,77>>J)'++o*F*FGGM  &  r=   filename_prefix	extensionOptional[Dict[str, Any]]c                  t          |           }|d         sdS t          j                            t          j                  }|                    d          }t          j                    j        dd         }|                    d          pd}| d| d| d| t          |d                   d	}|d
         |d
         |d
<   |S )zAReturn an xAI ``storage_options`` payload, or None when disabled.rK   Nz%Y%m%d-%H%M%S   .bin-rf   )filenamerf   rg   )
ri   datetimenowUTCstrftimeuuiduuid4hexlstripr   )	r?   rj   rk   rC   rt   tsshortextpayloads	            r+   build_xai_storage_optionsr      s     *,
7
7Cy> t




-
-C	o	&	&BJLLRaR E


3


(5C&;;;;e;;c;;3|,-- G ?'#&#7 Nr=   c                    t          |           }|d         sdS |d         d}n|d         dz  }d|dd	|d
k    rdnd }d| d|  dS )z5User-facing notice for first xAI Imagine storage use.rK   r   rg   Nzwithout an automatic expiryiQ z
for about gz day   szMxAI Imagine storage is enabled so generated media gets a reusable public URL zK. xAI may bill for stored files and public URL hosting. Disable this with `zL.xai.storage.enabled: false` or set `expires_after` to change the retention.)ri   )r?   rC   	retentiondayss       r+   xai_storage_notice_textr      s    
),
7
7Cy> r
?#1		?#|4GGGGDAIISS2GG		:	: 	:'3	: 	: 	:r=   Optional[str]c                   t          |           }|sdS 	 ddlm}  |            dz  }|                    dd           ||  dz  }|                                rdS |                    t          j                            t          j                  	                                dz   d	
           |S # t          $ r |cY S w xY w)zBReturn the storage notice once per Hermes home, then mark it seen.Nr   r   stateT)parentsexist_ok_xai_storage_notice_seen
zutf-8r   )r   r   r   mkdirr   
write_textrs   rt   ru   	isoformatr%   )r?   noticer   
marker_dirmarkers        r+   "maybe_mark_xai_storage_notice_seenr      s    $\22F t444444$_&&0
555GGGG==?? 	4(+//==GGIIDP[bccc   s   AB2 AB2 2C Cc                     	 ddl m}   | ddt                    S # t          $ r3 t	          t          d          pd                                          cY S w xY w)u  Read the explicit ``XAI_API_KEY`` through the shared secret machinery.

    Delegates to :func:`tools.tool_backend_helpers.resolve_provider_secret`
    (config → profile secret scope → env/.env → credential pool) so the
    preferred-key path enforces the exact same scope policy as the no-OAuth
    fallback branch — including failing closed under a multiplexed gateway
    turn. Never re-implement scope policy per-caller.
    r   resolve_provider_secretr   rd   
env_getterr   )tools.tool_backend_helpersr   r/   r   r#   r   r   s    r+   _resolve_explicit_xai_api_keyr     sy    ?FFFFFF&&}eVVVV ? ? ?=//5266<<>>>>>?s    :AAhttps://api.x.ai/v1c                
   t          t          d          pt          d          pd                                                              d          }	 ddlm} |                    ||           S # t          $ r |p| cY S w xY w)ao  Base URL for the explicit-API-key path.

    Honors ``HERMES_XAI_BASE_URL`` then ``XAI_BASE_URL`` (the same override
    pair the OAuth branch reads) and pins the origin with
    :func:`hermes_cli.auth._xai_validate_inference_base_url` so a tampered
    env override can't exfiltrate the bearer; on rejection it falls back to
    the default rather than raising.
    HERMES_XAI_BASE_URLXAI_BASE_URLr   /r   Nfallback)r#   r/   r   rstriphermes_cli.authauth _xai_validate_inference_base_urlr%   )r1   overrideauth_mods      r+   _resolve_explicit_xai_base_urlr     s     +,, 	((	  eggffSkk	 
#******88G8TTT # # #"7"""#s   A1 1BBF)force_refreshapi_key_hintprefer_api_keyr   r   r   c                   |rCt          t                      pd                                          }|rd|t                      dS 	 ddlm} ddlm}  |d          }| r|                    |          n|	                                }| r||	                                }t          t          |dd          pt          |d	d                                                    }t          t          |d
d          pt          |dd          p|j                                                                      d          }	t          t          d          pt          d          pd                                                              d          }
|                    |
|	          }|rd||dS n# t          $ r Y nw xY w	 ddlm}  |ddt                    }n@# t$          $ r3 t          t          d          pd                                          }Y nw xY wt          t          d          pd                                                              d          }d||dS )u  Resolve bearer credentials for direct xAI HTTP endpoints.

    Prefers Hermes-managed xAI OAuth credentials when available, then falls back
    to ``XAI_API_KEY`` resolved via ``hermes_cli.config.get_env_value`` so keys
    stored in ``~/.hermes/.env`` (the standard Hermes location) are honored —
    not just ones already exported into ``os.environ``. This keeps direct xAI
    endpoints (images, TTS, STT, etc.) aligned with the main runtime auth model
    and preserves the regression contract from PR #17140 / #17163.

    Set ``prefer_api_key=True`` to invert that order: check the explicit
    ``XAI_API_KEY`` first and use OAuth only as the fallback. This is for
    API-metered endpoints where a subscription OAuth bearer *authorizes* but
    misbehaves — ``/v1/responses`` x_search answers in a degraded Grok
    explanatory mode with no citations (#88040), and ``/v1/tts`` returns 403
    (#87045). The key is read through
    :func:`tools.tool_backend_helpers.resolve_provider_secret` so profile
    secret scoping is identical to the fallback branch, and the base URL
    honors ``HERMES_XAI_BASE_URL`` / ``XAI_BASE_URL`` behind the same
    origin-pinning validation as the OAuth branch.

    Set ``force_refresh=True`` to perform an unconditional OAuth refresh.
    Reactive callers should also pass the rejected bearer as ``api_key_hint``
    so a freshly loaded multi-account pool refreshes the exact issuing entry,
    not whichever entry its strategy would otherwise select first.
    r   rd   )providerapi_keybase_urlr   )	load_poolNr   runtime_api_keyr   runtime_base_urlr   r   r   r   r   r   r   r   r   )r#   r   r   r   agent.credential_poolr   r   r   try_refresh_matchingselectgetattrDEFAULT_XAI_OAUTH_BASE_URLr   r/   r   r%   r   r   r   )r   r   r   explicit_keyr   r   poolr*   r   fallback_base_urloverride_base_urlr   r   r   s                 r+   resolve_xai_http_credentialsr   (  s   >  8::@bAAGGII 	!':<<  (333333******y%% D%%l333 	
  	"U] KKMMEE,d33 2unb11
 
 %'' 	  E-t44 3uj"--32
 
 %''&&++	 	
  /00 ^,,
 
 %''&&++	 	
 <<& = 
 
  	''$  	    BFFFFFF))-=YYY B B BmM228b99??AAB=00I4IJJPPRRYYZ]^^H  s%   E&F/ /
F<;F< G :HH)r   r   )N)r-   r#   )r   r#   )r   r:   )r?   r#   r   r@   )r3   r   r1   r   r   r   )r3   r   r   rT   )r?   r#   rj   r#   rk   r#   r   rl   )r?   r#   r   r#   )r?   r#   r   r   )r   )r1   r#   r   r#   )r   r   r   r   r   r   r   r:   )__doc__
__future__r   rs   r   r   rw   typingr   r   r   r`   r]   r,   r/   r9   r>   rE   rS   rb   ri   r   r   r   r   r   r   r<   r=   r+   <module>r      s   6 6 " " " " " "   				  & & & & & & & & & & ): %)9 &< < < <~3 3 3 3 3 ) ) ) )3 3 3 3	 	 	 		 	 	 	2 2 2 26   >   0   $   &? ? ? ?"# # # # #0  "& 	] ] ] ] ] ] ] ]r=   