
    sj5                   $   U 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Zddl	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 ddlmZmZmZmZ erddlmZ ddlmZ dd	lmZmZ dd
l m!Z!m"Z"  ej#        e$          Z%de Z&dZ'e' dZ(dZ)g dZ*g dZ+ddddZ,ddZ-g dZ.de/d<   da0de/d<   g dZ1de/d <   da2de/d!<   dd$Z3g d%Z4d#e/d&<   g d'Z5d#e/d(<   d)Z6dd+Z7dd,Z8dd-Z9dd.Z:i d/d0gd1g d2d3g d4d5g d6d7 e3            d8 e:            d9d9gd:g d;d<g d=d>g d?d@ e:            dAg dBdCg dDdEg dFdGdHdIgdJg dKdLg dMi dNg dOdPg dMdQg dRdSdTdUgdVg dWdXdYgdZg d[d\g d]d^g d_d`g dadbg dcddg dedfg dgdhg didjg dkdlg dmdng g dog dpdqZ;dre/ds<   dt e1D             e;du<   ddzZ<dd}Z=ddZ>	 dddddZ?	 dddddZ@dZAde/d<   daBde/d<   ddddZCdZDdZEde/d<   i ZFde/d<   ddZGddZHddZI	 	 dddddZJddZKddZLdddddddZM G d de          ZNg  eNd1dd           eNddd           eNddd           eNd/dd           eNddd           eNddd           eNdQdd           eNd7dd           eNd5dd¦           eNdfddĦ           eNd8ddƦ           eNdVddȦ           eNdXddʦ           eNdAdd̦           eNd:ddΦ           eNd9ddЦ           eNdjddҦ           eNd<ddԦ           eNdddצ           eNdSdd٦           eNd@ddۦ           eNd>ddݦ           eNdCddߦ           eNdEdd           eNdGdd           eNdLdd           eNdNdd           eNdPdd           eNddd           eNdZdd           eNd\dd           eNdddd           eNd^dd           eNdbdd           eNdldd           eNdndd           eNdudd           eNddd          ZOd e/d<   d eOD             ZP	 ddlQmRZS  eS            D ]qZTeTjU        ePv reTjV        dv reTjW        peTjU        ZXeTjY        peX dZZeO[                     eNeTjU        eXeZ                     eP\                    eTjU                   rn# e]$ r Y nw xY wd eOD             Z^de^d<   d	d
dCdEgfddg dfddd@d8gfddd<gfddd7d5gfddg dfddg dfd͐dd:d9gfdZ_de/d<   d e_`                                D             Zade/d<   dސd!Zbd" Zci d#d>d$d>d%d>d&d>d'd:d(d:d)d:d*d:d+d9d,d9d-d<d.d<d/d<d0dՓd1dՓd2dՓd3dՓi d4dCdJdCd5dEd6dEd7dGd8dGd9dZd:dZd;d\d<d\d=dd>dd?d@dAd@dBd@dCdPdDdPi dEdNdFdNdGdNdHdQdIdQdJdSdKd^dLd^dMdbdNdbdOd`dPd`dQdudRdudSdudTdddUddi dVdddWdfdXdfdYdfdZdfd[dd\djd]djd^djd_dd`ddadVdbdVdcdXdddXdedXdfdXi dgdldhdldidldjdldkd@dld8d8d8dmd8dnd8dod@dpd@dqdAdrdAdsdAdtdAdddudddddvZddwZedߐddyZfdߐddzZg ehd1dh          Zid{e/d|<   dd}Zjdd~ZkddZlddZmdande/d<   daode/d<   dZpddZqddZrddZsddZtddZuddZvddZwddZxdZy	 dddddZzddZ{dddddZ|da}da~ddZddZdade/d<   dade/d<   dېdZ	 dddddZddZdddddZdadaddZddZddlmZ ddlmZ ddZ	 dddddZddddZdΐdZddZ	 dddddZddddZi Zde/d<   dZi Zde/d<   ddZddZddÄZddȄZ	 	 	 ddddʜdd΄Z	 dddddτZdېdЄZdZddӄZddddԄZddddՄZ	 dddddքZ ee^                                           eed                                          z  ddhz  Zde/d<   ddلZd dۄZddބZdېd߄ZddZddZddZdېdZddZddddZdZde/d<   i Zde/d<   i Zde/d<   i Zde/d<   dZde/d<   dZde/d<   ddZddZd	dZ	 	 	 d
ddZ	 	 	 d
ddZdd Z	 dddZddZddZdddZddddZÐddZdSdiZŐde/d	<   ddZƐddZ ehh d          Z eh            Zɐd{e/d<    ehd^dbh          Zʐd{e/d<   ddZːddZ̐ddZ͐ddZΐddZϐddZАdZѐd
e/d<   ddZҐddZӐddZԐdd ZՐdd!Z֐dېd"Z ehh d#          Zؐd{e/d$<   dd%Zِdd'Zڐdd(Zdddd)Zܐd*Zݐd+Z e            Zߐd,e/d-<    e
j                    Zddd.Zdd/Zdd0Zdd2Z e
j                    Zdd3Zd d5Zdeݐd6d!d8Zdd"d9Z	 d#ddd:d$d;Zd%d>Zd?d@d&dBZd'dCZdadDe/dE<   dade/dF<   dGade/dH<   dZ	 d(d)dIZi adJe/dK<   dGade/dL<   d*Zdd*dNZd+dOZd,dPZdd-dQZ	 	 	 d.d/dSZ	 	 	 d.d0dTZ	 	 	 d.d1dUZ G dV dWe          Z	 d2ddXd3d]Z	 	 d(d4d^Z 	 	 d(d5d_Zd(d6d`Zi dadbdcdbdddbdedbdfdgdhdgdidgdjdkdldmdndodpdbdqdodrdsdtdbdudbdvdwdxdyi dzd{d|d}d~ddddydyddwdd{dd}ddddddwdxdydd{dd}ddddZ	 	 d7d8dZdddd9dZd:dZd;dZdddd9dZdZ	d<dZ
d=dZd>dZdZdZ ehdh          ZddZddZd?dZd>dZd@dZddddAdZ	 	 	 dBdCdZ e	j        d          Z ehh d          Zd{e/d<   dZdZi Zde/d<   i Zde/d<   dZddZddddDdZ ddddEdZ!	 d#dddFdZ"dddGdZ#ddHdZ$	 d#ddddZ%d#dIdZ&	 	 	 dBdJdZ'dKdZ(dddLdZ)dddddeݐddMdZ*d*Z+ddÄZ,ddĄZ-ddŜdNdǄZ.dOdȄZ/	 	 d7dddPdɄZ0dddddʜdQd˄Z1dS (R  u   
Canonical model catalogs and lightweight validation helpers.

Add, remove, or reorder entries here — both `hermes setup` and
`hermes` provider-selection will pick up the change automatically.
    )annotationsN)get_close_matches)Path)Any
NamedTupleOptionalTYPE_CHECKING)	TypeGuard__version__)open_credentialed_url
url_origin)atomic_json_writebase_url_host_matcheszhermes-cli/zhttps://api.githubcopilot.com/modelszvscode/1.104.1)minimallowmediumhigh)r   r   r   )ssl_contextrequrllib.request.Requesttimeoutfloatc               &    t          | ||          S )z@Open catalog requests without forwarding headers across origins.)r   r   )r   )r   r   r   s      5/home/agent/.hermes/hermes-agent/hermes_cli/models.py_urlopen_model_catalog_requestr   ,   s     g;OOOO    base_urlstrc                   | sdS 	 ddl m}  ||           }|sdS ddl}|                    d          du r)|                                }d|_        |j        |_        |S |                    d          }t          |t                    r7|r5t          j                            |          r|                    |          S n# t          $ r Y dS w xY wdS )a  Build an ``ssl.SSLContext`` from a custom provider's TLS settings.

    Mirrors the httpx/requests TLS resolution so the urllib ``/models``
    discovery probe honors a provider's ``ssl_ca_cert`` / ``ssl_verify``
    instead of falling back to the process-wide ``SSL_CERT_FILE`` / certifi
    bundle. Returns None when no per-provider TLS override applies, so the
    caller keeps urllib's default policy for public/unconfigured endpoints.
    Nr   ) get_custom_provider_tls_settings
ssl_verifyFssl_ca_cert)cafile)hermes_cli.configr"   sslgetcreate_default_contextcheck_hostname	CERT_NONEverify_mode
isinstancer    ospathisfile	Exception)r   r"   tlsr'   ctxcas         r   _custom_provider_ssl_contextr5   1   s
     tFFFFFF..x88 	4


77<  E)),,..C!&C!mCOJWW]##b# 	92 	9"'..*<*< 	9--R-888   tt4s   C AC A C 
CC)+)anthropic/claude-fable-5 )anthropic/claude-opus-5r7   )zanthropic/claude-opus-5-fast2x price, higher output speed)anthropic/claude-opus-4.8r7   )zanthropic/claude-opus-4.8-fastr9   )anthropic/claude-sonnet-5r7   anthropic/claude-haiku-4.5r7   )openai/gpt-5.6-solr7   )openai/gpt-5.6-sol-pror7   )openai/gpt-5.6-terrar7   )openai/gpt-5.6-terra-pror7   )openai/gpt-5.6-lunar7   )openai/gpt-5.6-luna-pror7   )openai/gpt-5.5r7   )openai/gpt-5.5-pror7   openai/gpt-5.4-minir7   google/gemini-3.1-pro-previewr7   )google/gemini-3.7-flashr7   )x-ai/grok-4.6r7   )deepseek/deepseek-v4-pror7   )deepseek/deepseek-v4-pro-0813zdated snapshot of v4-pro)deepseek/deepseek-v4-flashr7   )deepseek/deepseek-v4-flash-0731zdated snapshot of v4-flash)qwen/qwen3.8-maxr7   )moonshotai/kimi-k3recommended)minimax/minimax-m3r7   )z-ai/glm-5.2default)z-ai/glm-5.1r7   )xiaomi/mimo-v2.5-pror7   )tencent/hy3r7   )stepfun/step-3.7-flashr7   )!nvidia/nemotron-3-super-120b-a12br7   )zmeta/muse-spark-1.2r7   )sakana/fugu-ultrar7   )zopenrouter/pareto-codezAauto-routes to cheapest coder meeting openrouter.min_coding_score)zstealth/ox-alphafree)zopenrouter/elephant-alphar\   )zz-ai/glm-5.2:freer\   )zpoolside/laguna-s-2.1:freer\   )zpoolside/laguna-xs-2.1:freer\   )z&nvidia/nemotron-3-super-120b-a12b:freer\   )z&nvidia/nemotron-3-ultra-550b-a55b:freer\   )z"nvidia/nemotron-3.5-lightning:freer\   list[tuple[str, str]]OPENROUTER_MODELSzlist[tuple[str, str]] | None_openrouter_catalog_cache))moonshotai/kimi-k2.6rR   )zalibaba/qwen3.6-plusr7   )zzai/glm-5.1r7   )minimax/minimax-m2.7r7   )anthropic/claude-sonnet-4.6r7   )zanthropic/claude-opus-4.7r7   )anthropic/claude-opus-4.6r7   r<   )openai/gpt-5.4r7   rF   )zopenai/gpt-5.3-codexr7   rH   )zgoogle/gemini-3-flashr7   )$google/gemini-3.1-flash-lite-previewr7   )zxai/grok-4.20-reasoningr7   VERCEL_AI_GATEWAY_MODELS_ai_gateway_catalog_cachereturn	list[str]c                 B    ddl m} m}  |t          |                     S )a  Derive the openai-codex curated list from codex_models.py.

    Single source of truth: DEFAULT_CODEX_MODELS + forward-compat synthesis.
    This keeps the gateway /model picker in sync with the CLI `hermes model`
    flow without maintaining a separate static list.
    r   DEFAULT_CODEX_MODELS_add_forward_compat_models)hermes_cli.codex_modelsrl   rm   listrk   s     r   _codex_curated_modelsrp      s7     YXXXXXXX%%d+?&@&@AAAr   )grok-4.6grok-build-0.1grok-4.5zgrok-4.3zgrok-4.20-0309-reasoningzgrok-4.20-0309-non-reasoningzgrok-4.20-multi-agent-0309_XAI_STATIC_FALLBACK)rq   rs   zgrok-composer-2.5-fast_XAI_CURATED_EXTRASrq   idsc                B    t           | v rt           gd | D             z   S | S )z:Pin the headline xAI model to the top of the curated list.c                (    g | ]}|t           k    |S  _XAI_TOP_MODEL.0ms     r   
<listcomp>z$_xai_promote_top.<locals>.<listcomp>   s"    "I"I"IQ.5H5H15H5H5Hr   rz   rv   s    r   _xai_promote_topr      s/    "I"Ic"I"I"IIIJr   c                    t          |           }t          D ]A}||v r|r|d         t          k    rdnt          |          }|                    ||           B|S )zBAppend Hermes-curated xAI models that are missing from models.dev.r      )ro   ru   r{   leninsert)rv   outextra	insert_ats       r   _xai_merge_curated_extrasr      sf    
s))C$ % %C<<GQ>!9!9AAs3xx	

9e$$$$Jr   c                :    t          t          |                     S N)r   r   r   s    r   _xai_finalize_catalogr      s    5c::;;;r   c                    	 ddl m}   |             }t          |t                    r|                    d          nd}t          |t                    r|                    d          nd}t          |t                    r>|r<d |                                D             }|rt          t          |                    S n# t          $ r Y nw xY wt          t          t                              S )zOffline curated floor for xAI / xAI OAuth pickers.

    Reads $HERMES_HOME/models_dev_cache.json directly (no network). Falls
    back to ``_XAI_STATIC_FALLBACK`` when the cache is empty or unreadable.
    r   )_load_disk_cachexaiNmodelsc                <    g | ]}t          |t                    |S ry   )r-   r    )r}   mids     r   r   z'_xai_curated_models.<locals>.<listcomp>   s'    HHH3:c33G3GH3HHHr   )agent.models_devr   r-   dictr(   keysr   sortedr1   ro   rt   )r   datar   r   rv   s        r   _xai_curated_modelsr      s    555555!!!+D$!7!7AdhhuoooT&0d&;&;E"""fd## 	: 	:HH&++--HHHC :,VC[[999    	 !&:!;!;<<<s   B:B> >
C
CmoarU   nous)r6   r8   r:   r;   r=   r>   r?   r@   rA   rB   rC   rD   rE   rG   rI   rJ   rK   rL   rM   rN   rO   rP   rQ   rS   rT   rV   rW   rX   rY   rZ   r[   openai)gpt-5.4gpt-5.4-mini
gpt-5-minigpt-5.3-codexgpt-5.2-codexgpt-4.1gpt-4ogpt-4o-mini
openai-api)gpt-5.6-solzgpt-5.6-sol-progpt-5.6-terrazgpt-5.6-terra-progpt-5.6-lunazgpt-5.6-luna-progpt-5.5gpt-5.5-pror   r   gpt-5.4-nanor   r   r   r   r   openai-codex	xai-oauthcopilot-acpcopilot)r   r   r   r   r   r   r   r   claude-sonnet-4.6claude-sonnet-5claude-sonnet-4claude-sonnet-4.5claude-haiku-4.5gemini-3.1-pro-previewgemini-3-pro-previewzgemini-3-flash-previewzgemini-2.5-progemini)r   r   gemini-3.6-flashzgemini-3.1-flash-lite-previewzai)glm-5.2glm-5.1glm-5zglm-5v-turbozglm-5-turboglm-4.7zglm-4.5zglm-4.5-flashr   nvidia)z!nvidia/nemotron-3-ultra-550b-a55brZ   z%nvidia/nemotron-3.5-lightning-30b-a3bz-nvidia/nemotron-3-nano-omni-30b-a3b-reasoningrT   r`   zminimaxai/minimax-m3zkimi-coding)
kimi-k3kimi-k2.7-code	kimi-k2.6	kimi-k2.5zkimi-for-codingzkimi-for-coding-highspeedkimi-k2-thinkingzkimi-k2-thinking-turbokimi-k2-turbo-previewkimi-k2-0905-previewzkimi-coding-cn)r   r   zkimi-k2.7-code-highspeedr   r   r   r   r   stepfunzstep-3.5-flashzstep-3.5-flash-2603moonshot)r   r   r   r   r   r   minimax)
MiniMax-M3MiniMax-M2.7MiniMax-M2.5zMiniMax-M2.1z
MiniMax-M2minimax-oauth)r   r   zMiniMax-M2.7-highspeed
minimax-cn	anthropic)claude-fable-5r   claude-opus-4-8claude-opus-4-7claude-opus-4-6claude-sonnet-4-6zclaude-opus-4-5-20251101zclaude-sonnet-4-5-20250929zclaude-opus-4-20250514zclaude-sonnet-4-20250514zclaude-haiku-4-5-20251001deepseekdeepseek-v4-prodeepseek-v4-flashxiaomi)mimo-v2.5-pro	mimo-v2.5mimo-v2-promimo-v2-omnizmimo-v2-flashztencent-tokenhubhy3-previewarcee)ztrinity-large-thinkingztrinity-large-previewztrinity-minigmi)zzai-org/GLM-5.1-FP8deepseek-ai/DeepSeek-V3.2moonshotai/Kimi-K2.5re   r;   rb   rd   opencode-zen)@x-preview-f-freer   r   r   r   r   r   r   r   zgpt-5.4-pror   r   r   r   zgpt-5.3-codex-sparkgpt-5.2r   zgpt-5.1zgpt-5.1-codexzgpt-5.1-codex-maxzgpt-5.1-codex-minigpt-5zgpt-5-codexz
gpt-5-nanor   zclaude-opus-5r   r   r   r   zclaude-opus-4-5r   claude-sonnet-4-5r   claude-haiku-4-5zgemini-3.7-flashr   zgemini-3.5-flashzgemini-3.5-flash-litezgemini-3.1-prozgemini-3-flashrq   rs   rr   zmuse-spark-1.2
minimax-m3minimax-m2.7minimax-m2.5r   r   r   r   r   r   zdeepseek-v4-flash-freeqwen3.6-plusqwen3.5-plus
big-picklezmimo-v2.5-freehy3-freelaguna-s-2.1-freenemotron-3-ultra-freenemotron-3.5-lightning-freemuse-spark-1.2-contributor-freeopencode-free)r   r   r   r   r   r   opencode-go)r   r   r   r   r   rs   zglm-5.3r   r   r   r   r   r   r   r   r   r   r   r   zqwen3.8-maxqwen3.7-maxqwen3.7-plusr   r   hy3r   zmuse-spark-1.2-contributorzox-alpha-freekilocode)rc   rb   rd   google/gemini-3-pro-previewgoogle/gemini-3-flash-previewalibaba)
r   r   r   r   r   qwen3-coder-plusqwen3-coder-nextr   r   r   alibaba-coding-plan)
r   r   r   zqwen3-max-2026-01-23r   r   r   r   r   r   huggingface)	r   zQwen/Qwen3.5-397B-A17BzQwen/Qwen3.5-35B-A3Br   zMiniMaxAI/MiniMax-M2.5zzai-org/GLM-5zXiaomiMiMo/MiMo-V2-Flashzmoonshotai/Kimi-K2-Thinkingzmoonshotai/Kimi-K2.6bedrock)zus.anthropic.claude-sonnet-5zus.anthropic.claude-sonnet-4-6zus.anthropic.claude-opus-4-6-v1z+us.anthropic.claude-haiku-4-5-20251001-v1:0z,us.anthropic.claude-sonnet-4-5-20250929-v1:0zus.amazon.nova-pro-v1:0zus.amazon.nova-lite-v1:0zus.amazon.nova-micro-v1:0zdeepseek.v3.2z)us.meta.llama4-maverick-17b-instruct-v1:0z&us.meta.llama4-scout-17b-instruct-v1:0zazure-foundry)rI   r   zgoogle/gemini-3.6-flashzgoogle/gemini-3.5-flashzgoogle/gemini-3.5-flash-liter   re   zgoogle/gemini-3.1-flash-lite)zmoonshotai/kimi-k2.5ra   zzai-org/glm-5zdeepseek/deepseek-v3-0324zdeepseek/deepseek-r1-0528zqwen/qwen3-235b-a22b-fp8)vertexnovitazdict[str, list[str]]_PROVIDER_MODELSc                    g | ]\  }}|S ry   ry   r}   r   _s      r   r   r     s    !M!M!M&#q#!M!M!Mr   
ai-gatewaymodel_idpricingdict[str, dict[str, str]]boolc                   |                     |           }|sdS 	 t          |                     dd                    dk    o&t          |                     dd                    dk    S # t          t          f$ r Y dS w xY w)zFReturn True if *model_id* has zero-cost prompt AND completion pricing.Fprompt1r   
completion)r(   r   	TypeError
ValueError)r  r  ps      r   _is_model_freer    s    HA uQUU8S))**a/XE!%%c:R:R4S4SWX4XXz"   uus   AA) )A>=A>account_infodict[str, Any]c                   |                      d          }t          |t                    rZ|                     d          }t          |t                    r| S |                     d          }t          |t                    r| S |                      d          }t          |t                    sdS |                     d          }|dS 	 t	          |          dk    S # t
          t          f$ r Y dS w xY w)	a$  Return True if the account info indicates a free (unpaid) tier.

    Prefer the Portal's explicit ``paid_service_access.allowed`` entitlement
    decision.  Legacy payloads fall back to ``subscription.monthly_charge == 0``.
    Returns False when both signals are missing or unparseable.
    paid_service_accessallowedpaid_accesssubscriptionFmonthly_chargeNr   )r(   r-   r   r	  r   r  r  )r  r  r  paidsubcharges         r   is_nous_free_tierr    s    ""#899K+t$$ //),,gt$$ 	;}--dD!! 	8O


>
*
*Cc4   uWW%&&F~uV}}!!z"   uus   C C32C3	model_ids	free_tiertuple[list[str], list[str]]c                    |s| g fS |s| g fS g }g }| D ]=}t          ||          r|                    |           (|                    |           >||fS )a  Split Nous models into (selectable, unavailable) based on user tier.

    For paid-tier users: all models are selectable, none unavailable.

    For free-tier users: only free models are selectable; paid models
    are returned as unavailable (shown grayed out in the menu).
    )r  append)r  r  r  
selectableunavailabler   s         r   partition_nous_models_by_tierr%    s      2 2JK $ $#w'' 	$c""""s####$$r   r7   Fforce_refreshcurated_idsportal_base_urlr'  +tuple[list[str], dict[str, dict[str, str]]]c                  	 t          ||          }n.# t          $ r! t          |           t          |          fcY S w xY wt	          |t                    r|                    d          nd}t	          |t                    r|st          |           t          |          fS g }|D ](}t          |          }|r|                    |           )|st          |           t          |          fS t          |          }	ddd}
|D ]}||	vrt          |
          |	|<   t          |           }t          |          fd|D             }|r||z   }||	fS )u  Augment curated list + pricing with the Portal's ``freeRecommendedModels``.

    The Portal's ``/api/nous/recommended-models`` endpoint advertises which
    models are free *right now* — independent of what the in-repo
    ``_PROVIDER_MODELS["nous"]`` list happens to contain or whether the
    docs-hosted catalog manifest has been rebuilt since the last release.

    For free-tier users this is the source of truth: any model the Portal
    flags as free should be selectable, even if the user is running an
    older Hermes that doesn't ship that model in its hardcoded curated
    list.  This function returns an augmented ``(model_ids, pricing)``
    pair where:

    * Portal free recommendations missing from ``curated_ids`` are
      appended after the curated list (so the in-repo curated models
      show first and Portal-only picks follow).
    * ``pricing`` gets a synthetic ``{"prompt": "0", "completion": "0"}``
      entry for any free recommendation missing from the live pricing
      map, so :func:`partition_nous_models_by_tier` keeps it.

    Failures (network, parse, missing field) are silent and degrade to
    returning the inputs unchanged.
    r&  freeRecommendedModelsN0r  r  c                    g | ]}|v|	S ry   ry   r}   r   seens     r   r   z:union_with_portal_free_recommendations.<locals>.<listcomp>O      BBB#T/////r   	fetch_nous_recommended_modelsr1   ro   r   r-   r(   _extract_model_namer"  set)r(  r  r)  r'  payload
free_blockportal_free_idsentrynameaugmented_pricingfree_syntheticr   augmented_idsnew_onesr1  s                 @r   &union_with_portal_free_recommendationsr@    s   <2/=
 
 
  2 2 2[!!4==11112 :DGT9R9R\4555X\Jj$'' 2z 2[!!4==11!#O ) )"5)) 	)""4((( 2[!!4==11W #377N : :'''%).%9%9c"%%M}D CBBBBBBH 1%0,--    (A A c               j   	 t          ||          }n.# t          $ r! t          |           t          |          fcY S w xY wt	          |t                    r|                    d          nd}t	          |t                    r|st          |           t          |          fS g }|D ](}t          |          }|r|                    |           )|st          |           t          |          fS t          |           }	t          |	          fd|D             }
|
r|	|
z   }	|	t          |          fS )uF  Augment curated list with the Portal's ``paidRecommendedModels``.

    Mirror of :func:`union_with_portal_free_recommendations` for paid-tier
    users. The Portal's ``/api/nous/recommended-models`` endpoint advertises
    which paid models are blessed *right now* — independent of what the
    in-repo ``_PROVIDER_MODELS["nous"]`` list happens to contain or whether
    the docs-hosted catalog manifest has been rebuilt since the last release.

    For paid-tier users this lets newly-launched paid models surface in the
    picker even if the user is running an older Hermes that doesn't ship
    them in its hardcoded curated list. This function returns an augmented
    ``(model_ids, pricing)`` pair where:

    * Portal paid recommendations missing from ``curated_ids`` are
      appended after the curated list (so the in-repo curated models
      show first and Portal-only picks follow).
    * ``pricing`` is left untouched — we deliberately do NOT synthesize
      pricing entries for paid models. Live pricing is fetched separately
      via :func:`get_pricing_for_provider`; if the live endpoint hasn't
      published pricing yet, the picker shows a blank price column rather
      than fabricating numbers. (The free helper synthesizes ``$0`` so
      :func:`partition_nous_models_by_tier` keeps free models selectable;
      no equivalent gating applies on the paid side, so synthesis would
      only mislead the user.)

    Failures (network, parse, missing field) are silent and degrade to
    returning the inputs unchanged — never block the picker on a
    Portal-side hiccup.
    r&  paidRecommendedModelsNc                    g | ]}|v|	S ry   ry   r0  s     r   r   z:union_with_portal_paid_recommendations.<locals>.<listcomp>  r2  r   r3  )r(  r  r)  r'  r7  
paid_blockportal_paid_idsr:  r;  r>  r?  r1  s              @r   &union_with_portal_paid_recommendationsrG  V  st   H2/=
 
 
  2 2 2[!!4==11112 :DGT9R9R\4555X\Jj$'' 2z 2[!!4==11!#O ) )"5)) 	)""4((( 2[!!4==11%%M}D CBBBBBBH 1%04==))rA     int_FREE_TIER_CACHE_TTLztuple[bool, float] | None_free_tier_cacheforce_freshrM  c                    t          j                    }| s!t          t          \  }}||z
  t          k     r|S 	 ddlm}  ||           }|j        }||fa|S # t          $ r d|faY dS w xY w)a  Check if the current Nous Portal user is on a free (unpaid) tier.

    Results are cached for ``_FREE_TIER_CACHE_TTL`` seconds to avoid
    hitting the Portal API on every call.  The cache is short-lived so
    that an account upgrade is reflected within a few minutes.

    Returns True only when entitlement is known to be free.  Unknown/error
    states return False so this compatibility wrapper does not block users.
    Nr   )get_nous_portal_account_inforL  F)time	monotonicrK  rJ  hermes_cli.nous_accountrO  is_free_tierr1   )rM  nowcached_result	cached_atrO  r  results          r   check_nous_free_tierrX    s     .

C !+7#3 y?111  	HHHHHH33LLL*"C=   !3<uus   A A)(A)z/api/nous/recommended-modelsiX  _NOUS_RECOMMENDED_CACHE_TTLz'dict[str, tuple[dict[str, Any], float]]_nous_recommended_cache'Path'c                 .    ddl m}   |             dz  dz  S )z5Disk path for the persisted recommended-models cache.r   get_hermes_homecacheznous_recommended_cache.jsonhermes_constantsr^  r]  s    r   _nous_recommended_disk_pathrb    s-    000000?w&)FFFr   basedict[str, Any] | Nonec                   	 t          t                      d          5 }t          j        |          }ddd           n# 1 swxY w Y   n# t          t          j        f$ r Y dS w xY wt          |t                    sdS |                    |           }t          |t                    sdS |                    d          }t          |t                    r|r|ndS )zReturn the last-known-good payload for ``base`` from disk, or None.

    The disk file is a JSON object keyed by portal base URL so staging and
    prod don't collide:
    ``{"<base>": {"data": {...}, "ts": <epoch_seconds>}}``.
    utf-8encodingNr   )	openrb  jsonloadOSErrorJSONDecodeErrorr-   r   r(   )rc  fhblobr:  r   s        r   _read_nous_recommended_diskrp    s   -//'BBB 	!b9R==D	! 	! 	! 	! 	! 	! 	! 	! 	! 	! 	! 	! 	! 	! 	!T)*   ttdD!! tHHTNNEeT"" t99VDdD))<d<44<s3   A A A  AA AA A&%A&r   Nonec                   |sdS t                      }	 	 t          |d          5 }t          j        |          }ddd           n# 1 swxY w Y   t	          |t
                    si }n# t          t          j        f$ r i }Y nw xY w|t          j                    d|| <   |j	        
                    dd           |                    |j        dz             }t          |dd          5 }t          j        ||d	
           |                    d           ddd           n# 1 swxY w Y   t          j        ||           dS # t          $ r8}ddl} |j        t&                                        d|           Y d}~dS d}~ww xY w)u   Persist ``data`` as the last-known-good payload for ``base``.

    Merges into any existing per-base map, then writes atomically. Failures
    are non-fatal (logged at debug) — the in-process cache still works.
    Nrf  rg  )r   tsTparentsexist_okz.tmpw   indent
r   z3nous recommended-models disk cache write failed: %s)rb  ri  rj  rk  r-   r   rl  rm  rP  parentmkdirwith_suffixsuffixdumpwriter.   replacelogging	getLogger__name__debug)rc  r   r/   rn  ro  tmpexcr  s           r   _write_nous_recommended_diskr    s?     &((D
	dW--- %y}}% % % % % % % % % % % % % % %dD)) -. 	 	 	DDD	"$)++66T
$666t{V344#sW--- 	IdBq))))HHTNNN	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	
3 
 
 
(##))A3	
 	
 	
 	
 	
 	
 	
 	
 	

s|   A* AA* AA* AA* )E *BE BA'E ,-D%E %D))E ,D)-E 
F	-FF	      @c                  | pd                     d          }t          j                    }t                              |          }|s||\  }}||z
  t
          k     r|S | t           }	 t          j        	                    |ddi          }	t          |	|          5 }
t          j        |
                                                                          }ddd           n# 1 swxY w Y   t          |t                     si }n# t"          $ r i }Y nw xY w|r||ft          |<   t%          ||           |S t'          |          }|r||ft          |<   |S ||ft          |<   |S )u  Fetch the Nous Portal's curated recommended-models payload.

    Hits ``<portal>/api/nous/recommended-models``. The endpoint is public —
    no auth is required. Results are cached per portal URL for
    ``_NOUS_RECOMMENDED_CACHE_TTL`` seconds in process; pass
    ``force_refresh=True`` to bypass the in-process cache.

    A successful live fetch is also persisted to a per-base disk cache
    (``$HERMES_HOME/cache/nous_recommended_cache.json``) as last-known-good.
    When the live fetch fails (network, parse, non-2xx) and the in-process
    cache is empty, the disk copy is returned instead of ``{}`` — so a
    transient Portal hiccup no longer silently drops the free/paid model
    recommendations from the picker. Self-heals on the next successful fetch.

    Returns the parsed JSON dict, or ``{}`` only when neither the network nor
    any cache layer can supply data. Callers must treat missing/null fields
    as "no recommendation" and fall back to their own default.
    https://portal.nousresearch.com/NAcceptapplication/jsonheadersr   )rstriprP  rQ  rZ  r(   rY  NOUS_RECOMMENDED_MODELS_PATHurllibrequestRequestr   rj  loadsreaddecoder-   r   r1   r  rp  )r)  r   r'  rc  rT  cachedr7  rV  urlr   respr   disks                r   r4  r4    s   0 @@HHMMD
.

C$((..F V/#?888N
1/
1
1C
n$$12 % 
 
 ,CAAA 	4T:diikk002233D	4 	4 	4 	4 	4 	4 	4 	4 	4 	4 	4 	4 	4 	4 	4$%% 	D     )-s%$T4000 't,,D )-s%%)3KD!Ks6   *4D 9C#D #C''D *C'+D DDc                 .   	 ddl m} m}  |d          pi }t          |                    d          pd                                          }|r|                    d          S t          |                               d          S # t          $ r Y dS w xY w)zEBest-effort lookup of the Portal base URL the user is authed against.r   )DEFAULT_NOUS_PORTAL_URLget_provider_auth_stater   r)  r7   r  r  )hermes_cli.authr  r  r    r(   stripr  r1   )r  r  stateportals       r   _resolve_nous_portal_urlr  O  s    1	
 	
 	
 	
 	
 	
 	
 	
 ('//52UYY0117R88>>@@ 	&==%%%*++223777 1 1 10001s   A!B $!B 
BBr:  r   Optional[str]c                    t          | t                    sdS |                     d          }t          |t                    r(|                                r|                                S dS )zGPull the ``modelName`` field from a recommended-model entry, else None.N	modelName)r-   r   r(   r    r  )r:  
model_names     r   r5  r5  _  sf    eT"" t;''J*c"" "z'7'7'9'9 "!!!4r   )visionr  r)  r'  r  Optional[bool]c                   |pt                      }t          ||          }|sdS |"	 t                      }n# t          $ r d}Y nw xY w| rd\  }}nd\  }}|r|gn||g}|D ]*}	t	          |                    |	                    }
|
r|
c S +dS )u  Return the Portal's recommended model name for an auxiliary task.

    Picks the best field from the Portal's recommended-models payload:

    * ``vision=True``  → ``paidRecommendedVisionModel``  (paid tier) or
                         ``freeRecommendedVisionModel``  (free tier)
    * ``vision=False`` → ``paidRecommendedCompactionModel`` or
                         ``freeRecommendedCompactionModel``

    When ``free_tier`` is ``None`` (default) the user's tier is auto-detected
    via :func:`check_nous_free_tier`. Pass an explicit bool to bypass the
    detection — useful for tests or when the caller already knows the tier.

    For paid-tier users we prefer the paid recommendation but gracefully fall
    back to the free recommendation if the Portal returned ``null`` for the
    paid field (common during the staged rollout of new paid models).

    Returns ``None`` when every candidate is missing, null, or the fetch
    fails — callers should fall back to their own default (currently
    ``google/gemini-3-flash-preview``).
    r&  NF)paidRecommendedVisionModelfreeRecommendedVisionModel)paidRecommendedCompactionModelfreeRecommendedCompactionModel)r  r4  rX  r1   r5  r(   )r  r  r)  r'  rc  r7  paid_keyfree_key
candidateskeyr;  s              r   get_nous_recommended_aux_modelr  i  s    8 8688D+DNNNG t	,..II 	 	 	 III	
  `W((_(
  )B(x.BJ  "7;;s#3#344 	KKK	4s   8 AAc                  .    e Zd ZU ded<   ded<   ded<   dS )ProviderEntryr    sluglabeltui_descN)r  
__module____qualname____annotations__ry   r   r   r  r    s+         IIIJJJMMMMMr   r  zNous PortalzLNous Portal (Everything your agent needs, 300+ models with bundled tool use)	fireworkszFireworks AIz1Fireworks AI (OpenAI-compatible direct model API)
openrouter
OpenRouterz'OpenRouter (Pay-per-use API aggregator)zMixture of AgentszIMixture of Agents (named presets; aggregator acts after reference models)r   NovitaAIz5NovitaAI (Cloud: Model API, Agent Sandbox, GPU Cloud)lmstudioz	LM Studioz8LM Studio (Local desktop app with built-in model server)	Anthropicz4Anthropic (Claude models via API key or Claude Code)zChatGPT or Codex SubscriptionzTChatGPT or Codex Subscription (Sign in with your ChatGPT account, uses Codex models)z
OpenAI APIz$OpenAI API (api.openai.com, API key)z
Qwen Cloudz.Qwen Cloud / DashScope (Qwen + multi-provider)%xAI Grok OAuth (SuperGrok / Premium+)z2xAI Grok OAuth (SuperGrok / Premium+ subscription)zXiaomi MiMoz7Xiaomi MiMo (MiMo-V2.5 and V2 models: pro, omni, flash)zTencent TokenHubz;Tencent TokenHub (Hy3 Preview via tokenhub.tencentmaas.com)z
NVIDIA NIMz>NVIDIA NIM (Nemotron models via build.nvidia.com or local NIM)zGitHub Copilotz3GitHub Copilot (Uses GITHUB_TOKEN or gh auth token)zGitHub Copilot ACPz1GitHub Copilot ACP (Spawns copilot --acp --stdio)zHugging Facez Hugging Face Inference ProviderszGoogle AI Studioz$Google AI Studio (Native Gemini API)r   zGoogle Vertex AIzTGoogle Vertex AI (Gemini via GCP; OAuth2 service account or ADC, GCP billing/quotas)DeepSeekz$DeepSeek (V3, R1, coder, direct API)xAIzxAI Grok (Direct API)z
Z.AI / GLMzZ.AI / GLM (Zhipu direct API)zKimi / Kimi Coding Planz.Kimi Coding Plan (api.kimi.com & Moonshot API)zKimi / Moonshot (China)z+Kimi / Moonshot China (Domestic direct API)zStepFun Step Planz;StepFun Step Plan (Agent / coding models via Step Plan API)MiniMaxzMiniMax (Global direct API)zMiniMax (OAuth)z9MiniMax via OAuth browser login (Coding Plan, minimax.io)zMiniMax (China)z#MiniMax China (Domestic direct API)ollama-cloudzOllama Cloudz3Ollama Cloud (Cloud-hosted open models, ollama.com)zArcee AIz%Arcee AI (Trinity models, direct API)z	GMI Cloudz"GMI Cloud (Multi-model direct API)z	Kilo CodezKilo Code (Kilo Gateway API)zOpenCode Zenz,OpenCode Zen (Curated models, pay-as-you-go)zOpenCode Goz&OpenCode Go (Open models subscription)zAWS Bedrockz;AWS Bedrock (Claude, Nova, Llama, DeepSeek; IAM or API key)zAzure FoundryzRAzure Foundry (OpenAI-style or Anthropic-style endpoint, your Azure AI deployment)zVercel AI Gatewayz*Vercel AI Gateway (Multi-model aggregator)
qwen-oauthzQwen OAuth (Portal)z(Qwen OAuth (Reuses local Qwen CLI login)zlist[ProviderEntry]CANONICAL_PROVIDERSc                    h | ]	}|j         
S ry   r  r}   r  s     r   	<setcomp>r    s    888qAF888r   )list_providers>   r   aws_sdkr   oauth_externalexternal_processoauth_device_codez (direct API)c                (    i | ]}|j         |j        S ry   )r  r  r  s     r   
<dictcomp>r    s    AAAAFAGAAAr   zCustom endpointcustomzKimi / Moonshotz.Coding Plan, Moonshot global & China endpointsz+Global, OAuth Coding Plan & China endpoints)r   r   r   zxAI Grokz(Direct API or SuperGrok / Premium+ OAuthzGoogle GeminizGoogle AI Studio (API key)OpenAIz/ChatGPT/Codex subscription or direct OpenAI APIQwenz4Qwen Cloud / DashScope, Coding Plan & Qwen CLI OAuth)r   r   r  OpenCodez0Zen pay-as-you-go, Go subscription, or free tier)r   r   r   z)GitHub token API or copilot --acp process)kimir   r   googler   qwenopencoder   z%dict[str, tuple[str, str, list[str]]]PROVIDER_GROUPSc                ,    i | ]\  }\  }}}|D ]}||S ry   ry   )r}   gid_label_descmembersr  s         r   r  r    sG     " " "/#/wW^" "OSD#" " " "r   dict[str, str]_SLUG_TO_GROUPr  c                    t                               t          | pd                                                                          d          S )zCReturn the group_id a provider slug belongs to, or "" if ungrouped.r7   )r  r(   r    r  lowerr  s    r   provider_group_for_slugr    s:    c$*"oo3355;;==rBBBr   c           
         t                      }i }t                                          D ]\  }\  }}} fd|D             }|r|||<   g }t                      }	 D ]&}
t          |
pd                                                                          }|r||v r?|                    |           t                              |d          }|s|	                    d|d           ||	v r|	                    |           |                    ||g          }t          |          dk    r|	                    d|d         d           t          |         \  }}}|	                    d|||t          |          d           (|S )	u  Fold a flat ordered slug iterable into picker rows by provider group.

    DISPLAY ONLY. Used by every interactive picker (``hermes model``, the
    setup wizard, the Telegram ``/model`` keyboard) so grouping is identical
    across surfaces.

    Each returned row is a dict::

        {"kind": "single", "slug": <slug>}                       # ungrouped, or
                                                                  # 1-member group
        {"kind": "group", "group_id": <gid>, "label": <label>,
         "description": <desc>, "members": [<slug>, ...]}        # 2+ members

    Rules:
      * A group row appears at the position of its FIRST present member, in
        the input order. Subsequent members fold into that row (and are not
        emitted again).
      * Member order inside a group follows ``PROVIDER_GROUPS`` declaration,
        restricted to the members actually present in ``slugs``.
      * A group reduced to a single present member degrades to a ``single``
        row — no pointless one-item submenu.
      * Slugs not in any group pass through as ``single`` rows, order
        preserved.
      * Duplicate slugs in the input are ignored after first sight.
    c                6    g | ]}|t                    v |S ry   )r6  )r}   r~   slugss     r   r   z#group_providers.<locals>.<listcomp>=  s$    999c%jj1r   r7   single)kindr  r   r   group)r  group_idr  descriptionr  )r6  r  itemsr    r  r  addr  r(   r"  r   ro   )r  r1  group_membersr  r  r  r  presentrowsemitted_groupsr  sr  descr  s   `              r   group_providersr    s   4 UUD*,M)8)>)>)@)@ ) )%%feW9999g999 	)!(M#D"uuN  
OO!!##))++ 	AII  B'' 	KK155666.  3##C!--w<<1KK71:>>????,S1NE4KK cE $g@ @    Kr   glmzz-aizz.aizhipugithubzgithub-copilotzgithub-modelszgithub-modelzgithub-copilot-acpzcopilot-acp-agentr  zgoogle-geminizgoogle-ai-studiozgoogle-vertexz	vertex-aiz
gcp-vertexvertexair  zkimi-cnzmoonshot-cnstepzstepfun-coding-planzarcee-aiarceeaiz	gmi-cloudgmicloudzfireworks-aifwzactual-computeractualactualcomputeracizminimax-china
minimax_cnzminimax-portalzminimax-globalminimax_oauthclaudezclaude-codez	deep-seekr  zengozopencode-go-subr\   opencode_free	aigatewayvercelzvercel-ai-gatewaykiloz	kilo-codezkilo-gateway	dashscopealiyunr  zalibaba-cloudzqwen-portalhfzhugging-facezhuggingface-hubz	novita-ainovitaaimimozxiaomi-mimotencenttokenhubztencent-cloudtencentmaasawszaws-bedrockzamazon-bedrockamazongrokz
grok-oauthz
x-ai-oauthzxai-grok-oauthzx-aizx.ainimz
nvidia-nimzbuild-nvidianemotronz	lm-studio)	lm_studioollamaollama_cloudrT   providerc                ^    	 ddl m}  ||           }|r|S n# t          $ r Y nw xY wt          S )u^  Return the silent-default model id — catalog label first, constant second.

    Reads the ``"default": true`` label from the cached remote catalog
    (never hits the network — safe on hot resolution paths), falling back to
    :data:`PREFERRED_SILENT_DEFAULT_MODEL` when no cached manifest exists or
    the provider block carries no label.
    r   )get_default_model_from_cache)hermes_cli.model_catalogr!  r1   PREFERRED_SILENT_DEFAULT_MODEL)r  r!  labeleds      r   "get_preferred_silent_default_modelr%    sa    IIIIII..x88 	N	   ))s    
%%c                D    t          |          }|| v r|S | r| d         ndS )a  Pick the silent default from an available-models list.

    Returns the catalog-labeled default (see
    :func:`get_preferred_silent_default_model`) when the list carries it,
    else the first entry, else "". Used by every surface that must choose a
    model on the user's behalf without an interactive picker (GUI onboarding
    recommended-default, empty-model runtime fallback).
    r   r7   )r%  )r  r  	preferreds      r   pick_silent_default_modelr(    s5     38<<II$,9Q<<",r   zfrozenset[str]_SILENT_DEFAULT_PROVIDERSc                    t                               | g           }| t          v rt          |           }|r||v s|s|S |r|d         ndS )u  Return a cost-safe default model for a provider, or "" if unknown.

    Used as a NON-INTERACTIVE fallback when a provider is configured but no
    model was ever selected (e.g. ``hermes auth add openai-codex`` without
    ``hermes model``, or a profile that sets ``provider`` with no ``model``).

    For most providers this is the first entry in ``_PROVIDER_MODELS`` — the
    same model the ``hermes model`` picker offers first. For metered aggregators
    whose curated list is ordered most-capable-first, that entry is also the
    most EXPENSIVE one, so silently defaulting to it is a billing footgun.
    Those providers (``_SILENT_DEFAULT_PROVIDERS``) resolve through the
    catalog-labeled default instead; a missing model must never auto-escalate
    to the flagship.
    r   r7   )r  r(   r)  r%  )r  r   r'  s      r   get_default_model_for_providerr+    sd     !!(B//F,,,6x@@	  	)v--V-&6!99B&r   c                    t          | t                    sdS 	 t          |                     dd                    dk    o&t          |                     dd                    dk    S # t          t
          f$ r Y dS w xY w)z=Return True when both prompt and completion pricing are zero.Fr  r-  r   r  r-   r   r   r(   r  r  r  s    r   _openrouter_model_is_freer/    s    gt$$ uW[[3//00A5d%LZ]@^@^:_:_cd:ddz"   uu   AA' 'A<;A<itemc                    t          | t                    sdS |                     d          }t          |t                    sdS d|v S )u1  Return True when the model's ``supported_parameters`` advertise tool calling.

    hermes-agent is tool-calling-first — every provider path assumes the model
    can invoke tools. Models that don't advertise ``tools`` in their
    ``supported_parameters`` (e.g. image-only or completion-only models) cannot
    be driven by the agent loop and would fail at the first tool call.

    **Permissive when the field is missing.** Some OpenRouter-compatible gateways
    (Nous Portal, private mirrors, older catalog snapshots) don't populate
    ``supported_parameters`` at all. Treat that as "unknown capability → allow"
    so the picker doesn't silently empty for those users. Only hide models
    whose ``supported_parameters`` is an explicit list that omits ``tools``.

    Ported from Kilo-Org/kilocode#9068.
    Tsupported_parameterstools)r-   r   r(   ro   )r1  paramss     r    _openrouter_model_supports_toolsr6    sO      dD!! tXX,--Ffd## tfr   Optional[dict[str, Any]]c                
   t          | t                    sdS |                     d          }t          |t                    sdS d|vrddiS |                     d          }t          |t                    o|                    d          du }d}t          |t                    r[|                    d          }t          |t                    r1t          t                              d	 |D                                 }d||d
S )au  Normalize one OpenRouter catalog entry's reasoning metadata.

    OpenRouter's ``/v1/models`` catalog advertises reasoning support two ways:
    ``supported_parameters`` contains ``"reasoning"`` when the route accepts
    reasoning controls at all, and a top-level ``reasoning`` object may add
    detail (``mandatory``, ``supported_efforts``). Per OpenRouter semantics
    the top-level object is only trusted after ``supported_parameters``
    confirms the route accepts reasoning controls; ``supported_efforts``
    omitted/None means every effort is accepted.

    Returns:
        ``{"supports_reasoning": True, "supported_efforts": [...] | None,
        "mandatory": bool}`` when the entry advertises reasoning controls,
        ``{"supports_reasoning": False}`` when it explicitly does not
        (``supported_parameters`` is a list omitting ``reasoning``), or
        ``None`` when capability can't be determined from the entry
        (missing/malformed ``supported_parameters``).

    Ported from PrimeIntellect-ai/prime-agent#1258 (derive reasoning levels
    from provider metadata instead of hardcoded model-family lists).
    Nr3  	reasoningsupports_reasoningF	mandatoryTsupported_effortsc              3     K   | ]X}t          |                                          #t          |                                                                          V  Yd S r   r    r  r  r}   efforts     r   	<genexpr>z:parse_openrouter_reasoning_capabilities.<locals>.<genexpr>]  sl       ) )v;;$$&&)F!!##))++) ) ) ) ) )r   )r:  r<  r;  )r-   r   r(   ro   fromkeys)r1  r5  r9  r;  effortsraw_effortss         r   'parse_openrouter_reasoning_capabilitiesrE  8  s"   , dD!! tXX,--Ffd##  t&  $e,,%%I9d++R	k0J0Jd0RI#'G)T"" mm$788k4(( 	4== ) ))) ) )    G #$  r   z*dict[str, Optional[dict[str, Any]]] | None _openrouter_reasoning_caps_cachefloat | None$_openrouter_reasoning_caps_failed_atiQ r   c                 .    ddl m}   |             dz  dz  S )Nr   r]  r_  zreasoning_caps.jsonr`  r]  s    r   _reasoning_caps_disk_pathrJ    s-    000000?w&)>>>r   c                     	 t                                          d          5 } t          j        |           }d d d            n# 1 swxY w Y   n# t          $ r i cY S w xY wt          |t                    r|ni S )Nrf  rg  )rJ  ri  rj  rk  r1   r-   r   )rn  r   s     r   _read_reasoning_caps_diskrL    s    &((--w-?? 	!29R==D	! 	! 	! 	! 	! 	! 	! 	! 	! 	! 	! 	! 	! 	! 	!   			dD))144r1s3   "A AA A		A A	A A A r  ;tuple[Optional[dict[str, Optional[dict[str, Any]]]], float]c           	        t                                          |           }t          |t                    sdS |                    d          }t          |t                    r|sdS 	 t	          dt          j                    t          |                    d          pd          z
            }n+# t          t          f$ r t          t                    }Y nw xY wd |
                                D             |fS )z=Return ``(caps, age_seconds)`` for *url*, or ``(None, 0.0)``.)N        capsrO  rs  r   c                4    i | ]\  }}t          |          |S ry   )r    )r}   r   
model_capss      r   r  z-_load_reasoning_caps_disk.<locals>.<dictcomp>  s$    EEE_S*CHHjEEEr   )rL  r(   r-   r   maxrP  r   r  r   _REASONING_CAPS_DISK_TTL_SECONDSr  )r  r:  rP  ages       r   _load_reasoning_caps_diskrV    s     &''++C00EeT"" y99VDdD!!  y6#ty{{U599T??+?a%@%@@AAz" 6 6 64556EE

EEEsJJs   (AB/ /%CCrP  #dict[str, Optional[dict[str, Any]]]c                6   	 t                      }t          j                    |d|| <   t                      }|j                            dd           t          ||dd           dS # t          $ r&}t                              d|           Y d}~dS d}~ww xY w)	z>Merge *url*'s catalog into the shared disk mirror, atomically.)rs  rP  Trt  r   ,:)rz  
separatorsz,Failed to save reasoning-caps disk cache: %sN)	rL  rP  rJ  r|  r}  r   r1   loggerr  )r  rP  r   r/   r  s        r   _save_reasoning_caps_diskr^    s    J(**9;;55S	(**$666$Q:FFFFFF J J JCSIIIIIIIIIJs   A$A( (
B2BBc                    t           j                            d          rdS t          j        | dd                                           dS )u  Run *refresh* in a background thread. Fire-and-forget.

    Called from hot paths that found the cache cold or the disk copy stale, so
    the next call — or, via the disk mirror, the next process — benefits
    without this turn ever blocking on HTTP. Callers own the once-per-process
    guard; the fetch keeps its own failure TTL. Skipped under pytest, where a
    mid-suite background fetch would make cache state, and therefore test
    behavior, timing-dependent.
    PYTEST_CURRENT_TESTNzreasoning-caps-warmT)targetr;  daemon)r.   environr(   	threadingThreadstart)refreshs    r   _warm_reasoning_caps_asyncrh    sL     
z~~+,, 24  egggggr   c                f    t          |           \  }}|dS |t          k    rt          |           |S )u  The disk copy of *url*'s catalog, queueing *refresh* when it's stale.

    A copy past its TTL is still returned — a stale verdict beats no verdict,
    and reasoning capabilities change rarely — with a background refresh so
    the next run is current.
    N)rV  rT  rh  )r  rg  rP  rU  s       r   !_hydrate_reasoning_caps_from_diskrj    s>     *#..ID#|t
..."7+++Kr   r  -Optional[dict[str, Optional[dict[str, Any]]]]c                ,   t          |t                    sdS i }|D ]c}t          |t                    st          |                    d          pd                                          }|sQt          |          ||<   d|sdS t          | |           |S )uv  Parse a ``/v1/models`` ``data`` array and mirror it for *url*.

    Takes the payload rather than fetching it, so the picker and pricing
    fetches — which pull the same document a capability fetch would — leave the
    mirror warm at no network cost. None when the array has no usable entries,
    which callers remember as a failure rather than caching as empty.
    Nidr7   )r-   ro   r   r    r(   r  rE  r^  )r  r  
caps_by_idr1  r   s        r   _seed_reasoning_capsro    s     eT"" t68J H H$%% 	$((4..&B''--// 	A$GG
3 tc:...r   c                   dt           d}	 t          j                            | |          }t	          ||          5 }t          j        |                                                                          }ddd           n# 1 swxY w Y   n# t          $ r Y dS w xY wt          | |                    d                    S )u  Fetch one OpenRouter-shaped ``/v1/models`` catalog → per-model caps.

    Shared by every aggregator that serves OpenRouter's catalog schema
    (OpenRouter itself, Nous Portal). Returns None when the catalog is
    unreachable or carries no usable entries, so callers can remember the
    failure and fall back rather than caching an empty result.

    Sends a User-Agent because the Portal 403s anonymous catalog reads.
    r  r  
User-Agentr  r  Nr   )_HERMES_USER_AGENTr  r  r  r   rj  r  r  r  r1   ro  r(   )r  r   r  r   r  r7  s         r   _fetch_reasoning_caps_catalogrt    s     ,;MNNGn$$S'$::+CAAA 	7Tj!3!3!5!566G	7 	7 	7 	7 	7 	7 	7 	7 	7 	7 	7 	7 	7 	7 	7   ttW[[%8%8999s4   2B 9B7B BB 
BB 
BBz#https://openrouter.ai/api/v1/models      @forcerw  c                   t           	|st           S t          !t          j                    t          z
  dk     rdS t	          t
          |           }|t          j                    adS |a |S )a  Fetch + cache per-model reasoning capabilities from the live catalog.

    Returns None (without poisoning the cache) when the catalog is
    unreachable so callers can retry later and fall back in the meantime.
    Failed fetches are remembered for 60 seconds so hot per-turn callers
    don't pay an HTTP round-trip on every call while offline. *force* refetches
    past a cache populated from the disk mirror.
    N<   )rF  rH  rP  rQ  rt  _OPENROUTER_CATALOG_URLr   rw  rn  s      r    _fetch_openrouter_reasoning_capsr|    sn     (3E3//,8^ DDJJt./FPPJ/3~/?/?,t'1$r   c                 &    t          d           d S NTrv  )r|  ry   r   r   "_refresh_openrouter_reasoning_capsr    s    $4000000r   )r   allow_fetchr  c                   t          | pd                                          }|sdS t                      }||rt          |          }|dS |                    |          S )u?  Return live-catalog reasoning capabilities for an OpenRouter model.

    Tri-state contract for callers deciding whether to emit reasoning
    controls:
      - dict with ``supports_reasoning: True`` (+ ``supported_efforts``,
        ``mandatory``) — the route advertises reasoning controls;
      - dict with ``supports_reasoning: False`` — the catalog knows the model
        and it does NOT accept reasoning controls (definitive negative);
      - ``None`` — unknown: catalog not loaded yet, model not listed
        (private/custom route), or entry malformed. Callers should fall back
        to their static heuristics rather than treating this as a negative.

    By default this is a CACHE-ONLY lookup — safe on per-request hot paths
    (never blocks on HTTP). The cache is populated for free whenever
    ``fetch_openrouter_models()`` runs (model picker, setup), from the disk
    mirror a previous run left behind, by the non-blocking
    ``warm_openrouter_reasoning_caps_async()`` warmer, or by passing
    ``allow_fetch=True`` from non-latency-sensitive callers.
    r7   Nr  )r    r  _openrouter_caps_cachedr|  r(   r  r   r  modelrn  s        r   'openrouter_model_reasoning_capabilitiesr  !  sq    2 B%%''E t(**Jk5gFFF
t>>%   r   c                 d    t           #t          sdat          t          t                    a t           S )zECache-only OpenRouter caps: memory, else the disk mirror. Never HTTP.NT)rF  _openrouter_caps_disk_checkedrj  rz  r  ry   r   r   r  r  I  s4     (/8U/(,%+L#%G,
 ,
( ,+r   c                 `    t           st                      dS da t          t                     dS )zAWarm the OpenRouter reasoning-capability cache in the background.NT)_openrouter_caps_warm_startedr  rh  r  ry   r   r   $warm_openrouter_reasoning_caps_asyncr  T  s8     % (?(A(A(M$(!ABBBBBr   _nous_reasoning_caps_cache_nous_reasoning_caps_failed_atc                 0    t                      d          dS )uu  The Portal ``/v1/models`` URL for the endpoint we actually talk to.

    Resolved through the documented ladder rather than pinned to production —
    ``NOUS_INFERENCE_BASE_URL`` → resolved credential base → prod — so a
    staging profile reads staging's capabilities. Reading prod's would answer
    the reasoning-mandatory question for the wrong deployment.
    r   
/v1/models)!_resolve_nous_pricing_credentialsry   r   r   nous_catalog_urlr  d  s     011!4@@@@r   c                   t           	|st           S t          !t          j                    t          z
  dk     rdS t	          t                      |           }|t          j                    adS |a |S )zDNous Portal counterpart of :func:`_fetch_openrouter_reasoning_caps`.Nry  )r  r  rP  rQ  rt  r  r{  s      r   _fetch_nous_reasoning_capsr  o  st    
 "-e-))&2^ >>"DDt./?/A/A7KKJ)-)9)9&t!+r   c                 &    t          d           d S r~  )r  ry   r   r   _refresh_nous_reasoning_capsr    s    T******r   c                   t          | pd                                          }|sdS t                      }||rt          |          }|dS |                    |          S )a  Return live-catalog reasoning capabilities for a Nous Portal model.

    Same tri-state contract and cache-only default as
    :func:`openrouter_model_reasoning_capabilities`; warm the cache with
    :func:`warm_nous_reasoning_caps_async` from hot paths.
    r7   Nr  )r    r  _nous_caps_cachedr  r(   r  s        r   !nous_model_reasoning_capabilitiesr    sq     B%%''E t"$$Jk/@@@
t>>%   r   c                 r    t           *t          s#dat          t                      t                    a t           S )u$  Cache-only Portal caps: memory, else the disk mirror. Never HTTP.

    Guarded to one attempt per process because naming the catalog means
    resolving Portal credentials, which can itself reach the network to
    refresh a token — far too expensive for a caller that runs every turn.
    NT)r  _nous_caps_disk_checkedrj  r  r  ry   r   r   r  r    s:     ")2I)"&%F <&
 &
" &%r   c                 `    t           st                      dS da t          t                     dS )zHNous Portal counterpart of :func:`warm_openrouter_reasoning_caps_async`.NT)_nous_caps_warm_startedr  rh  r  ry   r   r   warm_nous_reasoning_caps_asyncr    s8      "3"5"5"A";<<<<<r   )EFFORT_LADDER)clamp_effortr@  r<  Optional[list[str]]c                "    t          | |          S )a;  Clamp a requested reasoning effort to a provider's supported levels.

    Thin wrapper over the canonical policy in
    :func:`agent.reasoning_effort.clamp_effort` (single implementation for
    every transport and provider profile): keep a supported level verbatim,
    otherwise nearest WEAKER supported level (never silently escalate cost),
    weakest supported level when nothing weaker exists, pass through unknown
    supported-sets and bespoke level names unchanged.

    Ported from PrimeIntellect-ai/prime-agent#1258's thinking-level-map
    normalization.
    )_clamp_effort)r@  r<  s     r   #clamp_reasoning_effort_to_supportedr    s      !2333r          @c               B   t           |st          t                     S 	 ddlm}  |            }n# t          $ r d}Y nw xY w|rt          |          nt          t
                    }d |D             }	 t          j                            t          ddi          }t          ||           5 }t          j        |                                                                          }ddd           n# 1 swxY w Y   n&# t          $ r t          t           p|          cY S w xY w|                    d	g           }	t!          |	t                    st          t           p|          S i }
|	D ]V}t!          |t"                    st%          |                    d
          pd                                          }|sQ||
|<   Wt)          t          |	          }t*          ||ag }t-          d          }|D ]p}|
                    |          }|t/          |          s*||k    rd}n&t1          |                    d                    rdnd}|                    ||f           q|st          t           p|          S |d         \  }}|s|df|d<   |a t          |          S )zYReturn the curated OpenRouter picker list, refreshed from the live catalog when possible.Nr   )get_curated_openrouter_modelsc                    g | ]\  }}|S ry   ry   r  s      r   r   z+fetch_openrouter_models.<locals>.<listcomp>      000VS!S000r   r  r  r  r  r   rm  r7   r  rU   r  r\   rR   )r_   ro   r"  r  r1   r^   r  r  r  rz  r   rj  r  r  r  r(   r-   r   r    r  ro  rF  r%  r6  r/  r"  )r   r'  r  remotefallbackpreferred_idsr   r  r7  
live_items
live_by_idr1  r   seededcuratedsilent_defaultpreferred_id	live_itemr  first_id
first_descs                        r   fetch_openrouter_modelsr    s>    !,],-...JJJJJJ..00   %BtF|||40A+B+BH00x000M;n$$#12 % 
 
 ,CAAA 	7Tj!3!3!5!566G	7 	7 	7 	7 	7 	7 	7 	7 	7 	7 	7 	7 	7 	7 	7 ; ; ;-9:::::; VR((Jj$'' ;-9:::,.J  $%% 	$((4..&B''--// 	
3 ""9:FFF'/F4F+1(%'G7EEN% - -NN<00	 0	:: 	>)) DD6y}}Y7O7OPPX66VXDd+,,,, ;-9:::"1:Hj /.
 '==sB   0 ??49C> -9C2&C> 2C66C> 9C6:C> > D! D!c                6    d t          |           D             S )z,Return just the OpenRouter model-id strings.c                    g | ]\  }}|S ry   ry   r  s      r   r   zmodel_ids.<locals>.<listcomp>*      SSSFCCSSSr   r&  )r  r&  s    r   r  r  (  "    SS5MRRRSSSSr   c                     	 ddl m}   |             }n# t          $ r d}Y nw xY w|rt          |          S t          t                              dg                     S )a6  Return the curated Nous Portal model-id list.

    Prefers the remotely-hosted catalog manifest (published under
    ``website/static/api/model-catalog.json``); falls back to the in-repo
    snapshot in ``_PROVIDER_MODELS["nous"]`` when the manifest is
    unreachable. Always returns a list (never None).
    r   )get_curated_nous_modelsNr   )r"  r  r1   ro   r  r(   )r  r  s     r   get_curated_nous_model_idsr  -  s    DDDDDD((**    F|| $$VR00111s    ""c                    t          | t                    sdS 	 t          |                     dd                    dk    o&t          |                     dd                    dk    S # t          t
          f$ r Y dS w xY w)zCReturn True if an AI Gateway model has $0 input AND output pricing.Finputr-  r   outputr-  r.  s    r   _ai_gateway_model_is_freer  ?  s    gt$$ uW[[#..//14_w{{8UX?Y?Y9Z9Z^_9__z"   uur0  c               >   t           |st          t                     S ddlm} t          t                    }d |D             }	 t
          j                            |                    d           dddi	          }t
          j        	                    || 
          5 }t          j        |                                                                          }ddd           n# 1 swxY w Y   n&# t          $ r t          t           p|          cY S w xY w|                    dg           }t!          |t                    st          t           p|          S i }	|D ]V}
t!          |
t"                    st%          |
                    d          pd                                          }|sQ|
|	|<   Wg }|D ]W}|	                    |          }|t)          |                    d                    rdnd}|                    ||f           X|st          t           p|          S t-          d |	                                D             d          r'fd|D             }|                    ddf           n|d         \  }}|df|d<   |a t          |          S )zYReturn the curated AI Gateway picker list, refreshed from the live catalog when possible.Nr   AI_GATEWAY_BASE_URLc                    g | ]\  }}|S ry   ry   r  s      r   r   z+fetch_ai_gateway_models.<locals>.<listcomp>W  r  r   r  r   r  r  r  r  r   rm  r7   r  r\   c              3     K   | ]@\  }}|                     d           t          |                    d                    <|V  AdS )zmoonshotai/r  N)
startswithr  r(   )r}   r   r1  s      r   rA  z*fetch_ai_gateway_models.<locals>.<genexpr>~  sj       	
 	
T~~m,,	
 *$((9*=*=>>		
	
 	
 	
 	
 	
 	
r   c                *    g | ]\  }}|k    ||fS ry   ry   )r}   r   r  free_moonshots      r   r   z+fetch_ai_gateway_models.<locals>.<listcomp>  s+    PPP933-;O;OC;;O;O;Or   rR   )rg   ro   ra  r  rf   r  r  r  r  urlopenrj  r  r  r  r1   r(   r-   r   r    r  r  r"  nextr  r   )r   r'  r  r  r  r   r  r7  r  r  r1  r   r  r  r  r  r  r  r  s                     @r   fetch_ai_gateway_modelsr  I  s1    !,],-...444444,--H00x000M;n$$"))#..77712 % 
 
 ^##C#99 	7Tj!3!3!5!566G	7 	7 	7 	7 	7 	7 	7 	7 	7 	7 	7 	7 	7 	7 	7 ; ; ;-9:::::; VR((Jj$'' ;-9:::,.J  $%% 	$((4..&B''--// 	
3%'G% - -NN<00	29==3K3KLLTvvRTd+,,,, ;-9::: 	
 	
'--//	
 	
 	
 	 M  /PPPPPPPq=-89999aj!.
 '==s7   AC1  9C%C1 %C))C1 ,C)-C1 1 DDc                6    d t          |           D             S )z,Return just the AI Gateway model-id strings.c                    g | ]\  }}|S ry   ry   r  s      r   r   z(ai_gateway_model_ids.<locals>.<listcomp>  r  r   r&  )r  r&  s    r   ai_gateway_model_idsr    r  r   z$dict[str, dict[str, dict[str, str]]]_pricing_cache      ^@zdict[str, float]_pricing_cache_retry_after	cache_key#Optional[dict[str, dict[str, Any]]]c                   t                               |           }|dS t                              |           }|Ot          j                    |k    r8t                               | d           t                              | d           dS |S )z;The cached catalog for *cache_key*, or None to go fetch it.N)r  r(   r  rP  rQ  pop)r  r  retry_afters      r   _cached_catalogr    s    	**F~t,00;;K4>#3#3{#B#B9d+++"&&y$777tMr   rW  dict[str, dict[str, Any]]c                    |t           | <   |rt                              | d           n#t          j                    t
          z   t          | <   |S )z6Cache a catalog result, giving an empty one an expiry.N)r  r  r  rP  rQ  _FAILED_CATALOG_TTL_SECONDS)r  rW  s     r   _cache_catalogr    sS     !'N9 
"&&y$7777 N:: 	#9- Mr   per_token_strc                   	 t          |           }n# t          t          f$ r Y dS w xY w|dk    rdS |dz  }|d}|dk     rzd}|dk     r3t          ||          dk    r|d	z  }|dk     rt          ||          dk    |d
t	          |d	z   d           d                    d                              d
          }d| S )u%  Convert a per-token price string to a human-friendly $/Mtok string.

    Always uses 2 decimal places so that prices align vertically when
    right-justified in a column (the decimal point stays in the same position).

    Sub-cent prices (e.g. deep-discount cache-hit promos) extend precision
    instead of collapsing to "$0.00": the smallest decimal place that makes
    the value non-zero is found, then one extra digit is kept and trailing
    zeros trimmed.

    Examples:
        "0.000003"        → "$3.00"      (per million tokens)
        "0.00003"         → "$30.00"
        "0.00000015"      → "$0.15"
        "0.0000001"       → "$0.10"
        "0.00018"         → "$180.00"
        "0.0000000018"    → "$0.0018"    (promo: $0.0018/Mtok)
        "0"               → "free"
    ?r   r\   @B z.2fg{Gz?      r   .fr-  $)r   r  r  roundminr  )r  valper_mtextprecs        r   _format_price_per_mtokr    s    (M""z"   ss
axxv)OE>>Dt|| RiiE%..!33AID RiiE%..!33/#dQh++////66s;;BB3GGt::    ''r  r  originaltuple[int, str, str] | Nonec                   t          |t                    sdS |                    d          }|                    d          }|dv r|dv rdS dd	}dd
}| dvr ||           nd}|dvr ||          nd}|dk    r|dk    rdS | dvr ||           nd}	|dvr ||          nd}
|	Z|
X|	|
k     rRt          t	          d|	|
z  z
  dz                      }|dk     rdS |t          |          |dvrt          |          ndfS |dvr ||          nd}|dvr ||          nd}|Z|X||k     rRt          t	          d||z  z
  dz                      }|dk     rdS ||dvrt          |          ndt          |          fS dS )u  Derive sale chrome from gateway ``pricing.original`` when cheaper.

    Nous Portal-only feature: callers gate on the provider; this helper only
    sees ``original`` because the Nous fetch path opted in via
    ``include_sale_original=True``.

    Returns ``(discount_percent, was_prompt_raw, was_completion_raw)`` only when
    ``original`` is a dict and the current prompt (fallback: completion) rate
    is strictly below the corresponding original. Percent is
    ``round((1 - current/original) * 100)`` — never hardcoded, and a discount
    that rounds below 1% is treated as no sale (never render "-0%"). Returns
    ``None`` when there is no sale (missing/equal/invalid original), so UIs
    show normal prices.
    Nr  r  Nr7   rawr   rh   rG  c                t    	 t          |           }n# t          t          f$ r Y d S w xY w|dk    r||k    r|nd S Nr   r   r  r  r  ns     r   _finitez&compute_sale_discount.<locals>._finite	  sU    	c

AA:& 	 	 	44	EEa1ffqq$.r  c                t    	 t          |           }n# t          t          f$ r Y d S w xY w|dk    r||k    r|nd S r  r  r  s     r   _nonnegz&compute_sale_discount.<locals>._nonneg	  sU    	c

AA:& 	 	 	44	FFqAvvqq4/r  r   g      ?d   r   r7   )r  r   rh   rG  )r-   r   r(   rI  r  r    )r  r  r  
was_promptwas_completionr  r  cur_prompt_anycur_comp_any
cur_promptorig_promptpctcur_comp	orig_comps                 r   compute_sale_discountr    s@   & h%% th''J\\,//NZNj$@$@t/ / / /0 0 0 0 )/j(@(@WWV___dN*4J*F*F77:&&&DL|q00t$**$<$<$J)3:)E)E''*%%%4K+"9j;>V>V%
[ 89S@AABB774
OO#1#C#CC
 	
 '1
&B&Bwwz"""H+9+K+K'''QUI	 5(Y:N:N%9 45<==>>774);;C
OOO
 	
 4r   https://openrouter.ai/api)r'  include_sale_originalapi_key
str | Noner  c               h   |pd                     d          }|st          |          }||S |dz   }dt          d}| rd|  |d<   	 t          j                            ||	          }	t          |	|
          5 }
t          j        |
	                                
                                          }ddd           n# 1 swxY w Y   n # t          $ r t          |i           cY S w xY wt          ||                    d                     i }|                    dg           D ]}}|                    d          }|                    d          }|rMt          |t                     r7t#          |                    dd                    t#          |                    dd                    d}|                    d          rt#          |d                   |d<   |                    d          rt#          |d                   |d<   |r|                    d          }t          |t                     rei }dD ]1}|                    |          dvrt#          ||                   ||<   2|                    d          s|                    d          r||d<   |||<   t          ||          S )u  Fetch ``/v1/models`` and return ``{model_id: {prompt, completion, ...}}``.

    Results are cached per *base_url* so repeated calls are free.
    Works with any OpenRouter-compatible endpoint (OpenRouter, Nous Portal).

    When *include_sale_original* is true (Nous Portal only) and the gateway
    advertises a global discount under ``pricing.original``, those
    pre-discount rates are copied through as a nested ``original`` dict so
    pickers can show sale chrome. Other providers never opt in — OpenRouter
    (and anything else sharing this helper) keeps the legacy
    ``{prompt, completion}`` shape even if a response happens to nest
    ``original``.
    r7   r  Nr  r  rq  Bearer Authorizationr  r  r   rm  r  r  r  r.  input_cache_readinput_cache_writer  )r  r  r  r  r  )r  r  rs  r  r  r  r   rj  r  r  r  r1   r  ro  r(   r-   r   r    )r  r   r   r'  r  r  r  r  r  r   r  r7  rW  r1  r   r  r:  r  
orig_entryr  s                       r   fetch_models_with_pricingr  6	  s"   * R'',,I  ++M
l
"C$( G  7#6W#6#6 -n$$S'$::+CAAA 	7Tj!3!3!5!566G	7 	7 	7 	7 	7 	7 	7 	7 	7 	7 	7 	7 	7 	7 	7 - - -i,,,,,- gkk&11222(*FFB''    hhtnn((9%% 	 :gt,, 	 gkk(B7788!'++lB"?"?@@% %E {{-.. M,/8J0K,L,L(){{.// O-09L1M-N-N)* % 7";;z22h-- 713J  A A $<<,,J>>.1(3-.@.@JsO!~~h// 7:>>,3O3O 7,6j)F3K)V,,,s6   2C
 99B>2C
 >CC
 CC
 
C'&C'c               *   ddl m} |                    d          }|st          |          }||S 	 t          j                            | dddi          }t          j                            || 	          5 }t          j	        |
                                                                          }ddd           n# 1 swxY w Y   n # t          $ r t          |i           cY S w xY wi }|                    d
g           D ]}	t          |	t                     s|	                    d          }
|	                    d          }|
rt          |t                     s[t#          |                    dd                    t#          |                    dd                    d}|                    d          rt#          |d                   |d<   |                    d          rt#          |d                   |d<   |||
<   t          ||          S )zFetch Vercel AI Gateway /v1/models and return hermes-shaped pricing.

    Vercel uses ``input`` / ``output`` field names; hermes's picker expects
    ``prompt`` / ``completion``. This translates. Cache read/write field names
    already match.
    r   r  r  Nr   r  r  r  r  r   rm  r  r  r7   r  r.  r  r  )ra  r  r  r  r  r  r  r  rj  r  r  r  r1   r  r(   r-   r   r    )r   r'  r  r  r  r   r  r7  rW  r1  r   r  r:  s                r   fetch_ai_gateway_pricingr  	  sq    544444#**3//I  ++M-n$$!!!12 % 
 
 ^##C#99 	7Tj!3!3!5!566G	7 	7 	7 	7 	7 	7 	7 	7 	7 	7 	7 	7 	7 	7 	7 - - -i,,,,,- )+FFB''  $%% 	hhtnn((9%% 	
7D11 	'++gr2233gkk(B7788!
 !
 ;;)** 	I(+G4F,G(H(HE$%;;*++ 	K),W5H-I)J)JE%&s)V,,,s6   AC
 99B>2C
 >CC
 CC
 
C'&C'c                 P    t          j        dd                                          S )z1Best-effort OpenRouter API key for pricing fetch.OPENROUTER_API_KEYr7   )r.   getenvr  ry   r   r   _resolve_openrouter_api_keyr  	  s!    9)2..44666r   z&https://inference-api.nousresearch.comtuple[str, str]c                    d} 	 ddl m}  |            } n# t          $ r d} Y nw xY wd}d}	 ddl m}  |            }|rB|                    dd          pd}|                    dd          pd                                }n# t          $ r Y nw xY w| p|pt                              d          }|                    d	          r
|dd
         }||fS )u  Return ``(api_key, base_url)`` for Nous Portal pricing.

    The Nous inference ``/v1/models`` endpoint exposes pricing without
    authentication, so the api_key is best-effort: when runtime credential
    resolution fails (expired refresh token, missing auth.json, etc.) we
    still return a usable inference base URL so the picker keeps working
    with anonymous pricing data.  Free-tier users in particular need this
    — pricing drives the free/paid partition, and silently returning empty
    pricing because of an auth blip makes the picker look broken ("No free
    models currently available").

    Base URL precedence (mirrors runtime credential resolution):
    1. ``NOUS_INFERENCE_BASE_URL`` env override (staging / preview)
    2. Resolved runtime credential ``base_url``
    3. Production default

    Without (1), a staging profile's sale ``pricing.original`` never
    reaches the pickers — the anonymous fallback would hit prod, which
    has no ``original`` field.
    Nr   )_nous_inference_env_overrider7   ) resolve_nous_runtime_credentialsr  r   r  /v1)	r  r  r1   r  r(   r  _DEFAULT_NOUS_INFERENCE_BASEr  endswith)env_baser  r  
creds_baser  credsr   s          r   r  r  	  s;   * H@@@@@@//11    GJDDDDDD0022 	Cii	2..4"G))J339r@@BBJ    FJF*FNNsSSH  !CRC=Xs    $$AB 
BBc               t   t          |           }|dk    rt          t                      d|          S |dk    rt          |          S |dk    rt	          |          S |dk    rt          |          S |dk    rt          |          S |d	k    r&t                      \  }}|rt          |||d
          S i S )zYReturn live pricing for providers that support it (openrouter, nous, ai-gateway, novita).r  r  )r  r   r'  r  r&  r   	deepinfrar  r   T)r  r   r'  r  )normalize_providerr  r  r  _fetch_novita_pricing_fetch_deepinfra_pricing"_fireworks_pricing_from_models_devr  )r  r'  
normalizedr  r   s        r   get_pricing_for_providerr'  	  s    #H--J\!!(/110'
 
 
 	

 \!!'mDDDDX$=AAAA[  'mDDDD[  1NNNNV=?? 	,!+&*    Ir   c                   d}| st          |          }||S i }	 ddlm}  |d          pi }|                                D ]\  }}t	          |t
                    s|                    d          }t	          |t
                    sG|                    d          }	|                    d          }
|	|
vt          t          |	pd          d	z            t          t          |
pd          d	z            d
}|                    d          }|r"t          t          |          d	z            |d<   ||t          |          <   n# t          $ r i }Y nw xY wt          ||          S )uI  Derive Fireworks picker pricing from the models.dev registry cache.

    No dedicated network fetch: ``fetch_models_dev()`` already maintains an
    in-memory + disk cache (1h TTL) that every picker surface shares, so this
    is a pure dict transform on the picker path — no added latency and no
    per-render network call. Results are additionally memoized in
    ``_pricing_cache`` so repeated menu renders within a process are free.

    models.dev publishes Fireworks costs in USD per 1M tokens; the shared
    pricing formatter expects per-token strings, so divide by 1M.
    zmodels.dev/fireworksNr   )_get_provider_modelsr  costr  r  r  r.  
cache_readr  )r  r   r)  r  r-   r   r(   r    r   r1   r  )r'  r  r  rW  r)  r   r   r:  r*  inpr   rowr+  s                r   r%  r%  
  s    'I  ++M(*F999999%%k228b ,,.. 	# 	#JCeT** 99V$$DdD)) ((7##C((8$$C{s{eCH1oo	9::!%q//I"=>># #C ,//J M*-eJ.?.?).K*L*L&'"F3s88#	#$     )V,,,s   D-E	 	EEc               .   t          j        dd                                          }|si S t          j        dd                                          pd}|                    d          }|st	          |          }||S |dz   }d| d	t
          d
}	 t          j                            ||          }t          ||           5 }	t          j        |	                                                                          }
ddd           n# 1 swxY w Y   n # t          $ r t          |i           cY S w xY wi }|
                    dg           D ]}t#          |t$                    s|                    d          }|s0|                    d          }|                    d          }||_t'          t)          |pd          dz  dz            t'          t)          |pd          dz  dz            d|t'          |          <   t          ||          S )u  Fetch pricing from NovitaAI /v1/models.

    NovitaAI returns input/output prices per million tokens in units of
    0.0001 USD. Convert them to the per-token strings used by the shared
    pricing formatter.

    Results are cached in ``_pricing_cache`` keyed on the resolved base URL,
    matching the pattern used by ``fetch_ai_gateway_pricing`` — without this,
    every menu render or pricing lookup re-hits the network.
    NOVITA_API_KEYr7   NOVITA_BASE_URLzhttps://api.novita.ai/openai/v1r  Nr   r	  r  )r
  r  rr  r  r  r   rm  input_token_price_per_moutput_token_price_per_mr   i'  r  r.  )r.   r  r  r  r  rs  r  r  r  r   rj  r  r  r  r1   r  r(   r-   r   r    r   )r   r'  r  r   r  r  r  r  r   r  r7  rW  r1  r   r,  r   s                   r   r#  r#  B
  sq    i("--3355G 	y*B//5577\;\H$$I  ++M
i
C,7,,$( G-n$$S'$::+CAAA 	7Tj!3!3!5!566G	7 	7 	7 	7 	7 	7 	7 	7 	7 	7 	7 	7 	7 	7 	7 - - -i,,,,,- )+FFB'' 
 
$%% 	hhtnn 	hh011hh122;3;%q//F2Y>??eCH1oo6BCC
 
s3xx
 )V,,,s6   2D 9D
>D 
DD DD D32D3set[str]_KNOWN_PROVIDER_NAMESc            
        dh} 	 ddl m} ddlm}  |            }|                    di           }t          |t                    r}|                                D ]h\  }}t          |t                    rN|                      |t          |                    d          p|          t          |                               i|                    dg           }t          |t                    rZ|D ]W}t          |t                    r@|                      |t          |                    d          pd                               Xn/# t          t          t          t          t          t           f$ r Y nw xY w| S )	z;Return routable custom-provider IDs configured by the user.r  r   load_config)custom_provider_slug	providersr;  custom_providersr7   )r&   r7  hermes_cli.providersr8  r(   r-   r   r  r  r    ro   ImportErrorrl  RuntimeErrorr  r  AttributeError)rv   r7  r8  configr9  r  r:  legacys           r   _configured_custom_provider_idsrA  
  s   *C111111======JJ{B//	i&& 	['oo// [ [
UeT** [GG00UYYv5F5F5M#1N1NPSTWPXPXYYZZZ.33fd## 	P P PeT** PGG00UYYv5F5F5L"1M1MNNOOO,	:~V   Js   EE	 	)E54E5list[dict[str, str]]c                    d t           D             dgz   } i }t                                          D ].\  }}|                    |g                               |           /g }| D ]	}t
                              ||          }|                    |g           }d}	 ddlm}	m	}
 |dk    r2t                      pd}t          |                                          }ng|dk    r |
t          j        dd                    }nB |	|          }t          |                    d	          p|                    d
                    }n# t          $ r Y nw xY w|                    ||||d           |S )aR  Return info about all providers the user could use with ``provider:model``.

    Each dict has ``id``, ``label``, and ``aliases``.
    Checks which providers have valid credentials configured.

    Derives the provider list from :data:`CANONICAL_PROVIDERS` (single
    source of truth shared with ``hermes model``, ``/model``, etc.).
    c                    g | ]	}|j         
S ry   r  r  s     r   r   z,list_available_providers.<locals>.<listcomp>
  s    :::af:::r   r  Fr   )get_auth_statushas_usable_secretr7   r  r  	logged_in
configured)rm  r  aliasesauthenticated)r  _PROVIDER_ALIASESr  
setdefaultr"  _PROVIDER_LABELSr(   r  rE  rF  _get_custom_base_urlr	  r  r.   r  r1   )provider_orderaliases_foralias	canonicalrW  pidr  
alias_list	has_credsrE  rF  custom_base_urlstatuss                r   list_available_providersrX  
  s    ;:&9:::hZGN )+K-3355 < <yy"--44U;;;;F   $$S#.. __S"--
		JJJJJJJJh"6"8"8">B !6!6!8!899		$$--bi8Lb.Q.QRR		(-- K!8!8!TFJJ|<T<TUU	 	 	 	D	!&	
 
 	 	 	 	 Ms   B'E
EEr  current_providerc                   |                                  }|                    d          }|dk    r|d|                                                                          }||dz   d                                          }|rK|rH|t          v r>|dk    r|                                }t	          t                      dhz
  t          d          D ]^}|                                 d}|                    |          r0||t          |          dz   d                                          fc S _|dk    rd|v r|                    d          }	|d|	                                          }
||	dz   d                                          }|
r1|r/d|
                                 }|t                      v r||fS d|fS t          |          |fS ||fS )	uh  Parse ``/model`` input into ``(provider, model)``.

    Supports ``provider:model`` syntax to switch providers at runtime::

        openrouter:anthropic/claude-sonnet-4.5  →  ("openrouter", "anthropic/claude-sonnet-4.5")
        nous:hermes-3                           →  ("nous", "hermes-3")
        anthropic/claude-sonnet-4.5             →  (current_provider, "anthropic/claude-sonnet-4.5")
        gpt-5.4                                 →  (current_provider, "gpt-5.4")

    The colon is only treated as a provider delimiter if the left side is a
    recognized provider name or alias.  This avoids misinterpreting model names
    that happen to contain colons (e.g. ``anthropic/claude-3.5-sonnet:beta``).

    Returns ``(provider, model)`` where *provider* is either the explicit
    provider from the input or *current_provider* if none was specified.
    r[  r   Nr   r  T)r  reversecustom:)	r  findr  r4  r   rA  r   r  r"  )r  rY  strippedcolonprovider_part
model_partlowered	custom_idprefixsecond_coloncustom_nameactual_models               r   parse_model_inputrh  
  s   " yy{{HMM#Eqyy %(..006688eaijj)//11
 	CZ 	CM=R,R,R(("..**!'355
B " " " Q QI
 !* 1 1444F))&11 Q((3y>>A3E3G3G*H*N*N*P*PPPPPQ
 ((SJ->->)s33(,7==??),*:*;*;<BBDD 2< 2 ?+*;*;*=*= ? ?I $C$E$EEE )<88$j11&}55zBBh''r   c                     t                      } t          |                     dd                                                    S )z2Get the custom endpoint base_url from config.yaml.r   r7   )_get_model_config_dictr    r(   r  )	model_cfgs    r   rN  rN  
  s5    &((Iy}}Z,,--33555r   c                   t          | pd                                          }|si S 	 ddlm}  |            }|                    di           }t          |t                    rS|                    |          p&|                    |                                          }t          |t                    r|S n/# t          t          t          t          t          t          f$ r Y nw xY wi S )z:Return config.yaml providers.<provider>, or an empty dict.r7   r   r6  r9  )r    r  r&   r7  r(   r-   r   r  r<  rl  r=  r  r  r>  )r  r  r7  r?  providers_cfgr:  s         r   _get_provider_config_dictrn  
  s    
hn"


#
#
%
%C 		111111

;33mT** 	!%%c**Lm.?.?		.L.LE%&& ,	:~V   Is   BB8 8)C$#C$c                L   t          | pd                                                              d          }|                    d          rd|z   }n|r	d|vrd|z   }dD ]D}|                    |          r-|dt          |                                        d          } nE|S )	z?Convert an OpenAI-style Ollama base URL to the native API root.r7   r  r[  http://127.0.0.1://http://)	/api/tagsr  /apir  N)r    r  r  r  r  r   r   rootr  s      r   _root_for_ollama_native_apirw    s    x~2$$&&--c22Ds  !D(	  %t##4<  ==   	3v;;,'..s33DE	 Kr   c                    t          | pd                                          }|                    d          rd|z   S |r	d|vrd|z   S |S )z=Add a usable HTTP scheme without changing an OpenAI API path.r7   r[  rp  rq  rr  )r    r  r  )r   values     r   _normalize_openai_base_urlrz    sb    B%%''E *!E)) !e##5  Lr   c                    t          d          } |                     dd          p-|                     dd          p|                     dd          pd}|r!t          |                                          S t	                      }t          |                    dd          pd                                                                          }t          |                    dd          pd                                          }|dk    r|r|S |dk    r9|r7	 t          d|          r|S n## t          t          t          t          f$ r Y nw xY wt          j        dd                                          }|rg|                    d	          r|                    d
          sd|z   }n4|                    d          r|                    d          r| d}nd|v r	 t          j                            |          }|j        r|j        |j        }d	|v r|                    d          sd| d}d|j        v r$|j                            dd          d         dz   nd}|                    | | d                                          }nN# t          $ r Y nBw xY w|                    d	          dk    r|                    d          sd| d}n	d	|vr| d}|S dS )ah  Resolve the local Ollama-compatible endpoint URL.

    Prefer explicit config under ``providers.ollama.base_url`` because this is
    how local Ollama-compatible endpoints can be wired without changing the
    active model provider. Fall back to active ``model.base_url`` only when the
    active provider is ollama/custom, then to Ollama's local default.
    r  r   r7   apir  r  r  OLLAMA_HOSTr[  z::z	127.0.0.1[]z:11434rq  N@r   r   )netlocz]:11434zhttp://localhost:11434)rn  r(   r    r  rj  r   should_use_ollama_native_catalogrl  r=  r  r  r.   r  r  r  r  parseurlsplithostnameportr  rsplit_replacegeturlcount)	provider_cfgrH  rk  model_provider
model_baseenv_hostparsedr  userinfos	            r   _get_ollama_base_urlr  (  sX    -X66LR(( 	E2&&	E2&&	 	   ':$$&&&&((Iz266<"==CCEEKKMMNY]]:r228b99??AAJ!!j!!!j!
	/*EE "!!"y*= 	 	 	D	 y++1133H s## 	+H,?,?,E,E 	+"X-HH  %% 	+(*;*;C*@*@ 	+"***HHh..x88? v{':%Hhx/B/B3/G/G#2x??? &-// ,,S!44Q7#== 
  &"*<H<<<  /    fhh     ^^C  1$$X-@-@-E-E$,8,,,HH  "***H##s%   D+ +E
EB/J 
JJc                 l   t          d          } |                     d          }	 ddlm}  ||          }n+# t          t
          t          t          t          f$ r i }Y nw xY wt          |                     d          pd          
                                }|svt          |                     d          p|                     d          pd          
                                }|r't          j        |d          
                                nd}|r!t          d	 |D                       sd
| |d<   |S )zEReturn configured headers and credentials for native Ollama requests.r  extra_headersr   normalize_extra_headersr  r7   key_envapi_key_envc              3  F   K   | ]}|                                 d k    V  dS authorizationNr  r}   r  s     r   rA  z._get_ollama_request_headers.<locals>.<genexpr>{  s/      DDc399;;/1DDDDDDr   r	  r
  )rn  r(   r&   r  r<  rl  r=  r  r  r    r  r.   r  any)r:  r  r  rW  r  r  s         r   _get_ollama_request_headersr  i  sT   %h//E
))O
$
$C======((--,	:F    %))I&&,"--3355G DIIi  BEIIm$<$<B
 

%'' 	 5<C")GR((..000 :DDVDDDDD 	:&9&9&9F?#Ms   8 %A A r  c               
   t          d          }t          |                    d          p+|                    d          p|                    d          pd                                          }t          |pd                                          }t	          |o| ot          | |                    }|s|si S |rt                      ni }|r5t          |          D ]}|                                dk    r||= d| |d<   |S )	z?Resolve Ollama credentials and headers for one endpoint origin.r  r   r|  r  r7   r  r	  r
  )	rn  r    r(   r  r	  _same_ollama_native_rootr  tupler  )r   r  r:  configured_baseexplicit_keyconfigured_matchesr  r  s           r   _get_ollama_native_headersr    s-    &h//E		*K5!1!1KUYYu5E5EK egg  w}"%%++--L 	@	@$X?? 
  l 	/AI)+++rG < >> 	! 	!Cyy{{o--CL#;\#;#; Nr   i,  _OLLAMA_LOCAL_MODELS_CACHE_TTLz(dict[str, tuple[tuple[str, ...], float]]_OLLAMA_LOCAL_MODELS_CACHE!_OLLAMA_LOCAL_PROBE_FAILURE_CACHEzdict[str, bool]_OLLAMA_LOCAL_PROBE_REACHABLE   _OLLAMA_LOCAL_PROBE_FAILURE_TTL   _OLLAMA_LOCAL_CACHE_MAX_ENTRIESr  c                   t                               | d            t                              | d            t          t                    D ];}|| k    s|                    |  d          rt                              |d            <d S )N	|timeout:)r  r  r  ro   r  r  )r  failure_keys     r   #_evict_related_ollama_cache_entriesr    s    ""3---!%%c4000=>> E E#!7!738I8I8I!J!J-11+tDDDE Er   r_  ry  c                    || vr]t          |           t          k    rEt          t          |                     }t	          |                    dd          d                    || |<   d S )Nr  r   r   )r   r  r  iterr  split)r_  r  ry  
oldest_keys       r   _remember_ollama_cacher    sj    
%CJJ*III$u++&&
+[!,,Q/	
 	
 	
 E#JJJr   rv  r  Optional[dict[str, str]]c                $   | }|rdd l }t          d |                                D                       }t          j        |dd                              dd          }|                    |d	
                                          }|  d| }|S )Nr   c              3  |   K   | ]7\  }}t          |                                          t          |          fV  8d S r   )r    r  )r}   r  ry  s      r   rA  z*_ollama_probe_cache_key.<locals>.<genexpr>  sR       $
 $
/9sESXX^^s5zz*$
 $
 $
 $
 $
 $
r   FrY  )ensure_asciir\  rf  r  errors   digest_sizez	|headers:)hashlibr   r  rj  dumpsencodeblake2b	hexdigest)rv  r  r  r  normalized_headersheader_blobheader_fingerprints          r   _ollama_probe_cache_keyr    s    I 
;# $
 $
=D]]__$
 $
 $
 
 
 jUz
 
 

&&
+
+ 	 %__[a_HHRRTT::&8::	r          @c           	        t          | pt                                }|sdS t          ||          }| dt          |          d}t                              |          }|3|\  }}t          j                    |z
  t          k     rt          |          S t                              |          }	|	<t          j                    |	z
  t          k     rdS t                              |d           	 |                    d          dz   }
dt          i}|                    |pi            t           j                            |
|          }t'          ||          5 }t)          j        |                                                                          }ddd           n# 1 swxY w Y   n# t0          t2          t4          t6          j        j        t           j        j        t(          j         tB          f$ rA tE          tF          |d	           tE          t          |t          j                               Y dS w xY wtI          |tJ                    r|                    d
          nd}tI          |t                    s?tE          tF          |d	           tE          t          |t          j                               dS g }tM                      }|D ]}tI          |tJ                    rLtO          |                    d          p|                    d          pd          (                                }n@tE          tF          |d	           tE          t          |t          j                                dS |r||v r|)                    |           |*                    |           |rA|s?tE          tF          |d	           tE          t          |t          j                               dS tE          tF          |d           t                              |d           tE          t          |tW          |          t          j                    f           |S )a{  Probe local Ollama-compatible models from native ``/api/tags``.

    Returns ``None`` when the endpoint cannot be reached or returns malformed
    data, and a list (possibly empty) when ``/api/tags`` was reachable. Stock
    Ollama exposes its authoritative local model catalog at ``/api/tags``;
    OpenAI-compatible ``/v1/models`` is not required for local Ollama servers.
    Nr  z.3fr  rs  rr  r  r  Fr   r  r;  r7   T),rw  r  r  r   r  r(   rP  rQ  r  ro   r  r  r  r  rs  updater  r  r  r   rj  r  r  r  r  rl  TimeoutErrorhttpclientHTTPExceptionerrorURLErrorrm  UnicodeDecodeErrorr  r  r-   r   r6  r    r  r  r"  r  )r   r   r  rv  r  r  r  cached_modelsrV  	failed_atr  request_headersr   r  r7  
raw_modelsr   r1  r1  r  s                       r   probe_ollama_local_modelsr    sn    'x'I3G3I3IJJD t'g66I==w===K'++I66F#) y>i'*HHH&&&155kBBI>i'*III4)--k4@@@kk#,');<w}"---n$$S/$BB+CAAA 	7Tj!3!3!5!566G	7 	7 	7 	7 	7 	7 	7 	7 	7 	7 	7 	7 	7 	7 	7 	!    	)9e	
 	
 	
 	-{DN<L<L	
 	
 	
 tt" +5Wd*C*CMX&&&Jj$'' )9e	
 	
 	
 	-{DN<L<L	
 	
 	
 tFUUD    dD!! 		488G,,F0@0@FBGGMMOOHH"-y%   #1;@P@P   44 	8t++h & )9e	
 	
 	
 	-{DN<L<L	
 	
 	
 t8)TJJJ%))+t<<<"	v(()  
 Ms8   /A*F* 9FF* F""F* %F"&F* *BH32H3c                &    t          | ||          S )zKFetch local Ollama-compatible models, preserving probe failure as ``None``.r  )r  )r   r   r  s      r   fetch_ollama_local_modelsr  '  s     %XwHHHHr   leftrightc                   t          |                               d          }t          |                              d          }|r|sdS 	 t          j                            |          }t          j                            |          }t          |          t          |          k    o5|j                            d          |j                            d          k    S # t          t          f$ r Y dS w xY w)zEReturn True when two Ollama/OpenAI-style base URLs share an API root.r  F)	rw  r  r  r  r  r   r/   r>  r  )r  r  	left_root
right_root
left_partsright_partss         r   r  r  0  s    +D1188==I,U33::3??J J u\**955
l++J77y!!Z
%;%;; L&&s++{/?/F/Fs/K/KK	
 J'   uus   BC   C54C5c                   t          | pd                                                                          }t          |pd          }|rg	 t          j                            |          j        pd                                }|dk    s|                    d          rdS n# t          $ r Y nw xY wh d}||v rdS |dk    r|sdS t          d          }t          |                    d          p+|                    d          p|                    d	          pd                                          }|r$t          ||          st          |d
|          duS dS t          d          }	t          |	                    dd          p-|	                    dd          p|	                    d	d          pd                                          }
|
rt          ||
          rdS |sdS h d}||vr|                    d          sdS |dk    s|                    d          rdS 	 t          j                            |          }|j        dk    rdS n# t          $ r Y dS w xY wt          |d
|          duS )az  Return True when model discovery should use local Ollama ``/api/tags``.

    Bare ``ollama`` is normalized to ``custom`` elsewhere so runtime paths can
    share the OpenAI-compatible chat client. For model discovery and validation,
    however, local Ollama's authoritative model list is ``/api/tags``. Use that
    path when the caller explicitly asked for Ollama, when the base URL matches
    configured ``providers.ollama.base_url``, or when an ambiguous custom URL on
    Ollama's default local port actually serves ``/api/tags``. Ordinary custom
    endpoints stay on the existing OpenAI-compatible ``/models`` probe path.
    r7   z
ollama.comz.ollama.comF>   r   r   r   r   r  r  r   r  r   r|  r        ?r   r  NT>   	llama-cpp	llama.cppr7   vllmlocalr  llamacppr\  zcustom:ollamaz-ollamai,  )r    r  r  rw  r  r  urlparser  r  r  rn  r(   r  r  r  r  )r  r   r  	requestedrv  hostknown_non_local_providersrH  r  r  configured_ollama_base_urllocal_like_providersr  s                r   r  r  A  s	    HN##))++1133I&x~266D 	L))$//8>BEEGGD|##t}}]'C'C#u $ 	 	 	D	! ! ! ---uH 	5.x88
NN:&& ~~e$$~~e$$ 	
 

 %'' 	  	]#;D/#R#R 	],T3PPPX\\\t,X66L!$R(( 	E2&&	E2&&	 	" "
 egg  " &>tE_&`&` t u```,,,Y5I5I)5T5T,uO##y'9'9)'D'D#t&&t,,;%5     uu %T3HHHPTTTs%   
AB   
B-,B-"*I 
IIc                     	 ddl m}   |             }|                    di           }t          |t                    r|S n# t
          $ r Y nw xY wi S )z7Return the main model config mapping, or an empty dict.r   r6  r  )r&   r7  r(   r-   r   r1   )r7  r?  rk  s      r   rj  rj    sy    111111JJw++	i&& 		   Is   <A   
AAc                \   t          | pd                                                                                              d          }|sdS t          j                            |          j                            d          }|                    d          p|                    d          S )Nr7   r  Fz
/anthropicz/anthropic/v1)	r    r  r  r  r  r  r  r/   r  )r   r&  r/   s      r   '_base_url_looks_like_anthropic_messagesr    s    X^$$**,,2244;;C@@J u<  ,,188==D==&&H$--*H*HHr   c                    t          | pd                                                              d          }|                    d          r|dz   S |dz   S )Nzhttps://api.anthropic.comr  r  r   r  )r    r  r  r  )r   endpoints     r   _anthropic_models_urlr    s[    8::;;AACCJJ3OOH $)##l""r   c                   t          |           }|dk    rt          |          S t          |          }|rd |D             S t                              |g           }d |D             S )zReturn ``(model_id, description)`` tuples for a provider's model list.

    Tries to fetch the live model list from the provider's API first,
    falling back to the static ``_PROVIDER_MODELS`` catalog if the API
    is unreachable.
    r  r&  c                    g | ]}|d fS r7   ry   r|   s     r   r   z/curated_models_for_provider.<locals>.<listcomp>  s    &&&AB&&&r   c                    g | ]}|d fS r  ry   r|   s     r   r   z/curated_models_for_provider.<locals>.<listcomp>  s    $$$QG$$$r   )r"  r  provider_model_idsr  r(   )r  r'  r&  liver   s        r   curated_models_for_providerr    s     $H--J\!!&]CCCC j))D '&&&&&& !!*b11F$$V$$$$r   c                    | pd                                                                 }t          |           }d ||fD             S )Nr7   c                    h | ]}||S ry   ry   )r}   ks     r   r  z!_provider_keys.<locals>.<setcomp>  s    ...!A.A...r   )r  r  r"  )r  r  r&  s      r   _provider_keysr     sI    >r
 
 
"
"
(
(
*
*C#H--J..Z(....r   )zdeepseek-chatzdeepseek-reasonerzdict[str, tuple[str, ...]]_PROVIDER_RETIRED_ALIASEStuple[str, ...]c                    t          t                              | g                     }t                              | d          }||z   S )zCActive picker models plus retired aliases recognized for detection.ry   )r  r  r(   r  )r  activeretireds      r   _provider_catalog_namesr    s@    #''"5566F'++Hb99GGr   
name_lowerr9  c                :     t           fd|D                       S )Nc              3  l   K   | ].}t          |          D ]}|                                k    V  /d S r   )r  r  )r}   r  r  r  s      r   rA  z-_model_in_provider_catalog.<locals>.<genexpr>  sc        ,X66   	ekkmm#      r   )r  )r  r9  s   ` r   _model_in_provider_catalogr
    s;        !     r   >   r   r   r   r  r  _BORROWED_MODEL_PROVIDERS_LIVE_FIRST_PICKER_PROVIDERScurrent_keysOptional[tuple[str, str]]c                   	 ddl m} n# t          $ r Y dS w xY w|                    |           }|dS |j        |j        d	fd}|D ]} ||          x}r||fc S t          D ],}||v s|t          v s	|t          v r ||          x}r||fc S -t          D ]}||v r ||          x}r||fc S t          D ]}||v r ||          x}r||fc S dS )
zDResolve short aliases (e.g. sonnet/opus) using static catalogs only.r   )MODEL_ALIASESNr  r    rh   r  c                    t                               | g           }|sd S | t          v r d n                                }|D ]-}|                                                    |          r|c S .d S )Nr  )r  r(   _AGGREGATOR_PROVIDERSr  r  )r  r   rd  r  familyvendors       r   _matchz+_resolve_static_model_alias.<locals>._match
  s    !%%h33 	4 000      
%''	 	
  	 	E{{}}''// tr   )r  r    rh   r  )	hermes_cli.model_switchr  r1   r(   r  r  r  r  r  )	r  r  r  identityr  r  matchedr  r  s	          @@r   _resolve_static_model_aliasr    s   
9999999   tt   ,,Ht_F_F       ! % %fX&&&7 	%W$$$$	% % % %$$000444fX&&&7 	%W$$$$	% * % %|##FF84D4D)D#W$$$$
 . % %|##FF84D4D)D#W$$$$4s    
r  c                  	 | pd                                 }|sdS |                                	t          |          }t          	|          }|r|S t                              		          }|dvrCt                              |g           }|t          v r|r||vr|t          |          p|d         fS t          	|          rdS |dk    p|
                    d          }t          D ]J}||v s|t          v s	|t          v r|rt          	fdt          |          D                       r||fc S Kt          D ]5}||v rt          	fdt          |          D                       r||fc S 6dS )	a  Auto-detect a provider from static catalogs only.

    Returns ``(provider_id, model_name)``. The model name may be remapped
    when a static alias or bare provider name resolves to a catalog default.
    Returns ``None`` when no confident match is found.
    r7   N>   r  r  r   r  r\  c              3  H   K   | ]}|                                 k    V  d S r   r  r}   r~   r  s     r   rA  z3detect_static_provider_for_model.<locals>.<genexpr>z  0      MM1zQWWYY&MMMMMMr   c              3  H   K   | ]}|                                 k    V  d S r   r  r  s     r   rA  z3detect_static_provider_for_model.<locals>.<genexpr>  r  r   )r  r  r   r  rK  r(   r  rM  r+  r
  r  r  r  r  r  )
r  rY  r;  r  alias_matchresolved_providerdefault_models_is_custom_currentrS  r  s
            @r    detect_static_provider_for_modelr#  4  s    "##%%D tJ!"233L-j,GGK  *--j*EE 888)--.?DD!111 2!55 "./@AAV^TUEV  "*l;; t 	H$ 	2&&y11    
 
<+++/// 	MMMM0G0L0LMMMMM 	;	
 )  ,MMMM0G0L0LMMMMM 	;	 4r   c                   | pd                                 }|sdS t          ||          }|r|S t          |                                t	          |                    rdS t          |          }|r|dk    rd|fS ||k    rd|fS dS dS )u  Auto-detect the best provider for a model name.

    Returns ``(provider_id, model_name)`` — the model name may be remapped
    (e.g. bare ``deepseek-chat`` → ``deepseek/deepseek-chat`` for OpenRouter).
    Returns ``None`` when no confident match is found.

    Priority:
    0. Bare provider name → switch to that provider's default model
    1. Direct provider static catalog match
    2. OpenRouter catalog match
    r7   Nr  )r  r#  r
  r  r   _find_openrouter_slug)r  rY  r;  static_matchor_slugs        r   detect_provider_for_modelr(    s     "##%%D t3D:JKKL !$**,,?O0P0PQQ t $D))G |++ '**d?? '**t4r   c                H   |                                                                  }|sdS t                      D ]}||                                k    r|c S t                      D ];}d|v r5|                    dd          \  }}||                                k    r|c S <dS )u  Find the full OpenRouter model slug for a bare or partial model name.

    Handles:
    - Exact match: ``anthropic/claude-opus-4.6`` → as-is
    - Bare name: ``deepseek-chat`` → ``deepseek/deepseek-chat``
    - Bare name: ``claude-opus-4.6`` → ``anthropic/claude-opus-4.6``
    Nr  r   )r  r  r  r  )r  r  r   r  ra  s        r   r%  r%    s     !!##))++J t {{  $$JJJ % {{  #::IIc1--MAzZ--////


4r   c                    | pd                                                                 }t                              ||          S )u   Normalize provider aliases to Hermes' canonical provider ids.

    Note: ``"auto"`` passes through unchanged — use
    ``hermes_cli.auth.resolve_provider()`` to resolve it to a concrete
    provider based on credentials and environment.
    r  )r  r  rK  r(   )r  r&  s     r   r"  r"    s<     *l113399;;J  Z888r   c                    | pd                                 }|                                }|dk    rdS t          |          }t                              ||pd          S )z9Return a human-friendly label for a provider id or alias.r  autoAutor  )r  r  r"  rM  r(   )r  r  r&  s      r   provider_labelr.    s_    (L//11H!!JVv#J//J
H,DEEEr   )gpt-o1o3o4_OPENAI_FAST_MODE_PREFIXESc                    t          t          | pd                    }|                    d          d         sdS dv rdS t          fdt          D                       S )zPReturn True if the model is an OpenAI flagship eligible for Priority Processing.r7   r[  r   Fcodexc              3  B   K   | ]}                     |          V  d S r   r  )r}   rd  rc  s     r   rA  z(_is_openai_fast_model.<locals>.<genexpr>  s/      PP6tv&&PPPPPPr   )_strip_vendor_prefixr    r  r  r3  r  r  rc  s     @r   _is_openai_fast_modelr:    sq    
s8>r22
3
3C99S>>!D u $uPPPP5OPPPPPPr   c                    t          | pd                                                                          }d|v r|                    dd          d         }|S )z]Strip vendor/ prefix from a model ID (e.g. 'anthropic/claude-opus-4-6' -> 'claude-opus-4-6').r7   r  r   )r    r  r  r  )r  r  s     r   r8  r8    sQ    
hn"


#
#
%
%
+
+
-
-C
czziiQ"Jr   c                ~    ddl m} t          |           p(t          |           p |t	          | pd                    S )zDReturn whether Hermes should expose the /fast toggle for this model.r   )is_grok_46_familyr7   )agent.model_metadatar=  _is_anthropic_fast_modelr:  r    )r  r=  s     r   model_supports_fast_moder@    sU    666666 	!** 	2 **	2SR0011r   c                    t          t          | pd                    }|                    d          d         }|                    d          sdS d|v pd|v S )ua  Return True if the model accepts the Anthropic Fast Mode ``speed`` param.

    This gates the *speed=fast request parameter*, which Anthropic supports on
    Opus 4.6 only (Opus 4.7 explicitly 400s). It is deliberately NOT a general
    "is this a fast model" check: for Opus 4.8 the fast offering is a SEPARATE
    model id (``…-opus-4.8-fast``) selected via the model field, not the speed
    parameter — see ``agent.anthropic_adapter._supports_fast_mode`` and its
    test. Keep this in lock-step with that adapter gate so the UI never shows a
    Fast toggle that the runtime would silently drop.
    r7   r[  r   claude-Fzopus-4-6zopus-4.6)r8  r    r  r  r9  s      r   r?  r?    s_     s8>r22
3
3C99S>>!D??9%% u3t!33r   c                R    t          |           sdS t          |           rddiS ddiS )u-  Return request_overrides for fast/priority mode, or None if unsupported.

    Returns provider-appropriate overrides:
    - OpenAI models: ``{"service_tier": "priority"}`` (Priority Processing)
    - Anthropic models: ``{"speed": "fast"}`` (Anthropic Fast Mode beta)
    - Grok 4.6: ``{"service_tier": "priority"}`` (xAI Priority Processing)

    The overrides are injected into the API request kwargs by
    ``_build_api_kwargs`` in run_agent.py — each API path handles its own
    keys (service_tier for OpenAI/Codex, speed for Anthropic Messages).
    Nspeedfastservice_tierpriority)r@  r?  )r  s    r   resolve_fast_mode_overridesrH  ,  s?     $H-- t)) !  J''r   c                 (   	 ddl m}   | d          }t          |                    d          pd                                          }|r|S n# t
          $ r Y nw xY w	 ddl m} ddlm}m	}  |d          D ]}t          |t                    st          |                    d          pd                                          }|sQ ||          \  }}	|sb	  ||          \  }
}n# t
          $ r Y ~w xY w|
r|
c S n# t
          $ r Y nw xY wdS )	u3  Best-effort GitHub token for fetching the Copilot model catalog.

    Resolution order:
      1. ``resolve_api_key_provider_credentials("copilot")`` — env vars
         (``COPILOT_GITHUB_TOKEN`` / ``GH_TOKEN`` / ``GITHUB_TOKEN``) plus
         the ``gh auth token`` CLI fallback.
      2. ``read_credential_pool("copilot")`` — a token (typically a
         ``gho_*`` from device-code login, or a fine-grained PAT) stored in
         ``auth.json`` under ``credential_pool.copilot[]``. The pool is
         populated by ``hermes auth add copilot`` and by ``_seed_from_env``
         when the env var is set in ``~/.hermes/.env``.

    Without (2), users whose only Copilot credential is in the pool see
    the ``/model`` picker fall back to a stale hardcoded list because the
    live catalog fetch silently 401s. To avoid wedging on a malformed pool
    entry, each candidate is exchanged via ``exchange_copilot_token`` —
    only entries that actually exchange successfully are returned, so a
    later valid entry is reachable when an earlier one is unsupported.
    r   $resolve_api_key_provider_credentialsr   r  r7   )read_credential_pool)exchange_copilot_tokenvalidate_copilot_tokenaccess_token)r  rK  r    r(   r  r1   rL  hermes_cli.copilot_authrM  rN  r-   r   )rK  r  r  rL  rM  rN  r:  r  validr  	api_token_expires_ats               r    _resolve_copilot_catalog_api_keyrT  ?  s   (HHHHHH44Y??eii	**0b117799 	N	   888888	
 	
 	
 	
 	
 	
 	
 	

 *))44 	! 	!EeT** eii//5266<<>>C --c22HE1 )?)?)D)D&	;;    !    !	!     2sO   A
A 
AAA;D C*)D *
C74D 6C77D  D 
DD>   r   r   groqcoherer   r  r   mistralr   r   r  
perplexity
togetherair   r   r   r   _MODELS_DEV_PREFERREDc                    t          |                                                                           }	 ddlm}  ||          S # t
          $ r |cY S w xY w)a  Case-insensitive dedup key that also folds picker-search aliases.

    Some providers serve the same model under both a curated public slug and
    a bare live wire id (Kimi Coding Plan lists its flagship as ``k3`` while
    the curated catalog carries ``kimi-k3``). Folding through the search-alias
    table keeps the curated-first merge from emitting both as separate rows.
    The row that survives is the primary list's entry; selection still sends
    whichever id the surviving row carries.
    r   )model_alias_canonical)r    r  r  hermes_cli.model_searchr\  r1   )r  r  r\  s      r   _model_dedup_keyr^    ss     h--




%
%
'
'CAAAAAA$$S)))   


s   A AAr  c                   	 ddl m}  ||           }n# t          $ r g }Y nw xY w|st          |          S t	                      }g }|D ]R}t          |                                          }||v r(|                    |           |                    |           S|D ]R}t          |                                          }||v r(|                    |           |                    |           S|S )u  Merge curated list with fresh models.dev entries for a preferred provider.

    Returns models.dev entries first (in models.dev order), then any
    curated-only entries appended. Preserves case for curated fallbacks
    (e.g. ``MiniMax-M2.7``) while trusting models.dev for newer variants.

    If models.dev is unreachable or returns nothing, the curated list is
    returned unchanged — this is the offline/CI fallback path.
    r   list_agentic_models)	r   ra  r1   ro   r6  r    r  r  r"  )r  r  ra  mdev
seen_lowermergedr   r  s           r   _merge_with_models_devre    s,   888888""8,,     G}} 55JF  #hhnn*sc  #hhnn*scMs    ##c                4   t          j        dd                                                              d          }|r|S 	 t	                      }t          |                    d          pd                                                                          }|dv rmt          |           t          |          k    rMt          |                    d          pd                                                              d          }|r|S n# t          $ r Y nw xY wdS )u  Effective OpenAI endpoint for model discovery.

    Mirrors the runtime precedence so discovery probes the SAME endpoint
    inference uses: ``$OPENAI_BASE_URL`` (explicit env override) →
    ``model.base_url`` from config.yaml when the configured provider matches
    → the canonical default. Previously this read the env var only, so a
    config-set data-residency host (``us.api.openai.com``) was ignored and
    the catalog kept coming from ``api.openai.com``.
    OPENAI_BASE_URLr7   r  r  r   r   r   zhttps://api.openai.com/v1)
r.   r  r  r  rj  r    r(   r  r"  r1   )r  env_rawrk  cfg_providercfg_urls        r   _openai_discovery_base_urlrl    s    i)2..4466==cBBG *,,	9==44:;;AACCIIKK3338J88T8TXjkwXxXx8x8x)--
339r::@@BBII#NNG    &&s    CD 
DDc               f  - t          | pd                                                                          }|dk    r|rKt                                           t
                                           t                                           t                      }t          |          }t          d||          }|re|rt          ||          }nt          |          }t          t          |          |pd          }|st                              |          du r|pg S n	 t          d          }t          |                    d          pd                                          }	|	sat          |                    d          pd                                          }
|
r't          j        |
d                                          nd}	t#          |                    d          p|          }t          ||		          }t%          |	||pd          }|pg S t'          |           }|d
k    rt)          |          S |dk    rOddlm} d}	 ddlm}  |d          }|                    d          }n# t2          $ r d}Y nw xY w ||          S |dv r_	 t5          t7                                }|r|S n# t2          $ r Y nw xY w|dk    r(t9          t:                              dg                     S |dk    rq	 ddlm}m}  |            }|r9 ||                    dd          |                    dd                    }|r|S n# t2          $ r Y nw xY wtA                      }|r|S |dk    r	 ddlm!}  |d          }t          |                    d          pd                                          }t          |                    d          pd                                          }|r|rt%          ||          }|r|S n# t2          $ r Y nw xY w|dk    rtE                      }t'          t          |                    dd          pd                    }|dk    rot          |                    dd          pd                                          }t          |                    dd          pd                                          }nd}d}tG          |pd|pd          }|r|r|S t9          t:                              dg                     }t9          |          }d |D             }|D ]T} |                                 |vr<|$                    |            |%                    |                                            U|S t9          t:                              dg                     S |dk    rtM                      }|r|S |dk    rtO          |          pg S |d k    rtQ          |          }|r|S |d!v rt          j        d"d                                          }|rtS          |          }!dd#l*m+}"  |"|!          }#	 t%          ||!          }|rN|#rJd$ |D             -t9          t:                              |g                     }-fd%|D             }$|$r|$S |p|S |S n# t2          $ r Y nw xY w|d&k    r	 ddlm!}  |d&          }t          |                    d          pd                                          }t          |                    d          pd                                          }|r|rt%          ||          }|r|S n# t2          $ r Y nw xY w|d'k    rtY                      }|rtE                      }t          |                    dd          pd                                          p>t          j        d(d          p)t          j        d"d          pt          j        d)d          }t[          |          rd*nd}%t%          |||%+          }|r|S |d,k    r&	 dd-l.m/}&  |&            }'|'|'S n# t2          $ r Y nw xY w|d.k    r(t9          t:                              |g                     S 	 dd/l0m1}( ddlm!}  |(|          })|)r|)j2        dk    r|)j3        r	  ||          }t          |                    d          pd                                          }t          |                    d          pd                                          }n# t2          $ r d|)j3        }}Y nw xY w|s|)j3        }|r|)4                    ||pd0          }|rt9          t:                              |g                     pt9          |)j5        pd1          }|r||tl          v r||}+}*n||}+}*t9          |*          }d2 |*D             }|+D ]J} to          |           |vr7|$                    |            |%                    to          |                      K|S |S |)j5        rt9          |)j5                  S n# t2          $ r Y nw xY wt9          t:                              |g                     },|tp          v r%ts          ||,          }|d3v rtu          |          S |S |,S )4a  Return the best known model catalog for a provider.

    Tries live API endpoints for providers that support them (Codex, Nous),
    falling back to static lists. For providers in ``_MODELS_DEV_PREFERRED``
    (opencode-go/zen, xiaomi, deepseek, smaller inference providers, etc.),
    models.dev entries are merged on top of curated so new models released
    on the platform appear in ``/model`` without a Hermes release.
    r7   r  r  NTr  r  r   r  r  r&  r   r   )get_codex_model_ids)!resolve_codex_runtime_credentials)refresh_if_expiring)rO  >   r   r   r   r   r   )fetch_nous_modelsr  )r  inference_base_urlr   rJ  r   r  r   r  c                6    h | ]}|                                 S ry   r  r|   s     r   r  z%provider_model_ids.<locals>.<setcomp>r  s     777!AGGII777r   r  r!  r  rh  OPENAI_API_KEYis_official_openai_hostc                6    h | ]}|                                 S ry   r  r|   s     r   r  z%provider_model_ids.<locals>.<setcomp>  s     %>%>%>Aaggii%>%>%>r   c                @    g | ]}|                                 v |S ry   r  )r}   r~   
live_lowers     r   r   z&provider_model_ids.<locals>.<listcomp>  s+    #R#R#R!!''))z:Q:QA:Q:Q:Qr   r   r  CUSTOM_API_KEYr  anthropic_messagesapi_moder   )bedrock_model_ids_or_noner   )get_provider_profiler  r   ry   c                ,    h | ]}t          |          S ry   )r^  r|   s     r   r  z%provider_model_ids.<locals>.<setcomp>  s!    'M'M'M(8(;(;'M'M'Mr   >   r   r   );r    r  r  r  clearr  r  r  r  r  r  r  rw  r(   rn  r.   r  rz  fetch_api_modelsr"  r  rn   rn  r  ro  r1   _fetch_github_modelsrT  ro   r  rq  r  r  rK  rj  _fetch_anthropic_modelsr"  r  _fetch_ai_gateway_models_fetch_deepinfra_modelsfetch_ollama_cloud_modelsrl  r;  rw  rN  r  agent.bedrock_adapterr  r9  r  	auth_typer   fetch_modelsfallback_modelsr  r^  rZ  re  r   ).r  r'  r  r   r  
use_nativenative_models
native_keyr?  fallback_keyr  fallback_basefallback_headersr  r&  rn  rO  ro  r  r  rq  r  manifest_idsrK  r  rk  rj  cfg_base_urlcfg_api_keyr  rd  merged_lowerr~   rc  rw  is_default_openaifilteredr~  r  rv   r  _pprimary	secondarycurated_staticrz  s.                                                @r   r  r    s
    HN##))++1133IH 	2&,,...-33555)//111')),X665h
 
 

  	 D 9(G T T T 9( C C0+H55w$ J  + = A A* M MQU U U$** !V
 *8446::i006B77==?? 	M&**Y//5266<<>>G=DL29Wb1177999"L2JJz"".h
 
 6<
 
 
 +$,
 
 

 $"$#H--J\!!}5555^##?????? 	 IIIIII55$OOOE 99Y//LL 	  	  	 LLL	 ""====///	'(H(J(JKKD  	 	 	D	&&(,,Y;;<<<V	[[[[[[[[4466E  ((9b1I1I^c^g^ghrtv^w^wxxx  K 	 	 	D	
 233 	 Y	LLLLLL88CCE%))I..4"55;;==G599Z006B77==??H  8  '::  K 	 	 	D	[  *,,	)#immJ.K.K.Qr*R*RSS;&&y}}Z<<BCCIIKKLimmIr::@bAAGGIIKKLK&!)T'4
 
 
  	  +//R@@AAG']]F77w777L 0 07799L00MM!$$$ $$QWWYY///M$((b99:::\!!')) 	K[   ']CCCIrI^##(}EEE 	K---),b117799 	-j99D EDDDDD 7 7 = ='66  ( /%>%>%>%>%>
"&'7';';J'K'K"L"L $S#R#R#Rw#R#R#R# ,#+O  '$.K     U	LLLLLL88??E%))I..4"55;;==G599Z006B77==??H  8  '::  K 	 	 	D	X')) 	.00I IMM)R006B77==?? 79-r2279-r227 91266	  0WW_/`/`j++fjH#GXIIID 
 Y	GGGGGG++--C
  	 	 	D	 _$$$((R88999
5222222HHHHHH!!*-- .	0",)+++4<<ZHHeii	228b99??AAuyy44:;;AACC 4 4 4$&4 '; " wAQTRR   " ##3#7#7
B#G#GHH D*0bM MG  &%)EEE15wYGG18$YG!%g'M'MW'M'M'M!* F FA/22,FF &a 0 0 0 , 0 01A!1D1D E E E%K! 0B.///    *..z2>>??N***'
NCC---(000s   'I3 3JJJ6 6
KK;AM	 	
MM2BP 
PPAZ0 )Z0 -Z0 0
Z=<Z=B] 
]*)]*7a 
aa.i :A7d2 1i 2ei eC$i -i /i 
iii  i:	 r6  _swr_refresh_inflightc                    t           5   t          v r	 ddd           dS t                                          ddd           n# 1 swxY w Y    fdd	 fd}t          j        |dd                                             dS )
u  Kick a background refresh of *cache_key*'s model-id cache entry.

    Fire-and-forget daemon thread; at most one in flight per cache key.
    Failures are swallowed — the stale entry stays served until a later
    refresh succeeds (same degradation the blocking path already had).

    ``refresh_fn`` (no-args, returns the fresh cache-entry dict or ``None``)
    lets non-slug keys (``custom:<base_url>`` entries from
    :func:`cached_fetch_api_models`) reuse the same inflight-dedupe and
    thread scaffolding. When omitted, *cache_key* is treated as a
    ``PROVIDER_REGISTRY`` slug and refreshed via :func:`provider_model_ids`
    (the original behavior).
    Nc                    t          d          } | sdk    r|t                      }t          |          pd }t          t	          |          |          }t
                              |          du r$t                    t          j                    g dS | sd S t                    t          j                    t          |           dS )NTr&  r  fpatr   )
r  r  r  r  rw  r  r(   _credential_fingerprintrP  ro   )r  r   r  	probe_keyr  s       r   _default_refreshz,_spawn_swr_refresh.<locals>._default_refreshV  s    !)4@@@ 		X--+--H0::BdG/+H55w I -00;;tCC1)<<)++   
  	4))44)++4jj
 
 	
r   rh   rq  c                    	  p            } | r"t                      }| |<   t          |           n-# t          $ r  t                              dd           Y nw xY wt
          5  t                                         d d d            d S # 1 swxY w Y   d S # t
          5  t                                         d d d            w # 1 swxY w Y   w xY w)NzSWR refresh failed for %sT)exc_info)_load_provider_models_cache_save_provider_models_cacher1   r]  r  _swr_refresh_lockr  discard)r:  r_  r  r  
refresh_fns     r   _refreshz$_spawn_swr_refresh.<locals>._refreshl  s}   
	93Z3#366E 3355#(i +E222 	P 	P 	PLL4i$LOOOOO	P # 9 9%--i8889 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9" 9 9%--i8889 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9s\   04 B 'AB AB (BBBC%C CCCCCTzmodel-cache-swr-)ra  rb  r;  rh   rq  )r  r  r  rd  re  rf  )r  r  r  r  s   `` @r   _spawn_swr_refreshr  C  s&    
 - ----- - - - - - - - 	!!),,,- - - - - - - - - - - - - - -

 
 
 
 
,9 9 9 9 9 9 9 9 +Ii+I+I  egggggs   A	A		AAc                 (    ddl m}   |             dz  S )Nr   r]  zprovider_models_cache.jsonr`  r]  s    r   _provider_models_cache_pathr  ~  s(    000000?;;;r   c           	     B	   ddl }ddl}g }	 ddlm} |                    |           }|t          |dd          pdD ]5}|                    | d|j                            |d                      6t          |dd          pd}|r3|                    | d|j                            |d                      n# t          $ r Y nw xY w| d	v r7	 |                    d
t          |                       n# t          $ r Y nw xY w| dk    r|                    d|j                            dd                      t          d          }|                    d|                    dd          p+|                    dd          p|                    dd                      |                    d|                    dd                      |                    d          p|                    d          pd}	|                    d|	            |	r@|                    |	 d|j                            t          |	          d                      t                      }
|                    d|
                    dd           d|
                    dd                      |                    dt          j        |                    di           dt                    z              	 ddlm} dD ]t} |            |z  }	 |                    | d |                                j                    B# t&          $ r |                    | d!           Y ft          $ r Y qw xY wn# t          $ r Y nw xY w|j                            d"          |j                            d#          |j                            d$          |j                            d%          fD ]j}	 |                    |          j        }|                    | d |            8# t&          $ r |                    | d!           Y \t          $ r Y gw xY wd&                    |                              d'd()          }|                    |d*+                                          S ),aw  Return a short hash representing the credentials that
    ``provider_model_ids(provider)`` would see right now.

    Rotating any of the relevant env vars invalidates the cached entry
    for that provider. We hash AT LEAST the api-key + base-url env vars
    declared in ``PROVIDER_REGISTRY``. For OAuth-backed providers
    (codex, copilot, anthropic-via-claude-code, nous portal), the
    relevant tokens live in ``$HERMES_HOME/auth.json`` and external
    credential files. Rather than parse every shape, we additionally
    fold the mtime of those files into the fingerprint so refreshes
    after re-auth bust the cache.
    r   N)PROVIDER_REGISTRYapi_key_env_varsry   =r7   base_url_env_varrh  zeffective_base=r  zOLLAMA_HOST=r}  zproviders.ollama.base_url=r   r|  r  zproviders.ollama.api_key=r  r  r  zproviders.ollama.key_env=zmodel.provider=r  z|model.base_url=zproviders.ollama.extra_headers=r  T)	sort_keysrU   r]  )z	auth.jsonzcredentials.jsonr  z@missingz~/.codex/auth.jsonz~/.claude/.credentials.jsonz#~/.config/github-copilot/hosts.jsonz~/.minimax/credentials.json|rf  r  r  r  r  )r  r.   r  r  r(   getattrr"  rc  r1   rl  rn  r    rj  rj  r  ra  r^  statst_mtime_nsFileNotFoundErrorr/   
expanduserjoinr  r  r  )r  r  _ospartsr  pcfgevbevr  r  rk  r^  relr  r/   mtro  s                    r   r  r    s    NNNE
555555 $$X..d$6;;Ar @ @>>S[__R%<%<>>????$ 2B77=2C B@@ckooc2&>&>@@AAA    +++	LLQ+Eh+O+OQQRRRR 	 	 	D	 8HCKOOM2$F$FHHIII0::p
B//m<3C3CE23N3NmR^RbRbchjlRmRmp p	
 	
 	
 	R1A1A)R1P1PRRSSS""9--V1A1A-1P1PVTV:::;;; 	KLLGIIckooc'llB&G&GIIJJJ*,,	^}}Z,,^ ^>GmmJXZ>[>[^ ^	
 	
 	
 	-j))/2>>$X[\\\]	
 	
 	
4444444 	 	C!!C'A<<affhh&:<<====$ / / /---.....   	    
 	0119::ABB9::	  	$+BLLD2((((  	, 	, 	,LLD***+++++ 	 	 	D	 88E??!!')!<<D ??4Q?//99;;;s~   B.B; ;
CC%C6 6
DD/M- 1L98M- 9"M)M- 	M)&M- (M))M- -
M:9M:&4P"Q?	Q
Qr   c                    	 t                      } |                                 si S t          | d          5 }t          j        |          }ddd           n# 1 swxY w Y   t          |t                    r|ni S # t          $ r i cY S w xY w)z/Return the full cache dict, or {} on any error.rf  rg  N)r  existsri  rj  rk  r-   r   r1   )r/   r  r   s      r   r  r    s    *,,{{}} 	I$))) 	 Q9Q<<D	  	  	  	  	  	  	  	  	  	  	  	  	  	  	 !$--5tt25   			s9   #A< A< AA< AA< A A< <B
Bc                    	 ddl m} t                      }|j                            dd            ||| d           dS # t
          $ r Y dS w xY w)u<   Persist the cache dict. Best-effort — silent on any error.r   r   Trt  Nry  )utilsr   r  r|  r}  r1   )r   r   r/   s      r   r  r    s    ++++++*,,$666$T222222   s   >A 
AAr   c                B   	 t          |           p| pd}|r|sdS t          |          }t          5  t                      }|t	          j                    t          |          d||<   t          |           ddd           dS # 1 swxY w Y   dS # t          $ r Y dS w xY w)uf  Thread-safe single-entry update of the provider-models disk cache.

    Used by parallel prefetch workers so concurrent fetches don't clobber
    each other's writes via read-modify-write races on the shared JSON file.
    Each worker loads the latest cache state under the lock, writes its own
    entry, and saves — best-effort, silent on any error.
    r7   Nr  )r"  r  _cache_write_lockr  rP  ro   r  r1   )r  r   r&  r  r_  s        r   update_provider_cache_entryr    s   '11Ehn"
 	 	F$Z00 	/ 	//11Eikkv,,! !E*
 (...	/ 	/ 	/ 	/ 	/ 	/ 	/ 	/ 	/ 	/ 	/ 	/ 	/ 	/ 	/ 	/ 	/ 	/    s:   B B AB6B BB 
BB 
BB)r'  ttl_secondsr  c               ,   t          | pd                                                                          }|dk    r|nt          |           p| pd}|sg S |dk    rt	          |t
                    }t                      }t          |          }|                    |          }t          j	                    }|dk    }	|sot          |||	          r]||d         z
  }
|
|k     rt          |d                   S |d         r/|
t          k     r$t          |           t          |d                   S t          ||          }|r4||t          |          d||<   t          |           t          |          S |dk    rt!                      }t#          |          pd}t%          t'          |          |          }t(                              |          d	u r||g d||<   t          |           g S t+          |t,                    r^|                    d
          |k    rEt+          |                    d          t                    r|d         rt          |d                   S g S t          ||          rt          |d                   S t          |pg           S )zDisk-cached wrapper around :func:`provider_model_ids`.

    Hits the cache when fresh; otherwise calls the live function and
    persists a non-empty result. Always returns a list (never None).
    r7   r  allow_emptyr  r   r&  r  NTr  )r    r  r  r"  r  r  r  r  r(   rP  _cache_entry_validro    _PROVIDER_MODELS_STALE_SERVE_MAXr  r  r  r  r  r  rw  r  r-   r   )r  r'  r  r  r&  r_  r  r:  rT  allow_empty_ollamarU  r  r   r  r  s                  r   cached_provider_model_idsr    s    HN##))++1133I'833:LX:V:V:j[c[igiJ 	X+'EFF'))E	 	,	,BIIj!!E
)++C#x/ )/rGYZZZ )E$Kh((( ? 	)s%EEE z***h((( jFFFD 4jj
 
j
 	$E***DzzX')),X66>$+'117
 
	 ),,Y774?? (* C CE*'...I
 ud##	)		$2%%599X..55 &h & h(((	
 %$$ %E(O$$$
r   c                ,   	 t                                            t                                           t                                           | 8t	                      }|                                r|                                 dS t                      }t          | pd          	                                
                                }|dk    r|nt          |           p| pd}d}||v r||= d}|rt          |           dS dS # t          $ r Y dS w xY w)zDrop a single provider's cache entry, or wipe the whole cache.

    ``provider=None`` wipes everything; otherwise only that provider's
    entry is removed. Used by ``/model --refresh`` and
    ``hermes model --refresh``.
    Nr7   r  FT)r  r  r  r  r  r  unlinkr  r    r  r  r"  r  r1   )r  r/   r_  r  r&  changeds         r   clear_provider_models_cacher  p  s4   
 	#((***)//111%++---.00D{{}} F+--B''--//5577	"+x"7"7YY>PQY>Z>Z>l^f>ljl
j!G 	/'.....	/ 	/   s   BD A:D 
DDrs  c                   	 ddl m}m} n# t          $ r Y dS w xY w|pd                                p	 |            }|sdS ddi} ||          }|r.d| |d<   dd	l m}m}	m d
                    ||	z             |d<   n||d<   d fd}
	 	  |
|          }n# t          j
        j        $ r}|r|j        dk    r	 |                                                    d                                          }n# t           $ r d}Y nw xY wd|v rDd|v r@d
                    fd|D             t#          |	          z             |d<    |
|          }n  Y d}~nd}~ww xY wd |                    dg           D             }t'          |d           S # t           $ r8}ddl} |j        t,                                        d|           Y d}~dS d}~ww xY w)zFetch available models from the Anthropic /v1/models endpoint.

    Uses resolve_anthropic_token() to find credentials (env vars or
    Claude Code auto-discovery) unless api_key is provided explicitly.
    Returns sorted model IDs or None.
    r   )resolve_anthropic_token_is_oauth_tokenNr7   anthropic-version
2023-06-01r	  r
  )_COMMON_BETAS_OAUTH_ONLY_BETAS_CONTEXT_1M_BETArZ  zanthropic-beta	x-api-keyhr  c                &   t           j                            t                    |           }t	          |          5 }t          j        |                                                                          cd d d            S # 1 swxY w Y   d S )Nr  r  )	r  r  r  r  r   rj  r  r  r  )r  r   r  r   r   s      r   _do_requestz,_fetch_anthropic_models.<locals>._do_request  s    n$$!(++ % 
 
 ,CAAA 	4T:diikk002233	4 	4 	4 	4 	4 	4 	4 	4 	4 	4 	4 	4 	4 	4 	4 	4 	4 	4s   8BB
B
i  ignorer  zlong context betaznot yet availablec                     g | ]
}|k    |S ry   ry   )r}   br  s     r   r   z+_fetch_anthropic_models.<locals>.<listcomp>  s$    KKKqQ:J5J5J5J5J5Jr   c                H    g | ]}|                     d           |d           S rm  r(   r|   s     r   r   z+_fetch_anthropic_models.<locals>.<listcomp>  s+    GGGa155;;G!D'GGGr   r   c                    d| vd| vd| v| fS )Nopussonnethaikury   )r~   s    r   <lambda>z)_fetch_anthropic_models.<locals>.<lambda>  s#    !OA1	-
 r   )r  z$Failed to fetch Anthropic models: %s)r  r  )agent.anthropic_adapterr  r  r<  r  r  r  r  r  r  r  	HTTPErrorcoder  r  r  r1   ro   r(   r   r  r  r  r  )r   r   r  r  r  tokenr  is_oauthr  r  r  r   http_err	body_textr   er  r  s   ``               @r   r  r    s   TTTTTTTTT   tt ]!!##@'>'>'@'@E t2LAGu%%H %#4U#4#4 ^^^^^^^^^^$'HH]=N-N$O$O !!$4 4 4 4 4 4 4%	;w''DD|% 	 	 	 MS((# ( 6 6h 6 G G M M O OII  # # # "III#&)338Ky8X8X03KKKKMKKK01121 1G,- ';w//DD	 DDDD%	. HG488FB#7#7GGGf #
 #
    	    (##))*PRSTTTtttttsm    
B F E0E>:C98E9DEDAEF E4F 
G-GGr7  list[dict[str, Any]]c                    t          | t                    rd | D             S t          | t                    r7|                     dg           }t          |t                    rd |D             S g S )Nc                <    g | ]}t          |t                    |S ry   r-   r   r}   r1  s     r   r   z"_payload_items.<locals>.<listcomp>  s'    CCCJtT,B,BCCCCr   r   c                <    g | ]}t          |t                    |S ry   r  r  s     r   r   z"_payload_items.<locals>.<listcomp>  s'    DDDTZd-C-CDDDDDr   )r-   ro   r   r(   )r7  r   s     r   _payload_itemsr     s{    '4   DCCCCCC'4   E{{62&&dD!! 	EDDTDDDDIr   Tis_agent_turnr  c                h    	 ddl m}  ||           S # t          $ r t          dd| rdnddcY S w xY w)	zStandard headers for Copilot API requests.

    Includes Openai-Intent and x-initiator headers that opencode and the
    Copilot CLI send on every request.
    r   )copilot_request_headersr  zHermesAgent/1.0zconversation-editsagentuser)zEditor-Versionrr  zOpenai-Intentzx-initiator)rP  r  r<  COPILOT_EDITOR_VERSION)r  r  s     r   copilot_default_headersr    sv    	
CCCCCC&&]CCCC 
 
 
4+1&3?77	
 
 	
 	
 	

s    11c                F   t          |                     d          pd                                          }|sdS |                     d          du rdS |                     d          }t          |t                    rRt          |                    d          pd                                                                          }|r|dk    rdS |                     d          }t          |t                    r'd	 |D             }|r|                    h d
          sdS dS )Nrm  r7   Fmodel_picker_enabledcapabilitiestypechatsupported_endpointsc                    h | ]D}t          |                                          #t          |                                          ES ry   )r    r  )r}   r  s     r   r  z6_copilot_catalog_item_is_text_model.<locals>.<setcomp>  sR      
  
  
8}}""$$ 
MM!! 
  
  
r   >   
/responses/v1/messages/chat/completionsT)r    r(   r  r-   r   r  ro   intersection)r1  r  r  
model_typer  normalized_endpointss         r   #_copilot_catalog_item_is_text_modelr    sB   488D>>'R((..00H uxx&''500u88N++L,%% ))&117R88>>@@FFHH
 	*..5((#899%t,, 	 
  
/ 
  
  

   	(<(I(I???)
 )
 	 54r   Optional[list[dict[str, Any]]]_github_model_catalog_cache_github_model_catalog_cache_keyrO   _github_model_catalog_cache_timec                0   t           Ht          | k    r=t          j                    t          z
  t
          k     rt          j        t                     S g }| r*|                    i t                      dd|  i           |                    t                                 |D ]s}t          j                            t          |          }	 t          ||          5 }t          j        |                                                                          }t'          |          }g }t)                      }	|D ]y}
t+          |
          st-          |
                    d          pd                                          }|r||	v rO|	                    |           |                    |
           z|r9t          j        |          a | at          j                    a|cddd           c S 	 ddd           n# 1 swxY w Y   d# t4          $ r Y qw xY wdS )z=Fetch the live GitHub Copilot model catalog for this account.Nr
  r	  r  r  rm  r7   )r  r  rP  rQ  r  _GITHUB_MODEL_CATALOG_CACHE_TTLcopydeepcopyr"  r  r  r  r  COPILOT_MODELS_URLr   rj  r  r  r  r   r6  r  r    r(   r  r  r1   )r  r   attemptsr  r   r  r   r  r   seen_idsr1  r  s               r   fetch_github_model_catalogr"  '  s    	$/+w66^ @@Dccc }8999%'H  
%''
0w00
 
 	 	 	 OO+--...  n$$%7$II	/WEEE "z$))++"4"4"6"677&t,,/1%(UU! ( (D>tDD ! "488D>>#7R88>>@@H# !x8';'; LL***MM$'''' "26-2G2G/6=37;~7G7G4!#" " " " " " " " " """ " " " " " " " " " " " " " "$  	 	 	H	4s=   HD G8H,H8G<	<H?G<	 H
HHzdict[str, int]_copilot_context_cache_copilot_context_cache_timeOptional[int]c                J   t           r<t          j                    t          z
  t          k     r| t           v rt           |          S dS t	          |          }|sdS i }|D ]}t          |                    d          pd                                          }|s;|                    d          pi }|                    d          pi }|                    d          }t          |t                    r|dk    r|||<   |a t          j                    a|                    |           S )	zLook up max_prompt_tokens for a Copilot model from the live /models API.

    Results are cached in-process for 1 hour to avoid repeated API calls.
    Returns the token limit or None if not found.
    Nr  rm  r7   r  limitsmax_prompt_tokensr   )
r#  rP  r$  _COPILOT_CONTEXT_CACHE_TTLr"  r    r(   r  r-   rI  )	r  r  catalogr_  r1  r   rP  r'  
max_prompts	            r   get_copilot_model_contextr,  a  s+     49;;1L#LOi#i#i---)(33t )999G tE $ $$((4..&B''--// 	xx''-2(##)rZZ 344
j#&& 	$:>>#E#J""&)++99Xr   c                    | pd                                                     d                                          }|                    t                    p)|                    d          p|                    d          S )Nr7   r  z"https://models.github.ai/inferencez%https://models.inference.ai.azure.com)r  r  r  r  COPILOT_BASE_URL)r   r&  s     r   _is_github_models_base_urlr/    sx    .b''))0055;;==J.// 	J  !EFF	J  !HIIr   c                    | pd                                                     d          }dD ]D}|                    |          r-|dt          |                                        d          } nE|pdS )a  Return the LM Studio server root for native ``/api/v1`` endpoints.

    Users commonly copy either the OpenAI-compatible runtime URL
    (``.../v1``) or the native API prefix (``.../api`` / ``.../api/v1``).
    Native probes append ``/api/v1/...`` themselves, so normalize all accepted
    forms back to the bare server root to avoid ``/api/api/v1`` requests.
    Returns ``None`` when the base URL is empty/invalid.
    r7   r  )z/api/v1rt  r  N)r  r  r  r   ru  s      r   _lmstudio_server_rootr1    s     N!!##**3//D,  ==   	3v;;,'..s33DE	 <4r   c                r    dt           i}t          | pd                                          }|rd| |d<   |S )z5Build HTTP headers for LM Studio native API requests.rr  r7   r	  r
  )rs  r    r  )r  r  r  s      r   _lmstudio_request_headersr3    sI    /0G2$$&&E 5#4U#4#4 Nr   Optional[list[dict]]c                   t          |          }|sdS t          |           }t          j                            |dz   |          }	 t          ||          5 }t          j        |                                	                                          }ddd           n# 1 swxY w Y   n# t          j
        j        $ re}|j        dv rddlm}	  |	d|j         d	d
d          |ddl}
 |
j        t"                                        d||j                   Y d}~dS d}~wt&          $ r9}ddl}
 |
j        t"                                        d||           Y d}~dS d}~ww xY wt)          |t*                    r|                    d          nd}t)          |t.                    s/ddl}
 |
j        t"                                        d|           dS |S )zFetch the raw model list from LM Studio's ``/api/v1/models``.

    Returns the ``models`` list of dicts on success, ``None`` on network
    errors or malformed responses.  Raises ``AuthError`` on HTTP 401/403.
    Nz/api/v1/modelsr  r  >       r   	AuthErrorz)LM Studio rejected the request with HTTP r  r  auth_rejected)r  r  z)LM Studio probe at %s failed with HTTP %sz LM Studio probe at %s failed: %sr   zCLM Studio probe at %s returned malformed payload (no `models` list))r1  r3  r  r  r  r   rj  r  r  r  r  r  r  r  r9  r  r  r  r  r1   r-   r   r(   ro   )r  r   r   server_rootr  r  r  r7  r  r9  r  r  s               r   _lmstudio_fetch_raw_modelsr<    sl    (11K t'00Gn$$[3C%CW$UUG+GWEEE 	7j!3!3!5!566G	7 	7 	7 	7 	7 	7 	7 	7 	7 	7 	7 	7 	7 	7 	7<!   8z!!111111)GCHGGG#$   	
 	(##))7ch	
 	
 	
 ttttt   (##)).S	
 	
 	
 ttttt +5Wd*C*CMX&&&Jj$'' (##))Q	
 	
 	
 tsI   B* 9BB* B""B* %B"&B* *E$>ADE$+.EE$c                   t          | ||          }|dS g }|D ]}t          |t                    st          |                    d          pd                                                                          dk    ret          |                    d          p|                    d          pd                                          }|r||vr|                    |           |S )a  Probe LM Studio's model listing.

    Returns chat-capable model keys on success, including the valid empty-list
    case when the server is reachable but has no non-embedding models.
    Returns ``None`` on network errors, malformed responses, or empty/invalid
    base URLs.

    Raises ``AuthError`` on HTTP 401/403 so callers can surface token issues
    separately from reachability problems.
    r  r   r   Nr  r7   	embeddingr  rm  )r<  r-   r   r    r(   r  r  r"  )r  r   r   r  r   r  r  s          r   probe_lmstudio_modelsr@    s     ,GhX_```JtD  #t$$ 	swwv$"%%++--3355DD#''%..7CGGDMM7R88>>@@ 	3d??KKKr   c                .    t          | ||          }|pg S )u  Fetch LM Studio chat-capable model keys from native ``/api/v1/models``.

    Returns a list of model keys (e.g. ``publisher/model-name``) with embedding
    models filtered out. Returns an empty list on network errors, malformed
    responses, or empty/invalid base URLs.

    Raises ``AuthError`` on HTTP 401/403 so callers can distinguish a missing
    or wrong ``LM_API_KEY`` from an unreachable server — the most common
    LM Studio support case once auth-enabled mode is turned on.
    r>  )r@  )r  r   r   r   s       r   fetch_lmstudio_modelsrB    s"     #7XwWWWF<Rr   c                  :    e Zd ZU dZded<   dZded<   dZded<   dS )	LMStudioLoadResultz8Verified LM Studio runtime plus load-attempt provenance.r%  context_lengthFr	  load_attemptedrejectedN)r  r  r  __doc__r  rF  rG  ry   r   r   rD  rD    sF         BB!!!! N    Hr   rD  )return_load_resultr  target_context_lengthrI  "Optional[int] | LMStudioLoadResultc                    dddd%fd
}d&dd'fd}d( fd}t          |          }	|	s |d          S  |          }
||
 |d          S t          |          }	 t          ||d          }n# t          $ r d}Y nw xY w| |d          S  ||          }| |d          S  |                    d                    }|
||
|k    r |dd          S  ||          }| ||          S |                    d          }t          |t                    r|r |d          S  dd}|
|
|d<   t          j        |          	                                }t          |          }d|d<   	 t          j                            |	dz   ||d !          }t          ||"          5 }|                                }ddd           n# 1 swxY w Y   n# t          $ r  |dd#          cY S w xY w	 t          j        |                                          }n# t          $ r d}Y nw xY wt          |t                    r|                    d$          nd}t          |t                    r |                    d                    nd}| ||d#          S 	 t          ||d          }n# t          $ r d}Y nw xY w| |dd#          S  ||          }| ||          nd} ||d#          S ))a#  Ensure ``model`` is loaded and return verified runtime context.

    Existing loaded-instance context is authoritative. Cold loads omit
    ``context_length`` unless the caller supplied an explicit override; the
    returned context must come from LM Studio's echoed or refreshed state.
    F)rF  rG  rE  r%  rF  r	  rG  rh   rK  c               2    t          | ||          }r|n| S r   )rD  )rE  rF  rG  ry  rI  s       r   _resultz-ensure_lmstudio_model_loaded.<locals>._result%  s%     #>>8LL*>uu>r   ry  r   c                j    t          | t                    rt          | t                    s| dk    r| S d S r  )r-   rI  r	  )ry  s    r   _positive_intz3ensure_lmstudio_model_loaded.<locals>._positive_int.  s7    eS!! 	*UD*A*A 	eaiiLtr   r:  r   c                <   |                      d          }t          |t                    sd S |D ]k}t          |t                    r|                     d          nd }t          |t                    r|                     d          nd } |          }||c S ld S )Nloaded_instancesr?  rE  )r(   r-   ro   r   )r:  	instancesinstancer?  contextr  rP  s         r   _loaded_contextz5ensure_lmstudio_model_loaded.<locals>._loaded_context3  s    II011	)T** 	4! 	 	H/9(D/I/ISX\\(+++tF6@6N6NXfjj!1222TXG"]7++F! "tr   r  
list[dict]Optional[dict]c                    | D ]M}t          |t                    r6|                    d          k    s|                    d          k    r|c S Nd S )Nr  rm  )r-   r   r(   )r  r  r  s     r   _find_entryz1ensure_lmstudio_model_loaded.<locals>._find_entry?  s\     	 	C#t$$ #''%..E*A*ASWWT]]V[E[E[


tr   N
   r>  max_context_lengthT)rG  rR  )r  echo_load_configr  zContent-Typez/api/v1/models/loadPOST)r   r  methodr  )rF  r7  )rE  r%  rF  r	  rG  r	  rh   rK  )ry  r   rh   r%  )r:  r   rh   r%  )r  rW  rh   rX  )r1  r3  r<  r1   r(   r-   ro   rj  r  r  r   r  r  r  r   r  r  r  )r  r   r  rJ  r   rI  rN  rV  rZ  r;  explicit_contextr  r  target_entrymax_ctxcurrent_contextrR  load_payloadbodyload_headersload_requestr  response_bodyresponse_payloadr7  applied_contextrefreshed_modelsrefreshed_entryrefreshed_contextrP  s   `    `                       @r   ensure_lmstudio_model_loadedrn    s   &  %	? ? ? ? ? ? ? ?   

 
 
 
 
 
      (11K wt}}$}%:;;(-=-Ewt}}'00G/(\^___

   


wt}};z**Lwt}}mL,,-ABBCCG#(;@PSZ@Z@Zwtd++++%ol33O"w'''#''(:;;&-- 1A wt}}-2#M#ML#)9%&:l##**,,D==L#5L 
2~--// 	 . 
 
 ,L'JJJ 	(d IIKKM	( 	( 	( 	( 	( 	( 	( 	( 	( 	( 	( 	( 	( 	( 	( 2 2 2wtD1111112 :m&:&:&<&<==       9CDTVZ9[9[e"&&}555aeK k4((	koo&677888 
 "wt<<<< 5gPXbdeee       wtD1111!k"233O<K<W888]a7$T::::sl   $A7 7BB97G 0GG GG GG G76G7;&H" "H10H1%J8 8KKc                   	 t          |||          }n# t          $ r d}Y nw xY w|sg S |D ]}t          |t                    s|                    d          | k    r|                    d          | k    rK|                    d          }t          |t                    r|                    d          nd}t          |t                    r|                    d          nd}t          |t
                    rd |D             c S g c S g S )	a$  Return the reasoning ``allowed_options`` LM Studio publishes for ``model``.

    Pulls ``capabilities.reasoning.allowed_options`` from ``/api/v1/models``.
    Returns ``[]`` when the model is unknown, the endpoint is unreachable,
    or the model does not declare a reasoning capability.
    r>  Nr  rm  r  r9  allowed_optionsc                    g | ]J}t          |t                    t          |                                                                          KS ry   )r-   r    r  r  )r}   os     r   r   z4lmstudio_model_reasoning_options.<locals>.<listcomp>  sA    OOOqJq#<N<NOCFFLLNN((**OOOr   )r<  r1   r-   r   r(   ro   )	r  r   r  r   r  r  rP  r9  optss	            r    lmstudio_model_reasoning_optionsrt    s7   /(\cddd

   


 	 
 
#t$$ 	775>>U""swwt}}'='=ww~&&-7d-C-CMDHH[)))	3=i3N3NXy}}.///TXdD!! 	POODOOOOOO			Is    $$c                   ddl }|pd                                                    d          }|                    d          r
|dd         }|sdS t	          | pd                                          }|sdS t          |pd                                          }|rdd| ini }	 |                    ||	          5 }	|	                    | d
d|i          }
|
j        dk    r	 ddd           dS |
	                                
                    d          }t          |t                    rd|v cddd           S 	 ddd           n# 1 swxY w Y   n# t          $ r Y dS w xY wdS )u  Return True if an Ollama (Cloud or local) model advertises ``thinking``.

    Probes the native ``/api/show`` endpoint and checks the ``capabilities``
    list, which Ollama populates from the model's metadata (e.g.
    ``deepseek-v4-pro`` → ``["completion", "tools", "thinking"]`` while
    ``gemma3:27b`` → ``["completion", "vision"]``). This is the authoritative
    capability source — the OpenAI-compat ``/v1/models`` endpoint omits it.

    Returns:
        True  — the model declares the ``thinking`` capability.
        False — ``/api/show`` succeeded but the model has no ``thinking`` cap.
        None  — the probe failed (unreachable / non-Ollama / error); the caller
                decides the fallback (we treat None as "don't emit").
    r   Nr7   r  r  r  r
  r	  r  z	/api/showr;  )rj     r  thinking)httpxr  r  r  _strip_ollama_cloud_suffixr    Clientpoststatus_coderj  r(   r-   ro   r1   )r  r   r  r   rx  
server_url
bare_modelr  r  r  r  rP  s               r   ollama_model_supports_thinkingr    s   ( LLL.b''))0055J5!! %_
 t+U[b,?,?,A,ABBJ t2$$&&E6;C 1% 1 122G	\\'7\;; 	*v;;*777vz>R;SSD3&&	* 	* 	* 	* 	* 	* 	* 	* 99;;??>22D$%% *!T)	* 	* 	* 	* 	* 	* 	* 	*
*	* 	* 	* 	* 	* 	* 	* 	* 	* 	* 	* 	* 	* 	* 	*    tt4sH   'E >)E'E 4?E3E E EE EE 
E'&E'c                D    t          | |          }|sd S d |D             S )Nr  r   c                d    g | ]-}|                     d           |                     d d          .S rm  r7   r  r  s     r   r   z(_fetch_github_models.<locals>.<listcomp>  s5    EEE4dhhtnnEDHHT2EEEr   )r"  )r  r   r*  s      r   r  r    s5    ('JJJG tEE7EEEEr   zopenai/gpt-5r   zopenai/gpt-5-chatzopenai/gpt-5-minizopenai/gpt-5-nanozopenai/gpt-4.1r   zopenai/gpt-4.1-minizopenai/gpt-4.1-nanozopenai/gpt-4or   zopenai/gpt-4o-minir   	openai/o1r   zopenai/o1-minizopenai/o1-preview	openai/o3r   zopenai/o3-minizopenai/o4-minirc   zclaude-opus-4.6r;   r   rb   r   zanthropic/claude-sonnet-4r   zanthropic/claude-sonnet-4.5r   r=   r   r   r   zclaude-sonnet-4-0r   r   zanthropic/claude-opus-4-6zanthropic/claude-sonnet-4-6zanthropic/claude-sonnet-4-0zanthropic/claude-sonnet-4-5zanthropic/claude-haiku-4-5r*  c                b    | |rt          |          } | st                      S d | D             S )Nr  c                    h | ]n}t          |                    d           pd                                          8t          |                    d           pd                                          oS r  )r    r(   r  r  s     r   r  z'_copilot_catalog_ids.<locals>.<setcomp>  st       txx~~#$$**,,DHHTNN b!!''))  r   )r"  r6  r*  r  s     r   _copilot_catalog_idsr    sO     7,W=== uu    r   r  c               p   t          | pd                                          }|sdS t          ||          }t                              |          }|r|S |g}d|v rA|                    |                    dd          d                                                    |                    d          r|                    |d d                    |                    d          r|                    |d d                    |                    d          r|                    |d d                    t                      }|D ]>}|r||v r	|	                    |           |t          v rt          |         c S ||v r|c S ?d|v r.|                    dd          d                                         S |S )	Nr7   r  r  r   z-miniz-nanoz-chat)
r    r  r  _COPILOT_MODEL_ALIASESr(   r"  r  r  r6  r  )	r  r*  r  r  catalog_idsrQ  r  r1  	candidates	            r   normalize_copilot_model_idr  !  s    hn"


#
#
%
%C r&wHHHK"&&s++E J
czz#))C++A.4466777
||G $#crc(###
||G $#crc(###
||G $#crc(###UUD  	 	I--...))4444## $ czzyya  #))+++Jr   c                <   | pd                                                                 }|                    d          rt          t                    S t          |                                           }|                    d          rt          t                    S g S )Nr7   )r  r  z	openai/o4r0  r1  r2  r   )r  r  r  ro   "COPILOT_REASONING_EFFORTS_O_SERIESr  COPILOT_REASONING_EFFORTS_GPT5)r  r  r&  s      r   &_github_reasoning_efforts_for_model_idr  J  s    >r
 
 
"
"
(
(
*
*C
~~OPP 86777+H55;;==JW%% 42333Ir   c                    ddl } |j        d|           }|sdS t          |                    d                    }|dk    o|                     d           S )a%  Decide whether a Copilot model should use the Responses API.

    Replicates opencode's ``shouldUseCopilotResponsesApi`` logic:
    GPT-5+ models use Responses API, except ``gpt-5-mini`` which uses
    Chat Completions.  All non-GPT models (Claude, Gemini, etc.) use
    Chat Completions.
    r   Nz
^gpt-(\d+)Fr      r   )rematchrI  r  r  )r  r  r  majors       r   !_should_use_copilot_responses_apir  T  sd     IIIBH]H--E uAEA:?h11,????r   c               |    ||rt          |          }t          | ||          }|sdS t          |          rdS dS )zDetermine the API mode for a Copilot model.

    Uses the model ID pattern (matching opencode's approach) as the
    primary signal.  Falls back to the catalog's ``supported_endpoints``
    only for models not covered by the pattern check.
    Nr  r  chat_completionscodex_responses)r"  r  r  )r  r*  r  r&  s       r   copilot_model_api_moder  e  s_     7,W===+HgwWWWJ "!! )44 !   r   )r5  r   r0  r1  r2  c                    t          | pd                                                                          }|sdS d|v r|                    dd          d         }t          D ]}|                    |          r dS dS )u  Infer Azure Foundry api_mode from a deployment/model name.

    Returns ``"codex_responses"`` when the model name matches a family that
    only accepts the Responses API on Azure Foundry (GPT-5.x, codex, o1/o3/o4
    reasoning models).  Returns ``None`` otherwise — the caller should fall
    back to the configured/default api_mode (typically ``chat_completions``)
    so GPT-4o, GPT-4 Turbo, Llama, Mistral, etc. keep working.

    Intentionally does NOT return ``anthropic_messages``; Anthropic-style
    Azure endpoints are disambiguated by URL (``/anthropic`` suffix) in
    ``runtime_provider._detect_api_mode_for_url`` and by the user setting
    ``model.api_mode: anthropic_messages`` explicitly.
    r7   Nr  r   r  )r    r  r  r  !_AZURE_FOUNDRY_RESPONSES_PREFIXESr  )r  r  rd  s      r   azure_foundry_model_api_moder    s     jB


%
%
'
'
-
-
/
/C t
czzjja  $ 4 % %>>&!! 	%$$$	%4r   provider_idc                ,   t          | pd                                                                          }|sdS t          |           }|dv r|S |                    d          rdS |                    d          rdS |                    d          rdS dS )u  Resolve a provider id to its OpenCode family, or None.

    Returns ``"opencode-zen"`` or ``"opencode-go"`` for the built-in
    providers AND for custom providers whose name extends a family slug
    (e.g. ``opencode-go-bridge`` pointing at ``https://opencode.ai/zen/go/v1``,
    issue #85589). Matching is case-insensitive. Custom family providers
    need the same per-model api_mode routing and /v1 base-url normalization
    as the built-ins — this predicate is the single owner of that
    family-membership question; do not re-implement it inline.

    ``opencode-go`` is checked before ``opencode-zen`` but the two slugs are
    not prefixes of each other, so order is cosmetic.
    r7   N>   r   r   r   r   r   r   )r    r  r  r"  r  )r  r  rR  s      r   opencode_provider_familyr    s     kR
 
 
&
&
(
(
.
.
0
0C t";//IDDD
~~o&& 
~~m$$ }
~~n%% ~4r   c                   t          |           }t          |pd                                          }|r||S | r|  dn| d}|                                                    |                                          r|t          |          d         S | d}|                                                    |                                          r|t          |          d         S |S )zJNormalize OpenCode config IDs to the bare model slug used in API requests.r7   Nr  )r  r    r  r  r  r   )r  r  r  currentrd  fallback_prefixs         r   normalize_opencode_model_idr    s    %k22F(.b!!''))G fn"-?f<<<F}}!!&,,..11 %s6{{||$$lllO}}!!/"7"7"9"9:: .s?++,,--Nr   opencode-zen-free-keylesszhttps://opencode.ai/zen/v1r   c                    t          | pd                                                              dd          d                                         }|sdS |                    d          p|t
          v S )aj  True when ``model_id`` is an OpenCode Zen free-tier slug.

    Matches the ``*-free`` suffix plus the known unsuffixed free slugs
    (``big-pickle``). Tolerates provider-prefixed ids
    (``opencode-zen/x-preview-f-free``). The Go catalog serves no free
    models (verified 2026-08-21), so this identifies the Zen free tier
    across the OpenCode family.
    r7   r  r   r  Fz-free)r    r  r  r  r  _OPENCODE_KEYLESS_EXTRA_SLUGS)r  bares     r   is_opencode_zen_free_modelr    sl     x~2$$&&--c155b9??AAD u==!!JT-J%JJr   c                 J    	 ddl m}  n# t          $ r d} Y nw xY wdddd|  dS )	uj  Client default_headers for anonymous OpenCode Zen free-tier requests.

    ``Authorization: ""`` overrides the OpenAI SDK's ``Bearer <api_key>``
    header so the placeholder key never reaches the wire — the Zen relay
    accepts anonymous requests for free models but 401s any unknown bearer.
    Attribution headers mirror the opencode provider profile.
    r   r   r-  r7   z%https://hermes-agent.nousresearch.comzHermes AgentzHermesAgent/)r
  zHTTP-RefererzX-Titlerr  )
hermes_clir   r1   )_vs    r   opencode_zen_free_headersr     se    0000000    ?!)R))	  s   	 rX  c                   t          |           }|dS |dk    r]t          | |                                                                          }|d t                              dg           D             vrdS t          | |          }t          d|          }t          d|t                    }|||t          t                      ddS )u  Keyless runtime entry for an OpenCode Zen free-tier model, or None.

    Returns a resolve_runtime_provider-shaped dict pinning the request to the
    Zen relay with the keyless placeholder whenever:

    - ``provider_id`` is ``opencode-free`` (the dedicated keyless provider —
      EVERY model on it routes anonymously; that is the provider's contract), or
    - ``provider_id`` is any other OpenCode-family provider and ``model_id``
      is in the VERIFIED keyless catalog (``_PROVIDER_MODELS["opencode-free"]``)
      — heals a free-model selection made under opencode-zen/opencode-go,
      whose keys the free tier rejects.

    Membership, not the ``-free`` suffix, is the heal criterion: the suffix
    stopped being a reliable keyless signal when ``ox-alpha-free`` appeared
    on the Go relay as a KEYED subscription model (2026-08-21) — suffix-based
    healing would have routed it to a Zen relay that doesn't serve it.
    Nr   c                6    h | ]}|                                 S ry   r  r|   s     r   r  z,opencode_zen_free_runtime.<locals>.<setcomp>+  s     UUUa		UUUr   r   r  )r  r~  r   r  default_headerssource)r  r  r  r  r  r(   opencode_model_api_modenormalize_opencode_base_url_OPENCODE_ZEN_FREE_BASE_URL%OPENCODE_ZEN_FREE_KEYLESS_PLACEHOLDERr  )r  r  r  r  r&  r~  r   s          r   opencode_zen_free_runtimer    s    $ &k22F~t  *;AAGGIIOOQQUU+;+?+?QS+T+TUUUUU4,[(CCJ&~zBBH*"= H 8466-  r   c                d   t          |           }|dk    rd}t          | |                                          }|sdS |dk    rs|                    d          s|                    d          rdS |                    d          rdS |                    d	          rd
S |                    d          rd
S dS |dk    rs|                    d          rd
S |                    d          s|                    d          rdS |                    d          rdS |                    d          rd
S dS dS )a,  Determine the API mode for an OpenCode Zen / Go model.

    OpenCode routes different models behind different API surfaces:

    - GPT-5 / Codex / Grok models on Zen use ``/v1/responses``
    - GPT / Grok models on Go (gpt-5.6-luna, grok-4.5) use ``/v1/responses``
    - Muse Spark on Go and Zen uses ``/v1/responses`` (chat/completions 503s)
    - Claude models on Zen use ``/v1/messages``
    - MiniMax and Qwen models on Go use ``/v1/messages``
    - GLM / Kimi / DeepSeek / MiMo on Go use ``/v1/chat/completions``
    - Qwen models on Zen use ``/v1/messages``
    - Other Zen models (Gemini, GLM, Kimi, MiniMax, DeepSeek, etc.) use
      ``/v1/chat/completions``

    This follows the published OpenCode docs for Zen and Go endpoints
    (https://opencode.ai/docs/zen/ and https://opencode.ai/docs/go/).
    r   r   r  r   r/  grok-r  z
muse-sparkzminimax-r|  r  rB  )r  r  r  r  )r  r  r  r&  s       r   r  r  <  s   $ &k22F   ,[(CCIIKKJ "!!  (( 	%J,A,A',J,J 	% %$  .. 	% %$  ,, 	(''  (( 	( ('!!  ++ 	(''  (( 	%J,A,A',J,J 	% %$  .. 	% %$  (( 	( ('!!r   r~  c                   t          |pd                                                              d          }|s|S t          |           |S ddl}|dk    r|                    dd|          S |                    d          r|S 	 t          j        	                    |          j
                                        }n# t          $ r d}Y nw xY w|dk    s|                    d	          r|dz   S |S )
u  Normalize an OpenCode Zen / Go base URL for the target API mode.

    OpenCode's OpenAI-compatible endpoints live under ``/v1`` (the OpenAI SDK
    appends ``/chat/completions`` or ``/responses``), while the Anthropic SDK
    appends its own ``/v1/messages`` — so anthropic_messages needs the ``/v1``
    suffix stripped.

    Crucially this must be SYMMETRIC.  The stripped URL gets persisted to
    config (``model.base_url``) by the TUI/desktop and gateway after switching
    into an anthropic-routed model (e.g. minimax-m2.7 on Go).  A later switch
    to a chat_completions model (glm, deepseek, kimi) then inherited the
    stripped URL and POSTed to ``https://opencode.ai/zen/go/chat/completions``
    — a 404 (the marketing site).  Re-append ``/v1`` for non-anthropic modes
    so previously-stripped URLs heal themselves.

    Only opencode.ai-hosted URLs are re-suffixed; custom proxy overrides via
    ``OPENCODE_*_BASE_URL`` are left alone unless they already carry ``/v1``.
    r7   r  Nr   r|  z/v1$r  zopencode.aiz.opencode.ai)r    r  r  r  r  r  r  r  r  r  r  r  r1   )r  r~  r   r  _rer  s         r   r  r    s   * hn"


#
#
%
%
,
,S
1
1C 
,,4
'''wwwC((( ||E 
|$$S))06688   }n = =U{Js   6B< <C
Cc                 
 t          | ||          

sg S d}|t          
fd|D             d          }n0|r.t          |          }|rt          
fd|D             d          }||                    d          }t	          |t
                    r|                    d          }t	          |t
                    r]|                    d          }t	          |t                    r3d	 |D             }t          t
                              |                    S g S d
 |                    dg           D             }	d|	vrg S t          t          | p
                    S )zEReturn supported reasoning-effort levels for a Copilot-visible model.r  Nc              3  N   K   | ]}|                     d           k    |V   dS rm  Nr  r}   r1  r&  s     r   rA  z1github_model_reasoning_efforts.<locals>.<genexpr>  s7      WWt$((4..J:V:Vd:V:V:V:VWWr   r  c              3  N   K   | ]}|                     d           k    |V   dS r  r  r  s     r   rA  z1github_model_reasoning_efforts.<locals>.<genexpr>  s8      !c!c4dhhtnnXbFbFb$FbFbFbFb!c!cr   r  supportsreasoning_effortc                    g | ]V}t          |                                          #t          |                                                                          WS ry   r>  r?  s     r   r   z2github_model_reasoning_efforts.<locals>.<listcomp>  s^     * * *"v;;,,..*F))++1133* * *r   c                    h | ]V}t          |                                          #t          |                                                                          WS ry   r>  )r}   
capabilitys     r   r  z1github_model_reasoning_efforts.<locals>.<setcomp>  s^     
 
 
:$$&&

OO!!##))++
 
 
r   r9  )
r  r  r"  r(   r-   r   ro   rB  r  r    )r  r*  r  catalog_entryfetched_catalogr  r  rC  normalized_effortslegacy_capabilitiesr&  s             @r   github_model_reasoning_effortsr    s    ,HgwWWWJ 	MWWWWwWWWY]^^	 k4WEEE 	k !c!c!c!c?!c!c!ceijjM $((88lD)) 	#''
33H(D)) C",,'9::gt,, C* *&-* * *&
  .@ A ABBBI
 
+//CC
 
 

 111I1#h6L*2M2MNNNr   r  c                   |pd                                                     d          }|sddddddS t          |          r#t          | |          }|t          t
          dddS |                    d          r|dd                             d          }n|dz   }|dfg}|r||k    r|                    |d	f           g }	d
t          i}
t          j
                            |          j        dk    rdt           |
d<   | r|dk    r| |
d<   d|
d<   n
| rd|  |
d<   |                    t
                    r!|
                    t!                                 t#          |t$                    r$ddlm} |
                     ||                     t+          |          }|D ]\  }}|                    d          dz   }|	                    |           t          j                            ||
          }d|i}|||d<   	 t1          |fi |5 }t3          j        |                                                                          }d |                    dg           D             ||                    d          ||k    r|n||dcddd           c S # 1 swxY w Y   # t<          $ r Y w xY wd|	r|	d         n|                    d          dz   |||k    r|ndddS )a8  Probe a ``/models`` endpoint with light URL heuristics.

    For ``anthropic_messages`` mode, uses ``x-api-key`` and
    ``anthropic-version`` headers (Anthropic's native auth) instead of
    ``Authorization: Bearer``.  The response shape (``data[].id``) is
    identical, so the same parser works for both.
    r7   r  NF)r   
probed_urlresolved_base_urlsuggested_base_urlused_fallbackr  r  r  Trr  z!generativelanguage.googleapis.comzhermes-agent/zX-Goog-Api-Clientr|  r  r  r  r	  r
  r   r  r   r  r   r   c                :    g | ]}|                     d d          S r  r  r|   s     r   r   z$probe_api_models.<locals>.<listcomp>$  s$    MMM1quuT2MMMr   r   )r  r  r/  r  r  r.  r  r"  rs  r  r  r  r  _HERMES_VERSIONr  r  r  r-   r   r&   r  r5   r  r  r   rj  r  r  r  r(   r1   )r  r   r   r~  r  r&  r   alternate_baser  triedr  r  _ssl_contextcandidate_baseis_fallbackr  r   _open_kwargsr  r   s                       r   probe_api_modelsr    s    .b''))0055J 
!#"&"
 
 	
 "*-- 
%gwGGG,!1"&"
 
 	
 5!! ,#CRC//44#e++5u*=)>J 2.J66>40111E+-?@G|Z((15XXX'H'H'H#$ 78333&'3#$$	 7#6W#6#6 -.. 2.00111/4(( A 	>=====..??@@@/
;;L'1  ###C((94Sn$$S'$:: )27';#*6L'	/DD|DD z$))++"4"4"6"677MM8L8LMMM"%)7)>)>s)C)C<Jn<\<\..bl%0                     	 	 	H	 "'OeAhhZ->->s-C-Ci-O'0>*0L0LnnRV  s7   J&A9J
J&J	J& J	!J&&
J43J4z^(?i)(embed|rerank|whisper|stable-diffusion|flux|sdxl|tts|bark|speech|image-gen|clip|vit-|dpt-)>   	image-gen	video-gensttttsr  embed_DEEPINFRA_SURFACE_TAGSz#https://api.deepinfra.com/v1/openaizfilter=true&sort_by=hermeszdict[str, list[dict]]_deepinfra_catalog_cache_deepinfra_catalog_neg_cacheg      N@c                     t          j        dd                                          pt          } |                     d          }|| dt
           fS )zDReturn ``(cache_key, full_url)`` for the DeepInfra catalog endpoint.DEEPINFRA_BASE_URLr7   r  z/models?)r.   r  r  _DEEPINFRA_DEFAULT_BASE_URLr  _DEEPINFRA_MODELS_QUERY)rc  r  s     r   _deepinfra_catalog_urlr  Z  sQ    9)2..4466U:UDC  IEE,CEEEEr   r   r'  c                ~   t                      \  }}|sS|t          v rt          |         S t                              |          }|!t	          j                    |z
  t          k     rdS dt          i}t          j	        dd          
                                }|rd| |d<   t          j                            ||          }	 t          ||           5 }t          j        |                                                                          }	ddd           n# 1 swxY w Y   n,# t&          $ r t	          j                    t          |<   Y dS w xY w|	                    d	          }
t)          |
t*                    st	          j                    t          |<   dS |
t          |<   t                              |d           |
S )
a  Fetch the raw DeepInfra catalog list with module-level caching.

    The endpoint serves chat + embed + image-gen + tts + stt models in one
    response. Authentication is optional but Bearer-attached when available
    so user-scoped catalogs (private fine-tunes etc.) are visible.
    Nrr  DEEPINFRA_API_KEYr7   r	  r
  r  r  r   )r  r  r  r(   rP  rQ  _DEEPINFRA_CATALOG_NEG_TTLrs  r.   r  r  r  r  r  r   rj  r  r  r  r1   r-   ro   r  )r   r'  r  r  	last_failr  r  r   r  r7  r   s              r   _fetch_deepinfra_catalogr  a  s    ,--NIs 000+I66044Y??	 dn&6&6&BF`%`%`4+-?@Gi+R006688G 7#6W#6#6 
.
 
 g
 
6
6C+CAAA 	7Tj!3!3!5!566G	7 	7 	7 	7 	7 	7 	7 	7 	7 	7 	7 	7 	7 	7 	7   26.2B2B$Y/tt ;;vDdD!! 26.2B2B$Y/t*.Y' $$Y555Ks6   D% 9DD% DD%  D!D% %%EEtagc                  t          ||          }|dS g }|D ]}|                    d          }|s|                    d          }|2t          |t                    r|ni }|                    d          }	t          |	t                    r|	ng }
t          d |
D                       }|r| |
v r|                    ||d           | dk    r2t                              |          s|                    ||d           |S )	a  Return DeepInfra models whose ``metadata.tags`` includes *tag*.

    Each returned item is ``{"id": str, "metadata": dict}`` so callers can
    inspect context length, pricing, default dimensions (image-gen),
    pricing units (tts ``input_characters``, stt ``input_seconds``), etc.

    For the chat surface, items without any ``tags`` field fall through
    to the legacy name-regex exclusion so this keeps working while the
    tag rollout (mid-2026) is still in flight.

    Returns ``None`` on network failure.
    r  Nrm  metadatatagsc              3  (   K   | ]}|t           v V  d S r   )r  )r}   ts     r   rA  z1_fetch_deepinfra_models_by_tag.<locals>.<genexpr>  s(      IIqa#::IIIIIIr   )rm  r  r  )	r  r(   r-   r   ro   r  r"  _DEEPINFRA_EXCLUDE_REsearch)r  r   r'  r   r  r1  r   raw_metadatar  raw_tagsr  has_surface_tags               r   _fetch_deepinfra_models_by_tagr    s3   $ $G=QQQD|tG > >hhtnn 	 xx
++#-lD#A#AI<<r<<''%h55=xx2IIDIIIII 	d{{cx@@AAA &==!6!=!=c!B!B=NN#8<<===Nr   c               J    t          d| |          }|dS d |D             pdS )ab  Return DeepInfra chat-model ids (tag-aware, regex fallback).

    Thin wrapper over :func:`_fetch_deepinfra_models_by_tag` so historical
    callers in :func:`provider_model_ids` keep their string-list contract.
    Returns ``None`` on network failure, an empty list if the catalog
    contains no chat-tagged ids (which would itself be surprising).
    r  r  Nc                    g | ]
}|d          S r  ry   r  s     r   r   z+_fetch_deepinfra_models.<locals>.<listcomp>      )))4DJ)))r   r  )r   r'  r  s      r   r  r    sD     +}  E }t))5)))1T1r   c               D    t          | |          }|rd |D             ng S )u(  Return DeepInfra model ids carrying surface *tag* (``[]`` on failure).

    Single source of truth for the per-surface model shims (TTS/STT/vision),
    replacing the copy-pasted ``import _fetch_deepinfra_models_by_tag → fetch
    → [item["id"] …]`` wrapper each of them used to carry.
    r&  c                    g | ]
}|d          S r  ry   r  s     r   r   z'deepinfra_model_ids.<locals>.<listcomp>  r  r   r  )r  r'  r  s      r   deepinfra_model_idsr    s5     +3mLLLE-2:))5)))):r   sectionc                    t          | t                    r|                     d          nd}|pt          j        d          pt
          }t          |                                                              d          S )u`  Resolve the DeepInfra OpenAI-compatible base URL, normalized.

    Precedence: config-section ``base_url`` → ``DEEPINFRA_BASE_URL`` env →
    default. Always stripped with any trailing slash removed. Single source
    of truth for the base-URL chain the TTS/STT/image/video shims each used
    to re-code (with subtly divergent normalization).
    r   Nr  r  )	r-   r   r(   r.   r  r  r    r  r  )r  r  ry  s      r   deepinfra_base_urlr    sj     ,6gt+D+DNJ'''$IW#788W<WEu::$$S)))r   c               j   t          d| |          }|si S i }|D ]}|                    d          pi }t          |t                    r|                    d          nd}t          |t                    s\i }|                    d          }|                    d          }	|                    d          }
|"t	          t          |          d	z            |d
<   |	"t	          t          |	          d	z            |d<   |
"t	          t          |
          d	z            |d<   |r|||d         <   |S )a  Return picker-shape pricing for DeepInfra chat models.

    DeepInfra publishes ``input_tokens`` / ``output_tokens`` /
    ``cache_read_tokens`` in $/MTok; the picker expects per-token strings
    under ``prompt`` / ``completion`` / ``input_cache_read`` (mirrors the
    OpenRouter shape consumed by
    :func:`format_model_pricing_table`). Cached via the catalog helper so
    repeated picker renders are free.
    r  r  r  r  Ninput_tokensoutput_tokenscache_read_tokensr  r  r  r  rm  )r  r(   r-   r   r    r   )r   r'  r  rW  r1  r  r  r:  r,  r   r+  s              r   r$  r$    sP    +}  E  	(*F ' '88J''-2-7$-G-GQ(,,y)))T'4(( 	 "kk.))kk/**[[!455
?!%**y"899E(O?"%eCjj9&<"="=E,!(+E*,=,=	,I(J(JE$% 	'!&F4:Mr   c                   t          j        dd                                          }|sdS t          j        dd                                          }|sddlm} |}|                    d          dz   }d	| t          d
}t          j        	                    ||          }	 t          j        
                    ||           5 }t          j        |                                                                          }d |                    dg           D             cddd           S # 1 swxY w Y   dS # t           $ r Y dS w xY w)z>Fetch available language models with tool-use from AI Gateway.AI_GATEWAY_API_KEYr7   Nr  r   r  r  r   r	  )r
  rr  r  r  c                    g | ]Q}|                     d           r:|                     d          dk    r!d|                     d          pg v I|d          RS )rm  r  languageztool-user  r  r|   s     r   r   z,_fetch_ai_gateway_models.<locals>.<listcomp>&  sk       55;; EE&MMZ//155==#6B77	 $ 877r   r   )r.   r  r  ra  r  r  rs  r  r  r  r  rj  r  r  r  r(   r1   )	r   r  r   r  r  r  r   r  r   s	            r   r  r    s   i,b117799G ty.3399;;H '888888&
//#


*C,7,,( G .
 
 g
 
6
6C^##C#99 	T:diikk002233D &"--  	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	    tts7   $!D7 AD*D7 *D..D7 1D.2D7 7
EEc                P    t          | ||||                              d          S )zFetch the list of available model IDs from the provider's ``/models`` endpoint.

    Returns a list of model ID strings, or ``None`` if the endpoint could not
    be reached (network error, timeout, auth failure, etc.).
    )r   r~  r  r   )r  r(   )r  r   r   r~  r  s        r   r  r  1  s7        
c(mmr   c                    ddl }d                    | pd|pdt          j        |pi d          f                              dd	          }|                    |d
                                          S )a  Fingerprint the credentials/wire-shape used to probe a custom endpoint.

    Custom OpenAI-compatible endpoints have no ``PROVIDER_REGISTRY`` slug to
    key off (unlike ``_credential_fingerprint``), so this hashes exactly the
    values callers pass to :func:`fetch_api_models`: a rotated ``api_key``, a
    changed ``api_mode``, or an edited ``extra_headers`` block each bust the
    cache entry on their own.
    r   Nr  r7   T)r  rf  r  r  r  r  )r  r  rj  r  r  r  r  )r  r~  r  r  ro  s        r   _custom_endpoint_fingerprintr  F  s     NNN882B
7=bD111   vgiv((	 	 ??4Q?//99;;;r   r  r  r  'TypeGuard[dict[str, Any]]'c                  t          | t                    o|                     d          |k    ot          |                     d          t                    on|pt	          | d                   oWt          |                     d          t
          t          f          o(t          |                     d          t                     S )au  True when *entry* is a well-formed cache row for fingerprint *fp*.

    Requires a numeric ``at`` so corrupt disk state (hand-edited JSON with
    ``"at": "yesterday"`` or ``null``) degrades to a cache miss / live fetch
    instead of raising out of the wrapper. Empty model lists are valid only
    for callers that explicitly opt into an authoritative empty catalog.
    r  r   r  )r-   r   r(   ro   r	  rI  r   )r:  r  r  s      r   r  r  _  s     	5$ 	2IIdOOr!	2uyy**D11	2 1Dx11	2 uyye55		2
 599T??D111r   )r   r~  r  r'  
cache_onlyr  r  c                   t          pd                                                              d                                          }|s|rdS t	                     S d| }	t                     t                      }
|
                    |	          }t          j                    }|r?|st          |          sdS ||d         z
  t          k    rdS t          |d                   S |spt          |          r`||d         z
  }||k     rt          |d                   S |t          k     r/ fd}t          |	|           t          |d                   S t	                     }|r4|t          |          d	|
|	<   t          |
           t          |          S t          |          rt          |d                   S |S )
u  Disk-cached wrapper around :func:`fetch_api_models` for custom endpoints.

    Mirrors :func:`cached_provider_model_ids` — including its
    stale-while-revalidate tier — but keys ``provider_models_cache.json``
    off ``custom:<base_url>`` instead of a ``PROVIDER_REGISTRY`` slug, since
    custom endpoints (named ``custom_providers`` rows, bare
    ``provider: custom``, and per-endpoint-map entries) have none. Same
    stale-beats-nothing fallback policy: a live-fetch failure serves the
    last same-fingerprint result rather than an empty list. Returns whatever
    :func:`fetch_api_models` would (a list or ``None``); corrupt cache rows
    degrade to a live fetch instead of raising.

    ``cache_only`` serves a previously-discovered catalog without touching
    the network at all — no live fetch, no background revalidation — and
    returns ``None`` when nothing usable is cached. Callers that deliberately
    skip live probing for latency reasons (GUI picker opens, which must not
    block on a stopped local endpoint) use this so a warm catalog still
    reaches the picker instead of collapsing to the config-declared subset.
    r7   r  Nr   r~  r  r\  r  r   c                 |    t                    } | sd S t          j                    t          |           dS )Nr  r  )r  rP  ro   )r  r  r~  r   r  r  r   s    r   _refresh_customz0cached_fetch_api_models.<locals>._refresh_custom  sN    'X#h     4 	tDzzJJJr   r  )r    r  r  r  r  r  r  r(   rP  r  r  ro   r  r  )r  r   r   r~  r  r'  r  r  normalized_urlr  r_  r:  rT  rU  r  r  r  s   `````           @r   cached_fetch_api_modelsr  v  sO   < R((..0077<<BBDDN 
 	4  Xw7
 
 
 	
 +.**I	%gx	A	AB'))EIIi  E
)++C 	%
  	 25" = = 	4t @@@4E(O$$$ )/r:: )E$Kh(((111
K K K K K K K K K K y/:::h(((7Xw  D  "$C4::FFi#E***Dzz %$$ %E(O$$$Kr   c                n    dD ]1}|                      |          r| dt          |                    c S 2| S )a"  Strip :cloud / -cloud suffixes that models.dev appends to Ollama Cloud IDs.

    The live API uses clean IDs (e.g. 'kimi-k2.6') while models.dev sometimes
    returns them as 'kimi-k2.6:cloud'. Normalising before the dedup merge
    prevents duplicate entries in the merged model list.
    )z:cloudz-cloudN)r  r   )r  r  s     r   ry  ry    sP     ' , ,V$$ 	,Ns6{{lN++++	,Or   c                 (    ddl m}   |             dz  S )z1Return the path for the Ollama Cloud model cache.r   r]  zollama_cloud_models_cache.jsonr`  r]  s    r   _ollama_cloud_cache_pathr    s(    000000????r   
ignore_ttlr  c                   	 t                      }|                                sdS t          |d          5 }t          j        |          }ddd           n# 1 swxY w Y   t          |t                    sdS |                    d          }t          |t                    r|sdS | s7|                    dd          }t          j	                    |z
  t          k    rdS |S # t          $ r Y nw xY wdS )zLoad cached Ollama Cloud models from disk.

    Args:
        ignore_ttl: If True, return data even if the TTL has expired (stale fallback).
    Nrf  rg  r   rV  r   )r  r  ri  rj  rk  r-   r   r(   ro   rP  _OLLAMA_CLOUD_CACHE_TTLr1   )r  
cache_pathr  r   r   rV  s         r   _load_ollama_cloud_cacher!    sD   -//
  "" 	4*w/// 	 19Q<<D	  	  	  	  	  	  	  	  	  	  	  	  	  	  	 $%% 	4(##64(( 	V 	4 	a00I	i'+BBBt   4sK   "C# C# AC# AC# A C# :,C# (7C# !C# #
C0/C0c                    	 ddl m} t                      }|j                            dd            ||| t          j                    dd           dS # t          $ r Y dS w xY w)z3Persist the merged Ollama Cloud model list to disk.r   r  Trt  )r   rV  Nry  )r  r   r  r|  r}  rP  r1   )r   r   r   s      r   _save_ollama_cloud_cacher#  
  s    ++++++-//
t<<<*dikk&R&R[_``````   s   AA 
A$#A$c                  |st                      }||d         S | st          j        dd          } |st          j        dd          pd}g }| rt          | |d          }|r|}g }	 d	d
lm}  |d          }n# t          $ r Y nw xY w|s|rt                      }g }	|D ]2}
|
r.|
|vr*|                    |
           |		                    |
           3|D ]A}
t          |
          }|r.||vr*|                    |           |		                    |           B|	rt          |	           |	S t          d          }||d         S g S )u  Fetch Ollama Cloud models by merging live API + models.dev, with disk cache.

    Resolution order:
      1. Disk cache (if fresh, < 1 hour, and not force_refresh)
      2. Live ``/v1/models`` endpoint (primary — freshest source)
      3. models.dev registry (secondary — fills gaps for unlisted models)
      4. Merge: live models first, then models.dev additions (deduped)

    Returns a list of model IDs (never None — empty list on total failure).
    Nr   OLLAMA_API_KEYr7   OLLAMA_BASE_URLzhttps://ollama.com/v1r  r  r   r`  r  Tr  )r!  r.   r  r  r   ra  r1   r6  r  r"  ry  r#  )r  r   r'  r  live_modelsrW  mdev_modelsra  r1  rd  r~   r&  stales                r   r  r    s   "  $)++(##  2),b11 O9.33N7NK !!'8SAAA 	! K  K888888)).99     k  	! 	!A !Qd]]a    	* 	*A3A66J *j44$$$j))) 	$V,,,M %555EXIs   (A: :
BB)r  r   r~  r  c          
       +, | pd                                 }t          |          }|dk    r|rt          |d          sd}|}|dk    rt          ||          p|}|sddddd	S |d
k    rr	 ddlm}	 ddlm}
  |
 |	                                d
          pi           }||d         v rddddd	S dddd| dd	S # t          $ r}dddd| d	cY d}~S d}~ww xY wt          d |D                       rddddd	S |dk    rrddlm} 	 t          ||          }n# |$ r}ddd| dd	cY d}~S d}~ww xY w|dddd| dd	S |sdddd| dd	S |t          |          v rddddd	S dddd| dd	S t          |pd                                                                           d k    r|st#                      }|}t          t%          d                               d!          pEt%          d                               d"          p#t%          d                               d#          pd                                           }|ot'          |pd|            }|i }|r$|                    t+          ||                     t-          |          D ]}|                                d$k    r||= |                    |           t          d% |D                       }|r7|s5t-          |          D ]}|                                d$k    r||= d&| |d'<   n|rt+          ||          }ni }t/          |||(          rt1          ||(          }|2t3          |t5          |          |)                              d*          }|dddd+| d,d	S |t          |          v rddddd	S t7          ||d-d./          }d}|r"d0d1                    d2 |D                       z   }|sd3nd}dddd4| d5| d6| d	S |dk    s|                    d7          r}|d8k    rt3          ||||9          }nt3          |||)          }|                    d*          }||t          |          v rddddd	S t7          ||d:d;/          }|rddd|d         d<| d=|d          d>d?S t7          ||d-d./          }d}|r"d@d1                    dA |D                       z   }d4| dB|                    dC           dD| }|                    dE          r|dF|                    dG           dHz  }ddd|d	S dI|                    dC           dJ| dK}|d8k    r|dLz  }|                    dM          r|dN|                    dM           d>z  }|d8k    dd|d	S |dOv r0	 t=          |          }n# t          $ r g }Y nw xY w|r
|t          |          v rddddd	S t7          ||d:d;/          }|rddd|d         d<| d=|d          d>d?S t7          ||d-d./          }d}|r"d@d1                    dP |D                       z   }|dQk    rdRndS}dTdUdV                    |dW          }|                                                                 +| pt          +fdX|D                       } | sdddd>| dY| dZ| d	S dddd4| d[| d\| d	S |d]v r	 t=          |          }n# t          $ r g }Y nw xY w|rd^ |D             ,|                                ,v rddddd	S t?          ,                                           }!t7          |                                |!d:d;/          }|r,|d                  }"ddd|"d<| d=|" d>d?S t7          |                                |!d-d./          }d}|r$d@d1                    ,fd_|D                       z   }dddd4| d`| dad	S |dbk    rtC          |pd|pdc          }#|#|t          |#          v rddddd	S t7          ||#d:d;/          }|rddd|d         d<| d=|d          d>d?S t7          ||#d-d./          }d}|r"d@d1                    dd |D                       z   }dddd4| de| d	S |d8k    rgtE          |||f          }|H|t          |          v rddddd	S t7          ||d:d;/          }|rddd|d         d<| d=|d          d>d?S ddddg| dhd	S tE          ||          }||dik    rdj |D             }|t          |          v rddddd	S t7          ||d:d;/          }|rddd|d         d<| d=|d          d>d?S t7          ||d-d./          }d}|r"d@d1                    dk |D                       z   }d}$|dlv rddml#m$}%  |%|          }$|$s:tK          |                                tM          |                    rdddd4| dnd	S dddd| do| d	S |dpk    r	 ddql'm(}&m)}'  |'            }( |&|(          })dr |)D             }*||*v rddddd	S t7          |t?          |*          d-ds/          }d}|r"d@d1                    dt |D                       z   }dddd4| du|( dv| d	S # t          $ r Y nw xY wtT                              ||          }	 t=          |          }n# t          $ r g }Y nw xY w|rdw |D             ,|                                ,v rddddd	S t?          ,                                           }!t7          |                                |!d:d;/          }|r,|d                  }"ddd|"d<| d=|" d>d?S t7          |                                |!d-d./          }d}|r$d@d1                    ,fdx|D                       z   }dddd4| d[| dy| dzd	S dddd{| d|| d}d	S )~a  
    Validate a ``/model`` value for the active provider.

    Performs format checks first, then probes the live API to confirm
    the model actually exists.

    Returns a dict with:
      - accepted: whether the CLI should switch to the requested model now
      - persist: whether it is safe to save to config
      - recognized: whether it matched a known provider catalog
      - message: optional warning / guidance for the user
    r7   r  zopenrouter.air  r   r  FzModel name cannot be empty.)acceptedpersist
recognizedmessager   r   r6  )normalize_moa_configpresetsTNzMoA preset `z'` was not found. Run `hermes moa list`.zCould not read MoA presets: c              3  >   K   | ]}|                                 V  d S r   )isspace)r}   chs     r   rA  z+validate_requested_model.<locals>.<genexpr>  s*      
,
,B2::<<
,
,
,
,
,
,r   z"Model names cannot contain spaces.r  r8  r  zD Set `LM_API_KEY` (or update it) to match the server's bearer token.z:Could not reach LM Studio's `/api/v1/models` to validate `z`.zDLM Studio is reachable but no chat-capable models are loaded. Load `u<   ` in LM Studio (Developer tab → Load Model) and try again.zModel `z-` was not found in LM Studio's model listing.r  r   r|  r  r  c              3  F   K   | ]}|                                 d k    V  dS r  r  r  s     r   rA  z+validate_requested_model.<locals>.<genexpr>  s@       '
 '
/2CIIKK?*'
 '
 '
 '
 '
 '
r   r	  r
  r  )r  r   zTNote: could not reach this Ollama endpoint's `/api/tags` model listing to validate `zY`. Hermes will save the model name, but local Ollama model discovery could not verify it.r  r  )r  cutoffz 
  Similar local Ollama models: z, c              3  "   K   | ]
}d | d V  dS `Nry   r}   r  s     r   rA  z+validate_requested_model.<locals>.<genexpr>  s,      MlMl[\hRShhhMlMlMlMlMlMlr   z/ No models are currently listed by `/api/tags`.zNote: `zD` was not found in this Ollama endpoint's `/api/tags` model listing.zC It may still work if the server supports hidden or aliased models.r\  r|  )r~  r  r   g?zAuto-corrected `u   ` → `r8  )r+  r,  r-  corrected_modelr.  z
  Similar models: c              3  "   K   | ]
}d | d V  dS r7  ry   r9  s     r   rA  z+validate_requested_model.<locals>.<genexpr>7  +      DcDcRSXXXXDcDcDcDcDcDcr   z9` was not found in this custom endpoint's model listing (r  zE). It may still work if the server supports hidden or aliased models.r  z1
  Endpoint verification succeeded after trying `r  z)`. Consider saving that as your base URL.z?Note: could not reach this custom endpoint's model listing at `z`. Hermes will still save `z=`, but the endpoint should expose `/models` for verification.z
  Many Anthropic-compatible proxies do not implement the Models API (GET /v1/models).  The model name has been accepted without verification.r  z0
  If this server expects `/v1`, try base URL: `>   r   r   c              3  "   K   | ]
}d | d V  dS r7  ry   r9  s     r   rA  z+validate_requested_model.<locals>.<genexpr>y  r<  r   r   zOpenAI Codexr  )r/  zcodex-r0  r1  r2  )r  )r   r   ry   c              3  B   K   | ]}                     |          V  d S r   r7  )r}   r  _lowers     r   rA  z+validate_requested_model.<locals>.<genexpr>  sB       7 7)*!!!$$7 7 7 7 7 7r   z` doesn't look like a z model and isn't in its listing, so it was not accepted. If it belongs to another configured provider, switch with `--provider <slug>` (or select it from the `/model` picker).z` was not found in the z[ model listing. It may still work if your account has access to a newer or hidden model ID.>   r   r   c                8    i | ]}|                                 |S ry   r  r|   s     r   r  z,validate_requested_model.<locals>.<dictcomp>  s"    BBBaQWWYYBBBr   c              3  0   K   | ]}d |          d V  dS r7  ry   r}   r  catalog_lowers     r   rA  z+validate_requested_model.<locals>.<genexpr>  s6      DrDrabE\WXIYE\E\E\DrDrDrDrDrDrr   z'` was not found in the MiniMax catalog.z
  MiniMax does not expose a /models endpoint, so Hermes cannot verify the model name.
  The model may still work if it exists on the server.r   rs  c              3  "   K   | ]
}d | d V  dS r7  ry   r9  s     r   rA  z+validate_requested_model.<locals>.<genexpr>  r<  r   zn` was not found in Anthropic's /v1/models listing. It may still work if you have early-access or snapshot IDs.r}  zNote: could not verify `z` against this endpoint's model listing.  Many Anthropic-compatible proxies do not implement GET /v1/models.  The model name has been accepted without verification.r   c                    g | ]E}t          |t                    r,|                    d           r|t          d           d         n|FS )zmodels/N)r-   r    r  r   r|   s     r   r   z,validate_requested_model.<locals>.<listcomp>/  s]        '1C&8&8[Q\\)=T=T[#i..//""Z[  r   c              3  "   K   | ]
}d | d V  dS r7  ry   r9  s     r   rA  z+validate_requested_model.<locals>.<genexpr>O  r<  r   rh  rv  u^   ` was not found in the live /v1/models listing but exists in the curated catalog — accepted.z1` was not found in this provider's model listing.r   )discover_bedrock_modelsresolve_bedrock_regionc                    h | ]
}|d          S r  ry   r|   s     r   r  z+validate_requested_model.<locals>.<setcomp>  s    :::!ag:::r   g?c              3  "   K   | ]
}d | d V  dS r7  ry   r9  s     r   rA  z+validate_requested_model.<locals>.<genexpr>  r<  r   z/` was not found in Bedrock model discovery for zK. It may still work with custom inference profiles or cross-account access.c                8    i | ]}|                                 |S ry   r  r|   s     r   r  z,validate_requested_model.<locals>.<dictcomp>  s"    >>>!A>>>r   c              3  0   K   | ]}d |          d V  dS r7  ry   rB  s     r   rA  z+validate_requested_model.<locals>.<genexpr>  sM       A A,-'M!$'''A A A A A Ar   z: curated catalog and the /models endpoint was unreachable.z9
  The model may still work if it exists on the provider.zNote: could not reach the z API to validate `z:`. If the service isn't down, this model may not be valid.)+r  r"  r   r  r&   r7  hermes_cli.moa_configr/  r(   r1   r  r  r9  r@  r6  r    r  r  rn  r  r  r  r  r  r  r  rz  r   r  r  r  ro   r   r  r  r;  rw  r
  r   r  rG  rH  rM  )-r  r  r  r   r~  r  r  r&  requested_for_lookupr7  r/  cfgr  r9  r   ollama_base_urlr  configured_headers_allowedollama_headersr  caller_has_authorizationollama_modelssuggestionssuggestion_text
empty_hintprobe
api_modelsr,  r.  catalog_modelsr.  _family_prefixes
_plausiblecatalog_lower_list	correctedanthropic_models _openai_listing_is_authoritativerw  rG  rH  region
discovereddiscovered_idsr?  rC  s-                                              @@r   validate_requested_modelrd  X  s   * !r((**I#H--J\!!h!7LXWf7g7g!
$Y9 
  
  
    	
  
4	
 
 	
 U	555555BBBBBB&&{{}}'8'8'?'?'E2FFCC	N**$(TZ^___!e5\)\\\    	 	 	!e5?#??       	 
,
,)
,
,
,,, 
;	
 
 	
 Z------	*7XNNNFF 	 	 	!e5```       	 >!e5eXaeee    	!e5e&e e e    3v;;.. $TVZ[[[%uYYYY
 
 	

 8>r  ""((**h66x6'))O!$%h//33J?? (2266u==(2266u== " " egg  	# 	\()>B@Z[[[" % 	!!*?GLLL   (( 	( 	(Cyy{{o--"3'g&&&#& '
 '
6='
 '
 '
 $
 $
   	B3 	B^,, , ,99;;/11&s+.A.A.AN?+	# 3OWUUU'/>   -
 2^
 
 
   -*?;; .   c(mm	 
   #mkt m m m    3}#5#555 "	   ((<mqY\]]] 	mADIIMlMl`kMlMlMlDlDllON[cFFac
%) % %% %"% %	
 	
 		
 X!6!6y!A!A+++$! '	  EE % '  E
 YYx((
!#s:66 $#"&#	   %%9:SVWWWD  $#"&'+AwN)NNDGNNN   ,IzQsSSSK O d"8499DcDcWbDcDcDc;c;c"c%) % %IIl++% %"% % 
 yy)) >SfIgIg > > > !#"	  peiiXdNeNe p p'0p p p 	 +++\G 99)** 	nm599UiKjKjmmmmG !$88	
 
 	
 222	 /
;;NN 	  	  	 NNN	  ?	#s>':'::: $#"&#	   %%9>QWZ[[[D  $#"&'+AwN)NNDGNNN   ,,@.TU^abbbK O d"8499DcDcWbDcDcDc;c;c"c/9^/K/K^^QxN !E'    c*b!!  *//117799F.. 3 7 7 7 7.>7 7 7 4 4J   %$"'-I - -^ - -
 +- -   !#)i ) ) ) )&) )	 	 	 ...	 /
;;NN 	  	  	 NNN	  $	BB>BBBM#))++}<< $#"&#	   "&m&8&8&:&:!;!;$%9%?%?%A%ACUYZcfgggD )$q'2	 $#"&'0P)PPIPPP   ,,@,F,F,H,HJ\`ajmnnnK O s"8499DrDrDrDrfqDrDrDr;r;r"r #Oi O O&O O O
 
 
$ [  2%Ot
 
 
 '#s+;'<'<<< $#"&#	   %%9;KqY\]]]D  $#"&'+AwN)NNDGNNN   ,I7G1UXYYYK O d"8499DcDcWbDcDcDc;c;c"c
 !#)i ) )&) )	 	 	 '''%gx(KKK
!#s:66 $#"&#	   %%9:SVWWWD  $#"&'+AwN)NNDGNNN   )9 ) ) )

 

 
	
 "'844J !! #  J  3z??22 !"	   %%9:SVWWWD  $#"&'+AwN)NNDGNNN   ,IzQsSSSK O d"8499DcDcWbDcDcDc;c;c"c 05,555HHHHHH3J3J83T3T03 8R$**,,nZ.H.H9 9  !%#"&K) K K K   %) % %"% %
 
 	
  Y	]]]]]]]]++--F0088J::z:::NN** $#"&#	   ,ItN7K7KqY\]]]K O d"8499DcDcWbDcDcDc;c;c"c #)i ) )X^ ) )&) )	 	 	  	 	 	D	 &))*jAAN+J77     '
>>~>>>%%''=88 "	   "-"4"4"6"677  &&((*<#
 
 
  	%d1g.I "#,LiLL	LLL   ( &&((*<#
 
 
  	4tyy A A A A1<A A A 8 8 O N) N NN N N<KN N N	
 	
 		
 G G G9 G G G  s   -AB= 2
B= =
C	CCCD D6"	D1+D61D6V- -V<;V<[" "[10[13j 7Aj 
jj9k	 	kk)r   r   r   r   )r   r    )rh   ri   )rv   ri   rh   ri   )r  r    r  r  rh   r	  )r  r  rh   r	  )r  ri   r  r  r  r	  rh   r   r  )
r(  ri   r  r  r)  r    r'  r	  rh   r*  )rM  r	  rh   r	  )rh   r[  )rc  r    rh   rd  )rc  r    r   r  rh   rq  )r7   r  )r)  r    r   r   r'  r	  rh   r  )rh   r    )r:  r   rh   r  )
r  r	  r  r  r)  r    r'  r	  rh   r  )r  r    rh   r    )r  )r  r    rh   r    )r  ri   r  r    rh   r    )r  r   rh   r	  )r1  r   rh   r	  )r1  r   rh   r7  )rh   r   )rh   r  )r  r    rh   rM  )r  r    rP  rW  rh   rq  r  )r  r    )r  r    r  r   rh   rk  )r  r    r   r   rh   rk  )ru  )r   r   rw  r	  rh   rk  )r  r  r   r   r  r	  rh   r7  )rh   rk  )r@  r  r<  r  rh   r  )r  )r   r   r'  r	  rh   r]   )r'  r	  rh   ri   )r  r    rh   r  )r  r    rW  r  rh   r  )r  r    rh   r    )r  r    r  r    r  r   rh   r  )Nr  r  )r  r  r   r    r   r   r'  r	  r  r	  rh   r  )r   r   r'  r	  rh   r  )rh   r  )r  r    r'  r	  rh   r  )r'  r	  rh   r  )rh   r3  )rh   rB  )r  r    rY  r    rh   r  )r  r    rh   r  )r   r    rh   r    )r   r  rh   r    )rh   r  )r   r  r  r  rh   r  )r  r    rh   rq  )r_  r  r  r    ry  r   rh   rq  )rv  r    r  r  rh   r    )Nr  N)r   r  r   r   r  r  rh   r  )r  r    r  r    rh   r	  r   )r  r  r   r  r  r  rh   r	  )r   r    rh   r	  )r  r  r'  r	  rh   r]   )r  r    rh   r3  )r  r    rh   r  )r  r    r9  r3  rh   r	  )r  r    r  r3  rh   r  )r  r    rY  r    rh   r  )r  r    rh   r  )r  r  rh   r    )r  r  rh   r	  )r  r    rh   r    )r  r  rh   rd  )r  r    r  ri   rh   ri   )r  r  r'  r	  rh   ri   )r  r    rh   rq  )rh   r   )r   r   rh   rq  )r  r    r   ri   rh   rq  )r  r  r'  r	  r  rI  rh   ri   )r  r  rh   rq  )r  )r   r   r   r  r  r  rh   r  )r7  r   rh   r  )r  r	  rh   r  )r1  r  rh   r	  )Nr  )r  r  r   r   rh   r  )r  r    r  r  rh   r%  )r   r  rh   r	  )r   r  rh   r  )r  r  rh   r   )NNr  )r  r  r   r  r   r   rh   r4  )r  r  r   r  r   r   rh   r  )r  r  r   r  r   r   rh   ri   )r  )r  r    r   r  r  r  rJ  r%  r   r   rI  r	  rh   rK  )
r  r    r   r  r  r  r   r   rh   ri   )
r  r    r   r  r  r  r   r   rh   r  )r  r  r   r   rh   r  )NN)r*  r  r  r  rh   r3  )r  r  r*  r  r  r  rh   r    )r  r    rh   ri   )r  r    rh   r	  )r  r  rh   r  )r  r  rh   r  )r  r  r  r  rh   r    )r  r  r  r  rh   rX  )r  r  r~  r  r   r  rh   r    )r  r  r*  r  r  r  rh   ri   )r  NN)r  r  r   r  r   r   r~  r  r  r  rh   r  )r   r   r'  r	  rh   r4  )r  r    r   r   r'  r	  rh   r4  )r   r   r'  r	  rh   r  )r  r    r'  r	  rh   ri   )r  rX  rh   r    )r   r   rh   r  )r  r  r   r  r   r   r~  r  r  r  rh   r  )r  r  r~  r  r  r  rh   r    )r:  r   r  r    r  r	  rh   r  )r  r  r   r  r   r   r~  r  r  r  r'  r	  r  r	  r  rI  rh   r  )r  r	  rh   rX  )r   ri   rh   rq  )r  r  r   r  r'  r	  rh   ri   )r  r    r  r  r  r  r   r  r~  r  r  r  rh   r  (2  rH  
__future__r   r  rj  http.clientr  r  r.   r  rd  urllib.parser  urllib.requesturllib.errorrP  difflibr   pathlibr   typingr   r   r   r	   r
   r  r   r  hermes_cli.urllib_securityr   r   r  r   r   r  r  r]  rs  r.  r  r  r  r  r   r5   r^   r  r_   rf   rg   rp   rt   ru   r{   r   r   r   r   r  r  r  r%  r@  rG  rJ  rK  rX  r  rY  rZ  rb  rp  r  r4  r  r5  r  r  r  _canonical_slugsr9  r  _list_providers_for_canonical_ppr;  r  display_namer  r  r  r"  r  r1   rM  r  r  r  r  r  rK  r#  r%  r(  	frozensetr)  r+  r/  r6  rE  rF  rH  rT  rJ  rL  rV  r^  rh  rj  ro  rt  rz  r|  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  agent.reasoning_effortr  _REASONING_EFFORT_ORDERr  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r'  r%  r#  r6  r   r4  rA  rX  rh  rN  rn  rw  rz  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  rj  r  r  r  r   r  r  r
  r  r  r  r  r#  r(  r%  r"  r.  r3  r:  r8  r@  r?  rH  rT  rZ  r^  re  rl  r  _PROVIDER_MODELS_CACHE_TTLr  r  Lockr  r  r  r  r  r  r  r  r  r  r  r   r  r  r  r  r  r  r"  r#  r$  r)  r,  r/  r1  r3  r<  r@  rB  rD  rn  rt  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  compiler  r  r  r  r  r  r  r  r  r  r  r  r  r$  r  r  r  r  r  r  ry  r  r!  r#  r  rd  ry   r   r   <module>rx     s*     # " " " " "        				 				                  % % % % % %       ; ; ; ; ; ; ; ; ; ; ; ; !       5 5 5 5 5 5 H H H H H H H H : : : : : : : :		8	$	$ 5?44 2 (111 ) !E!E!E %>%>%> "_c P P P P P P
   D=, =, =,  = = = =~ ;?  > > > >3 3 3     $ ;?  > > > >B B B B*# # #     " " "         	 	 	 	< < < <= = = =,y*	I;y*
 . . .y*f  	 	 	gy*z    {y*^ ))++_y*` $$&&ay*b cy*h    iy*N    Oy*Z 
 	 	 	[y*n 
  oy*p    qy*H    Iy*`  	 	 	ay*t uy*|    }y*L    My* y*Z    [y*d    ey*r    sy*L My*T    Uy*b cy*h    iy*r 
   sy*L  A A AMy*^	    _	y*r	    s	y*r
    s
y*J    Ky*h    iy*B  
 
 
Cy*`    ay*~ Ry* y*N	 	 	  cy* y* y*  y y y y~ "N!M4L!M!M!M        8% % % %> ?.
  ?. ?. ?. ?. ?. ?.J ?*
  ?* ?* ?* ?* ?* ?*L       .2  2 2 2 2 16      b  > #&  & & & &CE  E E E EG G G G= = = =*
 
 
 
@ ;  	; ; ; ; ; ;|1 1 1 1      $6 6 6 6 6 6H    J   
',M&M  AO  P  P',M+N@stt', M,L@ijj', M%$7  AL  M  M	',
 M(J@wxx', M*K@z{{', M+K@vww', M.$C  F\  ]  ]', M,L@fgg', M)L@pqq', M+$K  NB  C  C', M(M@yzz', M$&8@}~~', M(L  AA  B  B', M)$4@uvv',  M-$8@stt!'," M-N@bcc#',$ M($6@fgg%',& M($6  AW  X  X'',( M*J@fgg)',* M%E@WXX+',, M%L@_``-',. M-$=@pqq/',0 M"$=@mnn1',2 M)$7?|}}3',4 M)I@]^^5',6 M/$5@{||7',8 M,$5@eff9',: M.N@uvv;',< M'J@ghh=',> M%K@dee?',@ M*K@^__A',B M.N@nooC',D M-M@hiiE',F M)M@}~~G',H M/O  AU  V  VI',J M,$7@lmmK',L M,$9@jkkM',  ' ' ' 'Z 98$7888 	IIIIII,,.. ' '8'''=www!-SX;f#;#;#;""==65#I#IJJJSX&&&&'  	 	 	D	 BA-@AAA .  4 #$TWdfvVwx$Q  XB  XB  XB  C$NX]_jWkl $@W_V`a$UXfhtWuv$Z  ]M  ]M  ]M  N$V  YI  YI  YI  J!$OXacpWqr	: 	: 	 	 	 	" "3B3H3H3J3J" " "    
C C C C
9 9 9xY	5Y
EY EY U	Y
 iY iY YY IY -Y Y hY XY Y XY Y  (!Y" #Y Y$ M%Y& 'Y( )Y* #+Y, I-Y. 9/Y0 1Y2 w3Y4 5Y6 7Y8 K9Y: 	+;Y< x=Y> h?Y@ 
8AYB \CYD ,EY Y YF oGYH oIYJ _KYL kMYN ;OYP QYR SYT 
>UYV 	-WYX }YYZ O[Y\ _]Y^ _Y` laYb cYd JeYf gY Y Yh JiYj kYl imYn IoYp YqYr <sYt 	-uYv MwYx }yYz {Y| }Y~ HY@ 8AYB !CYD "EYF 'GYH %IY Y YJ 
9KYL 9MYN iOYP iQYR ESYT +UYV WYX +YYZ k[Y\ E]Y^ E_Y` 
8aYb (cYd HeYf gYh 
iYj kY Yl "qY Y Y P "0 * * * * *$- - - - -@ -6Iv|6L,M,M  M M M M' ' ' '4      2. . . .h PT   S S S S 6: $ 9 9 9 9 $-  ? ? ? ?
2 2 2 2K K K K"J J J J   "      4: : : :, @  +0     61 1 1 1 	!! !! !! !! !! !!H !&  % , , , ,C C C C JN  M M M M/3  3 3 3 3A A A A +0     (+ + + + 	! ! ! ! ! !.    & & & & = = = = L K K K K K @ @ @ @ @ @4 4 4 4( Q  Q Q Q Q Q Qh (- T T T T T T
2 2 2 2$    E  E E E E E EP 38 T T T T T T 8: 9 9 9 9 $ /1  1 1 1 1
 
 
 
   # # # #LH H H HX /N-
  "'N- N- N- N- N- N-d /-  /- /- /- /- /- /-d7 7 7 7
  H . . . .b FK      @  /- /- /- /- /- /-f 8-  8- 8- 8- 8- 8- 8-z C  	c

 
 
"
"##$X        ,) ) ) )X.( .( .( .(b6 6 6 6   $      >$ >$ >$ >$B   4 "     : '*  ) ) ) )GI  I I I I68 ! 8 8 8 813  3 3 3 3')  ) ) ) )'*  * * * *E E E E      " #(,^ ^ ^ ^ ^D #(,I I I I I   ( )-NU NU NU NU NUb
 
 
 
I I I I# # # # #  % % % % % %2/ / / / 69     
       "	???   -6IKK  7 7 7 7 09y]#0 0     
8 8 8 8vQ Q Q Qh$ $ $ $N   69 9 9 9F F F F&/     
Q 
Q 
Q 
Q,      4 4 4 4&( ( ( (&7 7 7 7P )2	 3 3 3 ) )     *   $" " " "J' ' ' '2 JO l l l l l lH
 "  $1  
 !SUU  " " " ""IN$$ 8 8 8 8 8v< < < <
e< e< e< e<P
 
 
 
 #IN$$       8  1	N N N N N Nb    D K #!	K K K K K K\    6: 
 
 
 
 
 
$   L ?C  B B B B15  5 5 5 5*-   - - - -"%  58/ / / / /j *,  + + + +%(  ( ( ( (! " " " " "J      "     ""/ / / / /f ""    @ ""    &        x;  %x; x; x; x; x; x;| "	    H "	- - - - -`F F F F F'L'' ' 	'
 i' 9' 9' X' -' ' l' ' ' l' l'   !2!'"  !2#' '$ "#6%'&  !2''( "#6)'* !"4+'6 (7'8 (9': ,;'< *='> ,?'@ *A'B  !2C'D  !2E'F "#6G'H "#4I'J "#6K'L !"4M' ' V /3!    $ /3!	& & & & & &R   @ @ @ @( /3!	     R% !   :   :   0 )D %:  !*	<. 9 9 K K K K   (% % % %P@ @ @ @F* * * *` /3!	)O )O )O )O )O )O^ "04[ [ [ [ [D #
1   +4) 5 5 5 + +      D 6  35  4 4 4 4 24  3 3 3 3! F F F F ' ' ' ' ' 'Z 	0 0 0 0 0 0h 2  2 2 2 2 2 2( <A ; ; ; ; ; ;
* 
* 
* 
* 
* &  & & & & & &R    B "(,    *< < < <: 	     6 "(,1Z Z Z Z Z ZF  
 
 
 
@ @ @ @ 49      6    ""@  	@ @ @ @ @ @N """(,I
 I
 I
 I
 I
 I
 I
 I
s   BS SS