
    )j[                      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<   ddZg dZded<   dZddZ ddZ!i dg d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 d/d0g d1d2g d3d4d5d6gd7g d3d8g d9i d:g d;d<g d9d=g d>d?g d@dAg dBdCdDgdEg dFdGg dHdIg dJdKg dLdMg dNdOg dPdQg dRdSg dTdUg dVdWg dXg dYZ"dZed[<   ddaZ#dddZ$ddhZ%	 ddjdkddpZ&	 ddjdkddqZ'drZ(dsedt<   da)duedv<   djdwddyZ*dzZ+d{Z,dsed|<   i Z-d}ed~<   ddZ.ddZ/ddZ0	 	 ddjdkddZ1ddZ2ddZ3djddidjdddZ4 G d de          Z5g  e5ddd           e5ddd           e5dXdd           e5ddd           e5d=dd           e5d"dd           e5d dd           e5dOdd           e5d#dd           e5dAdd           e5dCdd           e5d.dd           e5d%dd           e5d$dd           e5dSdd           e5d'dd           e5d)dd           e5d?dd           e5d-dd           e5d+dd           e5d0dd           e5d2ddæ           e5d4ddŦ           e5d8ddǦ           e5d:ddɦ           e5d<dd˦           e5dddΦ           e5dEddЦ           e5dGddҦ           e5dMddԦ           e5dIdd֦           e5dKddئ           e5dUddڦ           e5dWddܦ           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<   ddd0d2gfddg dfddd-d#gfddd'd)gfddd"d gfdddIdKgfddd%d$gfdZFded<   d eFG                                D             ZHded<   ddZId ZJi dd+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d0d7d0dd2dd2i dd4dd4ddEddEddGddGdd<dd<dd:dd:dd:dd=dd=dd?ddIddIddKi d dKd!dMd"dMd#dMd$dOd%dOd&dOd'dOd(dݓd)d)d*d)d+dSd,dSd-dSd.dXd/dXd0dAi d1dAd2dCd3dCd4dCd5dCd6dUd7dUd8dUd9dUd:d-d;d#d#d#d<d#d=d#d>d-d?d-d@d.d.d.d.ddddd̐dAZKddBiZLdedC<   ddEZMddFZNddHZO	 ddjdkddJZPdjdkddKZQddLZRi ZSdMedN<   ddPZT	 	 	 ddjdkddUZUddVZVdWZWddYZXdjdkddZZY	 ddjdkdd[ZZ e[eE\                                           e[eK\                                          z  ddhz  Z]d\ed]<   dd_Z^ddbZ_ddcZ`djdkd ddZaddeZbddhZc edh di          ZeddlZfddnZgddoZhddpZiddqZjddrZkdsZldtedu<   ddvZmddwZnddxZoddyZpd	dzZqdd{Zr edh d|          Zsd}ed~<   d
dZtdjdkddZudZvddZwddZxddZyddZzdjevdddZ{dddZ|dddZ}ddZ~ddZddZ	 dddZi aded<   daded<   dZ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dZddքZddׄZdjd؜d0dۄZd1d݄Z	 	 d%djdkd2dބZddddߜd3dZdS (4  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-fable-5 )anthropic/claude-opus-4.8r   )zanthropic/claude-opus-4.8-fastz2x price, higher output speed)anthropic/claude-sonnet-4.6r   )anthropic/claude-haiku-4.5r   )openai/gpt-5.5r   )openai/gpt-5.5-pror   )openai/gpt-5.4-minir   )google/gemini-3-pro-previewr   )google/gemini-3.1-pro-previewr   )google/gemini-3.5-flashr   )x-ai/grok-4.3r   )deepseek/deepseek-v4-pror   )deepseek/deepseek-v4-flashr   )qwen/qwen3.7-maxr   )qwen/qwen3.7-plusr   )qwen/qwen3.6-35b-a3br   )moonshotai/kimi-k2.6recommended)minimax/minimax-m3r   )z-ai/glm-5.1r   )xiaomi/mimo-v2.5-pror   )tencent/hy3-previewr   )stepfun/step-3.7-flashr   )!nvidia/nemotron-3-super-120b-a12br   )zopenrouter/pareto-codezAauto-routes to cheapest coder meeting openrouter.min_coding_score)zopenrouter/elephant-alphafree)zopenrouter/owl-alphar(   )zpoolside/laguna-m.1:freer(   )ztencent/hy3-preview:freer(   )z&nvidia/nemotron-3-super-120b-a12b:freer(   )z&nvidia/nemotron-3-ultra-550b-a55b:freer(   )zinclusionai/ring-2.6-1t:freer(   list[tuple[str, str]]OPENROUTER_MODELSzlist[tuple[str, str]] | None_openrouter_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_modelsr0   r1   listr/   s     6/home/ubuntu/.hermes/hermes-agent/hermes_cli/models.py_codex_curated_modelsr5   Y   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_FALLBACKr7   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     r4   
<listcomp>z$_xai_promote_top.<locals>.<listcomp>|   s"    "I"I"IQ.5H5H15H5H5Hr6   r=   )r9   s    r4   _xai_promote_toprC   y   s/    "I"Ic"I"I"IIIJr6   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 r<   )
isinstancestr)r@   mids     r4   rB   z'_xai_curated_models.<locals>.<listcomp>   s'    HHH3:c33G3GH3HHHr6   )agent.models_devrE   rI   dictgetkeysrC   sorted	Exceptionr3   r8   )rE   datarF   rG   r9   s        r4   _xai_curated_modelsrS      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   r   r   r   r    r"   r#   r$   r%   r&   r'   openai)gpt-5.4gpt-5.4-mini
gpt-5-minigpt-5.3-codexgpt-5.2-codexgpt-4.1gpt-4ogpt-4o-mini
openai-api)
zgpt-5.5zgpt-5.5-prorV   rW   zgpt-5.4-nanorX   rY   r[   r\   r]   openai-codex	xai-oauthcopilot-acpcopilot)rV   rW   rX   rY   rZ   r[   r\   r]   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)rg   rh   gemini-3.5-flashzgemini-3.1-flash-lite-previewzgoogle-gemini-cli)rg   rh   ri   rk   zai)glm-5.1glm-5zglm-5v-turbozglm-5-turboglm-4.7zglm-4.5zglm-4.5-flashrF   nvidia)	r'   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)rq   rr   rs   rt   ru   stepfunzstep-3.5-flashzstep-3.5-flash-2603moonshotminimax)
MiniMax-M3MiniMax-M2.7MiniMax-M2.5zMiniMax-M2.1z
MiniMax-M2minimax-oauth)ry   rz   zMiniMax-M2.7-highspeed
minimax-cn	anthropic)
zclaude-fable-5zclaude-opus-4-8z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.5z$google/gemini-3.1-flash-lite-previewr   openai/gpt-5.4opencode-zen)#rr   zgpt-5.4-prorV   rY   gpt-5.2rZ   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-5rd   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.1rn   ro   zglm-4.6rs   zkimi-k2zqwen3-coderz
big-pickleopencode-go)rq   rr   rm   rn   r   r   r   r   r   r   qwen3.7-maxqwen3.6-plusqwen3.5-pluskilocode)anthropic/claude-opus-4.6r   r   r   zgoogle/gemini-3-flash-previewalibaba)	r   r   rr   r   qwen3-coder-plusqwen3-coder-nextrn   ro   r{   zalibaba-coding-plan)	r   r   r   r   r   rr   rn   ro   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.5zminimax/minimax-m2.7zzai-org/glm-5zdeepseek/deepseek-v3-0324zdeepseek/deepseek-r1-0528zqwen/qwen3-235b-a22b-fp8zdict[str, list[str]]_PROVIDER_MODELSmodel_idrJ   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)rN   float	TypeError
ValueError)r   r   ps      r4   _is_model_freer     s    HA uQUU8S))**a/XE!%%c:R:R4S4SWX4XXz"   uus   AA) )A>=A>account_infodict[str, Any]c                   |                      d          }t          |t                    rZ|                     d          }t          |t                    r| S |                     d          }t          |t                    r| S |                      d          }t          |t                    sdS |                     d          }|dS 	 t	          |          dk    S # t
          t          f$ r Y dS w xY w)	a$  Return True if the account info indicates a free (unpaid) tier.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    Merges into any existing per-base map, then writes atomically. Failures
    are non-fatal (logged at debug) — the in-process cache still works.
    Nr  r  )rR   tsTparentsexist_okz.tmpw   indent
r   z3nous recommended-models disk cache write failed: %s)r   r  r  r  rI   rM   r  r  r   parentmkdirwith_suffixsuffixdumpwriteosreplacelogging	getLogger__name__debug)r   rR   pathr	  r
  tmpexcr  s           r4   _write_nous_recommended_diskr&    s@     &((D
	dW--- %y}}% % % % % % % % % % % % % % %dD)) -. 	 	 	DDD	"$)++66T
$666t{V344#sW--- 	IdBq))))HHTNNN	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	
3 
 
 
(##))A3	
 	
 	
 	
 	
 	
 	
 	
 	

s|   A* AA* AA* AA* )E *BE BA'E ,-D%E %D))E ,D)-E 
F2F		F      @timeoutr   c                  | 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|r||ft          |<   t%          ||           |S t'          |          }|r||ft          |<   |S ||ft          |<   |S )u  Fetch the Nous Portal's curated recommended-models payload.

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

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

    Returns the parsed JSON dict, or ``{}`` only when neither the network nor
    any cache layer can supply data. Callers must treat missing/null fields
    as "no recommendation" and fall back to their own default.
    https://portal.nousresearch.com/NAcceptapplication/jsonheadersr(  )rstripr   r   r   rN   r   NOUS_RECOMMENDED_MODELS_PATHurllibrequestRequesturlopenr  loadsreaddecoderI   rM   rQ   r&  r  )r   r(  r   r   r   cachedr   r   urlreqresprR   disks                r4   r   r   >  s   0 @@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     )-s%$T4000 't,,D )-s%%)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_URLget_provider_auth_staterT   r   r   r+  r*  )hermes_cli.authr@  rA  rJ   rN   stripr1  rQ   )r@  rA  stateportals       r4   _resolve_nous_portal_urlrF  |  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)rI   rM   rN   rJ   rC  )r   
model_names     r4   r   r     sf    eT"" t;''J*c"" "z'7'7'9'9 "!!!4r6   )visionr   r   r   rK  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)rF  r   r   rQ   r   rN   )rK  r   r   r   r   r   paid_keyfree_key
candidateskeyr   s              r4   get_nous_recommended_aux_modelrV    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 )ProviderEntryrJ   sluglabeltui_descN)r!  
__module____qualname____annotations__r<   r6   r4   rX  rX    s+         IIIJJJMMMMMr6   rX  zNous PortalzLNous Portal (Everything your agent needs, 300+ models with bundled tool use)
openrouter
OpenRouterz'OpenRouter (Pay-per-use API aggregator)NovitaAIz5NovitaAI (Cloud: Model API, Agent Sandbox, GPU Cloud)lmstudioz	LM Studioz8LM Studio (Local desktop app with built-in model server)	Anthropicz4Anthropic (Claude models via API key or Claude Code)OpenAI Codexz<OpenAI Codex (Codex CLI via ChatGPT subscription or API key)z
OpenAI APIz$OpenAI API (api.openai.com, API key)z
Qwen Cloudz.Qwen Cloud / DashScope (Qwen + multi-provider)%xAI Grok OAuth (SuperGrok / Premium+)z2xAI Grok OAuth (SuperGrok / Premium+ subscription)zXiaomi MiMoz7Xiaomi MiMo (MiMo-V2.5 and V2 models: pro, omni, flash)zTencent TokenHubz;Tencent TokenHub (Hy3 Preview via tokenhub.tencentmaas.com)z
NVIDIA NIMz>NVIDIA NIM (Nemotron models via build.nvidia.com or local NIM)zGitHub Copilotz3GitHub Copilot (Uses GITHUB_TOKEN or gh auth token)zGitHub Copilot ACPz1GitHub Copilot ACP (Spawns copilot --acp --stdio)zHugging Facez Hugging Face Inference ProviderszGoogle AI Studioz$Google AI Studio (Native Gemini API)zGoogle Gemini (OAuth)z>Google Gemini via OAuth + Code Assist (Code Assist OAuth flow)DeepSeekz$DeepSeek (V3, R1, coder, direct API)xAIzxAI Grok (Direct API)z
Z.AI / GLMzZ.AI / GLM (Zhipu direct API)zKimi / Kimi Coding Planz.Kimi Coding Plan (api.kimi.com & Moonshot API)zKimi / Moonshot (China)z+Kimi / Moonshot China (Domestic direct API)zStepFun Step Planz;StepFun Step Plan (Agent / coding models via Step Plan API)MiniMaxzMiniMax (Global direct API)zMiniMax (OAuth)z9MiniMax via OAuth browser login (Coding Plan, minimax.io)zMiniMax (China)z#MiniMax China (Domestic direct API)ollama-cloudzOllama Cloudz3Ollama Cloud (Cloud-hosted open models, ollama.com)zArcee AIz%Arcee AI (Trinity models, direct API)z	GMI Cloudz"GMI Cloud (Multi-model direct API)z	Kilo CodezKilo Code (Kilo Gateway API)zOpenCode Zenz,OpenCode Zen (Curated models, pay-as-you-go)zOpenCode Goz&OpenCode Go (Open models subscription)zAWS Bedrockz;AWS Bedrock (Claude, Nova, Llama, DeepSeek; IAM or API key)zAzure FoundryzRAzure Foundry (OpenAI-style or Anthropic-style endpoint, your Azure AI deployment)z
qwen-oauthzQwen OAuth (Portal)z(Qwen OAuth (Reuses local Qwen CLI login)zlist[ProviderEntry]CANONICAL_PROVIDERSc                    h | ]	}|j         
S r<   rY  r@   r   s     r4   	<setcomp>rn  	  s    888qAF888r6   )list_providers>   aws_sdkrb   oauth_externalexternal_processoauth_device_codez (direct API)c                (    i | ]}|j         |j        S r<   )rY  rZ  rm  s     r4   
<dictcomp>ru    s    AAAAFAGAAAr6   zCustom endpointcustomzKimi / Moonshotz.Coding Plan, Moonshot global & China endpointsz+Global, OAuth Coding Plan & China endpoints)rx   r|   r}   zxAI Grokz(Direct API or SuperGrok / Premium+ OAuthzGoogle Geminiz$AI Studio API or OAuth + Code AssistOpenAIzCodex CLI or direct OpenAI APIOpenCodez$Zen pay-as-you-go or Go subscriptionz)GitHub token API or copilot --acp process)kimirx   rF   googlerU   opencoderb   z%dict[str, tuple[str, str, list[str]]]PROVIDER_GROUPSc                ,    i | ]\  }\  }}}|D ]}||S r<   r<   )r@   gid_label_descmembersrY  s         r4   ru  ru  >  sG     " " "/#/wW^" "OSD#" " " "r6   dict[str, str]_SLUG_TO_GROUPrY  c                    t                               t          | pd                                                                          d          S )zCReturn the group_id a provider slug belongs to, or "" if ungrouped.r   )r  rN   rJ   rC  lowerrl  s    r4   provider_group_for_slugr  C  s:    c$*"oo3355;;==rBBBr6   c           
         t                      }i }t                                          D ]\  }\  }}} fd|D             }|r|||<   g }t                      }	 D ]&}
t          |
pd                                                                          }|r||v r?|                    |           t                              |d          }|s|	                    d|d           ||	v r|	                    |           |                    ||g          }t          |          dk    r|	                    d|d         d           t          |         \  }}}|	                    d|||t          |          d           (|S )	u  Fold a flat ordered slug iterable into picker rows by provider group.

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

    Each returned row is a dict::

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

    Rules:
      * A group row appears at the position of its FIRST present member, in
        the input order. Subsequent members fold into that row (and are not
        emitted again).
      * Member order inside a group follows ``PROVIDER_GROUPS`` declaration,
        restricted to the members actually present in ``slugs``.
      * A group reduced to a single present member degrades to a ``single``
        row — no pointless one-item submenu.
      * Slugs not in any group pass through as ``single`` rows, order
        preserved.
      * Duplicate slugs in the input are ignored after first sight.
    c                6    g | ]}|t                    v |S r<   )r   )r@   rA   slugss     r4   rB   z#group_providers.<locals>.<listcomp>f  s$    999c%jj1r6   r   single)kindrY     r   group)r  group_idrZ  descriptionr  )r   r|  itemsrJ   rC  r  addr  rN   r   lenr3   )r  r   group_membersr~  r  r  r  presentrowsemitted_groupsrY  srZ  desc_s   `              r4   group_providersr  H  s   4 UUD*,M)8)>)>)@)@ ) )%%feW9999g999 	)!(M#D"uuN  
OO!!##))++ 	AII  B'' 	KK155666.  3##C!--w<<1KK71:>>????,S1NE4KK cE $g@ @    Kr6   glmzz-aizz.aizhipugithubzgithub-copilotzgithub-modelszgithub-modelzgithub-copilot-acpzcopilot-acp-agentrz  zgoogle-geminizgoogle-ai-studiory  z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-seekr{  zengozopencode-go-sub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-oauthzx-aizx.ainim)z
nvidia-nimzbuild-nvidianemotronrb  z	lm-studio	lm_studioollamaollama_cloudr   "_PROVIDER_SILENT_DEFAULT_OVERRIDESproviderc                    t                               | g           }t                              |           }|r||v r|S |r|d         ndS )u
  Return a cost-safe default model for a provider, or "" if unknown.

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

    For most providers this is the first entry in ``_PROVIDER_MODELS`` — the
    same model the ``hermes model`` picker offers first. For metered aggregators
    whose curated list is ordered most-capable-first, that entry is also the
    most EXPENSIVE one, so silently defaulting to it is a billing footgun. Such
    providers carry an explicit low-cost override in
    ``_PROVIDER_SILENT_DEFAULT_OVERRIDES``; a missing model must never
    auto-escalate to the flagship.
    r   r   )r   rN   r  )r  rG   overrides      r4   get_default_model_for_providerr    sV     !!(B//F155h??H H&&&6!99B&r6   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   )rI   rM   r   rN   r   r   )r   s    r4   _openrouter_model_is_freer    s    gt$$ uW[[3//00A5d%LZ]@^@^:_:_cd:ddz"   uus   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)rI   rM   rN   r3   )r  paramss     r4    _openrouter_model_supports_toolsr  	  sO      dD!! tXX,--Ffd## tfr6          @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 r<   r<   r@   rK   r  s      r4   rB   z+fetch_openrouter_models.<locals>.<listcomp>7  s    000VS!S000r6   z#https://openrouter.ai/api/v1/modelsr,  r-  r.  r0  rR   idr   r   r(   r!   )r+   r3   hermes_cli.model_catalogr  rQ   r*   r3  r4  r5  r6  r  r7  r8  r9  rN   rI   rM   rJ   rC  r  r  r   )r(  r   r  remotefallbackpreferred_idsr<  r=  r   
live_items
live_by_idr  rK   curatedpreferred_id	live_itemr  first_idr  s                      r4   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 r<   r<   r  s      r4   rB   zmodel_ids.<locals>.<listcomp>h  s    SSSFCCSSSr6   r   )r  r   s    r4   r   r   f  s"    SS5MRRRSSSSr6   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_modelsNrT   )r  r  rQ   r3   r   rN   )r  r  s     r4   get_curated_nous_model_idsr  k  s    DDDDDD((**    F|| $$VR00111s    ""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   r(   @B $z.2f)r   r   r   )r  valper_ms      r4   _format_price_per_mtokr    sb    M""z"   ss
axxv)OEu???s    ''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-AgentBearer Authorizationr.  r0  NrR   r  r   r   r   r   input_cache_readinput_cache_write)r1  r  _HERMES_USER_AGENTr3  r4  r5  r6  r  r7  r8  r9  rQ   rN   rI   rM   rJ   )r  r  r(  r   	cache_keyr;  r/  r<  r=  r   r   r  rK   r   r   s                  r4   fetch_models_with_pricingr    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                 P    t          j        dd                                          S )z1Best-effort OpenRouter API key for pricing fetch.OPENROUTER_API_KEYr   )r  getenvrC  r<   r6   r4   _resolve_openrouter_api_keyr    s!    9)2..44666r6   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   ) resolve_nous_runtime_credentialsr  r   r  )rB  r  rN   rQ   _DEFAULT_NOUS_INFERENCE_BASE)r  credss     r4   !_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               V   t          |           }|dk    rt          t                      d|          S |dk    rt          |          S |dk    rYt	                      \  }}|rF|                    d          }|                    d          r
|d	d
         }t          |||          S i S )zMReturn live pricing for providers that support it (openrouter, nous, novita).r_  r  )r  r  r   r   r   rT   r+  /v1N)normalize_providerr  r  _fetch_novita_pricingr  r1  endswith)r  r   
normalizedr  r  strippeds         r4   get_pricing_for_providerr    s    #H--J\!!(/110'
 
 
 	

 X$=AAAAV=?? 
	  s++H  '' )#CRC=,!+   
 Ir6   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 )uq  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 —
    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.  r0  NrR   r  input_token_price_per_moutput_token_price_per_mr   i'  r  r   )r  r  rC  r1  r  r  r3  r4  r5  r6  r  r7  r8  r9  rQ   rN   rI   rM   rJ   r   )r(  r   r  r  r  r;  r/  r<  r=  r   r   r  rK   inpouts                  r4   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 r<   rl  rm  s     r4   rB   z,list_available_providers.<locals>.<listcomp>U  s    :::af:::r6   rv  Fr   )get_auth_statushas_usable_secretr   r_  r  	logged_in
configured)r  rZ  aliasesauthenticated)rj  _PROVIDER_ALIASESr  
setdefaultr   _PROVIDER_LABELSrN   rB  r  r  _get_custom_base_urlr   rC  r  r  rQ   )provider_orderaliases_foralias	canonicalr   pidrZ  
alias_list	has_credsr  r  custom_base_urlstatuss                r4   list_available_providersr#  K  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  rv  custom:)rC  findr  r  r  )	r$  r%  r  colonprovider_part
model_partsecond_coloncustom_nameactual_models	            r4   parse_model_inputr0  w  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''r6   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.configr2  rN   rI   rM   rJ   rC  rQ   )r2  config	model_cfgs      r4   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   r<   r?   s     r4   rB   z/curated_models_for_provider.<locals>.<listcomp>  s    &&&AB&&&r6   c                    g | ]}|d fS r9  r<   r?   s     r4   rB   z/curated_models_for_provider.<locals>.<listcomp>  s    $$$QG$$$r6   )r  r  provider_model_idsr   rN   )r  r   r  liverG   s        r4   curated_models_for_providerr=    s     $H--J\!!&]CCCC j))D '&&&&&& !!*b11F$$V$$$$r6   c                    | pd                                                                 }t          |           }d ||fD             S )Nr   c                    h | ]}||S r<   r<   )r@   ks     r4   rn  z!_provider_keys.<locals>.<setcomp>  s    ...!A.A...r6   )rC  r  r  )r  rU  r  s      r4   _provider_keysrA    sI    >r
 
 
"
"
(
(
*
*C#H--J..Z(....r6   
name_lower	providersc                :     t           fd|D                       S )Nc              3     K   | ]:}t                               |g           D ]}|                                k    V  ;d S N)r   rN   r  )r@   r  r3  rB  s      r4   	<genexpr>z-_model_in_provider_catalog.<locals>.<genexpr>  sk        %))(B77   	ekkmm#      r6   )any)rB  rC  s   ` r4   _model_in_provider_catalogrI    s;        !     r6   >   rT   rb   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_ALIASESNr  rJ   r,   rG  c                    t                               | g           }|sd S | t          v r d n                                }|D ]-}|                                                    |          r|c S .d S )Nr+  )r   rN   _AGGREGATOR_PROVIDERSr  
startswith)r  rG   prefixr3  familyvendors       r4   _matchz+_resolve_static_model_alias.<locals>._match  s    !%%h33 	4 000      
%''	 	
  	 	E{{}}''// tr6   )r  rJ   r,   rG  )hermes_cli.model_switchrM  rQ   rN   rS  rR  r   rO  )	rB  rJ  rM  identityrT  r  matchedrR  rS  s	          @@r4   _resolve_static_model_aliasrX    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    
rJ  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>   rv  r_  r   c              3  H   K   | ]}|                                 k    V  d S rF  r  )r@   rA   rB  s     r4   rG  z3detect_static_provider_for_model.<locals>.<genexpr>1  s0      771zQWWYY&777777r6   )rC  r  rA  rX  r  rN   r   r  rI  r  rO  rH  )
rJ  r%  r   rJ  alias_matchresolved_providerdefault_modelsr  rG   rB  s
            @r4    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 	;	 4r6   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_  )rC  r_  rI  r  rA  _find_openrouter_slug)rJ  r%  r   static_matchor_slugs        r4   detect_provider_for_modelrd  7  s     "##%%D t3D:JKKL !$**,,?O0P0PQQ t $D))G |++ '**d?? '**t4r6   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  )rC  r  r   split)rJ  rB  rK   r  r,  s        r4   ra  ra  ^  s     !!##))++J t {{  $$JJJ % {{  #::IIc1--MAzZ--////


4r6   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_  )rC  r  r  rN   )r  r  s     r4   r  r  y  s<     *l113399;;J  Z888r6   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`  )rC  r  r  r  rN   )r  originalr  s      r4   provider_labelrl    s_    (L//11H!!JVv#J//J
H,DEEEr6   )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 rF  )rP  )r@   rQ  r   s     r4   rG  z(_is_openai_fast_model.<locals>.<genexpr>  s/      PP6tv&&PPPPPPr6   )_strip_vendor_prefixrJ   rf  rH  rq  r   r$  r   s     @r4   _is_openai_fast_modelrw    sq    
s8>r22
3
3C99S>>!D u $uPPPP5OPPPPPPr6   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  )rJ   rC  r  rf  )r   r$  s     r4   ru  ru    sQ    
hn"


#
#
%
%
+
+
-
-C
czziiQ"Jr6   c                >    t          |           pt          |           S )zDReturn whether Hermes should expose the /fast toggle for this model.)_is_anthropic_fast_modelrw  r   s    r4   model_supports_fast_moder|    s    #H--P1Fx1P1PPr6   c                    t          t          | pd                    }|                    d          d         }|                    d          sdS d|v pd|v S )ua  Return True if the model accepts the Anthropic Fast Mode ``speed`` param.

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

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

    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|  rz  r{  s    r4   resolve_fast_mode_overridesr    s?     $H-- t)) !  J''r6   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_credentialsrb   r  r   )read_credential_pool)exchange_copilot_tokenvalidate_copilot_tokenaccess_token)rB  r  rJ   rN   rC  rQ   r  hermes_cli.copilot_authr  r  rI   rM   )r  r  r  r  r  r  r   r$  validr  	api_token_expires_ats               r4    _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>   rl   groqcohererj   rz  rp   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)	rL   r  rQ   r3   r   rJ   r  r  r   )r  r  r  mdev
seen_lowermergedrK   rU  s           r4   _merge_with_models_devr  B  s,   888888""8,,     G}} 55JF  #hhnn*sc  #hhnn*scMs    ##c          	        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    rq	 ddlm}m}	  |	            }|r9 ||                    d
d          |                    dd                    }|r|S n# t          $ r Y nw xY wt                      }
|
r|
S |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    rt'                      }|rt          t                              dg                     }t          |          }d |D             }|D ]T}|                                |vr<|                    |           |                    |                                           U|S t          t                              dg                     S |dk    rt/          |          }|r|S |dv rt1          j        dd                                          }|rt1          j        dd                                                              d          }|pd}|                    d          d v }	 t%          ||          }|rN|rJd! |D             t          t                              |g                     }fd"|D             }|r|S |p|S |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    rct7                      }|rSt1          j        d%d          p)t1          j        dd          pt1          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                     }|tH          v rtK          ||          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   r_   r   )get_codex_model_idsN)!resolve_codex_runtime_credentialsT)refresh_if_expiringr  )r  r`   rF   >   rb   ra   ra   rb   rT   )fetch_nous_modelsr  r   r  )r  inference_base_urlrv   r  r~   c                6    h | ]}|                                 S r<   r[  r?   s     r4   rn  z%provider_model_ids.<locals>.<setcomp>  s     777!AGGII777r6   ri  )rU   r^   OPENAI_API_KEYOPENAI_BASE_URLr+  https://api.openai.com/v1)r  zhttps://api.openai.comc                6    h | ]}|                                 S r<   r[  r?   s     r4   rn  z%provider_model_ids.<locals>.<setcomp>  s     %>%>%>Aaggii%>%>%>r6   c                @    g | ]}|                                 v |S r<   r[  )r@   rA   
live_lowers     r4   rB   z&provider_model_ids.<locals>.<listcomp>  s+    #R#R#R!!''))z:Q:QA:Q:Q:Qr6   r   rv  CUSTOM_API_KEYr  r   )bedrock_model_ids_or_none)get_provider_profiler  )&r  r   r2   r  rB  r  rN   rQ   r3   r   _fetch_github_modelsr  r  r  r  r  rJ   rC  fetch_api_models_fetch_anthropic_modelsr  r   r  fetch_ollama_cloud_modelsr  r  r1  r  agent.bedrock_adapterr  rC  r  	auth_typer  fetch_modelsfallback_modelsr  r  )r  r   r  r  r  r  r  r<  r  r  manifest_idsr  r  r  r  r  merged_lowerrA   base_rawr   is_default_openaifilteredr  r9   r  _pcurated_staticr  s                              @r4   r;  r;  g  s    $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	
 233 	 Y	LLLLLL88CCE%))I..4"55;;==G599Z006B77==??H  8  '::  K 	 	 	D	[  &(( 	 +//R@@AAG']]F77w777L 0 07799L00MM!$$$ $$QWWYY///M$((b99:::^##(}EEE 	K---),b117799 	y!2B77==??FFsKKH::D !%C 0 0 5 !'66  ( /%>%>%>%>%>
"&'7';';J'K'K"L"L $S#R#R#Rw#R#R#R# ,#+O  '$.K     U	LLLLLL88??E%))I..4"55;;==G599Z006B77==??H  8  '::  K 	 	 	D	X')) 		 	*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( (
C54C5-AE; ;
FF$BH: :
IIAP" P" P" "
P/.P/9BS 
SSU$ $
U10U15+Y3 !A7X Y3 X/,Y3 .X//'Y3 Y3 3
Z ?Z i  r   c                 (    ddl m}   |             dz  S )Nr   r   zprovider_models_cache.jsonr   r   s    r4   _provider_models_cache_pathr  B	  s(    000000?;;;r6   c           	        ddl }ddl}g }	 ddlm} |                    |           }|t          |dd          pdD ]5}|                    | d|j                            |d                      6t          |dd          pd}|r3|                    | d|j                            |d                      n# t          $ r Y nw xY w	 dd	l	m
} d
D ]t}	 |            |	z  }
	 |                    |	 d|
                                j                    B# t          $ r |                    |	 d           Y ft          $ r Y qw xY wn# t          $ r Y nw xY w|j                            d          |j                            d          |j                            d          |j                            d          fD ]j}	 |                    |          j        }|                    | d|            8# t          $ r |                    | d           Y \t          $ r Y gw xY wd                    |                              dd          }|                    |d                                          S )aw  Return a short hash representing the credentials that
    ``provider_model_ids(provider)`` would see right now.

    Rotating any of the relevant env vars invalidates the cached entry
    for that provider. We hash AT LEAST the api-key + base-url env vars
    declared in ``PROVIDER_REGISTRY``. For OAuth-backed providers
    (codex, copilot, anthropic-via-claude-code, nous portal), the
    relevant tokens live in ``$HERMES_HOME/auth.json`` and external
    credential files. Rather than parse every shape, we additionally
    fold the mtime of those files into the fingerprint so refreshes
    after re-auth bust the cache.
    r   N)PROVIDER_REGISTRYapi_key_env_varsr<   =r   base_url_env_varr   )z	auth.jsonzcredentials.json@z@missingz~/.codex/auth.jsonz~/.claude/.credentials.jsonz#~/.config/github-copilot/hosts.jsonz~/.minimax/credentials.json|r  r  errors   )digest_size)hashlibr  rB  r  rN   getattrr   environrQ   r   r   statst_mtime_nsFileNotFoundErrorr#  
expanduserjoinencodeblake2b	hexdigest)r  r  _ospartsr  pcfgevbevr   relr   r#  mtr
  s                 r4   _credential_fingerprintr  G	  s.    NNNE
555555 $$X..d$6;;Ar @ @>>S[__R%<%<>>????$ 2B77=2C B@@ckooc2&>&>@@AAA   4444444 	 	C!!C'A<<affhh&:<<====$ / / /---.....   	    
 	0119::ABB9::	  	$+BLLD2((((  	, 	, 	,LLD***+++++ 	 	 	D	 88E??!!')!<<D ??4Q?//99;;;sl   B.B; ;
CCE
 $1DE
 "E8E
 :	EE
 EE
 

EE4G88"H(	H('H(rM   c                    	 t                      } |                                 si S t          | d          5 }t          j        |          }ddd           n# 1 swxY w Y   t          |t                    r|ni S # t          $ r i cY S w xY w)z/Return the full cache dict, or {} on any error.r  r  N)r  existsr  r  r  rI   rM   rQ   )r#  frR   s      r4   _load_provider_models_cacher  	  s    *,,{{}} 	I$))) 	 Q9Q<<D	  	  	  	  	  	  	  	  	  	  	  	  	  	  	 !$--5tt25   			s9   #A< A< AA< AA< A A< <B
Bc                    	 ddl m} t                      }|j                            dd            ||| d           dS # t
          $ r Y dS w xY w)u<   Persist the cache dict. Best-effort — silent on any error.r   atomic_json_writeTr  Nr  )utilsr  r  r  r  rQ   )rR   r  r#  s      r4   _save_provider_models_cacher  	  s    ++++++*,,$666$T222222   s   >A 
AA)r   ttl_secondsr  c                  t          |           p| pd}|sg S t                      }t          |          }|                    |          }t	          j                    }|st          |t                    r|                    d          |k    rot          |                    d          t                    rG|d         r?|t          |                    dd                    z
  |k     rt          |d                   S t          ||          }|r4||t          |          d||<   t          |           t          |          S t          |t                    r^|                    d          |k    rEt          |                    d          t                    r|d         rt          |d                   S t          |pg           S )zDisk-cached wrapper around :func:`provider_model_ids`.

    Hits the cache when fresh; otherwise calls the live function and
    persists a non-empty result. Always returns a list (never None).
    r   fprG   atr   r   )r  r  rG   )r  r  r  rN   r   rI   rM   r3   r   r;  r  )	r  r   r  r  r   r  r   r   r<  s	            r4   cached_provider_model_idsr  	  s    $H--A(.bJ 	'))E	 	,	,BIIj!!E
)++C %ud##% IIdOOr!!uyy**D11 "(O " 54++,,,;;E(O$$$ jFFFD 4jj
 
j
 	$E***Dzz 	5$%IIdOOr!!uyy**D11 "(O " E(O$$$
r6   c                   	 | 8t                      }|                                r|                                 dS t                      }t	          |           p| pd}||v r||= t          |           dS dS # t          $ r Y dS w xY w)zDrop a single provider's cache entry, or wipe the whole cache.

    ``provider=None`` wipes everything; otherwise only that provider's
    entry is removed. Used by ``/model --refresh`` and
    ``hermes model --refresh``.
    Nr   )r  r  unlinkr  r  r  rQ   )r  r#  r   r  s       r4   clear_provider_models_cacher  	  s    .00D{{}} F+--'11CXC
j!'.....     s   8A7 7A7 7
BB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hr  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.  r0  )r3  r4  r5  r6  r  r7  r8  r9  )r  r<  r=  r(  s      r4   _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  ignorer  r   zlong context betaznot yet availablec                     g | ]
}|k    |S r<   r<   )r@   br  s     r4   rB   z+_fetch_anthropic_models.<locals>.<listcomp>$
  s$    KKKqQ:J5J5J5J5J5Jr6   c                H    g | ]}|                     d           |d           S r  rN   r?   s     r4   rB   z+_fetch_anthropic_models.<locals>.<listcomp>,
  s+    GGGa155;;G!D'GGGr6   rR   c                    d| vd| vd| v| fS )Nopussonnethaikur<   )rA   s    r4   <lambda>z)_fetch_anthropic_models.<locals>.<lambda>.
  s#    !OA1	-
 r6   )rU  z$Failed to fetch Anthropic models: %s)r  r  )agent.anthropic_adapterr  r  ImportErrorr  r  r  r  r3  error	HTTPErrorcoder8  r9  r  rQ   r3   rN   rP   r  r   r!  r"  )r(  r  r  tokenr/  is_oauthr  r  r  rR   http_err	body_textrG   er  r  s   `              @r4   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 r<   rI   rM   r@   r  s     r4   rB   z"_payload_items.<locals>.<listcomp><
  s'    CCCJtT,B,BCCCCr6   rR   c                <    g | ]}t          |t                    |S r<   r  r  s     r4   rB   z"_payload_items.<locals>.<listcomp>@
  s'    DDDTZd-C-CDDDDDr6   )rI   r3   rM   rN   )r   rR   s     r4   _payload_itemsr  :
  s{    '4   DCCCCCC'4   E{{62&&dD!! 	EDDTDDDDIr6   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    r4   copilot_default_headersr   D
  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 r<   rJ   rC  r@   endpoints     r4   rn  z6_copilot_catalog_item_is_text_model.<locals>.<setcomp>f
  sR      
  
  
8}}""$$ 
MM!! 
  
  
r6   >   
/responses/v1/messages/chat/completionsT)rJ   rN   rC  rI   rM   r  r3   intersection)r  r   r#  
model_typer&  normalized_endpointss         r4   #_copilot_catalog_item_is_text_modelr1  V
  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4r6   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.  r0  r  r   N)r   r   r3  r4  r5  COPILOT_MODELS_URLr6  r  r7  r8  r9  r  r   r1  rJ   rN   rC  r  rQ   )r  r(  attemptsr/  r<  r=  rR   r  rG   seen_idsr  r   s               r4   fetch_github_model_catalogr7  s
  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_timeOptional[int]c                J   t           r<t          j                    t          z
  t          k     r| t           v rt           |          S dS t	          |          }|sdS i }|D ]}t          |                    d          pd                                          }|s;|                    d          pi }|                    d          pi }|                    d          }t          |t                    r|dk    r|||<   |a t          j                    a|                    |           S )	zLook up max_prompt_tokens for a Copilot model from the live /models API.

    Results are cached in-process for 1 hour to avoid repeated API calls.
    Returns the token limit or None if not found.
    Nr  r  r   r#  limitsmax_prompt_tokensr   )
r8  r   r9  _COPILOT_CONTEXT_CACHE_TTLr7  rJ   rN   rC  rI   r   )	r   r  catalogr   r  rK   capsr<  
max_prompts	            r4   get_copilot_model_contextrB  
  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r6   c                    | pd                                                     d                                          }|                    t                    p)|                    d          p|                    d          S )Nr   r+  z"https://models.github.ai/inferencez%https://models.inference.ai.azure.com)rC  r1  r  rP  COPILOT_BASE_URL)r  r  s     r4   _is_github_models_base_urlrE  
  sx    .b''))0055;;==J.// 	J  !EFF	J  !HIIr6   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  )rC  r1  r   )r  roots     r4   _lmstudio_server_rootrH  
  s`    
 N!!##**3//D}}U %CRCy$$<4r6   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  rJ   rC  )r  r/  r  s      r4   _lmstudio_request_headersrJ  
  sI    /0G2$$&&E 5#4U#4#4 Nr6   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.  r0  >       r   	AuthErrorz)LM Studio rejected the request with HTTP .rb  auth_rejected)r  r  z)LM Studio probe at %s failed with HTTP %sz LM Studio probe at %s failed: %srG   zCLM Studio probe at %s returned malformed payload (no `models` list))rH  rJ  r3  r4  r5  r6  r  r7  r8  r9  r  r  r  rB  rP  r  r   r!  r"  rQ   rI   rM   rN   r3   )r  r  r(  server_rootr/  r4  r=  r   r%  rP  r  
raw_modelss               r4   _lmstudio_fetch_raw_modelsrU  
  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   	embeddingrU  r  )rU  rI   rM   rJ   rN   rC  r  r   )r  r  r(  rT  rO   r$  rU  s          r4   probe_lmstudio_modelsrY    s     ,GhX_```JtD  #t$$ 	swwv$"%%++--3355DD#''%..7CGGDMM7R88>>@@ 	3d??KKKr6   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.
    rW  )rY  )r  r  r(  rG   s       r4   fetch_lmstudio_modelsr[  1  s"     #7XwWWWF<Rr6         ^@r3  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
   rW  rU  r  max_context_lengthr   loaded_instancesr5  context_length)r3  rb  r-  zContent-Typez/api/v1/models/loadPOST)rR   r/  methodr0  )rH  rJ  rU  rQ   rI   rM   rN   r   minr  dumpsr  r3  r4  r6  r5  r8  )r3  r  r  r]  r(  rS  r/  rT  target_entryr$  max_ctxinstcfg
loaded_ctxbodyload_headersr=  s                    r4   ensure_lmstudio_model_loadedrn  D  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.
    rW  NrU  r  r#  	reasoningallowed_optionsc                    g | ]J}t          |t                    t          |                                                                          KS r<   )rI   rJ   rC  r  )r@   os     r4   rB   z4lmstudio_model_reasoning_options.<locals>.<listcomp>  sA    OOOqJq#<N<NOCFFLLNN((**OOOr6   )rU  rQ   rI   rM   rN   r3   )	r3  r  r  r(  rT  r$  r@  rp  optss	            r4    lmstudio_model_reasoning_optionsru    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     r4   rB   z(_fetch_github_models.<locals>.<listcomp>  s5    EEE4dhhtnnEDHHT2EEEr6   )r7  )r  r(  r?  s      r4   r  r    s5    ('JJJG tEE7EEEEr6   zopenai/gpt-5rX   zopenai/gpt-5-chatzopenai/gpt-5-minizopenai/gpt-5-nanozopenai/gpt-4.1r[   zopenai/gpt-4.1-minizopenai/gpt-4.1-nanozopenai/gpt-4or\   zopenai/gpt-4o-minir]   	openai/o1r   zopenai/o1-minizopenai/o1-preview	openai/o3rY   zopenai/o3-minizopenai/o4-minir   zclaude-opus-4.6r   rc   rd   re   rf   )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 ry  )rJ   rN   rC  r  s     r4   rn  z'_copilot_catalog_ids.<locals>.<setcomp>  st       txx~~#$$**,,DHHTNN b!!''))  r6   )r7  r   r?  r  s     r4   _copilot_catalog_idsr    sO     7,W=== uu    r6   r~  c               p   t          | pd                                          }|sdS t          ||          }t                              |          }|r|S |g}d|v rA|                    |                    dd          d                                                    |                    d          r|                    |d d                    |                    d          r|                    |d d                    |                    d          r|                    |d d                    t                      }|D ]>}|r||v r	|	                    |           |t          v rt          |         c S ||v r|c S ?d|v r.|                    dd          d                                         S |S )	Nr   r~  r+  r  z-miniz-nanoz-chat)
rJ   rC  r  _COPILOT_MODEL_ALIASESrN   r   rf  r   r   r  )	r   r?  r  r$  catalog_idsr  rT  r   	candidates	            r4   normalize_copilot_model_idr    s    hn"


#
#
%
%C r&wHHHK"&&s++E J
czz#))C++A.4466777
||G $#crc(###
||G $#crc(###
||G $#crc(###UUD  	 	I--...))4444## $ czzyya  #))+++Jr6   c                <   | pd                                                                 }|                    d          rt          t                    S t          |                                           }|                    d          rt          t                    S g S )Nr   )rz  r{  z	openai/o4rn  ro  rp  r   )rC  r  rP  r3   "COPILOT_REASONING_EFFORTS_O_SERIESr  COPILOT_REASONING_EFFORTS_GPT5)r   r$  r  s      r4   &_github_reasoning_efforts_for_model_idr    s    >r
 
 
"
"
(
(
*
*C
~~OPP 86777+H55;;==JW%% 42333Ir6   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     rX   )rematchr   r  rP  )r   r  r  majors       r4   !_should_use_copilot_responses_apir    sd     IIIHH]H--E uAEA:?h11,????r6   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  r~  chat_completionscodex_responsesc              3  N   K   | ]}|                     d           k    |V   dS r  Nr  r@   r  r  s     r4   rG  z)copilot_model_api_mode.<locals>.<genexpr>F  7      WWt$((4..J:V:Vd:V:V:V:VWWr6   c                    h | ]D}t          |                                          #t          |                                          ES r<   r(  r)  s     r4   rn  z)copilot_model_api_mode.<locals>.<setcomp>H  sR     # # #x==&&((#H##%%# # #r6   r&  r,  r-  anthropic_messages)r7  r  r  nextrI   rM   rN   )r   r?  r  catalog_entryr&  r  s        @r4   copilot_model_api_moder  +  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++r6   )rs  r   rn  ro  rp  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  r  )rJ   rC  r  rsplit!_AZURE_FOUNDRY_RESPONSES_PREFIXESrP  )rJ  r$  rQ  s      r4   azure_foundry_model_api_moder  e  s     jB


%
%
'
'
-
-
/
/C t
czzjja  $ 4 % %>>&!! 	%$$$	%4r6   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  rJ   rC  r  rP  r  )r  r   r  currentrQ  s        r4   normalize_opencode_model_idr    s    !+..H(.b!!''))G h&EEE^^^F}}!!&)) %s6{{||$$Nr6   c                H   t          |           }t          | |                                          }|sdS |dk    r0|                    d          rdS |                    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-r  r   r   r~  rm  r  )r  r  r  rP  )r  r   r  r  s       r4   opencode_model_api_moder    s     "+..H,[(CCIIKKJ "!!=    ,, 	(''  // 	(''!!>!!  ++ 	(''  (( 	%$$!!r6   c                 
 t          | ||          

sg S d}|t          
fd|D             d          }n0|r.t          |          }|rt          
fd|D             d          }||                    d          }t	          |t
                    r|                    d          }t	          |t
                    r]|                    d          }t	          |t                    r3d	 |D             }t          t
                              |                    S g S d
 |                    dg           D             }	d|	vrg S t          t          | p
                    S )zEReturn supported reasoning-effort levels for a Copilot-visible model.r~  Nc              3  N   K   | ]}|                     d           k    |V   dS r  r  r  s     r4   rG  z1github_model_reasoning_efforts.<locals>.<genexpr>  r  r6   r  c              3  N   K   | ]}|                     d           k    |V   dS r  r  r  s     r4   rG  z1github_model_reasoning_efforts.<locals>.<genexpr>  s8      !c!c4dhhtnnXbFbFb$FbFbFbFb!c!cr6   r#  supportsreasoning_effortc                    g | ]V}t          |                                          #t          |                                                                          WS r<   rJ   rC  r  )r@   efforts     r4   rB   z2github_model_reasoning_efforts.<locals>.<listcomp>  s^     * * *"v;;,,..*F))++1133* * *r6   c                    h | ]V}t          |                                          #t          |                                                                          WS r<   r  )r@   
capabilitys     r4   rn  z1github_model_reasoning_efforts.<locals>.<setcomp>  s^     
 
 
:$$&&

OO!!##))++
 
 
r6   rp  )
r  r  r7  rN   rI   rM   r3   fromkeysr  rJ   )r   r?  r  r  fetched_catalogr#  r  effortsnormalized_effortslegacy_capabilitiesr  s             @r4   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Nr6   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)rG   
probed_urlresolved_base_urlsuggested_base_urlused_fallbackrw  r  r  Tr  r  r  r  r  r  r  r
   r.  r0  c                :    g | ]}|                     d d          S ry  r  r?   s     r4   rB   z$probe_api_models.<locals>.<listcomp>  s$    MMM1quuT2MMMr6   rR   r   )rC  r1  rE  r  r4  rD  r   r   r  rP  updater   r3  r4  r5  r6  r  r7  r8  r9  rN   rQ   )r  r  r(  r  r  rG   alternate_baserT  triedr/  candidate_baseis_fallbackr;  r<  r=  rR   s                   r4   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                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  rG   )r  rN   )r  r  r(  r  s       r4   r  r  -  s*     GXwRRRVVW_```r6   c                n    dD ]1}|                      |          r| dt          |                    c S 2| S )a"  Strip :cloud / -cloud suffixes that models.dev appends to Ollama Cloud IDs.

    The live API uses clean IDs (e.g. 'kimi-k2.6') while models.dev sometimes
    returns them as 'kimi-k2.6:cloud'. Normalising before the dedup merge
    prevents duplicate entries in the merged model list.
    )z:cloudz-cloudN)r   r  )r   r  s     r4   _strip_ollama_cloud_suffixr  D  sP     ' , ,V$$ 	,Ns6{{lN++++	,Or6   c                 (    ddl m}   |             dz  S )z1Return the path for the Ollama Cloud model cache.r   r   zollama_cloud_models_cache.jsonr   r   s    r4   _ollama_cloud_cache_pathr  Q  s(    000000????r6   
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).
    Nr  r  rG   r   r   )r  r  r  r  r  rI   rM   rN   r3   r   _OLLAMA_CLOUD_CACHE_TTLrQ   )r  
cache_pathr  rR   rG   r   s         r4   _load_ollama_cloud_cacher  W  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rG   c                    	 ddl m} t                      }|j                            dd            ||| t          j                    dd           dS # t          $ r Y dS w xY w)z3Persist the merged Ollama Cloud model list to disk.r   r  Tr  )rG   r   Nr  )r  r  r  r  r  r   rQ   )rG   r  r  s      r4   _save_ollama_cloud_cacher  r  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).
    NrG   OLLAMA_API_KEYr   OLLAMA_BASE_URLzhttps://ollama.com/v1r  r0  r   r  ri  Tr  )r  r  r  r  rL   r  rQ   r   r  r   r  r  )r  r  r   r:  live_modelsr   mdev_modelsr  r   r  rA   r  stales                r4   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.airv  rb   r  FzModel name cannot be empty.)acceptedpersist
recognizedmessagec              3  >   K   | ]}|                                 V  d S rF  )isspace)r@   chs     r4   rG  z+validate_requested_model.<locals>.<genexpr>  s*      
,
,B2::<<
,
,
,
,
,
,r6   z"Model names cannot contain spaces.rb  r   rO  )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(  r  )r  rG   r  g?)ncutoffzAuto-corrected `u   ` → ``)r  r  r  corrected_modelr     g      ?z
  Similar models: z, c              3  "   K   | ]
}d | d V  dS r  Nr<   r@   r  s     r4   rG  z+validate_requested_model.<locals>.<genexpr>/  +      DcDcRSXXXXDcDcDcDcDcDcr6   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: `>   r`   r_   c              3  "   K   | ]
}d | d V  dS r  r<   r  s     r4   rG  z+validate_requested_model.<locals>.<genexpr>q  r  r6   r_   rd  re  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.>   rx   r}   c                8    i | ]}|                                 |S r<   r[  r?   s     r4   ru  z,validate_requested_model.<locals>.<dictcomp>  s"    BBBaQWWYYBBBr6   c              3  0   K   | ]}d |          d V  dS r  r<   r@   r  catalog_lowers     r4   rG  z+validate_requested_model.<locals>.<genexpr>  s6      DrDrabE\WXIYE\E\E\DrDrDrDrDrDrr6   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  r<   r  s     r4   rG  z+validate_requested_model.<locals>.<genexpr>  r  r6   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.rj   c                    g | ]E}t          |t                    r,|                    d           r|t          d           d         n|FS )zmodels/N)rI   rJ   rP  r  r?   s     r4   rB   z,validate_requested_model.<locals>.<listcomp>  s]        '1C&8&8[Q\\)=T=T[#i..//""Z[  r6   c              3  "   K   | ]
}d | d V  dS r  r<   r  s     r4   rG  z+validate_requested_model.<locals>.<genexpr>%  r  r6   z1` was not found in this provider's model listing.r   )discover_bedrock_modelsresolve_bedrock_regionc                    h | ]
}|d          S r  r<   r?   s     r4   rn  z+validate_requested_model.<locals>.<setcomp><  s    :::!ag:::r6   g?c              3  "   K   | ]
}d | d V  dS r  r<   r  s     r4   rG  z+validate_requested_model.<locals>.<genexpr>I  r  r6   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 r<   r[  r?   s     r4   ru  z,validate_requested_model.<locals>.<dictcomp>f  s"    >>>!A>>>r6   c              3  0   K   | ]}d |          d V  dS r  r<   r  s     r4   rG  z+validate_requested_model.<locals>.<genexpr>  sM       A A,-'M!$'''A A A A A Ar6   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.)rC  r  r  rH  rB  rP  rY  r   rP  r  rN   r   r  r;  rQ   r  r3   rO   r  r  r  r  r  r  )rJ  r  r  r  r  	requestedr  requested_for_lookuprP  rG   r%  probe
api_modelsri  suggestionssuggestion_textr  catalog_modelsrl  catalog_lower_list	correctedanthropic_modelsr  r  region
discovereddiscovered_idsr  s                              @r4   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^^QxN #)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?[. .[=<[=)r,   r-   )r9   r-   r,   r-   )r   rJ   r   r   r,   r   )r   r   r,   r   )r   r-   r   r   r   r   r,   r   r9  )
r   r-   r   r   r   rJ   r   r   r,   r   )r   r   r,   r   )r,   r   )r   rJ   r,   r   )r   rJ   rR   r   r,   r  )r   r'  )r   rJ   r(  r   r   r   r,   r   )r,   rJ   )r   r   r,   rG  )
rK  r   r   rL  r   rJ   r   r   r,   rG  )rY  rJ   r,   rJ   )r  rJ   r,   rJ   )r   r   r,   r   )r  r   r,   r   )r  )r(  r   r   r   r,   r)   )r   r   r,   r-   )r  rJ   r,   rJ   )Nr  r  )
r  r  r  rJ   r(  r   r   r   r,   r   )r,   r  )r  rJ   r   r   r,   r   )r(  r   r   r   r,   r   )r,   r  )r$  rJ   r%  rJ   r,   r  )r  rG  r   r   r,   r)   )r  rJ   r,   r  )rB  rJ   rC  r  r,   r   )rB  rJ   rJ  r  r,   rK  )rJ  rJ   r%  rJ   r,   rK  )rJ  rJ   r,   rG  )r  rG  r,   rJ   )r   rG  r,   r   )r   rJ   r,   rJ   )r   rG  r,   r   )r  rJ   r  r-   r,   r-   )r  rG  r   r   r,   r-   )r,   r   )r,   rM   )rR   rM   r,   r  )r  rG  r   r   r  r   r,   r-   rF  )r  rG  r,   r  )r'  )r(  r   r,   r  )r   r   r,   r  )r,   r  )r  r   r,   r   )Nr'  )r  rG  r(  r   r,   r2  )r   rJ   r  rG  r,   r:  )r  rG  r,   r   )r  rG  r,   rG  )r  rG  r,   rM   )NNr'  )r  rG  r  rG  r(  r   r,   rK  )r  rG  r  rG  r(  r   r,   r  )r  rG  r  rG  r(  r   r,   r-   )r\  )r3  rJ   r  rG  r  rG  r]  r   r(  r   r,   r:  )
r3  rJ   r  rG  r  rG  r(  r   r,   r-   )r  rG  r(  r   r,   r  )NN)r?  r2  r  rG  r,   r  )r   rG  r?  r2  r  rG  r,   rJ   )r   rJ   r,   r-   )r   rJ   r,   r   )rJ  rG  r,   rG  )r  rG  r   rG  r,   rJ   )r   rG  r?  r2  r  rG  r,   r-   )r'  N)
r  rG  r  rG  r(  r   r  rG  r,   r   )
r  rG  r  rG  r(  r   r  rG  r,   r  )r  r   r,   r  )rG   r-   r,   r  )r  rG  r  rG  r   r   r,   r-   )rJ  rJ   r  rG  r  rG  r  rG  r  rG  r,   r   )__doc__
__future__r   r  r  urllib.requestr3  urllib.errorr   difflibr   pathlibr   typingr   r   r   
hermes_clir	   _HERMES_VERSIONr  rD  r4  r  r  r  r*   r^  r+   r5   r8   r>   rC   rS   r   r   r   r   r   r   r   r   r   r2  r   r   r   r  r&  r   rF  r   rV  rX  rj  _canonical_slugsrC  ro  _list_providers_for_canonical_ppr   r  display_namer  r  r  r   r  rQ   r  r|  r  r  r  r  r  r  r  r  r  r  r   r  r  r  r  r  r  r  r  r  r   rO   r  r#  r0  r  r=  rA  rI  	frozensetrO  rX  r_  rd  ra  r  rl  rq  rw  ru  r|  rz  r  r  r  r  r;  _PROVIDER_MODELS_CACHE_TTLr  r  r  r  r  r  r  r  r   r1  r7  r8  r9  r>  rB  rE  rH  rJ  rU  rY  r[  rn  ru  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r<   r6   r4   <module>r     sh     # " " " " "  				          % % % % % %       , , , , , , , , , , 5 5 5 5 5 5 5?44 2 (111 ) !E!E!E %>%>%> "
0, 0, 0,  0 0 0 0d ;?  > > > >
B B B B*# # #         & & & &6V*
 % % %V*R  	 	 	SV*f    gV*~ ))++V*@ $$&&AV*B CV*H    IV*l    mV*x  
 
 
yV*N 
   OV*` 
  aV*b    cV*~    V*P    QV*^ _V*f    gV*t    uV* V*B    CV*L    MV*Z    [V*r    sV*~    V*L MV*R    SV*\ 
   ]V*l  $ $ $mV*v    wV*T    UV*l    mV*H	  
 
 
I	V*`	  
 
 
a	V*~	    	V*Z
 R[
V*\
    ]
V* V*  V V V V@      8% % % %> ?.
  ?. ?. ?. ?. ?. ?.J ?*
  ?* ?* ?* ?* ?* ?*L       .2  2 2 2 2 16      b  > #&  & & & &CE  E E E EG G G G= = = =*
 
 
 
@ ;  	; ; ; ; ; ;|1 1 1 1      $6 6 6 6 6 6H    J   
$,M&M  AO  P  P$,M,L@ijj$, M(J@wxx$, M*K@z{{	$,
 M+K@vww$, M.N@~$, M,L@fgg$, M)L@pqq$, M+$K  NB  C  C$, M(M@yzz$, M$&8@}~~$, M(L  AA  B  B$, M)$4@uvv$, M-$8@stt$, M-N@bcc$,  M($6@fgg!$," M%'>  CC  D  D#$,$ M*J@fgg%$,& M%E@WXX'$,( M%L@_``)$,* M-$=@pqq+$,, M"$=@mnn-$,. M)$7?|}}/$,0 M)I@]^^1$,2 M/$5@{||3$,4 M,$5@eff5$,6 M.N@uvv7$,8 M'J@ghh9$,: M%K@dee;$,< M*K@^__=$,> M.N@noo?$,@ M-M@hiiA$,B M)M@}~~C$,D M/O  AU  V  VE$,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 .  4 #$TWdfvVwx$Q  XB  XB  XB  C$NX]_jWkl $JX`buWvw$DXfhtWuv$JXfhuWvw!$OXacpWqr: :    " "3B3H3H3J3J" " "    
C C C C
9 9 9xM	5M
EM EM U	M
 iM iM YM IM -M M hM XM M MM M  !M" ##M M$ I%M& 9'M( )M* w+M, -M. /M0 \1M2 ,3M4 o5M6 o7M8 _9M: k;M< ;=M> ?M@ AMB 
>CMD 	-EM M MF }GMH JIMJ KML JMMN OMP iQMR ISMT YUMV <WMX %YMZ '[M\ 	-]M^ M_M` }aMb cMd eMf HgM M Mh 8iMj !kMl "mMn 'oMp %qMr 
9sMt 9uMv iwMx iyMz E{M| +}M~ M@ +AMB kCMD EEMF EGMH 
8IM MJ "YM M M B (6 "    
' ' ' ',      4 A  A A A A A AH (- T T T T T T
2 2 2 2. 8: 9 9 9 9   2 /0
  0 0 0 0 0 0f7 7 7 7
  H . . . ., FK      8 7  7 7 7 7 7 7x C  	c

 
 
"
"##$X     ) ) ) )X!( !( !( !(H
 
 
 
   % % % % % %2/ / / /    "	111  
- - - -`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 @ @ @ @ @ @p " < < < <
D< D< D< D<N
 
 
 
     1	3 3 3 3 3 3l    ,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% !   :
 
 
 
! ! ! !N /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   P('P(