Ë
    âß²jßN  ã                  ó¨   — d Z ddlmZ ddlZddlmZmZ ddlmZm	Z	m
Z
 erddlmZ  ej                  e«      Z e«       Zd
d„Ze G d„ d	«      «       Zy)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)ÚTYPE_CHECKINGÚAnyÚCallable)ÚAccountUsageSnapshotc                 ó8   — 	 ddl m}  d| › �S # t        $ r Y y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    ú3/home/ubuntu/.hermes/hermes-agent/providers/base.pyÚ_profile_user_agentr      s)   € ðÝ2Ø˜T˜FÐ#Ð#øÜò Ùðú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<   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+<    ee¬(«      Z d,ed-<   dZ!d.ed/<   dZ"d0ed1<   dZ#ded2<   d	Z$ded3<    ee¬(«      Z%d4ed5<   ddd6œ	 	 	 	 	 dMd7„Z&dd8œdNd9„Z'dOd:„Z(dPd;„Z)dd<œ	 	 	 	 	 dQd=„Z*dRdSd>„Z+dd?œ	 	 	 	 	 dTd@„Z,dUdA„Z-dVdB„Z.dWdC„Z/dXdD„Z0dYdE„Z1	 	 	 	 dZdF„Z2d[dG„Z3d\dH„Z4d]dI„Z5dddJdKœ	 	 	 	 	 	 	 d^dL„Z6y)_ÚProviderProfileuA   Base provider profile â€” subclass or instantiate with overrides.ÚstrÚ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_checkÚsupports_model_listingNz Callable[[str, Any], Any] | NoneÚauth_handlerzCallable[[Any], Any] | NoneÚrefresh_credentialzCallable[..., Any] | NoneÚclassify_api_errorFÚsupports_visionÚsupports_vision_tool_messagesÚsupports_prompt_cache_keyú
str | NoneÚnative_reasoning_details_typeÚprocess_commandÚprocess_argsÚprocess_command_env_varsÚprocess_args_env_varÚfallback_models)Údefault_factoryÚdictÚmodel_aliasesÚhostnamezdict[str, str]Údefault_headersr   Úfixed_temperatureú
int | NoneÚdefault_max_tokensÚunsupported_response_formatsÚdefault_aux_modelzdict[str, dict[str, Any]]Úmodel_capabilities)r   r!   c                ó   — y)ao  Return an account-usage snapshot for this provider, if available.

        The ``/usage`` command invokes this only when no built-in account
        usage fetcher owns the provider. Implementations may make their
        provider-specific request and must return an ``AccountUsageSnapshot``
        or ``None``; exceptions fail open at the dispatch boundary.
        Nr   )Úselfr   r!   s      r   Úfetch_account_usagez#ProviderProfile.fetch_account_usage”   s   € ð ó    )Úvisionc                ó   — y)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   )r?   rB   s     r   Úresolve_aux_modelz!ProviderProfile.resolve_aux_model    s   € ð rA   c                ó˜   — | j                   r| j                   S | j                  r&ddlm}  || j                  «      j                   xs dS y)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   )r6   r   Úurllib.parserF   )r?   rF   s     r   Úget_hostnamezProviderProfile.get_hostname°   s<   € ð �=Š=Ø—=‘=Ð Ø�=Š=Ý-Ù˜DŸM™MÓ*×3Ñ3Ò9°rÐ9ØrA   c                ó   — |S )zœProvider-specific message preprocessing.

        Called AFTER codex field sanitization, BEFORE developer role swap.
        Default: pass-through.
        r   )r?   Úmessagess     r   Úprepare_messagesz ProviderProfile.prepare_messages½   s	   € ð ˆrA   )Ú
session_idc               ó   — i S )zrProvider-specific extra_body fields.

        Merged into the API kwargs extra_body. Default: empty dict.
        r   )r?   rL   Úcontexts      r   Úbuild_extra_bodyz ProviderProfile.build_extra_bodyÅ   s	   € ð ˆ	rA   c                 ó   — y)aê  Reasoning config the main loop sends when ``agent.reasoning_effort`` is unset.

        None (default) hands the unset state to ``build_api_kwargs_extras`` as ``reasoning_config=None``,
        where each profile already decides (Nous/OpenRouter fill medium; Anthropic omits). A profile
        that would otherwise leave the route's own default in charge returns the config here so the
        agent records it as what went on the wire (the reasoning-rejection ladder reads that).
        Nr   ©r?   Úmodels     r   Údefault_reasoning_configz(ProviderProfile.default_reasoning_configÎ   s   € ð rA   )Úreasoning_configc               ó
   — 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   )r?   rT   rN   s      r   Úbuild_api_kwargs_extrasz'ProviderProfile.build_api_kwargs_extrasØ   s   € ð$ �2ˆvˆrA   c                ó   — i S )zÖProvider-specific OpenAI client keyword arguments.

        Values are defaults: explicit runtime/custom-provider settings win.
        The returned mapping must be cheap to build and must not perform I/O.
        r   )r?   rN   s     r   Úbuild_client_kwargs_extrasz*ProviderProfile.build_client_kwargs_extrasì   s	   € ð ˆ	rA   c                 ó   — y)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   )r?   s    r   Údefault_vision_modelz$ProviderProfile.default_vision_modelô   s   € ð rA   c                 ó   — y)zJProvider-qualified context bound; explicit user overrides take precedence.Nr   rQ   s     r   Úget_model_context_lengthz(ProviderProfile.get_model_context_length  ó   € àrA   c                 ó   — y)zNOptional CostResult from canonical usage; distinguish estimates from invoices.Nr   )r?   rR   Úusages      r   Úget_usage_costzProviderProfile.get_usage_cost  r]   rA   c                ó   — | 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:   rQ   s     r   Úget_max_tokenszProviderProfile.get_max_tokens	  s   € ð ×&Ñ&Ð&rA   c                 ó   — y)u/  Declared reasoning-effort vocabulary for *model* on this provider.

        Overrideable hook for providers whose gateway validates
        ``reasoning.effort`` per model instead of ignoring or clamping
        unknown levels server-side (Ramp Router derives this from its live
        ``/v1/models`` catalog). The Responses transport consults it before
        falling back to its built-in per-backend vocabularies; it is the
        profile-declared analog of the OpenRouter catalog clamp on the
        chat-completions path (``openrouter_model_reasoning_capabilities``).

        Tri-state contract:
          - ``None`` â€” unknown/undeclared: the transport keeps its default
            vocabulary for the wire (this base implementation).
          - ``()`` â€” the model accepts NO reasoning parameters at all; the
            transport must omit reasoning fields entirely (some gateways
            return HTTP 400 rather than ignoring them).
          - non-empty tuple â€” clamp the requested effort onto these levels
            (``agent.reasoning_effort.clamp_effort`` semantics: nearest
            weaker supported level, never escalate).

        Implementations are called on the per-request hot path and must not
        block on network I/O â€” answer from a cache and return None while
        cold.
        Nr   rQ   s     r   Úsupported_reasoning_effortsz+ProviderProfile.supported_reasoning_efforts  s   € ð6 rA   c                 ó   — y)uÊ  Return a provider-specific client, or ``None`` for the standard one.

        Most providers speak OpenAI-compatible HTTP and want the shared
        ``openai.OpenAI`` client the core builds â€” they inherit this and return
        ``None``. A provider whose wire protocol is not HTTP at all (the ACP
        subprocess shims) or which needs a native SDK overrides this and
        returns its own client object.

        ``client_kwargs`` is the same mapping the core would have passed to
        ``openai.OpenAI`` (``api_key``, ``base_url``, ``command``, ``args``,
        timeouts, headersâ€¦). Unknown keys must be tolerated: the core adds to
        this mapping over time, so an override should accept ``**kwargs`` and
        pick what it needs rather than enumerate.

        Returning ``None`` (the default) is always safe â€” the caller falls
        through to its existing construction path.

        This is the hook that lets a provider ship *outside* this tree: with it,
        a profile registered from ``~/.hermes/plugins/model-providers/`` or a
        pip entry point can supply its own transport without any core edit. See
        ``plugins/model-providers/copilot-acp/`` for the in-tree example.
        Nr   )r?   Úclient_kwargss     r   Úcreate_clientzProviderProfile.create_client4  s   € ð. rA   c                 ó   — y)zÄExternal-process providers: ``{available, logged_in, plan, detail, login_command}`` from the
        CLI itself so setup can gate on login. ``None`` = nothing to report beyond executable presence.Nr   ©r?   Úkwargss     r   Úsetup_statuszProviderProfile.setup_statusM  ó   € ð rA   c                 ó   — y)z£External-process providers: the account's live picker as ``[{id, label, note}]`` without
        any inference request; ``None`` falls back to ``fallback_models``.Nr   ri   s     r   Údiscover_modelszProviderProfile.discover_modelsR  rl   rA   g       @)r!   r   Útimeoutc               óh  — | j                   sy|xs dj                  «       }|xs | j                  }t        |«      xr1 |j	                  d«      | j                  xs dj	                  d«      k7  }|r|j	                  d«      dz   }n7| j
                  xs dj                  «       }|s|sy|j	                  d«      dz   }ddl}ddl}	ddlm	}
 |	j                  j                  |«      }|r|j                  dd|› �«       |j                  d	d
«       |j                  dt        «       «       | j                  j                  «       D ]  \  }}|j                  ||«       Œ 	  |
||¬«      5 }|j!                  |j#                  «       j%                  «       «      }ddd«       t'        t(        «      r|n|j+                  dg «      }|D �cg c]  }t'        |t,        «      sŒd|v sŒ|d   ‘Œ c}S # 1 sw Y   ŒVxY wc c}w # t.        $ r+}t0        j3                  d| j4                  |«       Y d}~y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.
        Nr   ú/z/modelsr   )Úopen_credentialed_urlÚAuthorizationzBearer ÚAcceptzapplication/jsonz
User-Agent)ro   ÚdataÚidzfetch_models(%s): %s)r%   Ústripr   r#   Úrstripr    ÚjsonÚurllib.requestÚhermes_cli.urllib_securityrr   ÚrequestÚRequestÚ
add_headerr   r7   ÚitemsÚloadsÚreadÚdecodeÚ
isinstanceÚlistÚgetr4   r   ÚloggerÚdebugr   )r?   r!   r   ro   Úcaller_baseÚeffective_baseÚcustom_baseÚurlry   Úurllibrr   ÚreqÚkÚvÚrespru   r   ÚmÚexcs                      r   Úfetch_modelszProviderProfile.fetch_modelsW  sð  € ðB ×*Ò*ØØ’~ 2×,Ñ,Ó.ˆØ$Ò5¨¯©ˆÜ˜;Ó'ò 
Ø×Ñ˜sÓ#¨¯©Ò(;¸×'CÑ'CÀCÓ'HÑHð 	ñ Ø×$Ñ$ SÓ)¨IÑ5‰Cà—?‘?Ò( b×/Ñ/Ó1ˆCÙÙ%ØØ$×+Ñ+¨CÓ0°9Ñ<�ãÛåDà�n‰n×$Ñ$ SÓ)ˆÙØ�N‰N˜?¨g°g°YÐ,?Ô@Ø�‰�xÐ!3Ô4ð 	�‰�|Ô%8Ó%:Ô;Ø×(Ñ(×.Ñ.Ó0ò 	!‰DˆAˆqØ�N‰N˜1˜aÕ ð	!ð	Ù& s°GÔ<ð 8ÀØ—z‘z $§)¡)£+×"4Ñ"4Ó"6Ó7�÷8ä& t¬TÔ2‘D¸¿¹ÀÈÓ8LˆEØ%*ÖP ¬j¸¼DÕ.AÀdÈaÂi�A�d“GÒPÐP÷8ð 8üò QøÜò 	Ü�L‰LÐ/°·±¸CÔ@Üûð	úsH   Å
G= Å).G,Æ0G= ÇG8ÇG8Ç"G8Ç)G= Ç,G5Ç1G= Ç=	H1È!H,È,H1)r   r,   r!   r,   ÚreturnzAccountUsageSnapshot | None)rB   r#   r”   r   ©r”   r   )rJ   úlist[dict[str, Any]]r”   r–   )rL   r,   rN   r   r”   údict[str, Any])N)rR   r,   r”   údict | None)rT   r˜   rN   r   r”   z%tuple[dict[str, Any], dict[str, Any]])rN   r   r”   r—   )r”   r,   )rR   r   r”   r9   )rR   r   r_   r   r”   ú
Any | None)rR   r,   r”   r9   )rR   r,   r”   ztuple[str, ...] | None)rf   r   r”   r™   )rj   r   r”   zdict[str, Any] | None)rj   r   r”   zlist[dict[str, Any]] | None)r!   r,   r   r,   ro   Úfloatr”   zlist[str] | None)7Ú__name__Ú
__module__Ú__qualname__Ú__doc__Ú__annotations__r   r   r   r   r   r   r   r    r"   r$   r%   r&   r'   r(   r)   r*   r+   r-   r.   r/   r0   r1   r2   r   r4   r5   r6   r7   r8   r:   r;   r<   r=   r@   rD   rH   rK   rO   rS   rV   rX   rZ   r\   r`   rb   rd   rg   rk   rn   r“   r   rA   r   r   r   )   s„  … áKð ƒIØ&€HˆcÓ&Ø€GˆUÓð €L�#ÓØ€K�ÓØ€J�Óð €HˆeÓØ€HˆcÓØ€J�ÓØ€IˆsÓØ"&Ð˜4Ó&à#'Ð˜DÓ'ð 6:€LÐ2Ó9Ø6:ÐÐ3Ó:Ø48ÐÐ1Ó8ð "€O�TÓ!ð +/Ð! 4Ó.ð ',Ð˜tÓ+ð
 15Ð! :Ó4ð €O�SÓØ€L�%ÓØ&(Ð˜eÓ(Ø "Ð˜#Ó"ð
  €O�UÓñ  °Ô5€M�4Ó5ð €HˆcÓñ ',¸DÔ&A€O�^ÓAð "Ð�sÓ!Ø%)Ð˜
Ó)à*,Ð  %Ó,à
ð �só ñ 5:È$Ô4OÐÐ1ÓOð
 )-ÀDñ
Ø%ð
Ø7Að
à	$ó
ð 38õ ó óð +/ñØ'ðØ;>ðà	óôð )-ñð &ðð ð	ð
 
/óó(óóóó'ðØðà	óó:ó2ó
ð #Ø#ØñHð ðHð ð	Hð
 ðHð 
ôHrA   r   r•   )rž   Ú
__future__r   ÚloggingÚdataclassesr   r   Útypingr   r   r   Úagent.account_usager	   Ú	getLoggerr›   r†   ÚobjectÚOMIT_TEMPERATUREr   r   r   rA   r   ú<module>r¨      s^   ðñ	õ #ã ß (ß /Ñ /áÝ8à	ˆ×	Ñ	˜8Ó	$€ñ “8Ð óð ÷uð uó ñurA   