
    Jjm                      U d Z ddlmZ ddlZddlZddlmZ ddlmZm	Z	m
Z
mZ ddlmZmZmZmZmZmZ ddlmZ ddlmZmZmZmZmZ  ed	d
h          Zded<    ej        e          Z ddZ!ddZ"dZ# ej$        dej%                  Z&ddZ'ddZ( G d de
          Z)i d e)dd           d! e)dd"          d# e)dd$          d% e)dd%          d& e)d'd(          d) e)d'd)          d* e)d'd*          d+ e)d'd+          d, e)d'd,          d- e)d.d-          d/ e)d/d0          d1 e)d2d1          d3 e)d4d3          d5 e)d5d5          d6 e)d6d6          d7 e)d8d7          d9 e)d:d9           e)d;d<           e)d=d>           e)d?d@           e)dAdB          dCZ*dDedE<    G dF dGe
          Z+i Z,dHedI<   i Z-dHedJ<   ddKZ.ddMZ/e G dN dO                      Z0ddRZ1ddUZ2ddYZ3dd]Z4	 	 	 dddcZ5	 dddfZ6	 	 	 	 	 dddpZ7ddtZ8	 	 	 	 	 	 dddyZ9ddl:Z; e;j<                    Z=dd{Z>dd}Z?	 	 	 	 ddudd^dud~dddZ@dddZA	 	 	 	 	 	 	 dddZBdS )u  Shared model-switching logic for CLI and gateway /model commands.

Both the CLI (cli.py) and gateway (gateway/run.py) /model handlers
share the same core pipeline:

  parse flags -> alias resolution -> provider resolution ->
  credential resolution -> normalize model name ->
  metadata lookup -> build result

This module ties together the foundation layers:

- ``agent.models_dev``            -- models.dev catalog, ModelInfo, ProviderInfo
- ``hermes_cli.providers``        -- canonical provider identity + overlays
- ``hermes_cli.model_normalize``  -- per-provider name formatting

Provider switching uses the ``--provider`` flag exclusively.
No colon-based ``provider:model`` syntax — colons are reserved for
OpenRouter variant suffixes (``:free``, ``:extended``, ``:fast``).
    )annotationsN)	dataclass)AnyList
NamedTupleOptional)ProviderDefcustom_provider_slugdetermine_api_mode	get_labelis_aggregatorresolve_provider_full)normalize_model_for_provider)ModelCapabilities	ModelInfoget_model_capabilitiesget_model_infolist_provider_modelsopencode-zenopencode-gozfrozenset[str]_UNCAPPED_PICKER_PROVIDERSvaluer   return	list[str]c                8   g t                      dfd}t          | t                    r ||            S t          | t                    r| D ]} ||           S t          | t          t
          f          r| D ]}t          |t                    r ||           #t          |t                    r^|                    d          }t          |t                    r|                                s|                    d          } ||           S S )	zReturn configured model IDs from supported config shapes.

    Accepts:
    - ``{"model-id": {...}}``
    - ``["model-a", "model-b"]``
    - ``[{"id": "model-a"}, {"name": "model-b"}]``
    - ``"model-a"``
    	candidater   r   Nonec                    t          | t                    sd S |                                 }|sd S |                                }|v rd S                     |                               |           d S N)
isinstancestrstriploweraddappend)r   model_idloweredidsseens      =/home/rurouni/.hermes/hermes-agent/hermes_cli/model_switch.py_addz!_declared_model_ids.<locals>._addC   s}    )S)) 	F??$$ 	F..""d??F

8    idname)r   r   r   r   )setr    r!   dictlisttuplegetr"   )r   r+   r&   itemr(   r)   s       @@r*   _declared_model_idsr5   7   sF    CUUD
 
 
 
 
 
 
 % U
%  	 	HDNNNN
%$'' 
 	 	D$$$ T


$%% 88D>>!(C00 08H8H 0#xx//HX
Jr,   current_base_urlr!   Optional[ProviderDef]c           
     ~    t          | pd                                          }|sdS t          dddd|ddd	
          S )z=ProviderDef for a direct ``model.provider: custom`` endpoint. NcustomCustom endpointopenai_chat Fapi_keymodel-config)r-   r.   	transportapi_key_env_varsbase_urlr   	auth_typesource)r!   r"   r	   )r6   rB   s     r*   _bare_custom_provider_defrE   g   s]    #)r**0022H t	 	 	 	r,   zNous Research Hermes 3 & 4 models are NOT agentic and are not designed for use with Hermes Agent. They lack the tool-calling capabilities required for agent workflows. Consider using an agentic model instead (Claude, GPT, Gemini, DeepSeek, etc.).z&(?:^|[/:])hermes[-_ ]?[34](?:[-_.:]|$)
model_nameboolc                X    | sdS t          t                              |                     S )zReturn True if *model_name* is a real Nous Hermes 3/4 chat model.

    Used to decide whether to surface the non-agentic warning at startup.
    Callers in :mod:`cli.py` and here should go through this single helper
    so the two sites don't drift.
    F)rG   _NOUS_HERMES_NON_AGENTIC_REsearchrF   s    r*   is_nous_hermes_non_agenticrL      s.      u+22:>>???r,   c                2    t          |           rt          S dS )zHReturn a warning string if *model_name* is a Nous Hermes 3/4 chat model.r9   )rL   _HERMES_MODEL_WARNINGrK   s    r*   _check_hermes_model_warningrO      s    !*-- %$$2r,   c                  (    e Zd ZU dZded<   ded<   dS )ModelIdentityz:Vendor slug and family prefix used for catalog resolution.r!   vendorfamilyN__name__
__module____qualname____doc____annotations__r=   r,   r*   rQ   rQ      s(         DDKKKKKKKKr,   rQ   sonnet	anthropiczclaude-sonnetopuszclaude-opushaikuzclaude-haikuclaudegpt5openaizgpt-5gptcodexo3o4geminigoogledeepseekzdeepseek-chatgrokzx-aillamaz
meta-llamaqwenminimaxnemotronnvidiakimi
moonshotaizz-aiglmstepfunstepxiaomimimozarcee-aitrinity)rp   rr   rt   ru   zdict[str, ModelIdentity]MODEL_ALIASESc                  2    e Zd ZU dZded<   ded<   ded<   dS )DirectAliasz5Exact model mapping that bypasses catalog resolution.r!   modelproviderrB   NrT   r=   r,   r*   rx   rx      s1         ??JJJMMMMMMMMr,   rx   dict[str, DirectAlias]_BUILTIN_DIRECT_ALIASESDIRECT_ALIASESc                    t          t                    } 	 ddlm}  |            }|                    d          }t          |t                     r|                                D ]\  }}t          |t                     s|                    dd          }|                    dd          }|                    dd          }|r9t          |||	          | |                                	                                <   |                    di           }	t          |	t                     r |	                    d
          }
t          |
t                     r|	                    dd          }|
                                D ]\  }}t          |t                    r|                                s/|                                	                                }|| v rZ|                                }d|v r|                    dd          \  }}n|}|}t          |                                |                                p|d	          | |<   n# t          $ r Y nw xY w| S )a  Load direct aliases from config.yaml ``model_aliases:`` section.

    Config format::

        model_aliases:
          qwen:
            model: "qwen3.5:397b"
            provider: custom
            base_url: "https://ollama.com/v1"
          minimax:
            model: "minimax-m2.7"
            provider: custom
            base_url: "https://ollama.com/v1"

    Also reads ``model.aliases`` (set by ``hermes config set model.aliases.xxx``)
    and converts simple string entries (``ds-flash: deepseek/deepseek-v4-flash``)
    into DirectAlias objects.  The provider is parsed from the ``provider/``
    prefix in the value; if no slash, the current provider is used.
    r   load_configmodel_aliasesry   r9   rz   r:   rB   )ry   rz   rB   aliases/   )r0   r|   hermes_cli.configr   r3   r    itemsrx   r"   r#   r!   split	Exception)mergedr   cfguser_aliasesr.   entryry   rz   rB   model_sectionsimple_aliasescurrent_providerr   keyvals                  r*   _load_direct_aliasesr      si   ( )**F*111111kmm ww//lD)) 
	+1133 	 	e!%.. 		'2.. 99Z:: 99Z44 3>#h4 4 4F4::<<--//0
 ,,mT** 	*..y99N.$// #0#4#4Z#D#D #1#7#7#9#9  KD%%eS11 ! ! **,,,,..Cf}} ++--Cczz*-))C*;*;%%#3 #"-#kkmm!)!1!1!E5E!## # #F3KK
    Ms   H3I
 

IIr   c                 d    t           s(t                               t                                 dS dS )u5  Lazy-load direct aliases on first use.

    Mutates the existing DIRECT_ALIASES dict in place rather than rebinding
    the module attribute. This keeps `from hermes_cli.model_switch import
    DIRECT_ALIASES` references valid in callers — rebinding would leave them
    pointing at a stale empty dict.
    N)r}   updater   r=   r,   r*   _ensure_direct_aliasesr   =  s7      6244555556 6r,   c                      e Zd ZU dZded<   dZded<   dZded<   dZded	<   dZded
<   dZ	ded<   dZ
ded<   dZded<   dZded<   dZded<   dZded<   dZded<   dZded<   dZded<   dS )ModelSwitchResultz!Result of a model switch attempt.rG   successr9   r!   	new_modeltarget_providerFprovider_changedr>   rB   api_modeerror_messagewarning_messageprovider_labelresolved_via_aliasNzOptional[ModelCapabilities]capabilitiesOptional[ModelInfo]
model_info	is_global)rU   rV   rW   rX   rY   r   r   r   r>   rB   r   r   r   r   r   r   r   r   r=   r,   r*   r   r   M  s         ++MMMIO"""""GHHMON     04L4444&*J****Ir,   r   raw_args!tuple[str, str, bool, bool, bool]c                   d}d}d}d}ddl }|                    dd|           } d| v r*d}|                     dd                                          } d	| v r*d}|                     d	d                                          } d
| v r*d}|                     d
d                                          } |                                 }d}g }|t          |          k     rf||         dk    r'|dz   t          |          k     r||dz            }|dz  }n |                    ||                    |dz  }|t          |          k     fd                    |                                          }	|	||||fS )a  Parse --provider, --global, --session, and --refresh flags from /model command args.

    Returns ``(model_input, explicit_provider, is_global, force_refresh, is_session)``.

    ``is_global`` and ``is_session`` are independent flag presences; the
    *effective* persistence decision is resolved by
    :func:`resolve_persist_behavior` so the config-gated default
    (``model.persist_switch_by_default``) is applied in one place.

    Examples::

        "sonnet"                         -> ("sonnet", "", False, False, False)
        "sonnet --global"                -> ("sonnet", "", True, False, False)
        "sonnet --session"               -> ("sonnet", "", False, False, True)
        "sonnet --provider anthropic"    -> ("sonnet", "anthropic", False, False, False)
        "--provider my-ollama"           -> ("", "my-ollama", False, False, False)
        "--refresh"                      -> ("", "", False, True, False)
        "sonnet --provider anthropic --global" -> ("sonnet", "anthropic", True, False, False)
    Fr9   r   Nz;[\u2012\u2013\u2014\u2015](provider|global|session|refresh)z--\1z--globalTz	--sessionz	--refreshz
--providerr       )resubreplacer"   r   lenr%   join)
r   r   explicit_providerforce_refresh
is_session_repartsifilteredmodel_inputs
             r*   parse_model_flagsr   c  s   ( IMJ wwUW^`hiiH X	##J3399;; h
##K44::<< h##K44::<< NNE	AH
c%jj..8|##AE

(:(: %a!eFAAOOE!H%%%FA c%jj.. ((8$$**,,K*I}jQQr,   r   r   c                    |rdS | rdS 	 ddl m}  |                                d          }t          |t                    r#t          |                    dd                    S n# t          $ r Y nw xY wdS )uW  Decide whether a ``/model`` switch should persist to ``config.yaml``.

    Resolution order:

    1. ``--session`` explicitly opts out → ``False`` (this session only).
    2. ``--global`` explicitly opts in → ``True``.
    3. Otherwise defer to ``model.persist_switch_by_default`` in
       ``config.yaml`` (defaults to ``True``, so a plain ``/model <name>``
       survives across sessions — the behavior users expect).

    The config read is defensive: on a fresh install ``model`` may be a
    flat string rather than a dict, in which case the built-in default
    (``True``) applies.
    FTr   r   ry   persist_switch_by_default)r   r   r3   r    r0   rG   r   )r   r   r   	model_cfgs       r*   resolve_persist_behaviorr     s      u t111111KMM%%g..	i&& 	J	&A4HHIII	J   4s   AA& &
A32A3r&   prefixr2   c                   | t          |          d         }|                    d          r
|dd         }|                    d                                          }g }d}d}d}|D ]}|dk    r0|dv rd}|                                rd}||z  },|d	v r1d
}||z  }9|dk    r|                                r||z  }Z|dk    rTd|v rJ	 |                    t          |                    d                               n# t          $ r Y nw xY wd}||z  }|d	v rO|rI	 |                    t          |                    d                               n# t          $ r Y nw xY wd}d}|rI	 |                    t          |                    d                               n# t          $ r Y nw xY wd}d
}||z  }[|dk    r1|                                rd}|}{|dv rd}|d	v rd
}||z  }|d
k    r||z  }|rM|dk    rG	 |                    t          |                    d                               n# t          $ r Y nw xY w|	                                                    d	          }|                                }t          d |D                       }	ddddd}
|
                    |d          }|	||fz   S )uS  Sort key for model version preference.

    Extracts version numbers after the family prefix and returns a sort key
    that prefers higher versions.  Suffix tokens (``pro``, ``omni``, etc.)
    are used as tiebreakers, with common quality indicators ranked.

    Examples (with prefix ``"mimo"``)::

        mimo-v2.5-pro   → (-2.5, 0, 'pro')     # highest version wins
        mimo-v2.5       → (-2.5, 1, '')          # no suffix = lower than pro
        mimo-v2-pro     → (-2.0, 0, 'pro')
        mimo-v2-omni    → (-2.0, 1, 'omni')
        mimo-v2-flash   → (-2.0, 1, 'flash')
    Nr   r   -r9   startvV
in_versionz-_.	in_suffix.betweenc              3     K   | ]}| V  d S r   r=   ).0ns     r*   	<genexpr>z"_model_sort_key.<locals>.<genexpr>#  s$      ))q))))))r,   r   )promaxplusturbo)r   
startswithlstripr"   isdigitr%   floatrstrip
ValueErrorr#   r2   r3   )r&   r   restnums
suffix_bufstatenum_bufchsuffixversion_key_SUFFIX_RANKsuffix_ranks               r*   _model_sort_keyr     s     CKKLL!Ds ABBx;;s!!##D DJEG 6 6GTzz$ !$2u#b 

l""zz|| !2s'>>E'..*=*=$>$>????%    GGrMGGu !E'..*=*=$>$>????%    G! !E'..*=*=$>$>????%    G#b 

izz|| 	!$t$u#b 

k!!"J  5L((	KKgnnS11223333 	 	 	D	 %%e,,F\\^^F ))D)))))K QA>>L""61--K+v...sH   5D
DD#5E
E&%E&25F((
F54F55I 
II	raw_inputr   Optional[tuple[str, str, str]]c                   |                                                                  }t                       t                              |          }||j        |j        |fS t                                          D ]3\  }}|j                                        |k    r|j        |j        |fc S 4t                              |          }|dS |\  }}t          |          }		 ddl
m}
 |
                    |g           }|r<d |	D             }|D ]-}|                                |vr|	                    |           .n# t          $ r Y nw xY wt          |          }|r(| d|                                 fd|	D             }n"|                                fd|	D             }|sdS |r| d| n||                    fd	           ||d         |fS )
a  Resolve a short alias against the current provider's catalog.

    Looks up *raw_input* in :data:`MODEL_ALIASES`, then searches the
    current provider's models.dev catalog for the model whose ID starts
    with ``vendor/family`` (or just ``family`` for non-aggregator
    providers) and has the **highest version**.

    Returns:
        ``(provider, resolved_model_id, alias_name)`` if a match is
        found on the current provider, or ``None`` if the alias doesn't
        exist or no matching model is available.
    Nr   )_PROVIDER_MODELSc                6    h | ]}|                                 S r=   )r#   )r   ms     r*   	<setcomp>z resolve_alias.<locals>.<setcomp>Z  s     ///!AGGII///r,   r   c                b    g | ]+}|                                                               )|,S r=   r#   r   )r   midr   s     r*   
<listcomp>z!resolve_alias.<locals>.<listcomp>f  sE     
 
 
yy{{%%f--

 
 
r,   c                b    g | ]+}|                                                               )|,S r=   r   )r   r   family_lowers     r*   r   z!resolve_alias.<locals>.<listcomp>l  sE     
 
 
yy{{%%l33

 
 
r,   c                $    t          |           S r   )r   )r   prefix_for_sorts    r*   <lambda>zresolve_alias.<locals>.<lambda>v  s    q/BB r,   r   )r"   r#   r   r}   r3   rz   ry   r   rv   r   hermes_cli.modelsr   r%   r   r   sort)r   r   r   direct
alias_namedaidentityrR   rS   catalogr   staticr)   r   
aggregatormatchesr   r   r   s                   @@@r*   resolve_aliasr   -  sz     //


!
!
#
#C $$Fs33
 )..00 7 7
B8>>s""K:6666 #   %%HtNFF
 ##344G	666666!%%&6;; 	&//w///D & &7799D((NN1%%%    /00J 
%%V%%++--
 
 
 
"
 
 

 ||~~
 
 
 
"
 
 

  t /9D**&***fOLLBBBBLCCCgaj#..s   #AD> >
E
Er9   user_providersr0   custom_providerslist | Nonec                f    	 t          | ||d          }d |D             S # t          $ r g cY S w xY w)u   Return slugs of providers that have credentials.

    Uses ``list_authenticated_providers()`` which is backed by the models.dev
    in-memory cache (1 hr TTL) — no extra network cost.
    r   )r   r   r   
max_modelsc                    g | ]
}|d          S )slugr=   r   ps     r*   r   z4get_authenticated_provider_slugs.<locals>.<listcomp>  s    ---a&	---r,   )list_authenticated_providersr   )r   r   r   	providerss       r*    get_authenticated_provider_slugsr  z  sa    	0-)-	
 
 
	 .-9----   			s   ! 00r=   authenticated_providersc                D    |pd}|D ]}t          | |          }||c S dS )zTry to resolve an alias on the user's authenticated providers.

    Falls back to ``("openrouter", "nous")`` only when no authenticated
    providers are supplied (backwards compat for non-interactive callers).
    )
openrouternousN)r   )r   r  r  rz   results        r*   _resolve_alias_fallbackr    sF     (A+AI  y(33MMM 4r,   ry   rz   rB   r>   r   r   config_context_length
int | NoneOptional[int]c                    	 ddl m}  || |pd|pd|pd||          }|rt          |          S n# t          $ r Y nw xY w||j        rt          |j                  S dS )u	  Resolve the context length to show in /model output.

    models.dev reports per-vendor context (e.g. gpt-5.5 = 1.05M on openai)
    but provider-enforced limits can be lower (e.g. Codex OAuth caps the
    same slug at 272k). The authoritative source is
    ``agent.model_metadata.get_model_context_length`` which already knows
    about Codex OAuth, Copilot, Nous, and falls back to models.dev for the
    rest.

    When ``custom_providers`` is provided, per-model ``context_length``
    overrides from ``custom_providers[].models.<id>.context_length`` are
    honored — this closes #15779 where ``/model`` switch ignored user-set
    overrides.

    Prefer the provider-aware value; fall back to ``model_info.context_window``
    only if the resolver returns nothing.
    r   )get_model_context_lengthr9   N)rB   r>   rz   r   r  )agent.model_metadatar  intr   context_window)	ry   rz   rB   r>   r   r   r  r  ctxs	            r*   resolve_display_context_lengthr    s    4AAAAAA&&^Mr%-"7
 
 
  	s88O	   *";:,---4s   -1 
>>Optional[dict]Optional[list]dict[str, str]c                   | r|                                  si S |                                                                  dfd}i }t          |t                    rq|                                D ]\\  }}t          |t
                    rt          |t                    s0dD ])} ||                    |                    }|r|||<    n*]t          |t                    r|D ]}	t          |	t                    s|	                    d          }
t          |
t
                    r|
                                 sWd|
 }||v radD ])} ||	                    |                    }|r|||<    n*|S )u  Return ``{provider_slug: canonical_model_id}`` for every configured
    provider whose declared models contain an exact (case-insensitive) match
    for ``model_name``.

    Used by :func:`switch_model` to route a *typed* model name to the provider
    that actually declares it in user/custom provider config, instead of
    leaving it on the current provider.  Without this, a model declared under
    ``providers.<slug>`` / ``custom_providers`` but typed while the current
    provider is ``openai-codex`` stays on Codex and is soft-accepted as an
    unknown hidden Codex model (#45006).

    Matching is exact (case-insensitive); the configured spelling is returned
    so the downstream validation/override path sees the canonical id.  Only the
    explicitly-declared model collections are scanned (``models``, the singular
    ``model``, and ``default_model``) — never fuzzy/family matching.
    r   Optional[str]c                d    t          |           D ]}|                                k    r|c S dS )z`Canonical id if ``value`` (a model collection or scalar) declares
        ``target``, else None.N)r5   r#   )r   r&   targets     r*   _matchz,_configured_provider_matches.<locals>._match  sD     ,E22 	  	 H~~6)) *tr,   )modelsry   default_modelr.   custom:)r   r  )r"   r#   r    r0   r   r!   r3   r1   )rF   r   r   r  r   r   r   r   hitr   r.   r  s              @r*   _configured_provider_matchesr"    s   *  Z--// 	%%''F      !G.$'' '--// 	 	ID#dC(( 
30E0E ;  fSWWS\\** $'GDME "D)) % 	 	EeT** 99V$$DdC(( 

 #T##Dw;  fUYYs^^,, $'GDME Nr,   Fcurrent_modelcurrent_api_keyr   c	                  V ddl m}	m}
m}m} ddlm} d}|                                 }|}d}|r]t          |||          }|9|                                	                                dk    rt          |          }|[d| d	}	 dd
lm}  |            }|r|dz  }|dd         D ]}|d|j         z  }n# t          $ r Y nw xY wt          d||          S |j        }|dk    rP|sN	 ddlm} ddlm}  | |                                d          pi           d         }n# t          $ r d}Y nw xY wddl m} ddlm} |                                	                                V|                    V          }|r||k    rz|Vk    rt||v rpt1          |||          }||vrZVfd|D             }|rdd                    |           dnd}t          d||j        |dV dt7          |           d|           S |sr|j        rGddlm}  ||j                  } | r| }nPt          d||j        |d |j         d!|j         d"|           S t          d||j        |d|j         d#|           S t=          ||          }!|!|!\  }"}}n	 ddlm} dd$lm}#m}  | |                                d          pi           }$ |#|$|           }%|%rd}|%}d}d%}d}!nt=          | |          }!n # t          $ r t=          | |          }!Y nw xY w|rnu|!%|!\  }}}t@          !                    d&|||           nN|                                 	                                }&|&tD          v r{t1          |||          }'tG          | |'          }(|($|(\  }}}t@          !                    d'|||           ntD          |&         })t          d|d(|& d)|)j$         d*|)j%         d+          S |s| &                    d,          }*|*dk    rd*| vrtO          |          rt| d|*                                         	                                }+| |*d-z   d                                         },|+r%|,r#|+ d*|, }t@          !                    d.| |           d}-tO          |          r|stQ          |          }.|.rw|	                                }/|.D ] }0|0	                                |/k    r|0}d%}- nA!|.D ]=}0d*|0v r7|0)                    d*d-          \  }"}1|1	                                |/k    r|0}d%}- n>d}2|s|-s||k    rtU          |||          }3|3r||3v r|3|         }d%}2ntW          |3          }4tY          |4          d-k    r,t          d|d/| d0d                    |4           d1          S |4d         }|3|         }d%}2t@          !                    d2||           t[          |t\                    r||v r|}|pd}5|d3v p|/                    d4          pd5|5v pd6|5v }6||k    r|6s|s|-s|2s |
||          } | r| \  }}||k    }7t7          |          }8|dk    r|rd7}8|/                    d4          rt          |||          }9|9|9j        }8|}:|};d}<|7s|rddl0}=d}>|rF|rDdd8lm1}?  |?|                                	                                |          }>|> |?||          }>|>|>j        r|pi                     |                                	                                          p|pi                     |          pi }@te          |@                    d9d          pd                                          }A|A/                    d:          rJ|A3                    d;          r5|=j4                            |Ad<d=         d                                          }A|Asfte          |@                    d>d          pd                                          }B|Br-|=j4                            |Bd                                          }A	  |||Apd|>j        |?          }C|C                    d9d          p|A}:|C                    d@d          p|>j        };|C                    dAd          }<n# t          $ r |A}:|>j        };d}<Y nw xY w|dk    r|r|}:|};tk          ||;          }<n	  |||B          }C|C                    d9d          }:|C                    d@d          };|C                    dAd          }<n# t          $ r$}Dt          d||8|dC|8 dD|D           cY d}D~DS d}D~Dww xY w	  |||B          }C|C                    d9d          }:|C                    d@d          };|C                    dAd          }<n# t          $ r Y nw xY w|r>tm                       tn                              |          }E|E|Ej        r|Ej        };d}<|:sdE}:tq          ||          }	  ||||:|;|<pdF          }Fn$# t          $ r}DddddG| dH|D dI}FY d}D~Dnd}D~Dww xY w|F                    dJ          sVd}G|rI|9                                D ]4\  }H}I|H|k    r)|tu          |I                    dKi                     v rd%}G n5|Gs|rt[          |tv                    r|D ]}Jt[          |Jt\                    s|J                    dLd          }K|Krd4|K nd}L|J                    d@d          }M|L|k    s|M|;k    rK|J                    dMd          }N|J                    dKi           }O||Nk    rd%}G n|tu          |O          v rd%}G n|Grd%d%d|F                    dNd          dI}Fn+|F                    dNdO          }Pt          d|||8||PP          S |F                    dQ          r|FdQ         }|dRv r |	||:S          }<|dTv r |||          }<|<stk          ||;          }<|dUv r(t[          |;td                    rddVl m<}Q  |Q||<|;          };t{          ||          }Rt}          ||          }Sg }T|F                    dN          r|T?                    |FdN                    t          |          }U|Ur|T?                    |U           t          d%|||7|:|;|<|TrdW                    |T          nd|8||R|S|X          S )Ya  Core model-switching pipeline shared between CLI and gateway.

    Resolution chain:

      If --provider given:
        a. Resolve provider via resolve_provider_full()
        b. Resolve credentials
        c. If model given, resolve alias on target provider or use as-is
        d. If no model, auto-detect from endpoint

      If no --provider:
        a. Try alias resolution on current provider
        b. If alias exists but not on current provider -> fallback
        c. On aggregator, try vendor/model slug conversion
        d. Aggregator catalog search
        e. detect_provider_for_model() as last resort
        f. Resolve credentials
        g. Normalize model name for target provider

      Finally:
        h. Get full model metadata from models.dev
        i. Build result

    Args:
        raw_input: The model name (after flag parsing).
        current_provider: The currently active provider.
        current_model: The currently active model name.
        current_base_url: The currently active base URL.
        current_api_key: The currently active API key.
        is_global: Whether to persist the switch.
        explicit_provider: From --provider flag (empty = no explicit provider).
        user_providers: The ``providers:`` dict from config.yaml (for user endpoints).
        custom_providers: The ``custom_providers:`` list from config.yaml.

    Returns:
        ModelSwitchResult with all information the caller needs.
    r   )copilot_model_api_modedetect_provider_for_modelvalidate_requested_modelopencode_model_api_mode)resolve_runtime_providerr9   FNr:   zUnknown provider 'z`'. Check 'hermes model' for available providers, or define it in config.yaml under 'providers:'.)validate_config_structureu1   

Run 'hermes doctor' — config issues detected:   u   
  • )r   r   r   moar   )normalize_moa_configdefault_presetdefault_AGGREGATOR_PROVIDERSALIASES)r   r   r   c                J    g | ]}|                               r|k    | S r=   )r   )r   s_explicit_norms     r*   r   z switch_model.<locals>.<listcomp>  sB          ||N33 89^8K8K 8K8K8Kr,   z Did you mean: z, ?z
Provider 'z"' is an alias that routes through z&, which has no credentials configured.)r   r   r   r   r   )_auto_detect_local_modelzNo model detected on z (z@). Specify the model explicitly: /model <model-name> --provider zN' has no base URL configured. Specify a model: /model <model-name> --provider )exact_moa_preset_namer.  TzAlias '%s' resolved to %s on %sz,Alias '%s' resolved via fallback to %s on %szAlias 'z
' maps to r   z] but no matching model was found in any provider catalog. Try specifying the full model name.:r   z3Converted vendor:model '%s' to aggregator slug '%s''z0' is declared by multiple configured providers (z<). Re-run with --provider <slug> to choose which one to use.z/Configured-provider detection routed '%s' to %s>   localr:   r   	localhostz	127.0.0.1r;   )resolve_user_providerr>   z${}r   key_env)	requestedexplicit_api_keyexplicit_base_urltarget_modelrB   r   )rC  rF  z,Could not resolve credentials for provider 'z': zno-key-required)r>   rB   r   zCould not validate `z`: )acceptedpersist
recognizedmessagerG  r  r.   ry   rJ  zInvalid model)r   r   r   r   r   r   corrected_model>   github-copilotcopilot)r>   >   opencoder   r   >   r   r   )normalize_opencode_base_urlz | )r   r   r   r   r>   rB   r   r   r   r   r   r   r   )Ar   r&  r'  r(  r)  hermes_cli.runtime_providerr*  r"   r   r#   rE   r   r+  rJ  r   r   r-   r   hermes_cli.moa_configr.  r3   r2  hermes_cli.providersr4  r  r   r.   r   rB   r9  r   r:  loggerdebugrv   r  rR   rS   findr   r   r   r"  sortedr   r    r0   r   osr?  r!   endswithenvironr   r   r}   r   r   r5   r1   rO  r   r   r%   rO   )Wr   r   r#  r6   r$  r   r   r   r   r&  r'  r(  r)  r*  resolved_aliasr   r   resolved_moa_presetpdef_switch_errr+  _cfg_issues_cir   r.  _AGG_PROVIDERS_PROVIDER_ALIAS_TABLE_alias_target_authed_suggestions_hintr9  detectedalias_result_r:  _moa_cfg
_moa_matchr   authedfallback_resultr   	colon_posleftrightresolved_in_current_catalogr   new_model_lowerr   bareconfig_routedcfg_matchesmatch_slugs_base	is_customr   r   custom_pdefr>   rB   r   rW  
_user_pdef_ruser_ucfg_ukey_kenvruntimee_da
validationoverrider   r   r   
entry_name
entry_slug	entry_urlentry_modelentry_modelsmsgrO  r   r   warningshermes_warnr7  sW                                                                                         @r*   switch_modelr    s   `            EDDDDDN!!I&O
  _6$
 

 <-3355;;==II,-=>>D<6%6 6 6 6 GGGGGG7799 @#XXK*2A2 @ @#'?#+'?'??   $#)    'e##I#&999999FFFFFF001B1B51I1I1ORPPQab		 & & &%			& 	NMMMMMIIIIII*002288::-11.AA	00>11>116!1-!1  G
 g--       &      $,@dii&=&=@@@@)+  )!$3#'9'A^ A A#,_#=#=A A9>A A
 
 
 
  	} PPPPPP33DMBB  (II, %(7'+y"+pDI p p p p\mp p	 	 	 	 )!$3#'9'_TY _ _K\_ _	 	 	 	 %Y@@#+7(Ay.	F555555YYYYYYYY++KKMM,=,=e,D,D,JKKH..xCCJ J"'&	!#&*##,Y8HII 	F 	F 	F(4DEELLL	F
  2	%9E6OYLL1	?    //##))++Cm##9%5#1%5  
 #:)V"L"L".AP>OYLLF&	?   
  -S1H, %"+Cc C CX_ C Cx C C C	    ) 
 &NN3//	q==S	%9%9mL\>]>]%9$ZiZ06688>>@@D%i!mnn5;;==E  '+$5$5e$5$5	Q%y   ',#)) 	&. 	&*?;;G &"+//"3"3" & &Cyy{{o55$'	6:3 6
  ' & &#::&)iiQ&7&7GAt#zz||>>,/	>B ; % (	</(	<  #3336>+; K   <#{22 +,< =I$(MM"("5"5K;''!++0$)&/!QI !Q !Q.2ii.D.D!Q !Q !Q	       '2!nO +O <I$(MLLI!?   ".$77 <O~<]<],;) !&B 33 >**955>u$<u(< 	 /// 0" 0 0 0 "	 0 10<LMMH 6-5* '*:://N(""'7"*!!),, .+
 

 "(-N GHH K, K			 
 	E 	ELLLLLL 1 7 7 9 9 ? ? A A>RRJ!#VO^DD
!j&9!#)r../@/F/F/H/H/N/N/P/PQQ E"(b--o>>EBD 		)R006B77==??E%% @%..*=*= @
uQrT{B77==?? >EIIi44:;;AACC >JNN5"55;;==E22-%*]d&0&9!*	   "++i44=";;z266M*:M";;z266   %. ((-=(%G'H)/8DDHH22-!*   "++i44";;z266";;z266 
 
 
(!$3#1'3*3 3/03 3	 	 	 	 	 	 	 	 	
	..*&  G kk)R00G{{:r22H{{:r22HH 	 	 	D	  ,     00?s|?|HH ,+ -YHHI
--%
 
 


  
 
 
?i??A??	
 







 >>*%% ( 	+1133  	c?** $7"8M8M$N$NNN#'  	, 	<Ld1S1S 	)  !%.. "YYvr22
7AI3z333r
!IIj"55	00I4I4I"'))GR"8"8K#(99Xr#:#:L K//#' $7$E$EEE#' 	&*t5]g]k]kluwy]z]z{{JJ..O<<C$# /-#!    ~~'(( 201	 777)))WEEE EEE**?IFF  A%ox@@ 999jSV>W>W9AAAAAA..(SS */9EEL  ;;J H~~i   /
9-...-i88K %$$$ ')08@

8,,,b%)!   s   1B8 8
CC*:D% %D43D4AK/ /LL3A!b b/.b/Ad   
e*e	e	eAf" "
f/.f/h 
h7 h22h7r   c                x    t          | t                    si S ddlm}  ||                     d                    S )Nr   )normalize_extra_headersextra_headers)r    r0   r   r  r3   )r   r  s     r*   _extra_headers_from_configr  p  sH    eT"" 	999999""599_#=#=>>>r,   Optional['_threading.Thread']c                     t                                           rdS t                                            dd} t          j        | dd          }|                                 |S )	u  Warm the provider-models disk cache in a background daemon thread.

    The no-args ``/model`` picker calls ``list_authenticated_providers()``,
    which fetches each authenticated provider's live ``/v1/models`` list on a
    cold/stale cache. Those fetches are independent HTTP round-trips but run
    serially, so the first ``/model`` open in a session (or any open after the
    1h cache TTL expires) blocks ~1-2s on the user's critical path.

    This pre-warms that exact path off-thread during idle session time: it
    runs ``list_authenticated_providers()`` once, which populates
    ``provider_models_cache.json`` for every authed provider. By the time the
    user types ``/model``, the picker hits the warm disk cache and renders in
    ~100ms.

    Fire-and-forget. Process-level Event guard ensures it runs at most once.
    Fully exception-isolated — a slow or offline provider can never affect the
    session. Returns the spawned thread (for tests) or None if already warmed.
    Nr   r   c                     	 ddl m}   |             }t          |j        |j        |j        |j        |j                   d S # t          $ r  t          
                    dd           Y d S w xY w)Nr   )load_picker_context)r   r6   r#  r   r   zpicker cache prewarm failedT)exc_info)hermes_cli.inventoryr  r  r   r6   r#  r   r   r   rS  rT  )r  r  s     r*   _warmz)prewarm_picker_cache_async.<locals>._warm  s    	G@@@@@@%%''C )!$!5!$!5!/"1!$!5       	G 	G 	GLL6LFFFFFF	Gs   =A &A+*A+Tzpicker-cache-prewarm)r  daemonr.   r   r   )_picker_prewarm_doneis_setr/   
_threadingThreadr   )r  ts     r*   prewarm_picker_cache_asyncr  x  ss    & ""$$ tG G G G& 	t:PQQQAGGIIIHr,   T)force_fresh_nous_tierr   r#  refreshprobe_custom_providersr  r   r  r  
List[dict]c               F/   |}~ ddl ddlm}	m}
m} ddlm} ddlm}m	}m
}m}m}m}m} |r	  |             n# t          $ r Y nw xY wg }t!                      }t!                      }t!                      |ddd	de|fd}dffd~dg~ fd} |
            }t#          |          }d |D             |d<    |            |d<   d|vrddlm}  |            |d<   d|vrj                            d          sDj                            d          s*                                                                 dk    rddlm} ddlm}                                                                  dk    }j                            d          p	|rrndpd}	  |j                            dd          |d          } n# |$ r g } Y nw xY w| s|r|r|g} | |d<   ddlm}! ddlm}" |	                                D ]\  }#}$|"                    |#          }%|%r|%|#k    r|%|!v r(|$|v r-|                    |$          }&t;          |&t"                    sX|                    |#          }'|'r|'j        d k    r{|'r|'j        rtA          |'j                  }(n,|&                    d!g           }(t;          |(t@                    stC          fd"|(D                       })|)sO	 dd#lm"}*  |*            }+|+r+|+                    d$i                               |#          rd%})n# t          $ r Y nw xY w|)s5 ||#          },|,s&|                    |#g           },|#|v r ||#|,          },tG          |,          }-|#tH          v r|,}.n|
|,d|         n|,}.|#}/ ||$          }0|0r|0j%        n|$}1|&                    |/|1|/ k    p|$ k    d&|.|-d'd(           |'                    |/                                           |'                    |$            ||/           dd)lm(}2 ddlm}3 d* |	                                D             }4|2                                D ]~\  }5}6|5                                |v r|4                    |5|5          }7|7                                |v rJd&})|6j        d+k    r ||7          })n'|6j)        r tC          fd,|6j)        D                       })|)sT|6j        d k    rI|5|7fD ]D}8|3                    |8          }9|9r+|9j        r$tC          fd-|9j        D                       rd%}) nE|)sg	 dd#lm"}*  |*            }+|+                    d.i           }:|+r
|5|:v s|7|:v rd%})n3# t          $ r&};tT          +                    d/|5|;           Y d};~;nd};~;ww xY w|)s\	 dd0l,m-}<  |<|7          }=|=.                                rd%})n3# t          $ r&};tT          +                    d1|7|;           Y d};~;nd};~;ww xY w|)s|7d2k    r	 dd3l/m0}>m1}?  |?            }@ |>            }A|@r|@                    d4          s|Ar|A                    d4          rd%})n2# t          $ r%};tT          +                    d5|;           Y d};~;nd};~;ww xY w|)s3|7d6v r ||7          },n|6j        d+k    r{	  ||7          }B|Br|Bn+|                    |7g           p|                    |5g           },n;# t          $ r0 |                    |7g           p|                    |5g           },Y nw xY w|7dk    r|                    dg           },	 dd7lm2}Cm3}Dm4}Em5}F dd8lm6}G  |Cd          pi }Hd}I	  |Gd          pi }J|J                    d9d          pd}In# t          $ r d}IY nw xY w |D|:          r |E|,|H|I          \  },}Kn |F|,|H|I          \  },}KnY# t          $ r Y nMw xY w ||7          },|,s<|                    |7g           p|                    |5g           },|7|v r ||7|,          },tG          |,          }-|7tH          v r|,}.n|
|,d|         n|,}.|&                    |7to          |7          |7 k    p|5 k    d&|.|-d;d(           |'                    |5                                           |'                    |7                                            ||7           	 dd<lm8}L n# tr          $ r g }LY nw xY w|LD ]A}M|Mj:                                        |v r|3                    |Mj:                  }Nd&}O|Nr'|Nj        r tC          fd=|Nj        D                       }O|OsE	 dd#lm"}*  |*            }P|P                    d.i           }Q|Pr|Mj:        |Qv rd%}On# t          $ r Y nw xY w|Os>	 dd0l,m-}<  |<|Mj:                  }R|R.                                rd%}On# t          $ r Y nw xY w|Os'|Nr%tw          |Nd>d          d+k    r ||Mj:                  }O|Os|Nrqtw          |Nd>d          d+k    r\	  ||Mj:                  }B|Br|Bn|                    |Mj:        g           }SnX# t          $ r |                    |Mj:        g           }SY n1w xY w ||Mj:                  }S|Ss|                    |Mj:        g           }StG          |S          }T|
|Sd|         n|S}U|&                    |Mj:        |Mj<        |Mj:         k    d&|U|Td?d(           |'                    |Mj:                                                    ||Mj:                   Ct!                      }V|rt;          |t"                    r|                                D ]\  }W}Xt;          |Xt"                    s|W                                |v r3|X                    d@d          p|W}1|X                    dAd          p-|X                    dBd          p|X                    dd          pd}Y|X                    dCd          p|X                    dDd          }Zg }[|Zr|[&                    |Z           t{          |X                    dEg                     D ]}\|\|[vr|[&                    |\           |[s_t}          |Y                                                                          }]dF|]v r(|                    dG          pg }^|^rtA          |^          }[t}          |X                    d d          pd                                          }_|_sht}          |X                    dHd          pd                                          }`|`r-j                            |`d                                          nd}_|X                    dId%          }at;          |at|                    r|a                                dJv}at          |[          }b|o"t          |Y          o|aot          |_          p|b }c|cr9	 ddKlm@}d  |d|_|Yt          |X          pdL          }e|er|e}[n# t          $ r Y nw xY w|&                    |W|1|W k    d%|[|[rtG          |[          nddM|YdN           |'                    |W                                           |'                    t          |1                                                     t}          |1                                                                          t}          |Y                                          C                    dO                                          f}f|fd         r|fdP         r|V'                    |f           t}           pd                                                                          }g|gdQk    rrdQ|vrtC          fdR|pg D                       s|r|gng }h|&                    dQdSd%d%|
|hd|         n|htG          |h          dTt}                                                    C                    dO          dN           |'                    dQ           |rt;          |t@                    rddUlDmE}i  |i            }j|D ]j}kt;          |kt"                    s|k                    d@          pd                                }l|k                    dAd          p-|k                    dd          p|k                    dBd          pd                                C                    dO          }Y|lr|Ys|k                    d           pd                                }m|k                    dH          pd                                }`|mp0|`r-j                            |`d                                          nd}_t}          |k                    dV          p|k                    dW          pd                                                                          }n|mr|mn|`rdX|` nd}o|k                    dId%          }at;          |at|                    r|a                                dJv}at          |k          }pt          t          |p                                                    }q|Y|o|n|qf}r|r|jvr[|l}1dYD ]5}s|s|1v r/|1H                    |s          d                                         }1 n6|1s|l}1t          |1          }/|/|1|Y|_g |a|pdZ|j|r<   n5|_r&|j|r                             d           s|_|j|r         d <   |asd&|j|r         dI<   |k                    dD          pd                                }Z|Zr1|Z|j|r         dE         vr!|j|r         dE         &                    |Z           t{          |k                    dEi                     D ]3}\|\|j|r         dE         vr!|j|r         dE         &                    |\           4lt!                      }tt}          pd                                          C                    dO                                          }t          }fd[|jJ                                D                       }u|jJ                                D ]}v|vd\         }Y|v                    d d          }_|vd
         }/|/                                |v r|/                                |tvrV|/                                |tv rK|/}wd]}x|w d^|x                                 |v r |xdPz  }x|w d^|x                                 |v  |w d^|x }/|/|vd
<   t}          |vd@                                                                                   t}          |vd\                                                   C                    dO                                          f}y|yd         r|ydP         r|y|Vv rR|ydP         }z|zr|z|v rb|o<t          |Y          o-t          |_          p|vdE          o|v                    dId%          }c|crT	 ddKlm@}d  |d|_|Y|v                    d_          pdL          }e|er|e|vdE<   tG          |e          |vd`<   n# t          $ r Y nw xY w|&                    |/|vd@         |/ k    p  dQk    ot          }          o|z}k    o|udPk    d%|vdE         tG          |vdE                   dM|vd\         dN           |'                    |/                                           |t'                    |/                                           |rg|D ]d}{|{                    da          s|{                    dE          pg }h||hvr1|g|h|{dE<   |{                    d`tG          |h                    dPz   |{d`<    |K                    db c           |S )hu  Detect which providers have credentials and list their curated models.

    Uses the curated model lists from hermes_cli/models.py (OPENROUTER_MODELS,
    _PROVIDER_MODELS) — NOT the full models.dev catalog.  These are hand-picked
    agentic models that work well as agent backends.

    Returns a list of dicts, each with:
      - slug: str — the --provider value to use
      - name: str — display name
      - is_current: bool
      - is_user_defined: bool
      - models: list[str] — curated model IDs (up to max_models)
      - total_models: int — total curated count
      - source: str — "built-in", "models.dev", "user-config"

    Only includes providers that have API keys set or are user-defined endpoints.
    ``force_fresh_nous_tier`` bypasses the short Nous tier cache for explicit
    account-sensitive flows. UI picker opens should leave it false so they do
    not block on fresh Portal/account checks every time.

    ``refresh`` busts the per-provider model-id disk cache
    (``provider_models_cache.json``) up front so every row re-fetches its
    live catalog. Use for an explicit user-triggered "refresh models" action
    (e.g. the desktop picker's refresh control); leave false for normal picker
    opens so they stay snappy on the 1h cache.

    ``probe_custom_providers`` controls live ``/models`` discovery for saved
    custom OpenAI-compatible endpoints. Keep the default true for CLI parity;
    GUI picker opens can pass false to show configured models immediately
    without waiting on offline local endpoints.
    r   N)PROVIDER_TO_MODELS_DEVfetch_models_devget_provider_infoPROVIDER_REGISTRY)OPENROUTER_MODELSr   _MODELS_DEV_PREFERRED_merge_with_models_devcached_provider_model_idsclear_provider_models_cacheget_curated_nous_model_idsurlr!   r   c                    t          | pd                                                              d                                          S )Nr9   r   r!   r"   r   r#   )r  s    r*   	_norm_urlz/list_authenticated_providers.<locals>._norm_url  s8    39"~~##%%,,S1177999r,   r   r   c                H   	 ddl m} n# t          $ r Y dS w xY w|                    |           }|sdS d}t	          |dd          r"j                            |j        d          pd}|st	          |dd          pd} |          }|r                    |           dS dS )a	  Record the effective base URL for a built-in provider row.

        Prefers the live env-override (e.g. DASHSCOPE_BASE_URL) over the
        static inference_base_url so the dedup matches what a user typing
        that URL into custom_providers would actually hit.r   r  Nr9   base_url_env_varinference_base_url)hermes_cli.authr  r   r3   getattrrY  r  r$   )r   _regpcfgr  normed_builtin_endpointsr  rW  s        r*   _record_builtin_endpointz>list_authenticated_providers.<locals>._record_builtin_endpoint  s    	AAAAAAA 	 	 	FF	xx~~ 	F4+R00 	B*..!6;;ArC 	@$ 4b99?RC3 	+""6*****	+ 	+s   
 
rG   c                 P    j                             dd                                          rdS  j                             dd                                          r/ j                             dd                                          rdS t           fddD                       S )aT  Return True when explicit AWS auth config is present.

        This intentionally avoids botocore's full credential chain. Provider
        picker/model-switch discovery can run for non-Bedrock providers, and
        botocore may otherwise probe EC2 IMDS (169.254.169.254) on local
        machines before returning no credentials.
        AWS_BEARER_TOKEN_BEDROCKr9   TAWS_ACCESS_KEY_IDAWS_SECRET_ACCESS_KEYc              3  r   K   | ]1}j                             |d                                           V  2dS )r9   N)rY  r3   r"   )r   r.   rW  s     r*   r   zQlist_authenticated_providers.<locals>._has_fast_aws_sdk_signal.<locals>.<genexpr>  sS       
 
 JNN4$$**,,
 
 
 
 
 
r,   )AWS_PROFILE&AWS_CONTAINER_CREDENTIALS_RELATIVE_URI"AWS_CONTAINER_CREDENTIALS_FULL_URIAWS_WEB_IDENTITY_TOKEN_FILE)rY  r3   r"   any)rW  s   r*   _has_fast_aws_sdk_signalz>list_authenticated_providers.<locals>._has_fast_aws_sdk_signal  s     :>>4b99??AA 	4JNN.3399;;	
6;;AACC	 4 
 
 
 

 
 
 
 
 	
r,   c                ^   t          | pd                                                                          }t          pd                                                                          }             rdS ||k    rdS 	 ddlm} t           |                      S # t          $ r Y dS w xY w)z@Credential check for AWS SDK providers in non-runtime discovery.r9   TFr   )has_aws_credentials)r!   r"   r#   agent.bedrock_adapterr  rG   r   )r   	slug_normcurrent_normr  r  r   s       r*   _has_aws_sdk_creds_for_listingzDlist_authenticated_providers.<locals>._has_aws_sdk_creds_for_listing'  s    
OO))++1133	+1r2288::@@BB##%% 	4$$5	AAAAAA++--... 	 	 	55	s   B 
B,+B,c                    g | ]\  }}|S r=   r=   r   r   rh  s      r*   r   z0list_authenticated_providers.<locals>.<listcomp>9  s    AAAVS!SAAAr,   r  r	  zollama-cloud)fetch_ollama_cloud_modelslmstudio
LM_API_KEYLM_BASE_URL)fetch_lmstudio_models)	AuthErrorzhttp://127.0.0.1:1234/v1r9   g      ?)r>   rB   timeoutr1  r3  r>   envc              3  L   K   | ]}j                             |          V  d S r   rY  r3   r   evrW  s     r*   r   z/list_authenticated_providers.<locals>.<genexpr>  s1      >>r
r**>>>>>>r,   )_load_auth_storecredential_poolTFzbuilt-in)r   r.   
is_currentis_user_definedr  total_modelsrD   )HERMES_OVERLAYSc                    i | ]\  }}||	S r=   r=   )r   kvs      r*   
<dictcomp>z0list_authenticated_providers.<locals>.<dictcomp>  s    GGG1q!GGGr,   aws_sdkc              3  L   K   | ]}j                             |          V  d S r   r  r  s     r*   r   z/list_authenticated_providers.<locals>.<genexpr>  s1      PP2BJNN2..PPPPPPr,   c              3  L   K   | ]}j                             |          V  d S r   r  r  s     r*   r   z/list_authenticated_providers.<locals>.<genexpr>  s1      NN"2:>>"--NNNNNNr,   r  z"Auth store check failed for %s: %s)	load_poolz'Credential pool check failed for %s: %sr[   )read_claude_code_credentialsread_hermes_oauth_credentialsaccessTokenz)Anthropic external creds check failed: %s>   copilot-acpopenai-codexrM  )get_pricing_for_providercheck_nous_free_tier&union_with_portal_free_recommendations&union_with_portal_paid_recommendations)get_provider_auth_stateportal_base_url)force_freshhermes)CANONICAL_PROVIDERSc              3  L   K   | ]}j                             |          V  d S r   r  r  s     r*   r   z/list_authenticated_providers.<locals>.<genexpr>l  s1      YYr
r 2 2YYYYYYr,   rC   	canonicalr.   rB   apir  ry   r  zapi.openai.comr`   rB  discover_models>   0nofalse)fetch_api_models)headerszuser-config)r   r.   r  r  r  r  rD   api_urlr   r   r:   c              3    K   | ]}t          |t                    ot          |                    d d          p+|                    dd          p|                    dd                                                                        d                                          t                                                                        d                                          k    V  dS )rB   r9   r  r  r   N)r    r0   r!   r3   r"   r   r#   )r   _cpr6   s     r*   r   z/list_authenticated_providers.<locals>.<genexpr>  s       	
 	
  sD!! A
B'' &775"%%&775"%%  eggffSkk%%''#$$**,,33C88>>@@A	
 	
 	
 	
 	
 	
r,   r;   r?   )OrderedDictr   r@   zenv:)u   —z - )r   r.   r  r>   r  r  r  c              3     K   | ]X}rTt          |d                                                                        d                                          k    TdV  YdS )r  r   r   Nr  )r   _grp_current_base_url_norms     r*   r   z/list_authenticated_providers.<locals>.<genexpr>  sz       ,
 ,
%,
 DO$$**,,33C88>>@@DZZZ  [ZZZ	,
 ,
r,   r  r   r   r  r  r  c                &    | d          | d          fS )Nr  r  r=   )rs    r*   r   z.list_authenticated_providers.<locals>.<lambda>	  s    AlO 3a6G5GH r,   r   )r  r!   r   r!   )r   r!   r   r   )r   rG   )r   r!   r   rG   )LrW  agent.models_devr  r  r  r  r  r   r  r   r  r  r  r  r  r   r/   r0   r  rY  r3   r"   r#   r  r  r2  rR  r4  r   r    rC   rA   r1   r  r  r   r   r.   r%   r$   r  extra_env_varsrS  rT  agent.credential_poolr  has_credentialsagent.anthropic_adapterr  r  r  r  r  r  r  r   r  ImportErrorr   r  labelr5   r!   rG   r  r  r
   r   collectionsr  r2   rV  r   sumvaluesr   )r   r6   r   r   r  r   r#  r  r  r  r  _mdev_pinfor  r  r   r  r  r  r  r  results
seen_slugsseen_mdev_idsr  r  datacuratedr  r  r  is_current_lmstudiolm_baseliver`  ra  	hermes_idmdev_idrb  pdatapconfigenv_vars	has_credsr  store	model_idstotaltopr   pinfodisplay_namer  _auth_registry_mdev_to_hermespidoverlayhermes_slug_keyr  providers_storeexcr  poolr  r  hermes_credscc_creds_ids_nous_pricing
_nous_free_union_free_union_paid_nous_state_pricing_portal_strh  _canon_provsr  
_cp_config_cp_has_creds	_cp_store_cp_providers_store_cp_pool_cp_model_ids	_cp_total_cp_top_section3_emitted_pairsep_nameep_cfgr  r  models_listr&   	url_lowerfbr>   rB  discoverhas_explicit_modelsshould_prober  live_models_pair_current_provider_norm_modelsr  groupsr   raw_nameinline_api_keyr   credential_identityentry_extra_headersheaders_identity	group_keysep_section4_emitted_slugs_current_base_url_group_countgrp	base_slugr   	_pair_key_grp_url_norm_rowr  r  r  r  rW  s   ``                                                                                                                          @@@@@r*   r  r    s   V III         
 211111                   	'')))) 	 	 	D	 GeeJM "ee: : : :+ + + + + + + +,
 
 
 
 
 
2       D %))9$:$:GAA/@AAAGL 1022GFOW$$??????";";"="=   

|$$ !(*
}(E(E !IYI_I_IaIaIgIgIiIimwIwIw;;;;;;------.4466<<>>*LJNN=)) *$7V<LV  RV*) 	
	((
|R88   DD
  	 	 	DDD	 	#+ 	# 	#!?D"
 JIIIIIEEEEEE4::<< S' S'	7 .11)<<	**// m##!!%&& 	
 $''	22  	w(I55 	w/ 	G455HHyy++Hh--  >>>>X>>>>>	 	<<<<<<((** %UYY'8"==AA)LL % $I    	 .-i88	 	IIr22I111229iHH	I222CC,6,B)KZK((	CG$$%*7uzz "22QgAQ6Q$! 
 
 	 	 	 	tzz||$$$'"""  &&&& 544444CCCCCC
 HG(>(D(D(F(FGGGO'--// W. W.W99;;*$$ &))#s33*,, 		))66{CCII# 	QPPPP9OPPPPPI 	W.);;k*  %))$// D1 NNNN8MNNNNN $(	
  	MM<<<<<<((**"'))K"<"< %c_448V8V $I M M MA3LLLLLLLLM  	ZZ;;;;;; y--'')) % $I Z Z ZFUXYYYYYYYYZ  	O[K77O         =<>>7799  %\%5%5m%D%D %%!)m!<!<% $I O O OH#NNNNNNNNO 	DDD 21+>>II )++Q00==$(dDDw{{;/K/K/cw{{[^`bOcOc		 Q Q Q#KKR88PGKKR<P<P			QF""  FB//I            SRRRRR(=006B!%+f--3C!gg&7<<BGG  ! ! ! GGG!:*?@@@ M#.;y(G#L#LLIqq#.;y(G#L#LLIq    	 21+>>I O#KKR88PGKKR<P<P	"777 6 6{I N NII444CC,6,B)KZK((	Ck**%)99TSDT=T$!
 
 	 	 	 	syy{{###{((**+++  ----IIIIIII     ?+ ?+8>>z)) $''11
 	Z*5 	ZYYYYZ=XYYYYYM 	<<<<<<,,..	&/mmK&D&D# )-@!@!@$(M    	;;;;;;$9SX..++-- )$(M     	E 	E
KQS0T0TXa0a0a::38DDM 	  
	:'*k2>>)KK:00::(, K'++ch2K2K : : : 'CHb 9 9: 65ch??M  : 'CHb 9 9&&	0:0F-,,MHI(&66$%!
 
 	 	 	 	sx~~''(((  **** $'55 \3*^T:: \3-3355 [	3 [	3OGVfd++  }}*,,!::fb11<WL
 

:r** ::eR((::eR(( 	  #JJ;;Vvzz'SU?V?VM K 2""=111 0

8R0H0HII 1 1;..&&x000  /LL..006688	#y00 X..4"B /&*2hh &**Y339r::@@BBG QfjjB77=2>>DDFFAHP"*.."55;;===bzz"3T::H(C(( H#>>++3GG"&{"3"31 d7mm  W8%8!8   
BBBBBB"2"2 :6 B B Jd# # #K
 # 2&1    D NN$%)99#'%4? FK 0 0 0Q'"	 	 	 	 	 NN7==??+++NN/==CCEEFFFL!!''))//11G""$$++C006688E Qx 3E!H 3'++E222 !!1!7R88>>@@FFHH(** 	+J&& 	
 	
 	
 	
 ).B	
 	
 	
 	
 	
 ' &3:=//%#.8.Dgkzk**'LL$+,,2244;;C@@	
 	
 		 		 		 	x     Q6J'7>> Q6++++++ .9[]]% Y	A Y	AEeT** 		&))/R6688H		*b)) 99UB''99UB'' eggffSkk   7 #ii	228b??AANyy++1r88::G$ 7>F
w++11333B  		*%% 99[))  eggeegg	  ";*19&W&&&r   yy!2D99H(C(( H#>>++3GG #=U"C"C$V,?,E,E,G,G%H%HII "5xAQRI&&
  ()  Cl**'3'9'9#'>'>q'A'G'G'I'I + $ ,#+L+L99 (&& '/%8% %y!!  ;6)#4#8#8#C#C ;3:F9%i0
   A;@F9%&78 #YYw//52<<>>M BfY6G6Q!Q!Qy!(+22=AAA/		(B0G0GHH A A6)#4X#>>>9%h/66x@@@A (+uu!$%5%;!<!<!B!B!D!D!K!KC!P!P!V!V!X!X(+ ,
 ,
 ,
 ,
,
 ,
 ,
 )
 )
% ==?? e	6 e	6C)nGggi,,Gv;D zz||z))djjllBY.Y.Y
 zz||666 	"((Q((..00J>>FA #((Q((..00J>>#))a))"F CK  &&((..00C	N##))++22377==??I | 	! >U1U1U &aLM 2D!D!D6 ' 5MM5']]7#h-&75 GG-t44	   BBBBBB"2"2 # 8 8 @D# # #K
 # ?(3H.1+.>.>N+    DNNF"&66 $0 ;344;%)??; 6:#'h- #CM 2 2'y>     NN4::<<(((#''

5555   	 	D88L)) hhx((.BGG++"/!:'!:X'+xxG'M'MPQ'Q^$ LLHHLIIINs7  
< 
A	A	'G GG =L
L+*L+72U**
V4VV 'W
W8W33W8AY
Z Y;;Z $;[!!6\\;!_%^_^_^0_
__c c+*c+3f
ff,g
gg /i%i87i8;'x##
x0/x0XAAYY
AY&Y%AY&r  c                r    	 ddl m}  ||          }|| S |gd | D             z   S # t          $ r | cY S w xY w)a  Add the virtual MoA provider row used by interactive model pickers.

    ``list_authenticated_providers()`` only returns real/auth-backed providers.
    The CLI model inventory adds MoA separately so named presets appear next to
    normal providers; gateway pickers call ``list_picker_providers()`` directly,
    so they need the same virtual row here. Reuse the inventory's single row
    builder so the row shape stays defined in one place.
    r   )_moa_provider_rowNc                    g | ]=}t          |                    d d                                                    dk    ;|>S )r   r9   r-  )r!   r3   r#   r  s     r*   r   z0_prepend_moa_picker_provider.<locals>.<listcomp>3	  sE    \\\!Cfb8I8I4J4J4P4P4R4RV[4[4[A4[4[4[r,   )r  rc  r   )r  r   rc  moa_rows       r*   _prepend_moa_picker_providerrf  $	  sx    ::::::##$455?y\\y\\\\\   s   ' ' 66include_moac                   ddl m} t          | |||||          }|rt          ||           }g }	|D ]<}
t	          |
                    dd                                                    }|dk    r}	  |            }d |D             }n3# t          $ r& t          |
                    d	g                     }Y nw xY wt          |
          }
|
|d
|         n||
d	<   t          |          |
d<   t          |
                    d	                    }t          |
                    d                    o!t          |
                    d                    }|s|s'|	                    |
           >|	S )a  Interactive-picker variant of :func:`list_authenticated_providers`.

    Post-processes the base list so the ``/model`` picker (Telegram/Discord
    inline keyboards) only surfaces models that are actually callable in the
    current install:

    - OpenRouter's model list is replaced with the output of
      :func:`hermes_cli.models.fetch_openrouter_models`, which filters the
      curated ``OPENROUTER_MODELS`` snapshot against the live OpenRouter
      catalog.  IDs the live catalog no longer carries drop out, so the
      picker never offers a model the user can't call.
    - Provider rows whose model list ends up empty are dropped, except
      custom endpoints (``is_user_defined=True`` with an ``api_url``) where
      the user may supply their own model set through config.

    All other providers and metadata fields are passed through unchanged.
    The typed ``/model <name>`` path is unaffected -- only the interactive
    picker payload is narrowed.
    r   )fetch_openrouter_models)r   r6   r   r   r   r#  )r   r   r9   r  c                    g | ]\  }}|S r=   r=   r  s      r*   r   z)list_picker_providers.<locals>.<listcomp>g	  s    333FCC333r,   r  Nr  r  r  )r   ri  r  rf  r!   r3   r#   r   r1   r0   r   rG   r%   )r   r6   r   r   r   r#  rg  ri  r  r   r  r   r  live_ids
has_modelsis_custom_endpoints                   r*   list_picker_providersrn  8	  s   8 :99999,))%)#  I  _0M]^^^	H  155$$%%++--<5..0033d333 5 5 5h 3 3445QA3=3I(;J;//xAhK #HAn!%%//**
!!%%(9":":;;VQUU9EUEU@V@V 	"4 	Os   2B		-B98B9)r   r   r   r   )r6   r!   r   r7   )rF   r!   r   rG   )rF   r!   r   r!   )r   r{   r  )r   r!   r   r   )r   rG   r   rG   r   rG   )r&   r!   r   r!   r   r2   )r   r!   r   r!   r   r   )r9   NN)r   r!   r   r0   r   r   r   r   )r=   )r   r!   r  r   r   r   )r9   r9   NNN)ry   r!   rz   r!   rB   r!   r>   r!   r   r   r   r   r  r  r   r  )rF   r!   r   r  r   r  r   r  )r9   r9   Fr9   NN)r   r!   r   r!   r#  r!   r6   r!   r$  r!   r   rG   r   r!   r   r0   r   r   r   r   )r   r   r   r  )r   r  )r9   r9   NN)r   r!   r6   r!   r   r0   r   r   r  rG   r   r  r#  r!   r  rG   r  rG   r   r  )r9   )r  r  r   r!   r   r  )r9   r9   NNNr9   F)r   r!   r6   r!   r   r0   r   r   r   r  r#  r!   rg  rG   r   r  )CrX   
__future__r   loggingr   dataclassesr   typingr   r   r   r   rR  r	   r
   r   r   r   r   hermes_cli.model_normalizer   r
  r   r   r   r   r   	frozensetr   rY   	getLoggerrU   rS  r5   rE   rN   compile
IGNORECASErI   rL   rO   rQ   rv   rx   r|   r}   r   r   r   r   r   r   r   r  r  r  r"  r  	threadingr  Eventr  r  r  r  rf  rn  r=   r,   r*   <module>rz     s    ( # " " " " "  				 ! ! ! ! ! ! 2 2 2 2 2 2 2 2 2 2 2 2                                  .7Y7V-W-W  W W W W		8	$	$- - - -`   ,-   )bj-M  	@ 	@ 	@ 	@       J   1+{O<<1+ {M::1+ {N;;	1+
 {H551+ x111+ 
x//1+ x111+ 	x..1+ 	x..1+ x221+$ z?;;%1+* vv..+1+0 |W5511+6 vv..71+< y)44=1+B x44C1+H |V44I1+N vu-- y&11 x00 z955a1+ 1+ 1+ 1 1 1 1x    *    35  4 4 4 4 *, + + + +@ @ @ @F	6 	6 	6 	6         *:R :R :R :Rz   Dh/ h/ h/ h/VJ/ J/ J/ J/\ $(    0 *,    ( &*$((,* * * * *d= = = =P $(J	 J	 J	 J	 J	h    'z')) ? ? ? ?, , , ,` $(	z #(!#'z z z z z zz    * $(!< < < < < < <r,   