
    sj-                        d Z ddlmZ ddlZddlmZmZ ddlmZ  ej	        e
          Z e            ZddZe G d	 d
                      ZdS )u  Provider profile base class.

A ProviderProfile declares everything about an inference provider in one place:
auth, endpoints, client quirks, request-time quirks. The transport reads this
instead of receiving 20+ boolean flags.

Provider profiles are DECLARATIVE — they describe the provider's behavior.
They do NOT own client construction, credential rotation, or streaming.
Those stay on AIAgent.
    )annotationsN)	dataclassfield)Anyreturnstrc                 <    	 ddl m}  d|  S # t          $ r Y dS w xY w)u  Return a ``hermes-cli/<version>`` UA string, with a stable fallback.

    Used by ``ProviderProfile.fetch_models`` so the catalog probe is not
    served the default ``Python-urllib/<ver>`` UA — some providers
    (OpenCode Zen, etc.) sit behind a WAF that returns 403 for that.
    r   )__version__zhermes-cli/z
hermes-cli)
hermes_clir
   	Exception)_vers    2/home/agent/.hermes/hermes-agent/providers/base.py_profile_user_agentr      sI    222222#T###   ||s   
 
c                     e Zd ZU dZded<   dZded<   dZded<   d	Zded
<   d	Zded<   d	Z	ded<   dZ
ded<   d	Zded<   d	Zded<   dZded<   dZded<   dZded<   dZded<   dZded<   dZded<   d	Zded<    ee          Zded<   dZded <   dZd!ed"<   d	Zded#<   dd$d@d'ZdAd(ZdBd+Zdd,dCd1Zdd2dDd6ZdEd7ZdFd9Z ddd:d;dGd?Z!dS )HProviderProfileuA   Base provider profile — subclass or instantiate with overrides.r   namechat_completionsapi_mode tuplealiases display_namedescription
signup_urlenv_varsbase_url
models_urlapi_key	auth_typeTboolsupports_health_checkFsupports_visionsupports_vision_tool_messagessupports_prompt_cache_keyfallback_modelshostname)default_factoryzdict[str, str]default_headersNr   fixed_temperature
int | Nonedefault_max_tokensdefault_aux_model)visionr.   r   c                   dS )u  Return a LIVE cheap-model id for auxiliary tasks, or "".

        ``default_aux_model`` is a hardcoded id in source, so it rots: when the
        provider retires that model every auxiliary call spends a round-trip
        404ing before the retry net catches it. Providers that publish a
        machine-readable recommendation should override this and query it, so
        the cheap tier tracks the upstream catalog instead of a constant a human
        has to remember to bump.

        Contract: cheap to call (implementations must cache — this runs on
        client-resolution paths), never raises, and returns "" when it has no
        answer so the caller falls through to ``default_aux_model``.
        r   r   )selfr.   s     r   resolve_aux_modelz!ProviderProfile.resolve_aux_modelh   s	     r    c                j    | j         r| j         S | j        rddlm}  || j                  j         pdS dS )u   Return the provider's base hostname for URL-based detection.

        Uses self.hostname if set explicitly, otherwise derives it from base_url.
        e.g. 'https://api.gmi-serving.com/v1' → 'api.gmi-serving.com'
        r   )urlparser   )r'   r   urllib.parser4   )r0   r4   s     r   get_hostnamezProviderProfile.get_hostnamex   sQ     = 	!= = 	:------8DM**39r9rr2   messageslist[dict[str, Any]]c                    |S )zProvider-specific message preprocessing.

        Called AFTER codex field sanitization, BEFORE developer role swap.
        Default: pass-through.
        r   )r0   r7   s     r   prepare_messagesz ProviderProfile.prepare_messages   s	     r2   )
session_idr;   
str | Nonecontextdict[str, Any]c                   i S )zrProvider-specific extra_body fields.

        Merged into the API kwargs extra_body. Default: empty dict.
        r   )r0   r;   r=   s      r   build_extra_bodyz ProviderProfile.build_extra_body   s	     	r2   )reasoning_configrA   dict | None%tuple[dict[str, Any], dict[str, Any]]c               
    i i fS )a  Provider-specific kwargs split between extra_body and top-level api_kwargs.

        Returns (extra_body_additions, top_level_kwargs).
        The transport merges extra_body_additions into extra_body, and
        top_level_kwargs directly into api_kwargs.

        This split exists because some providers put reasoning config in
        extra_body (OpenRouter: extra_body.reasoning) while others put it
        as top-level api_kwargs (Kimi: api_kwargs.reasoning_effort).

        Default: ({}, {}).
        r   )r0   rA   r=   s      r   build_api_kwargs_extrasz'ProviderProfile.build_api_kwargs_extras   s    $ 2vr2   c                    dS )u  Return a default vision model id for this provider, or None.

        Overrideable hook for providers that discover their vision default at
        runtime (e.g. from a live catalog) rather than pinning one in code.
        Keeps provider-specific vision discovery inside the provider's plugin
        instead of a name-check branch in shared vision resolution.

        Default: None (no provider-specific vision model — the caller falls
        back to the user's chat model or the aggregator chain).
        Nr   )r0   s    r   default_vision_modelz$ProviderProfile.default_vision_model   s	     tr2   modelc                    | j         S )u  Return the default max_tokens cap for *model*.

        Overrideable hook for providers that need per-model output caps —
        e.g. a relay that fronts several upstream backends, each with a
        different completion-token limit. The transport calls this when
        the user hasn't set an explicit max_tokens.

        Default: return self.default_max_tokens (the static profile field),
        ignoring the model name. Override in a subclass to vary the cap
        per-model.
        )r,   )r0   rH   s     r   get_max_tokenszProviderProfile.get_max_tokens   s     &&r2   g       @)r   r   timeoutrK   floatlist[str] | Nonec               n   |pd                                 }|p| j        }t          |          o2|                    d          | j        pd                    d          k    }|r|                    d          dz   }n9| j        pd                                 }|s|sdS |                    d          dz   }ddl}ddl}	ddlm}
 |	j	        
                    |          }|r|                    dd|            |                    d	d
           |                    dt                                 | j                                        D ]\  }}|                    ||           	  |
||          5 }|                    |                                                                          }ddd           n# 1 swxY w Y   t%          |t&                    r|n|                    dg           }d |D             S # t*          $ r,}t,                              d| j        |           Y d}~dS d}~ww xY w)ug  Fetch the live model list from the provider's models endpoint.

        Returns a list of model ID strings, or None if the fetch failed or
        the provider does not support live model listing.

        Resolution order for the endpoint URL:
          1. base_url + "/models", but ONLY when the caller passed a base_url
             that differs from this profile's default (a user-configured
             model.base_url pointing at a proxy/custom endpoint). Callers
             pass base_url unconditionally — falling back to the profile
             default when the user configured nothing — so equality with
             self.base_url means "not customised" and must not shadow
             models_url.
          2. self.models_url  (explicit override — use when the models
             endpoint differs from the inference base URL, e.g. OpenRouter
             exposes a public catalog at /api/v1/models while inference is
             at /api/v1)
          3. self.base_url + "/models"  (standard OpenAI-compat fallback)

        The default implementation sends Bearer auth when api_key is given
        and forwards self.default_headers. Override to customise auth, path,
        response shape, or to return None for providers with no REST catalog.

        Callers must always fall back to the static _PROVIDER_MODELS list
        when this returns None.
        r   /z/modelsNr   )open_credentialed_urlAuthorizationzBearer Acceptzapplication/jsonz
User-Agent)rK   datac                P    g | ]#}t          |t                    d |v |d          $S )id)
isinstancedict).0ms     r   
<listcomp>z0ProviderProfile.fetch_models.<locals>.<listcomp>  s0    PPPjD.A.APdaiiAdGiiir2   zfetch_models(%s): %s)stripr   r!   rstripr   jsonurllib.requesthermes_cli.urllib_securityrP   requestRequest
add_headerr   r)   itemsloadsreaddecoderV   listgetr   loggerdebugr   )r0   r   r   rK   caller_baseeffective_basecustom_baseurlr]   urllibrP   reqkvresprS   rc   excs                     r   fetch_modelszProviderProfile.fetch_models   s   B  ~2,,..$5;'' 
s##(;'C'CC'H'HH 	  	=$$S))I5CC?(b//11C =%  4$++C009<DDDDDDn$$S)) 	ANN?,?g,?,?@@@x!3444 	|%8%:%:;;;(..00 	! 	!DAqNN1a    	&&sG<<< 8zz$))++"4"4"6"6778 8 8 8 8 8 8 8 8 8 8 8 8 8 8&tT22LDD8L8LEPPUPPPP 	 	 	LL/C@@@44444	s<   'G> 4:F:.G> :F>>G> F>;G> >
H4!H//H4)r.   r!   r   r   r   r   )r7   r8   r   r8   )r;   r<   r=   r   r   r>   )rA   rB   r=   r   r   rC   )r   r<   )rH   r<   r   r+   )r   r<   r   r<   rK   rL   r   rM   )"__name__
__module____qualname____doc____annotations__r   r   r   r   r   r   r   r   r    r"   r#   r$   r%   r&   r'   r   rW   r)   r*   r,   r-   r1   r6   r:   r@   rE   rG   rJ   ru   r   r2   r   r   r   &   s        KK III&H&&&&G LKJ HHJI"&&&&& "O!!!! +/!.... ',++++
  O H ',eD&A&A&AOAAAA "!!!!%)))))
      38              +/      )-     (   ' ' ' '" ##F F F F F F F Fr2   r   rv   )rz   
__future__r   loggingdataclassesr   r   typingr   	getLoggerrw   ri   objectOMIT_TEMPERATUREr   r   r   r2   r   <module>r      s   	 	 # " " " " "  ( ( ( ( ( ( ( (      		8	$	$ 688     d d d d d d d d d dr2   