
    PL
jw3                      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	m
Z
 ddlmZ ddlmZmZmZ ddlmZ de Zd	Ze d
ZdZg dZg dZg dZded<   daded<   g dZded<   daded<   ddZg dZ ded<   dZ!ddZ"ddZ#i dg d d!g d"d# e            d$ e#            d%d%gd&g d'd(g d)d*g d+d,g d-d. e#            d/g d0d1g d2d3g d4d5d6d7gd8g d4d9g d:d;d<d=gi d>g d:d?g d@dAg dBdCg dDdEdFgdGg dHdIg dJdKg dLdMg dNdOg dPdQg dRdSg dTdUg dVdWg dXdYg dZg d[Z$d\ed]<   d^ eD             e$d_<   ddeZ%dʐddjZ&ddlZ'ddpZ(	 ddqdrddvZ)	 ddqdrddwZ*dxZ+dyedz<   da,d{ed|<   dd}Z-d~Z.dZ/dyed<   i Z0ded<   	 	 ddqdrddZ1ddZ2ddZ3dqddfdqdddZ4 G d de          Z5g  e5ddd           e5ddd           e5dZdd           e5ddd           e5d?dd           e5d#dd           e5dQdd           e5d$dd           e5dCdd           e5dEdd           e5d/dd           e5d&dd           e5d%dd           e5dUdd           e5d(dd           e5d*dd           e5dAdd           e5d.dd           e5d,dd           e5d1dd           e5d3dd           e5d5dd           e5d9dd           e5d;dd           e5d>ddæ           e5dddƦ           e5dGddȦ           e5dIddʦ           e5dOdd̦           e5dKddΦ           e5dMddЦ           e5dWddҦ           e5dYddԦ           e5d_ddզ           e5dddئ          Z6ded<   dۄ e6D             Z7	 ddl8m9Z:  e:            D ]oZ;e;j<        e7v re;j=        dv re;j>        pe;j<        Z?e;j@        pe? dޝZAe6B                     e5e;j<        e?eA                     e7C                    e;j<                   pn# eD$ r Y nw xY wd߄ e6D             ZEdeEd<   i dd,dd,dd,dd,dd&dd&dd&dd&dd%dd%dd(dd(dd(dd1d8d1dd3dd3i dd5dd5ddGddGddIddIdd>dd>dd;dd;dd;dd?dd?ddAd dKddKddMi ddMdd_dd_dd_ddOddOd	dOd
dQddQddQddQdd֓dd*dd*ddUddUddUi ddZddZddCddCddEddEddEddEddWddWddWddWd d.d!d$d$d$d"d$d#d$d.d.d/d/d/d/dddddĐd$ZFdՐd&ZGd֐d'ZHdאd)ZI	 ddqdrdِd+ZJdqdrdڐd,ZKdǐd-ZLd֐d.ZM	 ddqdrdِd/ZNdqdrdڐd0ZOi ZPd1ed2<   dېd4ZQ	 	 dܐdݐd:ZR	 	 	 ddqdrdߐd?ZS	 ddqdrdd@ZTdҐdAZUdBZVddDZWdqdrddEZX	 ddqdrddFZY eZeE[                                           eZeF[                                          z  ddhz  Z\dGedH<   ddJZ]ddMZ^dҐdNZ_dqdrddOZ`ddPZaddSZb ech dT          ZdddWZeddYZfddZZgdd[Zhdd\Zidd]Zjd^Zkd_ed`<   ddaZlddbZmddcZndddZoddfZpdҐdgZq ech dh          Zrdiedj<   ddlZsdqdrddmZtdddoZuddrZvddtZwdduZx	 dddwZyi azdxedy<   dza{ded{<   d|Z|ddd~Z}ddZ~ddZdddZ	 	 	 dddZ	 	 	 dddZ	 	 	 dd dZ	 dddZ	 	 dddZdddZi ddddddddddddddddddddddddddddddddddddddddddddddddZ	 	 dddZdddddZddZd	dZdddddZdZd
dZddZddZdddddZ	 	 dddZdddZ	 	 dddZd|ZddZddZdqdddZddÄZ	 	 ddqdrddĄZddddŜddƄZdS (  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)__version__zhermes-cli/zhttps://api.githubcopilot.com/modelszvscode/1.104.1)minimallowmediumhigh)r   r   r   )anthropic/claude-opus-4.7 anthropic/claude-opus-4.6r   anthropic/claude-sonnet-4.6r   moonshotai/kimi-k2.6recommended)zopenrouter/pareto-codezAauto-routes to cheapest coder meeting openrouter.min_coding_score)qwen/qwen3.6-plusr   anthropic/claude-haiku-4.5r   )openai/gpt-5.5r   )openai/gpt-5.5-pror   openai/gpt-5.4-minir   )openai/gpt-5.4-nanor   openai/gpt-5.3-codexr   )xiaomi/mimo-v2.5-pror   )tencent/hy3-previewr   )z!google/gemini-3-pro-image-previewr   )google/gemini-3-flash-previewr   google/gemini-3.1-pro-previewr   $google/gemini-3.1-flash-lite-previewr   )qwen/qwen3.6-35b-a3br   )stepfun/step-3.5-flashr   minimax/minimax-m2.7r   )z-ai/glm-5.1r   )zx-ai/grok-4.20r   )x-ai/grok-4.3r   )!nvidia/nemotron-3-super-120b-a12br   )deepseek/deepseek-v4-pror   )zopenrouter/elephant-alphafree)zopenrouter/owl-alphar2   )ztencent/hy3-preview:freer2   )z&nvidia/nemotron-3-super-120b-a12b:freer2   )zinclusionai/ring-2.6-1t:freer2   list[tuple[str, str]]OPENROUTER_MODELSzlist[tuple[str, str]] | None_openrouter_catalog_cache)r   )zalibaba/qwen3.6-plusr   )zzai/glm-5.1r   r,   r   r   r   r   )openai/gpt-5.4r   r   r!   r&   )zgoogle/gemini-3-flashr   r(   )zxai/grok-4.20-reasoningr   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_modelsr=   r>   listr<   s     5/home/kuhnn/.hermes/hermes-agent/hermes_cli/models.py_codex_curated_modelsrB   a   s7     YXXXXXXX%%d+?&@&@AAA    )grok-4.3zgrok-4.20-0309-reasoningzgrok-4.20-0309-non-reasoningzgrok-4.20-multi-agent-0309_XAI_STATIC_FALLBACKrD   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     rA   
<listcomp>z$_xai_promote_top.<locals>.<listcomp>   s"    "I"I"IQ.5H5H15H5H5HrC   rJ   )rF   s    rA   _xai_promote_toprP      s/    "I"Ic"I"I"IIIJrC   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                    S )a  Derive the xAI-direct curated list from models.dev disk cache.

    Reads $HERMES_HOME/models_dev_cache.json directly (no network) so this
    runs at import time without blocking. Falls back to ``_XAI_STATIC_FALLBACK``
    when the cache is empty or unreadable. Hermes refreshes the cache from
    https://models.dev/api.json on normal use, so this list self-heals as
    xAI renames models.

    Mirrors ``_codex_curated_models()``'s role for openai-codex.
    r   )_load_disk_cachexaiNmodelsc                <    g | ]}t          |t                    |S rI   )
isinstancestr)rM   mids     rA   rO   z'_xai_curated_models.<locals>.<listcomp>   s'    HHH3:c33G3GH3HHHrC   )agent.models_devrR   rV   dictgetkeysrP   sorted	Exceptionr@   rE   )rR   datarS   rT   rF   s        rA   _xai_curated_modelsr`      s    555555!!!+D$!7!7AdhhuoooT&0d&;&;E"""fd## 	5 	5HH&++--HHHC 5's444    	 $%%%s   B:B> >
C
Cnous)r   r   r   r   r   r   r   r   r   r    r"   r#   r$   google/gemini-3-pro-previewr%   r'   r)   r*   r+   r-   r.   r/   r0   r1   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-codex	xai-oauthcopilot-acpcopilot)rd   re   rf   rg   rh   ri   rj   rk   claude-sonnet-4.6claude-sonnet-4claude-sonnet-4.5claude-haiku-4.5gemini-3.1-pro-previewgemini-3-pro-previewgemini-3-flash-previewzgemini-2.5-progemini)rt   ru   rv   zgemini-3.1-flash-lite-previewzgoogle-gemini-cli)rt   ru   rv   zai)glm-5.1glm-5zglm-5v-turbozglm-5-turboglm-4.7zglm-4.5zglm-4.5-flashrS   nvidia)	r0   znvidia/nemotron-3-nano-30b-a3bz(nvidia/llama-3.3-nemotron-super-49b-v1.5zqwen/qwen3.5-397b-a17bzdeepseek-ai/deepseek-v3.2r   zminimaxai/minimax-m2.5z	z-ai/glm5zopenai/gpt-oss-120bzkimi-coding)	kimi-k2.6	kimi-k2.5zkimi-for-codingkimi-k2-thinkingzkimi-k2-thinking-turbokimi-k2-turbo-previewkimi-k2-0905-previewzkimi-coding-cn)r}   r~   r   r   r   stepfunzstep-3.5-flashzstep-3.5-flash-2603moonshotminimax)MiniMax-M2.7MiniMax-M2.5zMiniMax-M2.1z
MiniMax-M2zminimax-oauthr   zMiniMax-M2.7-highspeed
minimax-cn	anthropic)z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)zdeepseek-v4-prozdeepseek-v4-flashzdeepseek-chatzdeepseek-reasonerxiaomi)mimo-v2.5-pro	mimo-v2.5mimo-v2-promimo-v2-omnizmimo-v2-flashztencent-tokenhubz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.5r)   r   r6   opencode-zen)#r~   zgpt-5.4-prord   rg   gpt-5.2rh   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-4-5zclaude-opus-4-1r   claude-sonnet-4-5rq   claude-haiku-4-5zclaude-3-5-haikuzgemini-3.1-prozgemini-3-prozgemini-3-flashminimax-m2.7minimax-m2.5zminimax-m2.5-freezminimax-m2.1rz   r{   zglm-4.6r   zkimi-k2zqwen3-coderz
big-pickleopencode-go)r}   r~   ry   rz   r   r   r   r   r   r   qwen3.6-plusqwen3.5-pluskilocode)r   r   r6   rb   r%   alibaba)r   r~   r   qwen3-coder-plusqwen3-coder-nextrz   r{   r   zalibaba-coding-plan)r   r   r   r   r~   rz   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-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novita)zmoonshotai/kimi-k2.5r-   zzai-org/glm-5zdeepseek/deepseek-v3-0324zdeepseek/deepseek-r1-0528zqwen/qwen3-235b-a22b-fp8zdict[str, list[str]]_PROVIDER_MODELSc                    g | ]\  }}|S rI   rI   rM   rX   _s      rA   rO   rO     s    !M!M!M&#q#!M!M!MrC   
ai-gatewaymodel_idrW   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[   float	TypeError
ValueError)r   r   ps      rA   _is_model_freer     s    HA uQUU8S))**a/XE!%%c:R:R4S4SWX4XXz"   uus   AA) )A>=A>r   access_tokenportal_base_urldict[str, Any]c                   |pd                     d          }| d}d|  dd}	 t          j                            ||          }t          j                            |d	          5 }t          j        |                                                                          cd
d
d
           S # 1 swxY w Y   d
S # t          $ r i cY S w xY w)a  Fetch the user's Nous Portal account/subscription info.

    Calls ``<portal>/api/oauth/account`` with the OAuth access token.

    Returns the parsed JSON dict on success, e.g.::

        {
            "subscription": {
                "plan": "Plus",
                "tier": 2,
                "monthly_charge": 20,
                "credits_remaining": 1686.60,
                ...
            },
            ...
        }

    Returns an empty dict on any failure (network, auth, parse).
    https://portal.nousresearch.com/z/api/oauth/accountBearer application/json)AuthorizationAcceptheaders   timeoutN)
rstripurllibrequestRequesturlopenjsonloadsreaddecoder^   )r   r   baseurlr   reqresps          rA   fetch_nous_account_tierr     s%   ( @@HHMMD
%
%
%C1<11$ Gn$$S'$::^##C#33 	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 	4   			s6   AB: (8B- B: -B11B: 4B15B: :C	C	account_infoc                    |                      d          }t          |t                    sdS |                     d          }|dS 	 t          |          dk    S # t          t
          f$ r Y dS w xY w)u   Return True if the account info indicates a free (unpaid) tier.

    Checks ``subscription.monthly_charge == 0``.  Returns False when
    the field is missing or unparseable (assumes paid — don't block users).
    subscriptionFmonthly_chargeNr   )r[   rV   rZ   r   r   r   )r   subcharges      rA   is_nous_free_tierr     s     

>
*
*Cc4   uWW%&&F~uV}}!!z"   uus   A A/.A/	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unavailablerX   s         rA   partition_nous_models_by_tierr   &  s      2 2JK $ $#w'' 	$c""""s####$$rC   Fforce_refreshcurated_idsr   +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 at the front (so the picker shows them first).
    * ``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 rI   rI   rM   rX   seens     rA   rO   z:union_with_portal_free_recommendations.<locals>.<listcomp>|      BBB#T/////rC   	fetch_nous_recommended_modelsr^   r@   rZ   rV   r[   _extract_model_namer   set)r   r   r   r   payload
free_blockportal_free_idsentrynameaugmented_pricingfree_syntheticrX   augmented_idsnew_onesr   s                 @rA   &union_with_portal_free_recommendationsr   B  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 )u  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 at the front (so the picker shows them first).
    * ``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 rI   rI   r   s     rA   rO   z:union_with_portal_paid_recommendations.<locals>.<listcomp>  r   rC   r   )r   r   r   r   r   
paid_blockportal_paid_idsr   r   r   r   r   s              @rA   &union_with_portal_paid_recommendationsr    st   F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==))r      int_FREE_TIER_CACHE_TTLztuple[bool, float] | None_free_tier_cachec                    t          j                    } t          t          \  }}| |z
  t          k     r|S 	 ddlm}m}  |d            |d          }|sd| fadS |                    dd	          }|                    d
d	          }|sd| fadS t          ||          }t          |          }	|	| fa|	S # t          $ r d| faY dS w xY w)u_  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 False (assume paid) on any error — never blocks paying users.
    Nr   )get_provider_auth_state resolve_nous_runtime_credentials<   )min_key_ttl_secondsra   Fr   r   r   )time	monotonicr  r  hermes_cli.authr
  r  r[   r   r   r^   )
nowcached_result	cached_atr
  r  stater   
portal_urlr   results
             rA   check_nous_free_tierr    s&    .

C##3 y?111  ]]]]]]]] 	)(R@@@@''// 	 %s|5yy44YY0"55
 	 %s|5.|ZHH"<00"C=   !3<uus   %B6 2B6 $B6 6CCz/api/nous/recommended-modelsiX  _NOUS_RECOMMENDED_CACHE_TTLz'dict[str, tuple[dict[str, Any], float]]_nous_recommended_cache      @r   r   c               l   | pd                     d          }t          j                    }t                              |          }|s||\  }}||z
  t
          k     r|S | t           }	 t          j        	                    |ddi          }	t          j        
                    |	|          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||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; pass ``force_refresh=True`` to
    bypass the cache.

    Returns the parsed JSON dict on success, or ``{}`` on any failure
    (network, parse, non-2xx). Callers must treat missing/null fields as
    "no recommendation" and fall back to their own default.
    r   r   Nr   r   r   r   )r   r  r  r  r[   r  NOUS_RECOMMENDED_MODELS_PATHr   r   r   r   r   r   r   r   rV   rZ   r^   )r   r   r   r   r  cachedr   r  r   r   r   r_   s               rA   r   r     s   " @@HHMMD
.

C$((..F V/#?888N
1/
1
1C
n$$12 % 
 
 ^##C#99 	4T:diikk002233D	4 	4 	4 	4 	4 	4 	4 	4 	4 	4 	4 	4 	4 	4 	4$%% 	D    &*3KD!Ks7   *AD .9C3'D 3C77D :C7;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_URLr
  ra   r   r   r   r   )r  r  r
  rW   r[   stripr   r^   )r  r
  r  portals       rA   _resolve_nous_portal_urlr"  9  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)rV   rZ   r[   rW   r   )r   
model_names     rA   r   r   I  sf    eT"" t;''J*c"" "z'7'7'9'9 "!!!4rC   )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"  r   r  r^   r   r[   )r'  r   r   r   r   r   paid_keyfree_key
candidateskeyr   s              rA   get_nous_recommended_aux_modelr2  S  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 )ProviderEntryrW   sluglabeltui_descN)__name__
__module____qualname____annotations__rI   rC   rA   r4  r4    s+         IIIJJJMMMMMrC   r4  zNous Portalz(Nous Portal (Nous Research subscription)
openrouter
OpenRouterz%OpenRouter (100+ models, pay-per-use)NovitaAIz?NovitaAI (AI-native cloud: Model API, Agent Sandbox, GPU Cloud)lmstudioz	LM Studioz8LM Studio (local desktop app with built-in model server)	Anthropicu4   Anthropic (Claude models — API key or Claude Code)OpenAI Codexz
Qwen Cloudz5Qwen Cloud / DashScope Coding (Qwen + multi-provider)'xAI Grok OAuth (SuperGrok Subscription)zXiaomi MiMou:   Xiaomi MiMo (MiMo-V2.5 and V2 models — pro, omni, flash)zTencent TokenHubuJ   Tencent TokenHub (Hy3 Preview — direct API via tokenhub.tencentmaas.com)z
NVIDIA NIMu>   NVIDIA NIM (Nemotron models — build.nvidia.com or local NIM)zGitHub Copilotz3GitHub Copilot (uses GITHUB_TOKEN or gh auth token)zGitHub Copilot ACPz3GitHub Copilot ACP (spawns `copilot --acp --stdio`)zHugging Facez2Hugging Face Inference Providers (20+ open models)zGoogle AI Studiou6   Google AI Studio (Gemini models — native Gemini API)zGoogle Gemini (OAuth)zNGoogle Gemini via OAuth + Code Assist (free tier supported; no API key needed)DeepSeeku0   DeepSeek (DeepSeek-V3, R1, coder — direct API)xAIu    xAI (Grok models — direct API)z
Z.AI / GLMz Z.AI / GLM (Zhipu AI direct API)zKimi / Kimi Coding Planz.Kimi Coding Plan (api.kimi.com) & Moonshot APIzKimi / Moonshot (China)z.Kimi / Moonshot China (Moonshot CN direct API)zStepFun Step Planz9StepFun 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 Cloudu6   Ollama Cloud (cloud-hosted open models — ollama.com)zArcee AIu(   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 Zenz0OpenCode Zen (35+ curated models, pay-as-you-go)zOpenCode Goz1OpenCode Go (open models, $10/month subscription)zAWS Bedrocku>   AWS Bedrock (Claude, Nova, Llama, DeepSeek — IAM or API key)zAzure FoundryuU   Azure Foundry (OpenAI-style or Anthropic-style endpoint — your Azure AI deployment)zVercel AI Gatewayz
qwen-oauthzQwen OAuth (Portal)z(Qwen OAuth (reuses local Qwen CLI login)zlist[ProviderEntry]CANONICAL_PROVIDERSc                    h | ]	}|j         
S rI   r5  rM   r   s     rA   	<setcomp>rK    s    888qAF888rC   )list_providers>   aws_sdkro   oauth_externalexternal_processoauth_device_codez (direct API)c                (    i | ]}|j         |j        S rI   )r5  r6  rJ  s     rA   
<dictcomp>rR    s    AAAAFAGAAArC   zCustom endpointcustomglmzz-aizz.aizhipugithubzgithub-copilotzgithub-modelszgithub-modelzgithub-copilot-acpzcopilot-acp-agentgooglezgoogle-geminizgoogle-ai-studiokimizkimi-cnzmoonshot-cnstepzstepfun-coding-planzarcee-aiarceeaiz	gmi-cloudgmicloudzminimax-china
minimax_cnzminimax-portalzminimax-globalminimax_oauthclaudezclaude-codez	deep-seekopencodezengozopencode-go-sub	aigatewayvercelzvercel-ai-gatewaykiloz	kilo-codezkilo-gateway	dashscopealiyunqwenzalibaba-cloudzqwen-portalz
gemini-clizgemini-oauth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-oauth)zx-aizx.ainimz
nvidia-nimzbuild-nvidianemotronr?  z	lm-studio	lm_studioollamaollama_cloudproviderc                P    t                               | g           }|r|d         ndS )a  Return the default model for a provider, or empty string if unknown.

    Uses the first entry in _PROVIDER_MODELS as the default.  This is the
    model a user would be offered first in the ``hermes model`` picker.

    Used as a fallback when the user has configured a provider but never
    selected a model (e.g. ``hermes auth add openai-codex`` without
    ``hermes model``).
    r   r   )r   r[   )rv  rT   s     rA   get_default_model_for_providerrx  -  s,     !!(B//F&6!99B&rC   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   rV   rZ   r   r[   r   r   r   s    rA   _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)rV   rZ   r[   r@   )r~  paramss     rA    _openrouter_model_supports_toolsr  E  sO      dD!! tXX,--Ffd## tfrC          @c                  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                            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 ]g}|
                    |          }|t'          |          s*t)          |                    d                    rdnd}|                    ||f           h|st          t           p|          S |d         \  }}|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 rI   rI   r   s      rA   rO   z+fetch_openrouter_models.<locals>.<listcomp>s      000VS!S000rC   z#https://openrouter.ai/api/v1/modelsr   r   r   r   r_   idr   r   r2   r   )r5   r@   hermes_cli.model_catalogr  r^   r4   r   r   r   r   r   r   r   r   r[   rV   rZ   rW   r   r  r|  r   )r   r   r  remotefallbackpreferred_idsr   r   r   
live_items
live_by_idr~  rX   curatedpreferred_id	live_itemdescfirst_idr   s                      rA   fetch_openrouter_modelsr  ^  s    !,],-...JJJJJJ..00   %BtF|||40A+B+BH00x000M;n$$1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	 0	:: 	29==3K3KLLTvvRTd+,,,, ;-9:::!*KHaM*GAJ '==sC   0 ??4AD	 89C=1D	 =DD	 DD	 	 D,+D,c                6    d t          |           D             S )z,Return just the OpenRouter model-id strings.c                    g | ]\  }}|S rI   rI   r   s      rA   rO   zmodel_ids.<locals>.<listcomp>      SSSFCCSSSrC   r   )r  r   s    rA   r   r     "    SS5MRRRSSSSrC   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_modelsNra   )r  r  r^   r@   r   r[   )r  r  s     rA   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   outputrz  r{  s    rA   _ai_gateway_model_is_freer    s    gt$$ uW[[#..//14_w{{8UX?Y?Y9Z9Z^_9__z"   uur}  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 rI   rI   r   s      rA   rO   z+fetch_ai_gateway_models.<locals>.<listcomp>  r  rC   r   r
   r   r   r   r   r_   r  r   r   r2   c              3     K   | ]@\  }}|                     d           t          |                    d                    <|V  AdS )zmoonshotai/r   N)
startswithr  r[   )rM   rX   r~  s      rA   	<genexpr>z*fetch_ai_gateway_models.<locals>.<genexpr>  sj       	
 	
T~~m,,	
 *$((9*=*=>>		
	
 	
 	
 	
 	
 	
rC   c                *    g | ]\  }}|k    ||fS rI   rI   )rM   rX   r  free_moonshots      rA   rO   z+fetch_ai_gateway_models.<locals>.<listcomp>  s+    PPP933-;O;OC;;O;O;OrC   r   )r8   r@   hermes_constantsr  r7   r   r   r   r   r   r   r   r   r   r^   r[   rV   rZ   rW   r   r  r   nextitemsinsert)r   r   r  r  r  r   r   r   r  r  r~  rX   r  r  r  r  r  r   r  s                     @rA   fetch_ai_gateway_modelsr    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 rI   rI   r   s      rA   rO   z(ai_gateway_model_ids.<locals>.<listcomp>  r  rC   r   )r  r   s    rA   ai_gateway_model_idsr    r  rC   z$dict[str, dict[str, dict[str, str]]]_pricing_cacheper_token_strc                z    	 t          |           }n# t          t          f$ r Y dS w xY w|dk    rdS |dz  }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).

    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"          → "free"
    ?r   r2   @B $z.2f)r   r   r   )r  valper_ms      rA   _format_price_per_mtokr    sb    M""z"   ss
axxv)OEu???s    ''      rT   pricing_mapcurrent_modelindentc                   | sg S g }d}| D ]\  }}||k    }|                     |          }	|	rtt          |	                     dd                    }
t          |	                     dd                    }|	                     dd          }|rt          |          nd}|rd}nd\  }
}}|                    ||
|||f           t          d |D                       d	z   }t          t          d
 |D             d          t          d |D             d          d          }|r)t          t          d |D             d          d          nd}g }|rb|                    | dd| ddd| ddd| ddd| d	           |                    | d|z   dd|z   dd|z   dd|z              nT|                    | dd| ddd| ddd| d           |                    | d|z   dd|z   dd|z              |D ]r\  }}
}}}|rdnd}|r5|                    | |d| d|
d| d|d| d|d| | 	           E|                    | |d| d|
d| d|d| |            s|S )zBuild a column-aligned model+pricing table for terminal display.

    Returns a list of pre-formatted lines ready to print.
    *models* is ``[(model_id, description), ...]``.
    Fr   r   r   input_cache_readT)r   r   r   c              3  @   K   | ]}t          |d                    V  dS )r   NlenrM   rs     rA   r  z-format_model_pricing_table.<locals>.<genexpr>Q  s,      ++3qt99++++++rC      c              3  P   K   | ]!}|d          
t          |d                    V  "dS )   Nr  r  s     rA   r  z-format_model_pricing_table.<locals>.<genexpr>T  5      --1!-S1YY------rC      )defaultc              3  P   K   | ]!}|d          
t          |d                    V  "dS )r  Nr  r  s     rA   r  z-format_model_pricing_table.<locals>.<genexpr>U  r  rC      c              3  P   K   | ]!}|d          
t          |d                    V  "dS )r  Nr  r  s     rA   r  z-format_model_pricing_table.<locals>.<genexpr>Y  r  rC      r   Model< In>z  OutCachez  /Mtok-u     ← current)r[   r  r   max)rT   r  r  r  rows	has_cacherX   _descis_curr   inpout
cache_readcachename_col	price_col	cache_collinesmarkers                      rA   format_model_pricing_tabler  2  s     	 35DI 4 4
U%OOC   	)(x)<)<==C(|R)@)@AAC1266J:DL*:666"E ! 	(OCeS#sE623333++d+++++a/H-----q999-----q999	 I 
-----q999	     E  W~~(~~~~d~Y~~~~5~S\~~~~ah~kt~~~~~hhhhyhhC)OhhWZ]fWfhhiiiiff(ffffdfYffff5fS\fffffgggUhUUyUUC)OUUVVV(, f f$S#uf$*2 	fLLFzCz(zzzzczIzzzz#zPYzzzz^czfozzzrxzz{{{{LLFdCd(ddddcdIdddd#dPYddd\bddeeeeLrC   https://openrouter.ai/apiapi_key
str | Nonebase_urlc               4   |pd                     d          }|s|t          v rt          |         S |                     d          dz   }dt          d}| rd|  |d<   	 t          j                            ||          }t          j                            ||	          5 }t          j        |	                                
                                          }	d
d
d
           n# 1 swxY w Y   n# t          $ r i t          |<   i cY S w xY wi }
|	                    dg           D ]}|                    d          }|                    d          }|rt          |t                    rt          |                    dd                    t          |                    dd                    d}|                    d          rt          |d                   |d<   |                    d          rt          |d                   |d<   ||
|<   |
t          |<   |
S )zFetch ``/v1/models`` and return ``{model_id: {prompt, completion}}`` pricing.

    Results are cached per *base_url* so repeated calls are free.
    Works with any OpenRouter-compatible endpoint (OpenRouter, Nous Portal).
    r   r   z
/v1/modelsr   )r   
User-Agentr   r   r   r   Nr_   r  r   r   r   r   r  input_cache_write)r   r  _HERMES_USER_AGENTr   r   r   r   r   r   r   r   r^   r[   rV   rZ   rW   )r  r  r   r   	cache_keyr   r   r   r   r   r  r~  rX   r   r   s                  rA   fetch_models_with_pricingr  p  sp    R'',,I )Y.88i((


3

,
.C$( G  7#6W#6#6 n$$S'$::^##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   $&y!			 )+FFB''    hhtnn((9%% 		 :gt,, 		 gkk(B7788!'++lB"?"?@@% %E {{-.. M,/8J0K,L,L(){{.// O-09L1M-N-N)*F3K &N9Ms7   AC0 9C$C0 $C((C0 +C(,C0 0D	D	c                   ddl m} |                    d          }|s|t          v r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 i 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   r
   r   r   r   r   Nr_   r  r   r  r   r  r   r  r  )r  r  r   r  r   r   r   r   r   r   r   r   r^   r[   rV   rZ   rW   )r   r   r  r  r   r   r   r  r~  rX   r   r   s               rA   fetch_ai_gateway_pricingr    sj    544444#**3//I )Y.88i((	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   $&y!			 )+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 &N9Ms6   AC <9C5C CC C	C C&%C&c                 P    t          j        dd                                          S )z1Best-effort OpenRouter API key for pricing fetch.OPENROUTER_API_KEYr   )osgetenvr   rI   rC   rA   _resolve_openrouter_api_keyr    s!    9)2..44666rC   z&https://inference-api.nousresearch.comtuple[str, str]c                     	 ddl m}   |             }|r,|                    dd          |                    dd          fS n# t          $ r Y nw xY wdt          fS )uh  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 the default 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").
    r   )r  r  r   r  )r  r  r[   r^   _DEFAULT_NOUS_INFERENCE_BASE)r  credss     rA   !_resolve_nous_pricing_credentialsr    s    DDDDDD0022 	IIIi,,eii
B.G.GHH	I   ,--s   =A 
AAc                  t          |           }|dk    rt          t                      d|          S |dk    rt          |          S |dk    rt	          |          S |dk    rYt                      \  }}|rF|                    d          }|                    d	          r
|d
d         }t          |||          S i S )zYReturn live pricing for providers that support it (openrouter, nous, ai-gateway, novita).r<  r  )r  r  r   r   r   r   ra   r   /v1N)normalize_providerr  r  r  _fetch_novita_pricingr  r   endswith)rv  r   
normalizedr  r  strippeds         rA   get_pricing_for_providerr    s    #H--J\!!(/110'
 
 
 	

 \!!'mDDDDX$=AAAAV=?? 
	  s++H  '' )#CRC=,!+   
 IrC   c               D   t          j        dd                                          }|si S t          j        dd                                          pd}|                    d          }|s|t          v rt          |         S |dz   }d| dt
          d	}	 t          j                            ||
          }t          j        	                    ||           5 }t          j        |                                                                          }	ddd           n# 1 swxY w Y   n# t          $ r i 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_KEYr   NOVITA_BASE_URLzhttps://api.novita.ai/openai/v1r   r
   r   r   )r   r   r  r   r   Nr_   r  input_token_price_per_moutput_token_price_per_mr   i'  r  r   )r  r  r   r   r  r  r   r   r   r   r   r   r   r   r^   r[   rV   rZ   rW   r   )r   r   r  r  r  r   r   r   r   r   r  r~  rX   r  r  s                  rA   r  r    sr    i("--3355G 	y*B//5577\;\H$$I )Y.88i((
i
C,7,,$( Gn$$S'$::^##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   $&y!			 )+FFB'' 
 
$%% 	hhtnn 	hh011hh122;3;%q//F2Y>??eCH1oo6BCC
 
s3xx
 !'N9Ms7   AD) 9DD) D!!D) $D!%D) )EEset[str]_KNOWN_PROVIDER_NAMES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 rI   rI  rJ  s     rA   rO   z,list_available_providers.<locals>.<listcomp>]  s    :::af:::rC   rS  Fr   )get_auth_statushas_usable_secretr   r<  r  	logged_in
configured)r  r6  aliasesauthenticated)rG  _PROVIDER_ALIASESr  
setdefaultr   _PROVIDER_LABELSr[   r  r  r  _get_custom_base_urlr   r   r  r  r^   )provider_orderaliases_foralias	canonicalr  pidr6  
alias_list	has_credsr  r  custom_base_urlstatuss                rA   list_available_providersr#  S  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rawcurrent_providerc                   |                                  }|                    d          }|dk    r|d|                                                                          }||dz   d                                          }|r|r|t          v rv|dk    r_d|v r[|                    d          }|d|                                          }||dz   d                                          }|r	|r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   Nr  rS  custom:)r   findlowerr  r   )	r$  r%  r  colonprovider_part
model_partsecond_coloncustom_nameactual_models	            rA   parse_model_inputr1    s0   " yy{{HMM#Eqyy %(..006688eaijj)//11
 
	CZ 
	CM=R,R,R ((SJ->->)s33(,7==??),*:*;*;<BBDD C< C3k33\BB&}55zBBh''rC   c                 
   	 ddl m}   |             }|                    di           }t          |t                    r5t          |                    dd                                                    S n# t          $ r Y nw xY wdS )z2Get the custom endpoint base_url from config.yaml.r   )load_configmodelr  r   )hermes_cli.configr3  r[   rV   rZ   rW   r   r^   )r3  config	model_cfgs      rA   r  r    s    111111JJw++	i&& 	>y}}Z4455;;===	>   2s   A/A3 3
B ?B 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 r   rI   rL   s     rA   rO   z/curated_models_for_provider.<locals>.<listcomp>  s    &&&AB&&&rC   c                    g | ]}|d fS r:  rI   rL   s     rA   rO   z/curated_models_for_provider.<locals>.<listcomp>  s    $$$QG$$$rC   )r   r  provider_model_idsr   r[   )rv  r   r  liverT   s        rA   curated_models_for_providerr>    s     $H--J\!!&]CCCC j))D '&&&&&& !!*b11F$$V$$$$rC   c                    | pd                                                                 }t          |           }d ||fD             S )Nr   c                    h | ]}||S rI   rI   )rM   ks     rA   rK  z!_provider_keys.<locals>.<setcomp>  s    ...!A.A...rC   )r   r*  r   )rv  r1  r  s      rA   _provider_keysrB    sI    >r
 
 
"
"
(
(
*
*C#H--J..Z(....rC   
name_lower	providersc                :     t           fd|D                       S )Nc              3     K   | ]:}t                               |g           D ]}|                                k    V  ;d S N)r   r[   r*  )rM   rv  r4  rC  s      rA   r  z-_model_in_provider_catalog.<locals>.<genexpr>  sk        %))(B77   	ekkmm#      rC   )any)rC  rD  s   ` rA   _model_in_provider_catalogrI    s;        !     rC   >   ra   ro   r   r<  r   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 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_ALIASESNrv  rW   r9   r#  c                    t                               | g           }|sd S | t          v r d n                                }|D ]-}|                                                    |          r|c S .d S )Nr   )r   r[   _AGGREGATOR_PROVIDERSr*  r  )rv  rT   prefixr4  familyvendors       rA   _matchz+_resolve_static_model_alias.<locals>._match  s    !%%h33 	4 000      
%''	 	
  	 	E{{}}''// trC   )rv  rW   r9   r#  )hermes_cli.model_switchrM  r^   r[   rR  rQ  r   rO  )	rC  rJ  rM  identityrS  rv  matchedrQ  rR  s	          @@rA   _resolve_static_model_aliasrW    sm   
9999999   tt   ,,Ht_F_F       ! % %fX&&&7 	%W$$$$	% % % %|##x3H'H'HfX&&&7 	%W$$$$	% * % %|##FF84D4D)D#W$$$$4s    
r&  c                  	 | pd                                 }|sdS |                                	t          |          }t          	|          }|r|S t                              		          }|dvr4t                              |g           }|t          v r|r||vr
||d         fS t          	|          rdS t          	                                D ]4\  }}||v s	|t          v rt          	fd|D                       r||fc S 5d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.
    r   N>   rS  r<  r   c              3  H   K   | ]}|                                 k    V  d S rG  r*  )rM   rN   rC  s     rA   r  z3detect_static_provider_for_model.<locals>.<genexpr>9  s0      771zQWWYY&777777rC   )r   r*  rB  rW  r  r[   r   r  rI  r  rO  rH  )
r&  r%  r   rJ  alias_matchresolved_providerdefault_modelsr  rT   rC  s
            @rA    detect_static_provider_for_modelr^    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'899 "*l;; t (--//  V,#)>">">777777777 	;	 4rC   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
    r   Nr<  )r   r^  rI  r*  rB  _find_openrouter_slug)r&  r%  r   static_matchor_slugs        rA   detect_provider_for_modelrc  ?  s     "##%%D t3D:JKKL !$**,,?O0P0PQQ t $D))G |++ '**d?? '**t4rC   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   split)r&  rC  rX   r   r-  s        rA   r`  r`  f  s     !!##))++J t {{  $$JJJ % {{  #::IIc1--MAzZ--////


4rC   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*  r  r[   )rv  r  s     rA   r   r     s<     *l113399;;J  Z888rC   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   r  r[   )rv  originalr  s      rA   provider_labelrk    s_    (L//11H!!JVv#J//J
H,DEEErC   )gpt-o1o3o4ztuple[str, ...]_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.r   r'  r   Fcodexc              3  B   K   | ]}                     |          V  d S rG  )r  )rM   rP  r   s     rA   r  z(_is_openai_fast_model.<locals>.<genexpr>  s/      PP6tv&&PPPPPPrC   )_strip_vendor_prefixrW   re  rH  rp  r   r$  r   s     @rA   _is_openai_fast_modelrv    sq    
s8>r22
3
3C99S>>!D u $uPPPP5OPPPPPPrC   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').r   r   r  )rW   r   r*  re  )r   r$  s     rA   rt  rt    sQ    
hn"


#
#
%
%
+
+
-
-C
czziiQ"JrC   c                >    t          |           pt          |           S )zDReturn whether Hermes should expose the /fast toggle for this model.)_is_anthropic_fast_modelrv  r   s    rA   model_supports_fast_moder{    s    #H--P1Fx1P1PPrC   c                    t          t          | pd                    }|                    d          d         }|                    d          sdS d|v pd|v S )a  Return True if the model is a Claude model eligible for Anthropic Fast Mode.

    Fast mode is currently supported on Claude Opus 4.6 only. Per Anthropic's
    docs (https://platform.claude.com/docs/en/build-with-claude/fast-mode):
    "Fast mode is currently supported on Opus 4.6 only. Sending speed: fast
    with an unsupported model returns an error." Opus 4.7 explicitly rejects
    the ``speed`` parameter with HTTP 400.
    r   r'  r   claude-Fzopus-4-6zopus-4.6)rt  rW   re  r  ru  s      rA   ry  ry    s_     s8>r22
3
3C99S>>!D??9%% u3t!33rC   dict[str, Any] | Nonec                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)

    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{  ry  rz  s    rA   resolve_fast_mode_overridesr    s?     $H-- t)) !  J''rC   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_credentialsro   r  r   )read_credential_pool)exchange_copilot_tokenvalidate_copilot_tokenr   )r  r  rW   r[   r   r^   r  hermes_cli.copilot_authr  r  rV   rZ   )r  r  r  r  r  r  r   r$  validr   	api_token_expires_ats               rA    _resolve_copilot_catalog_api_keyr    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>   rx   groqcohererw   rW  r|   mistralr   r   	fireworks
perplexity
togetherair   r   r   zfrozenset[str]_MODELS_DEV_PREFERREDr  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)	rY   r  r^   r@   r   rW   r*  addr   )rv  r  r  mdev
seen_lowermergedrX   r1  s           rA   _merge_with_models_devr  H  s,   888888""8,,     G}} 55JF  #hhnn*sc  #hhnn*scMs    ##c          	     P   t          |           }|dk    rt          |          S |dk    rOddlm} d}	 ddlm}  |d	          }|                    d
          }n# t          $ r d}Y nw xY w ||          S |dk    rAt          t                              dt                              dg                               S |dv r_	 t          t                                }|r|S n# t          $ r Y nw xY w|dk    r(t          t                              dg                     S |dk    r_	 ddlm}m}	  |	            }|r9 ||                    d
d          |                    dd                    }|r|S n# t          $ 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# t          $ r Y nw xY w|dk    rt%                      }|r|S |dk    rt'                      }|r|S |dk    rt)          |          }|r|S |dk    rt+          j        dd                                          }|rdt+          j        dd                                                              d          }|pd}	 t#          ||          }|r|S n# t          $ 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# t          $ r Y nw xY w|d!k    rct1                      }|rSt+          j        d"d          p)t+          j        dd          pt+          j        d#d          }t#          ||          }|r|S |d$k    r&	 dd%lm}  |            }||S n# t          $ r Y nw xY w	 dd&lm} ddlm}
  ||          }|r|j        d
k    r|j        r	  |
|          }t          |                    d
          pd                                          }t          |                    d          pd                                          }n# t          $ r d|j        }}Y nw xY w|s|j        }|r|                    |'          }|r|S |j         rt          |j                   S n# t          $ r Y nw xY wt          t                              |g                     }|tB          v rtE          ||          S |S )(a  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.
    r<  r   rl   r   )get_codex_model_idsN)!resolve_codex_runtime_credentialsT)refresh_if_expiringr  )r   rm   rS   >   ro   rn   rn   ro   ra   )fetch_nous_modelsr  r   r  )r  inference_base_urlr   r  r   r   rF  rc   OPENAI_API_KEYOPENAI_BASE_URLr   zhttps://api.openai.com/v1r   rS  CUSTOM_API_KEYr  r   )bedrock_model_ids_or_none)get_provider_profiler  )#r   r   r?   r  r  r  r[   r^   r@   r   _fetch_github_modelsr  r  r  r  rW   r   fetch_api_models_fetch_anthropic_models_fetch_ai_gateway_modelsfetch_ollama_cloud_modelsr  r  r   r  agent.bedrock_adapterr  rD  r  	auth_typer  fetch_modelsfallback_modelsr  r  )rv  r   r  r  r   r  r  r=  r  r  r  r  r  base_rawr   r  rF   r  _pcurated_statics                       rA   r<  r<  m  sb    $H--J\!!}5555^##?????? 	 IIIIII55$OOOE 99Y//LL 	  	  	 LLL	 ""====[  $((6F6J6J5RT6U6UVVWWW///	'(H(J(JKKD  	 	 	D	&&(,,Y;;<<<V	[[[[[[[[4466E  ((9b1I1I^c^g^ghrtv^w^wxxx  K 	 	 	D	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	[  &(( 	K\!!')) 	K^##(}EEE 	KX),b117799 	y!2B77==??FFsKKH::D'66  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')) 		 	*B// 79-r22791266 
 $GX66D 
 Y	GGGGGG++--C
  	 	 	D	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77  K! 0B.///    *..z2>>??N***%j.AAAs   'A A,+A,C' '
C43C4,AE: :
FFBH' '
H43H4/L 
LLBN1 1
N>=N>1Q 
QQ+U A7S; :U ;TU T'U 9U 
U"!U"Optional[list[str]]c                    	 ddl m}m} n# t          $ r Y dS w xY w |            }|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          $ r=}ddl}|                    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).  Returns sorted model IDs or None.
    r   )resolve_anthropic_token_is_oauth_tokenNanthropic-version
2023-06-01r   r   )_COMMON_BETAS_OAUTH_ONLY_BETAS_CONTEXT_1M_BETA,zanthropic-beta	x-api-keyhdict[str, str]c                ,   t           j                            d|           }t           j                            |          5 }t	          j        |                                                                          cd d d            S # 1 swxY w Y   d S )Nz#https://api.anthropic.com/v1/modelsr   r   )r   r   r   r   r   r   r   r   )r  r   r   r   s      rA   _do_requestz,_fetch_anthropic_models.<locals>._do_request"	  s    n$$1 % 
 
 ^##C#99 	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  ignore)errorsr   zlong context betaznot yet availablec                     g | ]
}|k    |S rI   rI   )rM   br  s     rA   rO   z+_fetch_anthropic_models.<locals>.<listcomp><	  s$    KKKqQ:J5J5J5J5J5JrC   c                H    g | ]}|                     d           |d           S r  r[   rL   s     rA   rO   z+_fetch_anthropic_models.<locals>.<listcomp>D	  s+    GGGa155;;G!D'GGGrC   r_   c                    d| vd| vd| v| fS )NopussonnethaikurI   )rN   s    rA   <lambda>z)_fetch_anthropic_models.<locals>.<lambda>F	  s#    !OA1	-
 rC   )r1  z$Failed to fetch Anthropic models: %s)r  r  )agent.anthropic_adapterr  r  ImportErrorr  r  r  joinr   error	HTTPErrorcoder   r   r*  r^   r@   r[   r]   logging	getLoggerr8  debug)r   r  r  tokenr   is_oauthr  r  r  r_   http_err	body_textrT   er  r  s   `              @rA   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%	;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    
8B E< EE&:C! E!C0-E/C00AE=E< E4E< <
G2F>>Gr   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 rI   rV   rZ   rM   r~  s     rA   rO   z"_payload_items.<locals>.<listcomp>T	  s'    CCCJtT,B,BCCCCrC   r_   c                <    g | ]}t          |t                    |S rI   r  r  s     rA   rO   z"_payload_items.<locals>.<listcomp>X	  s'    DDDTZd-C-CDDDDDrC   )rV   r@   rZ   r[   )r   r_   s     rA   _payload_itemsr  R	  s{    '4   DCCCCCC'4   E{{62&&dD!! 	EDDTDDDDIrC   r  c                 `    	 ddl m}   | d          S # t          $ r t          dd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_headersT)is_agent_turnzHermesAgent/1.0zconversation-editsagent)zEditor-Versionr  zOpenai-Intentzx-initiator)r  r  r  COPILOT_EDITOR_VERSIONr  s    rA   copilot_default_headersr  \	  sm    	
CCCCCC&&T:::: 
 
 
4+1"	
 
 	
 	
 	

s    --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 )Nr  r   Fmodel_picker_enabledcapabilitiestypechatsupported_endpointsc                    h | ]D}t          |                                          #t          |                                          ES rI   rW   r   rM   endpoints     rA   rK  z6_copilot_catalog_item_is_text_model.<locals>.<setcomp>~	  sR      
  
  
8}}""$$ 
MM!! 
  
  
rC   >   
/responses/v1/messages/chat/completionsT)rW   r[   r   rV   rZ   r*  r@   intersection)r~  r   r  
model_typer  normalized_endpointss         rA   #_copilot_catalog_item_is_text_modelr  n	  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4rC   Optional[list[dict[str, Any]]]c                `   g }| r*|                     i t                      dd|  i           |                     t                                 |D ]Z}t          j                            t
          |          }	 t          j                            ||          5 }t          j        |	                                
                                          }t          |          }g }t                      }	|D ]y}
t          |
          st          |
                    d          pd                                          }|r||	v rO|	                    |           |                     |
           z|r|cddd           c S 	 ddd           n# 1 swxY w Y   K# t$          $ r Y Xw xY wdS )z=Fetch the live GitHub Copilot model catalog for this account.r   r   r   r   r  r   N)r   r  r   r   r   COPILOT_MODELS_URLr   r   r   r   r   r  r   r  rW   r[   r   r  r^   )r  r   attemptsr   r   r   r_   r  rT   seen_idsr~  r   s               rA   fetch_github_model_catalogr  	  s7    &(H  
%''
0w00
 
 	 	 	 OO+--...  n$$%7$II	''W'== "z$))++"4"4"6"677&t,,/1%(UU! ( (D>tDD ! "488D>>#7R88>>@@H# !x8';'; LL***MM$'''' "!" " " " " " " " " """ " " " " " " " " " " " " " "  	 	 	H	4s=   <!FCF4FFF	FF	F
F+*F+zdict[str, int]_copilot_context_cacheg        _copilot_context_cache_timei  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  r  r   r  limitsmax_prompt_tokensr   )
r  r  r  _COPILOT_CONTEXT_CACHE_TTLr  rW   r[   r   rV   r  )	r   r  catalogr  r~  rX   capsr	  
max_prompts	            rA   get_copilot_model_contextr  	  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rC   c                    | pd                                                     d                                          }|                    t                    p)|                    d          p|                    d          S )Nr   r   z"https://models.github.ai/inferencez%https://models.inference.ai.azure.com)r   r   r*  r  COPILOT_BASE_URL)r  r  s     rA   _is_github_models_base_urlr  	  sx    .b''))0055;;==J.// 	J  !EFF	J  !HIIrC   c                    | pd                                                     d          }|                    d          r|dd                             d          }|pdS )zStrip ``/v1`` suffix from an LM Studio base URL to get the native API root.

    Returns ``None`` when the base URL is empty/invalid.
    r   r   r  Nr  )r   r   r  )r  roots     rA   _lmstudio_server_rootr  	  s`    
 N!!##**3//D}}U %CRCy$$<4rC   rZ   c                r    dt           i}t          | pd                                          }|rd| |d<   |S )z5Build HTTP headers for LM Studio native API requests.r  r   r   r   )r  rW   r   )r  r   r  s      rA   _lmstudio_request_headersr  	  sI    /0G2$$&&E 5#4U#4#4 NrC   Optional[list[dict]]c                   t          |          }|sdS t          |           }t          j                            |dz   |          }	 t          j                            ||          5 }t          j        |                                	                                          }ddd           n# 1 swxY w Y   n# t          j
        j        $ rj}|j        dv rddlm}	  |	d|j         d	d
d          |ddl}
|
                    t"                                        d||j                   Y d}~dS d}~wt&          $ r>}ddl}
|
                    t"                                        d||           Y d}~dS d}~ww xY wt)          |t*                    r|                    d          nd}t)          |t.                    s4ddl}
|
                    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?  auth_rejected)rv  r  z)LM Studio probe at %s failed with HTTP %sz LM Studio probe at %s failed: %srT   zCLM Studio probe at %s returned malformed payload (no `models` list))r  r  r   r   r   r   r   r   r   r   r  r  r  r  r  r  r  r8  r  r^   rV   rZ   r[   r@   )r  r  r   server_rootr   r   r   r   excr  r  
raw_modelss               rA   _lmstudio_fetch_raw_modelsr#  	  sw    (11K t'00Gn$$[3C%CW$UUG^##GW#== 	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: .B22B: 5B26B: :E>AD33E> 3E99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  r   	embeddingr1  r  )r#  rV   rZ   rW   r[   r   r*  r   )r  r  r   r"  r\   r$  r1  s          rA   probe_lmstudio_modelsr'  *
  s     ,GhX_```JtD  #t$$ 	swwv$"%%++--3355DD#''%..7CGGDMM7R88>>@@ 	3d??KKKrC   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   rT   s       rA   fetch_lmstudio_modelsr)  I
  s"     #7XwWWWF<RrC         ^@r4  target_context_lengthc                p   t          |          }|sdS t          |          }	 t          ||d          }n# t          $ r d}Y nw xY w|dS d}|D ]N}	t	          |	t
                    s|	                    d          | k    s|	                    d          | k    r|	} nO|dS |                    d          }
t	          |
t                    r|
dk    rt          ||
          }|                    d          pg D ]y}t	          |t
                    r|                    d	          nd}t	          |t
                    r|                    d
          nd}t	          |t                    r
||k    r|c S zt          j
        | |d                                          }t          |          }d|d<   	 t          j                            t          j                            |dz   ||d          |          5 }|                                 ddd           n# 1 swxY w Y   n# t          $ r Y dS w xY w|S )aw  Ensure LM Studio has ``model`` loaded with at least ``target_context_length``.

    No-op when an instance is already loaded with sufficient context. Otherwise
    POSTs ``/api/v1/models/load`` to (re)load with the target context, capped
    at the model's ``max_context_length``. Returns the resolved loaded context
    length, or ``None`` when the probe / load failed.
    N
   r%  r1  r  max_context_lengthr   loaded_instancesr6  context_length)r4  r0  r   zContent-Typez/api/v1/models/loadPOST)r_   r   methodr   )r  r  r#  r^   rV   rZ   r[   r  minr   dumpsencoder   r   r   r   r   )r4  r  r  r+  r   r   r   r"  target_entryr$  max_ctxinstcfg
loaded_ctxbodyload_headersr   s                    rA   ensure_lmstudio_model_loadedr=  \
  s    (11K t'00G/(\^___

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


tL  #t$$ 	775>>U""cggdmmu&<&<LE '= t344G'3 DGaKK #$97 C C  !344:  $.tT$:$:Ddhhx   2<S$2G2GQSWW-...T
j#&& 	:9N+N+N:/    vxx 	 ==L#5L ^##N""33$	 #    $ 
 
 		 IIKKK		 		 		 		 		 		 		 		 		 		 		 		 		 		 		    tt  sF   7 AA3AH% 8HH% HH%  H!H% %
H32H3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%  Nr1  r  r  	reasoningallowed_optionsc                    g | ]J}t          |t                    t          |                                                                          KS rI   )rV   rW   r   r*  )rM   os     rA   rO   z4lmstudio_model_reasoning_options.<locals>.<listcomp>
  sA    OOOqJq#<N<NOCFFLLNN((**OOOrC   )r#  r^   rV   rZ   r[   r@   )	r4  r  r  r   r"  r$  r  r?  optss	            rA    lmstudio_model_reasoning_optionsrD  
  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    t          | |          }|sd S d |D             S )Nr  r   c                d    g | ]-}|                     d           |                     d d          .S r  r   r  r  s     rA   rO   z(_fetch_github_models.<locals>.<listcomp>
  s5    EEE4dhhtnnEDHHT2EEErC   )r  )r  r   r  s      rA   r  r  
  s5    ('JJJG tEE7EEEErC   zopenai/gpt-5rf   zopenai/gpt-5-chatzopenai/gpt-5-minizopenai/gpt-5-nanozopenai/gpt-4.1ri   zopenai/gpt-4.1-minizopenai/gpt-4.1-nanozopenai/gpt-4orj   zopenai/gpt-4o-minirk   	openai/o1r   zopenai/o1-minizopenai/o1-preview	openai/o3rg   zopenai/o3-minizopenai/o4-minir   zclaude-opus-4.6r   rp   rq   rr   rs   )zanthropic/claude-sonnet-4zanthropic/claude-sonnet-4.5r   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 rH  )rW   r[   r   r  s     rA   rK  z'_copilot_catalog_ids.<locals>.<setcomp>
  st       txx~~#$$**,,DHHTNN b!!''))  rC   )r  r   r  r  s     rA   _copilot_catalog_idsrN  
  sO     7,W=== uu    rC   rM  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 )	Nr   rM  r   r  z-miniz-nanoz-chat)
rW   r   rN  _COPILOT_MODEL_ALIASESr[   r   re  r  r   r  )	r   r  r  r$  catalog_idsr  r0  r   	candidates	            rA   normalize_copilot_model_idrT  
  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  #))+++JrC   c                <   | pd                                                                 }|                    d          rt          t                    S t          |                                           }|                    d          rt          t                    S g S )Nr   )rI  rJ  z	openai/o4rm  rn  ro  r   )r   r*  r  r@   "COPILOT_REASONING_EFFORTS_O_SERIESrT  COPILOT_REASONING_EFFORTS_GPT5)r   r$  r  s      rA   &_github_reasoning_efforts_for_model_idrX  (  s    >r
 
 
"
"
(
(
*
*C
~~OPP 86777+H55;;==JW%% 42333IrC   c                    ddl }|                    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  rf   )rematchr  groupr  )r   rZ  r[  majors       rA   !_should_use_copilot_responses_apir^  2  sd     IIIHH]H--E uAEA:?h11,????rC   c               :   ||rt          |          }t          | ||          sdS t                    rdS |r\t          fd|D             d          }t	          |t
                    r+d |                    d          pg D             }d	|v rd
|v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  rM  chat_completionscodex_responsesc              3  N   K   | ]}|                     d           k    |V   dS r  Nr  rM   r~  r  s     rA   r  z)copilot_model_api_mode.<locals>.<genexpr>^  7      WWt$((4..J:V:Vd:V:V:V:VWWrC   c                    h | ]D}t          |                                          #t          |                                          ES rI   r  r  s     rA   rK  z)copilot_model_api_mode.<locals>.<setcomp>`  sR     # # #x==&&((#H##%%# # #rC   r  r  r  anthropic_messages)r  rT  r^  r  rV   rZ   r[   )r   r  r  catalog_entryr  r  s        @rA   copilot_model_api_moderi  C  s     7,W===+HgwWWWJ "!! )44 !    
,WWWWwWWWY]^^mT** 	,# #!.!2!23H!I!I!OR# # # !4449LTg9g9g++rC   )rr  r   rm  rn  ro  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.
    r   Nr   r  ra  )rW   r   r*  rsplit!_AZURE_FOUNDRY_RESPONSES_PREFIXESr  )r&  r$  rP  s      rA   azure_foundry_model_api_modern  }  s     jB


%
%
'
'
-
-
/
/C t
czzjja  $ 4 % %>>&!! 	%$$$	%4rC   provider_idc                    t          |           }t          |pd                                          }|r|dvr|S | d}|                                                    |          r|t          |          d         S |S )zJNormalize OpenCode config IDs to the bare model slug used in API requests.r   >   r   r   r   N)r   rW   r   r*  r  r  )ro  r   rv  currentrP  s        rA   normalize_opencode_model_idrr    s    !+..H(.b!!''))G h&EEE^^^F}}!!&)) %s6{{||$$NrC   c                   t          |           }t          | |                                          }|sdS |dk    r|                    d          rdS dS |dk    r0|                    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 models on Zen use ``/v1/responses``
    - Claude models on Zen use ``/v1/messages``
    - MiniMax models on Go use ``/v1/messages``
    - GLM / Kimi on Go use ``/v1/chat/completions``
    - Other Zen models (Gemini, GLM, Kimi, MiniMax, Qwen, etc.) use
      ``/v1/chat/completions``

    This follows the published OpenCode docs for Zen and Go endpoints.
    r`  r   zminimax-rg  r   r}  rl  ra  )r   rr  r*  r  )ro  r   rv  r  s       rA   opencode_model_api_modert    s     "+..H,[(CCIIKKJ "!!=    ,, 	(''!!>!!  ++ 	(''  (( 	%$$!!rC   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.rM  Nc              3  N   K   | ]}|                     d           k    |V   dS rc  r  rd  s     rA   r  z1github_model_reasoning_efforts.<locals>.<genexpr>  re  rC   r  c              3  N   K   | ]}|                     d           k    |V   dS rc  r  rd  s     rA   r  z1github_model_reasoning_efforts.<locals>.<genexpr>  s8      !c!c4dhhtnnXbFbFb$FbFbFbFb!c!crC   r  supportsreasoning_effortc                    g | ]V}t          |                                          #t          |                                                                          WS rI   rW   r   r*  )rM   efforts     rA   rO   z2github_model_reasoning_efforts.<locals>.<listcomp>  s^     * * *"v;;,,..*F))++1133* * *rC   c                    h | ]V}t          |                                          #t          |                                                                          WS rI   r{  )rM   
capabilitys     rA   rK  z1github_model_reasoning_efforts.<locals>.<setcomp>  s^     
 
 
:$$&&

OO!!##))++
 
 
rC   r?  )
rT  r  r  r[   rV   rZ   r@   fromkeysrX  rW   )r   r  r  rh  fetched_catalogr  rx  effortsnormalized_effortslegacy_capabilitiesr  s             @rA   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NrC   api_modec                   |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}	| r|dk    r| |	d<   d|	d<   n
| rd|  |	d<   |	                    t
                    r!|	
                    t                                 |D ]\  }
}|
                    d          dz   }|                    |           t          j                            ||	          }	 t          j                            ||          5 }t!          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.
    r   r   NF)rT   
probed_urlresolved_base_urlsuggested_base_urlused_fallbackrF  r  r  Tr  rg  r  r  r  r   r   r
   r   r   c                :    g | ]}|                     d d          S rH  r  rL   s     rA   rO   z$probe_api_models.<locals>.<listcomp>1  s$    MMM1quuT2MMMrC   r_   r   )r   r   r  r  r  r  r  r   r  r  updater  r   r   r   r   r   r   r   r   r[   r^   )r  r  r   r  r  rT   alternate_baser0  triedr   candidate_baseis_fallbackr   r   r   r_   s                   rA   probe_api_modelsr    sO    .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 78333&'3#$$	 7#6W#6#6 -.. 2.00111'1  ###C((94Sn$$S'$::	''W'== 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   <!H2A9H%H2%H)	)H2,H)	-H22
I ?I 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_KEYr   Nr  r   r  r   r
   r   )r   r  r   r   c                    g | ]Q}|                     d           r:|                     d          dk    r!d|                     d          pg v I|d          RS )r  r  languageztool-usetagsr  rL   s     rA   rO   z,_fetch_ai_gateway_models.<locals>.<listcomp>V  sk       55;; EE&MMZ//155==#6B77	 $ 877rC   r_   )r  r  r   r  r  r   r  r   r   r   r   r   r   r   r   r[   r^   )	r   r  r  r  r   r   r   r   r_   s	            rA   r  r  C  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                N    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  rT   )r  r[   )r  r  r   r  s       rA   r  r  a  s*     GXwRRRVVW_```rC   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   suffixs     rA   _strip_ollama_cloud_suffixr  x  sP     ' , ,V$$ 	,Ns6{{lN++++	,OrC   r   c                 (    ddl m}   |             dz  S )z1Return the path for the Ollama Cloud model cache.r   get_hermes_homezollama_cloud_models_cache.json)r  r  r  s    rA   _ollama_cloud_cache_pathr    s(    000000????rC   
ignore_ttlr  Optional[dict]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).
    Nzutf-8)encodingrT   r  r   )r  existsopenr   loadrV   rZ   r[   r@   r  _OLLAMA_CLOUD_CACHE_TTLr^   )r  
cache_pathfr_   rT   r  s         rA   _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Nonec                    	 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   )atomic_json_writeT)parentsexist_ok)rT   r  N)r  )utilsr  r  parentmkdirr  r^   )rT   r  r  s      rA   _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).
    NrT   OLLAMA_API_KEYr   OLLAMA_BASE_URLzhttps://ollama.com/v1r  r   r   r  rF  Tr  )r  r  r  r  rY   r  r^   r   r  r   r  r  )r  r  r   r  live_modelsr  mdev_modelsr  r   r  rN   r  stales                rA   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  c          
        | pd                                 }t          |          }|dk    r|rd|vrd}|}|dk    rt          ||          p|}|sddddd	S 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 |dk    s|                    d          rz|dk    rt          |||          }nt          ||          }|
                    d          }||t          |          v rddddd	S t          ||dd          }|rddd|d         d | d!|d          d"d#S t          ||d$d%          }d}|r"d&d'                    d( |D                       z   }d)| d*|
                    d+           d,| }|
                    d-          r|d.|
                    d/           d0z  }ddd|d	S d1|
                    d+           d2| d3}|dk    r|d4z  }|
                    d5          r|d6|
                    d5           d"z  }|dk    dd|d	S |d7v r	 t          |          }n# t          $ r g }Y nw xY w|r|t          |          v rddddd	S t          ||dd          }|rddd|d         d | d!|d          d"d#S t          ||d$d%          }d}|r"d&d'                    d8 |D                       z   }|d9k    rd:nd;}dddd)| 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!                                                    }t          |                                |dd          }|r|d                  }ddd|d | d!| d"d#S t          |                                |d$d%          }d}|r$d&d'                    fd@|D                       z   }dddd)| dA| dBd	S |dCk    rt%                      }||t          |          v rddddd	S t          ||dd          }|rddd|d         d | d!|d          d"d#S t          ||d$d%          }d}|r"d&d'                    dD |D                       z   }dddd)| dE| d	S |dk    rgt'          |||          }|H|t          |          v rddddd	S t          ||dd          }|rddd|d         d | d!|d          d"d#S ddddF| dGd	S t'          ||          }||dHk    rdI |D             }|t          |          v rddddd	S t          ||dd          }|rddd|d         d | d!|d          d"d#S t          ||d$d%          }d}|r"d&d'                    dJ |D                       z   }dddd| dK| d	S |dLk    r	 ddMlm}m}  |            } ||          }dN |D             }||v rddddd	S t          |t!          |          d$dO          }d}|r"d&d'                    dP |D                       z   }dddd)| dQ| dR| d	S # t          $ r Y nw xY wt.          
                    ||          }	 t          |          }n# t          $ r g }Y nw xY w|rdS |D             |                                v rddddd	S t!                                                    }t          |                                |dd          }|r|d                  }ddd|d | d!| d"d#S t          |                                |d$d%          }d}|r$d&d'                    fdT|D                       z   }dddd)| d<| dU| dVd	S ddddW| dX| dYd	S )Za  
    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
    r   r<  zopenrouter.airS  ro   r  FzModel name cannot be empty.)acceptedpersist
recognizedmessagec              3  >   K   | ]}|                                 V  d S rG  )isspace)rM   chs     rA   r  z+validate_requested_model.<locals>.<genexpr>  s*      
,
,B2::<<
,
,
,
,
,
,rC   z"Model names cannot contain spaces.r?  r   r  )r  r  zD Set `LM_API_KEY` (or update it) to match the server's bearer token.Nz: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.TzModel `z-` was not found in LM Studio's model listing.r(  rg  )r  rT   r  g?)ncutoffzAuto-corrected `u   ` → ``)r  r  r  corrected_modelr  r  g      ?z
  Similar models: z, c              3  "   K   | ]
}d | d V  dS r  NrI   rM   ss     rA   r  z+validate_requested_model.<locals>.<genexpr>c  +      DcDcRSXXXXDcDcDcDcDcDcrC   zNote: `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: `>   rm   rl   c              3  "   K   | ]
}d | d V  dS r  rI   r  s     rA   r  z+validate_requested_model.<locals>.<genexpr>  r  rC   rl   rA  rB  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 rI   rZ  rL   s     rA   rR  z,validate_requested_model.<locals>.<dictcomp>  s"    BBBaQWWYYBBBrC   c              3  0   K   | ]}d |          d V  dS r  rI   rM   r  catalog_lowers     rA   r  z+validate_requested_model.<locals>.<genexpr>  s6      DrDrabE\WXIYE\E\E\DrDrDrDrDrDrrC   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   c              3  "   K   | ]
}d | d V  dS r  rI   r  s     rA   r  z+validate_requested_model.<locals>.<genexpr>  r  rC   zn` was not found in Anthropic's /v1/models listing. It may still work if you have early-access or snapshot IDs.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.rw   c                    g | ]E}t          |t                    r,|                    d           r|t          d           d         n|FS )zmodels/N)rV   rW   r  r  rL   s     rA   rO   z,validate_requested_model.<locals>.<listcomp>9  s]        '1C&8&8[Q\\)=T=T[#i..//""Z[  rC   c              3  "   K   | ]
}d | d V  dS r  rI   r  s     rA   r  z+validate_requested_model.<locals>.<genexpr>Y  r  rC   z1` was not found in this provider's model listing.r   )discover_bedrock_modelsresolve_bedrock_regionc                    h | ]
}|d          S r  rI   rL   s     rA   rK  z+validate_requested_model.<locals>.<setcomp>p  s    :::!ag:::rC   g?c              3  "   K   | ]
}d | d V  dS r  rI   r  s     rA   r  z+validate_requested_model.<locals>.<genexpr>}  r  rC   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 rI   rZ  rL   s     rA   rR  z,validate_requested_model.<locals>.<dictcomp>  s"    >>>!A>>>rC   c              3  0   K   | ]}d |          d V  dS r  rI   r  s     rA   r  z+validate_requested_model.<locals>.<genexpr>  sM       A A,-'M!$'''A A A A A ArC   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   rT  rH  r  r  r'  r   r  r  r[   r   r  r<  r^   r*  r@   r\   r  r  r  r  r  r  )r&  rv  r  r  r  	requestedr  requested_for_lookupr  rT   r!  probe
api_modelsrh  suggestionssuggestion_textr  catalog_modelsrk  catalog_lower_list	correctedanthropic_modelsr  r  region
discovereddiscovered_idsr  s                              @rA   validate_requested_modelr    s   ( !r((**I#H--J\!!h!?(3R3R
$Y9 
  
  
    	
  
4	
 
 	
 
,
,)
,
,
,,, 
;	
 
 	
 Z------	*7XNNNFF 	 	 	!e5```       	 >!e5eXaeee    	!e5e&e e e    3v;;.. $TVZ[[[%uYYYY
 
 	

 X!6!6y!A!A+++$WhJJJEE$Wh77E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^^QzN #)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44'#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 %) % %"% %
 
 	
  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  sm   B B1	B,&B1,B1J J+*J+M! !M0/M0(3Z2 AZ2 2
Z?>Z?[. .[=<[=)r9   r:   )rF   r:   r9   r:   )r   rW   r   r   r9   r   r:  )r   rW   r   rW   r9   r   )r   r   r9   r   )r   r:   r   r   r   r   r9   r   )
r   r:   r   r   r   rW   r   r   r9   r   )r9   r   )r   r  )r   rW   r   r   r   r   r9   r   )r9   rW   )r   r   r9   r#  )
r'  r   r   r(  r   rW   r   r   r9   r#  )rv  rW   r9   rW   )r   r   r9   r   )r~  r   r9   r   )r  )r   r   r   r   r9   r3   )r   r   r9   r:   )r  rW   r9   rW   )r   r  )
rT   r3   r  r   r  rW   r  rW   r9   r:   )Nr  r  )
r  r  r  rW   r   r   r   r   r9   r   )r   r   r   r   r9   r   )r9   r  )rv  rW   r   r   r9   r   )r9   r  )r$  rW   r%  rW   r9   r  )rv  r#  r   r   r9   r3   )rv  rW   r9   r  )rC  rW   rD  r  r9   r   )rC  rW   rJ  r  r9   rK  )r&  rW   r%  rW   r9   rK  )r&  rW   r9   r#  )rv  r#  r9   rW   )r   r#  r9   r   )r   rW   r9   rW   )r   r#  r9   r~  )rv  rW   r  r:   r9   r:   )rv  r#  r   r   r9   r:   )r  )r   r   r9   r  )r   r   r9   r  )r9   r  )r~  r   r9   r   )Nr  )r  r#  r   r   r9   r  rG  )r   rW   r  r#  r9   r  )r  r#  r9   r   )r  r#  r9   r#  )r  r#  r9   rZ   )NNr  )r  r#  r  r#  r   r   r9   r  )r  r#  r  r#  r   r   r9   r  )r  r#  r  r#  r   r   r9   r:   )r*  )r4  rW   r  r#  r  r#  r+  r  r   r   r9   r  )
r4  rW   r  r#  r  r#  r   r   r9   r:   )r  r#  r   r   r9   r  )NN)r  r  r  r#  r9   r  )r   r#  r  r  r  r#  r9   rW   )r   rW   r9   r:   )r   rW   r9   r   )r&  r#  r9   r#  )ro  r#  r   r#  r9   rW   )r   r#  r  r  r  r#  r9   r:   )r  N)
r  r#  r  r#  r   r   r  r#  r9   r   )
r  r#  r  r#  r   r   r  r#  r9   r  )r9   r   )r  r   r9   r  )rT   r:   r9   r  )r  r#  r  r#  r   r   r9   r:   )r&  rW   rv  r#  r  r#  r  r#  r  r#  r9   r   )__doc__
__future__r   r   r  urllib.requestr   urllib.errorr  difflibr   pathlibr   typingr   r   r   
hermes_clir	   _HERMES_VERSIONr  r  r  r  rW  rV  r4   r;  r5   r7   r8   rB   rE   rK   rP   r`   r   r   r   r   r   r   r  r  r  r  r  r  r  r   r"  r   r2  r4  rG  _canonical_slugsrD  rL  _list_providers_for_canonical_ppr   r  display_name_labeldescriptionr  r   r  r^   r  r  rx  r|  r  r  r   r  r  r  r  r  r  r  r  r  r  r  r  r  r  r   r\   r  r#  r1  r  r>  rB  rI  	frozensetrO  rW  r^  rc  r`  r   rk  rp  rv  rt  r{  ry  r  r  r  r  r<  r  r  r  r  r  r  r  r  r  r  r  r  r#  r'  r)  r=  rD  r  rQ  rN  rT  rX  r^  ri  rm  rn  rr  rt  r  r  r  r  r  r  r  r  r  r  r  rI   rC   rA   <module>r     s     # " " " " "  				          % % % % % %       , , , , , , , , , , 5 5 5 5 5 5 5?44 2 (111 ) !E!E!E %>%>%> "
!, !, !,  ! ! ! !F ;?  > > > >3 3 3     $ ;?  > > > >B B B B*# # #         & & & &6p*
   p*:  	 	 	;p*N ))++Op*P $$&&Qp*R Sp*X    Yp*|    }p*H    Ip*R 
   Sp*d 
  ep*f    gp*B    Cp*T    Up*b cp*j    kp*x    yp*D  Ep* p*L    Mp*X  	 	 	Yp*l    mp*x    yp*F Gp*L    Mp*V 
   Wp*f  $ $ $gp*p    qp*L    Mp*d  
 
 
ep*~  	 	 	p*T  
 
 
Up*r    sp*N	 RO	p*P	    Q	p* p*  p p p pl	 "N!M4L!M!M!M         D   $% % % %> >.
  >. >. >. >. >. >.H >*
  >* >* >* >* >* >*J       .2  2 2 2 2& & & &z  > #&  & & & &CE  E E E E '  	' ' ' ' ' 'T1 1 1 1      $6 6 6 6 6 6H    J   
$,M&M@jkk$,M,L@ghh$, M(J  AB  C  C$, M*K@z{{	$,
 M+K@vww$, M.NOO$, M)L@wxx$, M+$MOxyy$, M(M@|}}$, M$&8  AM  N  N$, M(L  AA  B  B$, M)$4@uvv$, M-$8@uvv$, M-N@tuu$, M($6@xyy$,  M%'>  CS  T  T!$," M*J@rss#$,$ M%E@bcc%$,& M%L@bcc'$,( M-$=@pqq)$,* M"$=@pqq+$,, M)$7?z{{-$,. M)I@]^^/$,0 M/$5@{||1$,2 M,$5@eff3$,4 M.N@xyy5$,6 M'J@jkk7$,8 M%K@dee9$,: M*K@^__;$,< M.N@rss=$,> M-M@stt?$,@ M)M  AA  B  BA$,B M/O  AX  Y  YC$,D M,$7@STTE$,F M,$9@jkkG$,  $ $ $ $T 98$7888 	IIIIII,,.. ' '8'''=mmm!-SX;f#;#;#;""==65#I#IJJJSX&&&&'  	 	 	D	 BA-@AAA .  P	5P
EP EP U	P
 iP iP YP IP -P P hP XP P MP P  !P" ##P P$ I%P& 9'P( )P* w+P, -P. /P0 \1P2 ,3P4 o5P6 o7P8 _9P: k;P< ;=P> ?P@ APB 
>CPD 	-EP P PF }GPH IPJ lKPL MPN JOPP QPR JSPT UPV iWPX IYPZ Y[P\ <]P^ %_P` 'aPb 	-cPd MePf }gP P Ph iPj kPl HmPn 8oPp !qPr "sPt 'uPv %wPx 
9yPz 9{P| i}P~ iP@ EAPB +CPD EPF +GPH kIP PJ "_P P P f' ' ' '      4 A  A A A A A A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   6 	; ; ; ; ;~ /0
  0 0 0 0 0 0h /  / / / / / /d7 7 7 7
  H . . . ., FK      < 8  8 8 8 8 8 8z C  	c

 
 
"
"##$X     ) ) ) )X!( !( !( !(H
 
 
 
   % % % % % %2/ / / /    "	???  
- - - -`0 0 0 0f$ $ $ $N   69 9 9 9F F F F&/     
Q 
Q 
Q 
Q,   Q Q Q Q
4 4 4 4"( ( ( ($7 7 7 7P )2	 3 3 3 ) )     &" " " "J JO Z Z Z Z Z ZzE E E E EP   
 
 
 
$   < 58         L *,  + + + +%(  ( ( ( (! " " " " "J           ""/ / / / /f ""    @ ""    0 B! B! B! B! B!P "	    BF F F F F$L$$ $ 	$
 i$ 9$ 9$ X$ -$ $ l$ $ $ l$ l$   !2!$" "#6#$$ "3#6"4 ),*,*!2#6#4#6"4G$ $ $ P /3!    $ /3!	& & & & & &R   @ @ @ @( /3!	& & & & & &b% !   :
 
 
 
   J /3!	)O )O )O )O )O )O^ "	K K K K K\    B "	a a a a a(  
 
 
 
@ @ @ @ 49      6    ""@  	@ @ @ @ @ @N """X X X X X X X Xs   .B P/ /P76P7