
    `gj             
         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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!dcdZ"	 	 	 	 	 	 dddZ#dedZ$dZ% ejL                  dejN                        Z(dZ)ded<   dfdZ*dgdZ+dfd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+d,      d- e-d.d-      d/ e-d0d/      d1 e-d1d1      d2 e-d2d2      d3 e-d4d3      d5 e-d6d5       e-d7d8       e-d9d:       e-d;d<       e-d=d>      d?Z.d@edA<    G dB dCe
      Z/i Z0dDedE<   i Z1dDedF<   dhdGZ2didHZ3e G dI dJ             Z4 edKL       G dM dN             Z5djdOZ6dkdPZ7	 	 dl	 	 	 	 	 	 	 	 	 dmdSZ8dndTZ9	 	 	 	 	 	 dodUZ:	 	 	 dp	 	 	 	 	 	 	 dqdVZ;	 dr	 	 	 	 	 dsdWZ<	 	 	 	 	 dt	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 dudXZ=	 	 	 	 	 	 	 	 dvdYZ>	 	 	 	 	 	 dw	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 dxdZZ?ddl@ZA eAj                         ZCdQd[dyd\ZDdzd]ZEd{d^ZF	 	 	 	 d|dQddRdQdKdQdQdd_	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 d}d`ZGd~ddaZH	 	 	 	 	 	 	 	 d	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 ddbZIy)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host_mandated_api_mode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_PROVIDERSc                   g t               dfd}t        | t              r
 ||        S t        | t              r| D ]
  } ||        S t        | t        t
        f      r}| D ]v  }t        |t              r	 ||       t        |t              s-|j                  d      }t        |t              r|j                         s|j                  d      } ||       x 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"``
    c                    t        | t              sy | j                         }|sy |j                         }|v ry j	                  |       j                  |       y N)
isinstancestrstriploweraddappend)	candidatemodel_idloweredidsseens      J/root/.hermes/venv/lib/python3.12/site-packages/hermes_cli/model_switch.py_addz!_declared_model_ids.<locals>._addD   sP    )S)??$.."d?

8    idname)r"   r   returnNone)setr   r   dictlisttuplegetr   )valuer(   r#   itemr%   r&   s       @@r'   _declared_model_idsr5   8   s     CUD
 %U
% 	HN	
%$' 	D$$T
$%88D>!(C08H#xx/HX	 
Jr)   c                   | r|sy	 ddl m}m}  |       }|j                  d      xs g }t	        |t
              sy| j                         j                  d      j                         }d}|D ]  }t	        |t              s|j                  dd      xs |j                  d	d      xs dj                         }	|	j                  d      j                         |k7  ro|j                  d
      }
t	        |
t              rt	        |
t
              rt        d |
D              rt	        |
t
              r|
|k(  r||d
<   d} |r||d<    ||       yy# t        $ r Y yw xY w)u  Persist discovered models into ``custom_providers`` in config.yaml.

    Called after a successful ``/v1/models`` probe so that the next read
    with ``discover_models: false`` uses the cached list instead of a stale
    or minimal manually-configured subset.

    Matches entries by ``base_url`` (trailing-slash-normalised).  A failed
    config write is swallowed — the picker still shows the live models for
    this session.
    Nr   )load_configsave_configcustom_providers/Fbase_url urlmodelsc              3  <   K   | ]  }t        |t                y wr   )r   r/   ).0ms     r'   	<genexpr>z4_save_discovered_models_to_config.<locals>.<genexpr>   s      2()
1d#2   T)hermes_cli.configr7   r8   r2   r   r0   r   rstripr   r/   any	Exception)api_url	model_idsr7   r8   cfg	providersnorm_urlchangedentry	entry_urlexistings              r'   !_save_discovered_models_to_configrQ   h   s^    )&>mGG./52	)T*==?))#.446 	EeT*:r2Peiir6JPbWWYI$**,8yy*H
 (D)(D)c 2-52 /  (D)h).C'E(OG-	0 &/C"#   s   4E DE 	EEc           
     h    t        | xs d      j                         }|syt        dddd|ddd	
      S )z=ProviderDef for a direct ``model.provider: custom`` endpoint.r<   NcustomCustom endpointopenai_chat Fapi_keymodel-config)r*   r+   	transportapi_key_env_varsr;   r   	auth_typesource)r   r   r	   )current_base_urlr;   s     r'   _bare_custom_provider_defr^      sF    #)r*002H	 	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](?:[-_.:]|$))z*ri.language-model-service..language-model.ztuple[str, ...]_OPAQUE_MODEL_PREFIXESc                v    | s| S t         D ]+  }| j                  |      s| t        |      d }|r|c S | c S  | S )u\  Return a human-friendly form of *model_name* for CLI status output.

    Strips known opaque proxy prefixes (Palantir Foundry's
    ``ri.language-model-service..language-model.*``) and returns the
    trailing slug. Falls through to the original string for everything
    else, so real model IDs (``claude-4-7-opus-20260101``,
    ``gpt-5-4``, ``meta-llama/Llama-3.3-70B-Instruct``) are untouched.

    This is a DISPLAY-ONLY helper. Do NOT use the return value for any
    wire-side operation — the proxy expects the full opaque ID, and
    callers that compare or persist must keep the original.
    N)r_   
startswithlen)
model_nameprefixtails      r'   format_model_for_displayrf      sO     ( 0  (c&kl+D4/Z/0 r)   c                D    | syt        t        j                  |             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)bool_NOUS_HERMES_NON_AGENTIC_REsearchrc   s    r'   is_nous_hermes_non_agenticrl      s!     +22:>??r)   c                &    t        |       rt        S y)zHReturn a warning string if *model_name* is a Nous Hermes 3/4 chat model.r<   )rl   _HERMES_MODEL_WARNINGrk   s    r'   _check_hermes_model_warningro     s    !*-$$r)   c                  &    e Zd ZU dZded<   ded<   y)ModelIdentityz:Vendor slug and family prefix used for catalog resolution.r   vendorfamilyN__name__
__module____qualname____doc____annotations__rV   r)   r'   rq   rq     s    DKKr)   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)r   r   r   r   zdict[str, ModelIdentity]MODEL_ALIASESc                  0    e Zd ZU dZded<   ded<   ded<   y)DirectAliasz5Exact model mapping that bypasses catalog resolution.r   modelproviderr;   Nrt   rV   r)   r'   r   r   R  s    ?JMMr)   r   dict[str, DirectAlias]_BUILTIN_DIRECT_ALIASESDIRECT_ALIASESc                    t        t              } 	 ddlm}  |       }|j	                  d      }t        |t               r|j                         D ]|  \  }}t        |t               s|j	                  dd      }|j	                  dd      }|j	                  dd      }|sPt        |||	      | |j                         j                         <   ~ |j	                  di       }	t        |	t               r|	j	                  d
      }
t        |
t               r|	j	                  dd      }|
j                         D ]  \  }}t        |t              r|j                         s'|j                         j                         }|| v rJ|j                         }d|v r|j                  dd      \  }}n|}|}t        |j                         |j                         xs |d	      | |<    | S # t        $ r Y | S w xY w)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   r7   model_aliasesr   r<   r   rS   r;   )r   r   r;   aliasesr:      )r/   r   rD   r7   r2   r   itemsr   r   r   r   splitrG   )mergedr7   rJ   user_aliasesr+   rN   r   r   r;   model_sectionsimple_aliasescurrent_providerr3   keyvals                  r'   _load_direct_aliasesr   `  s   ( )*F*1m ww/lD)+113 	e!%.		'2. 99Z: 99Z43>#h4F4::<--/0	 ,mT**..y9N.$/#0#4#4Z#D #1#7#7#9 KD%%eS1 **,,,.Cf} ++-Ccz*-))C*;%#3 #"-#kkm!)!1!E5E!##F3K& M  Ms   BG  D?G   	G-,G-c                 L    t         st         j                  t                      yy)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   rV   r)   r'   _ensure_direct_aliasesr     s     245 r)   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<   y)ModelSwitchResultz!Result of a model switch attempt.rh   successr<   r   	new_modeltarget_providerFprovider_changedrW   r;   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   rW   r;   r   r   r   r   r   r   r   r   rV   r)   r'   r   r     s    +MIsOS"d"GSHcHcM3OSNC  04L-4&*J#*Itr)   r   T)frozenc                  b    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<   y)ModelFlagParseResultz"Parsed flags for a /model command.r   model_inputr<   explicit_providerFrh   r   force_refresh
is_sessionis_onceN)
ru   rv   rw   rx   ry   r   r   r   r   r   rV   r)   r'   r   r     s>    ,sItM4JGTr)   r   c                   d}d}d}d}d}ddl }|j                  dd|       } | j                         }d}g }	|t        |      k  r||   dk(  rd}|d	z  }np||   d
k(  rd}|d	z  }n`||   dk(  rd}|d	z  }nP||   dk(  rd}|d	z  }n@||   dk(  r|d	z   t        |      k  r||d	z      }|dz  }n|	j	                  ||          |d	z  }|t        |      k  rdj                  |	      j                         }
t        |
|||||      S )aq  Parse flags from /model command args.

    Returns a :class:`ModelFlagParseResult`. ``--once`` is intentionally
    parsed here but interpreted by each caller because each frontend has its
    own live-session restore hook.

    ``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 --once"                  -> is_once=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)
    Fr<   r   Nz@[\u2012\u2013\u2014\u2015](provider|global|session|refresh|once)z--\1z--globalTr   z	--sessionz	--refreshz--oncez
--provider    )r   r   r   r   r   r   )resubr   rb   r!   joinr   r   )raw_argsr   r   r   r   r   _repartsifilteredr   s              r'   parse_model_flags_detailedr     sS   . IMJG wwZ\cemnH NNE	AH
c%j.8z!IFA1X$JFA1X$ MFA1X!GFA1X%!a%#e**< %a!eFAOOE!H%FA% c%j.( ((8$**,K+# r)   c                    t        |       }|j                  |j                  |j                  |j                  |j
                  fS )zParse legacy /model flags and return the historical 5-tuple.

    New call sites that care about ``--once`` should use
    :func:`parse_model_flags_detailed`.
    )r   r   r   r   r   r   )r   parseds     r'   parse_model_flagsr     sE     (1F   r)   Fr<   c                    |ry|ry| ry|ry	 ddl m}  |       j                  d      }t        |t              rt        |j                  dd            S 	 y# t        $ r Y yw xY w)u2  Decide whether a ``/model`` switch should persist to ``config.yaml``.

    Resolution order:

    1. ``--once`` explicitly opts out → ``False`` (next turn only).
    2. ``--session`` explicitly opts out → ``False`` (this session only).
    3. ``--global`` explicitly opts in → ``True``.
    4. ``--provider`` given without an explicit persist flag → ``False``
       (session only).  Provider switches are typically exploratory — the
       user is trying a different backend for this conversation, not
       reconfiguring the default.  ``--global`` can still force persist.
    5. Otherwise defer to ``model.persist_switch_by_default`` in
       ``config.yaml`` (defaults to ``False``: a plain ``/model <name>``
       affects only the current session).  Users who want the old
       persist-by-default behavior can set the key to ``true``; a one-off
       ``--global`` always persists.

    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
    (``False``) applies.
    FTr   r   r   persist_switch_by_default)rD   r7   r2   r   r/   rh   rG   )r   r   r   r   r7   	model_cfgs         r'   resolve_persist_behaviorr   +  sx    6 1M%%g.	i&	&A5IJJ '   s   AA 	A#"A#c                   | t        |      d }|j                  d      r|dd }|j                  d      j                         }g }d}d}d}|D ]4  }|dk(  r,|dv rd}|j	                         rd}||z  }(|d	v r-d
}||z  }5|dk(  r|j	                         r||z  }P|dk(  r7d|v r-	 |j                  t        |j                  d                   d}||z  }|d	v r1|r,	 |j                  t        |j                  d                   d}d}|r,	 |j                  t        |j                  d                   d}d
}||z  }|dk(  r-|j	                         rd}|}|dv rd}|d	v r d
}||z  })|d
k(  s0||z  }7 |r/|dk(  r*	 |j                  t        |j                  d                   |j                         j                  d	      }|j                         }t        d |D              }	dddddd}
|
j                  |d      }|	||fz   S # t        $ r Y Tw xY w# t        $ r Y +w xY w# t        $ r Y 
w xY w# t        $ r Y w xY w)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   -r<   startvV
in_versionz-_.	in_suffix.betweenc              3  "   K   | ]  }|  	 y wr   rV   )r@   ns     r'   rB   z"_model_sort_key.<locals>.<genexpr>  s     )q)s   r   )promaxplusturbosol)rb   ra   lstripr   isdigitr!   floatrE   
ValueErrorr   r1   r2   )r#   rd   restnums
suffix_bufstatenum_bufchsuffixversion_key_SUFFIX_RANKsuffix_ranks               r'   _model_sort_keyr   ]  s     CKL!DsABx;;s!!#D DJEG 6GTz$$2u#b 
l"zz|2s'>E'..*=$>? !GrMGuE'..*=$>? !G!E'..*=$>? !G#b 
izz|$t$u#b 
k!"Jm6r 5L(	KKgnnS123 %%e,F\\^F )D))K QAaHL""61-K+v...w &  &  & .  		sH   ()H!)H))H9)I	 	H&%H&)	H65H69	II		IIc                "   | j                         j                         }t                t        j	                  |      }||j
                  |j                  |fS t        j                         D ]=  \  }}|j                  j                         |k(  s$|j
                  |j                  |fc S  t        j	                  |      }|y|\  }}t        |      }		 ddl
m}
 |
j	                  |g       }|rH|	D ch c]  }|j                          }}|D ]&  }|j                         |vs|	j                  |       ( t        |      }|rD| d| j                         }|	D cg c]#  }|j                         j                  |      r|% }}n>|j                         }|	D cg c]#  }|j                         j                  |      r|% }}|sy|r| d| n||j!                  fd       ||d   |fS c c}w # t        $ r Y w xY wc c}w c c}w )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_MODELSr:   c                    t        |       S r   )r   )rA   prefix_for_sorts    r'   <lambda>zresolve_alias.<locals>.<lambda>  s    q/B r)   r   )r   r   r   r   r2   r   r   r   r   r   hermes_cli.modelsr   r!   rG   r   ra   sort)	raw_inputr   r   direct
alias_namedaidentityrr   rs   catalogr   staticrA   r&   
aggregatorrd   midmatchesfamily_lowerr   s                      @r'   resolve_aliasr    s     //

!
!
#C $Fs33
 )..0 7
B88>>s"KK:667   %HNFF
 ##34G	6!%%&6;'./!AGGI/D/ &779D(NN1%& /0J81VH%++-"
yy{%%f- 
 

 ||~"
yy{%%l3 
 

  /9&*fOLLBLCgaj#..; 0  

s6   G8 5G3G8 &G8 (H(H3G8 8	HHc                t    	 t        | ||d      }|D cg c]  }|d   	 c}S c c}w # 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   user_providersr9   
max_modelsslug)list_authenticated_providersrG   )r   r  r9   rK   ps        r'    get_authenticated_provider_slugsr    sK    	0-)-	
	 $--a&	--- 	s   ) $) ) 77c                B    |xs d}|D ]  }t        | |      }||c S  y)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   authenticated_providersrK   r   results        r'   _resolve_alias_fallbackr  0  s:     (A+AI y(3M r)   c                    	 ddl m}  || |xs d|xs d|xs d||      }|rt        |      S 	 |!|j                  rt        |j                        S y# t        $ r Y /w xY w)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_lengthr<   N)r;   rW   r   r9   config_context_length)agent.model_metadatar  intrG   context_window)	r   r   r;   rW   r   r9   r  r  ctxs	            r'   resolve_display_context_lengthr   A  s~    4A&^Mr%-"7
 s8O  *";";:,,--	  s   ,A 	A A c                r   | r| j                         si S | j                         j                         dfd}i }t        |t              r`|j	                         D ]M  \  }}t        |t
              rt        |t              s'dD ]"  } ||j                  |            }|s|||<    M O t        |t              r{|D ]v  }	t        |	t              s|	j                  d      }
t        |
t
              r|
j                         sFd|
 }||v rPdD ]"  } ||	j                  |            }|s|||<    v x |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.
    c                T    t        |       D ]  }|j                         k(  s|c S  y)z`Canonical id if ``value`` (a model collection or scalar) declares
        ``target``, else None.N)r5   r   )r3   r#   targets     r'   _matchz,_configured_provider_matches.<locals>._match  s2     ,E2 	 H~~6)	  r)   )r>   r   default_modelr+   custom:)r,   zOptional[str])r   r   r   r/   r   r   r2   r0   )rc   r  r9   r$  r	  r  rJ   r   hitrN   r+   r#  s              @r'   _configured_provider_matchesr(  s  s@   * Z--/	%%'F !G.$''--/ 	ID#dC(
30E; SWWS\*$'GDM		 "D)% 	EeT*99V$DdC(

TF#Dw; UYYs^,$'GDM		 Nr)   c	                    ddl m}	m}
m}m} ddlm} d}| j                         }|}d}|r/t        |||      }|,|j                         j                         dk(  rt        |      }|Ed| d	}	 dd
lm}  |       }|r!|dz  }|dd D ]  }|d|j                   z  } t        d||      S |j                   }|dk(  r2|s0	 ddlm} ddlm}  | |       j)                  d      xs i       d   }ddl m} ddlm} |j                         j                         }|j)                  |      }|r||k(  r||k7  r||v r|t1        |||      }||vrj|D cg c]  }|j3                  |      r||k7  r| }}|rddj5                  |       dnd} t        d||j6                  |d| dt9        |       d|        S |s|j:                  rTddlm}!  |!|j:                        }"|"r|"}nat        d||j6                  |d|j6                   d |j:                   d!|       S t        d||j6                  |d|j6                   d"|       S t?        ||      }#|#|#\  }$}}n	 ddlm} dd#lm }%m}  | |       j)                  d      xs i       }& |%|&|       }'|'rd}|'}d}d$}d}#nt?        | |      }#|rn8|# |#\  }}}tB        jE                  d%|||       n| j                         j                         }(|(tF        v rpt1        |||      })tI        | |)      }*|*|*\  }}}tB        jE                  d&|||       ntF        |(   }+t        d|d'|( d(|+jJ                   d)|+jL                   d*      S |s~| jO                  d+      },|,dkD  rhd)| vrdtQ        |      rY| d|, j                         j                         }-| |,d,z   d j                         }.|-r |.r|- d)|. }tB        jE                  d-| |       d}/tQ        |      ry|swtS        |      }0|0rj|j                         }1|0D ]  }2|2j                         |1k(  s|2}d$}/ n; |0D ]5  }2d)|2v s|2jU                  d)d,      \  }$}3|3j                         |1k(  s1|2}d$}/ n d}4|s|/s||k(  rtW        |||      }5|5r||5v r|5|   }d$}4nvtY        |5      }6t[        |6      d,kD  r$t        d|d.| d/dj5                  |6       d0      S |6d   }|5|   }d$}4tB        jE                  d1||       t]        |t^              r||v r|}|xs d}7|d2v xs |j3                  d3      xs
 d4|7v xs d5|7v }8||k(  r|8s|s|/s|4s |
||      }"|"r|"\  }}||k7  }9t9        |      }:|dk(  r|rd6}:|j3                  d3      rt        |||      };|;|;j6                  }:|}<|}=d}>|9s|r1ddl0}?d}@|r8|r6dd7lm1}A  |A|j                         j                         |      }@|@	 A||      }@@@j:                  r|xs i j)                  |j                         j                               xs |xs i j)                  |      xs i }Bte        |Bj)                  d8d      xs d      j                         }C|Cj3                  d9      r>Cjg                  d:      r-|?jh                  j)                  Cd;d< d      j                         }CCsYte        Bj)                  d=d      xs d      j                         }D|Dr*|?jh                  j)                  Dd      j                         }C	  ||Cxs d@j:                  |>      }E|Ej)                  d8d      xs C}<Ej)                  d?d      xs @j:                  }=Ej)                  d@d      }>n|dk(  r|r|}<|}=tk        ||=      }>n	  |||A      }E|Ej)                  d8d      }<|Ej)                  d?d      }=|Ej)                  d@d      }>nA	  |||A      }E|Ej)                  d8d      }<|Ej)                  d?d      }=|Ej)                  d@d      }>|r?tm                tn        j)                  |      }G|GGj:                  rGj:                  }=d}>|<sdD}<tq        |=      }H|HH}>n|>stk        ||=      }>ts        ||      }	  ||||<|=|>xs dE      }IIj)                  dI      s1d}J|rNddJlm:}K |jw                         D ]5  \  }L}M K|M      sL|k(  s|ty        Mj)                  dKi             v s3d$}J n Js|rt]        |tz              r|D ]  }Nt]        |Nt^              sNj)                  dLd      }O|Ord3O nd}PNj)                  d?d      }Q|P|k(  sQ|=k(  sLNj)                  dMd      }R|Nj)                  dKi       }S||Rk(  rd$}J n|ty        S      v sd$}J n Jrd$d$dIj)                  dNd      dH}In#Ij)                  dNdO      }Tt        d|||:||TP      S Ij)                  dQ      rIdQ   }|dRv r
 |	||<S      }>|dTv r	 |||      }>|>stk        ||=      }>|dUv r t]        |=td              rddVl m>}U  |U||>|=      }=t        ||      }Vt        ||      }Wg }XIj)                  dN      rXj                  IdN          t        |      }Y|YrXj                  Y       t        d$|||9|<|=|>XrdWj5                  X      nd|:|VW|X      S # t        $ r Y 
)w xY w# t        $ r d}Y 	w xY wc c}w # t        $ r t?        | |      }#Y Pw xY w# t        $ r C}<@j:                  }=d}>Y 
w xY w# t        $ r }Ft        d||:|dB|: dCF       cY d}F~FS d}F~Fww xY w# t        $ r Y Fw xY w# t        $ r}FddddF| dGF dH}IY d}F~Fd}F~Fww xY w)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_providerr<   FNrS   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  r9   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_namer2  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>   localrS   r&  	localhostz	127.0.0.1rT   )resolve_user_providerrW   z${}r   key_env)	requestedexplicit_api_keyexplicit_base_urltarget_modelr;   r   )rD  rG  z,Could not resolve credentials for provider 'z': zno-key-required)rW   r;   r   zCould not validate `z`: )acceptedpersist
recognizedmessagerH  is_provider_enabledr>   r+   r   rK  zInvalid model)r   r   r   r   r   r   corrected_model>   github-copilotcopilot)rW   >   opencoder   r   >   r   r   )normalize_opencode_base_urlz | )r   r   r   r   rW   r;   r   r   r   r   r   r   r   )Cr   r*  r+  r,  r-  hermes_cli.runtime_providerr.  r   r   r   r^   rD   r/  rK  rG   r   r*   r7   hermes_cli.moa_configr2  r2   r6  hermes_cli.providersr8  r  ra   r   r+   r   r;   r:  r  r;  loggerdebugr   r  rr   rs   findr   r   r   r(  sortedrb   r   r/   osr@  r   endswithenvironr   r   r   r   r   rM  r   r5   r0   rR  r   r   r!   ro   )Zr   r   current_modelr]   current_api_keyr   r   r  r9   r*  r+  r,  r-  r.  resolved_aliasr   r   resolved_moa_presetpdef_switch_errr/  _cfg_issues_cir7   r2  _AGG_PROVIDERS_PROVIDER_ALIAS_TABLE_explicit_norm_alias_target_autheds_suggestions_hintr:  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_pdefrW   r;   r   rZ  
_user_pdef_ruser_ucfg_ukey_kenvruntimee_da_mandated_mode
validationoverriderM  r  rJ   rN   
entry_name
entry_slugrO   entry_modelentry_modelsmsgrR  r   r   warningshermes_warnsZ                                                                                             r'   switch_modelr    s   `  EN!I&O
 $

 <-335;;=I,-=>D<$%6$7 85 6 G79#XXK*2A @##++'??@ %#)  ''e#I&9F01B1B51I1ORPQab	 	NI*00288:-11.A0>1>16!1-!1G
 g-& ||N3^8K     $ &dii&=%>a@)+  )!$3#'99'$^$4 5##,_#="> ?99>A
 
 }}P3DMMB (I, %(7'+yy"+3DII;b P\\m[np	 	 )!$3#'99'$TYYK 0KK\J]_	 	 %Y@#+7(Ay.	F5Y+KM,=,=e,D,JKH.xCJ"'&	!#&*##,Y8HI %9E6OYLL1	? //#))+Cm#9%5#1%5
 #:)V"L".AP>OYLLF&	?
  -S1H, %"+%cU*X__4EQxFW XB C	  )
 &NN3/	q=S	%9mL\>]$Zi0668>>@D%i!mn5;;=E'+fAeW$5	Q%y ',#).*?;G"+//"3" &Cyy{o5$'	6:3	&  ' &#:&)iiQ&7GAt#zz|>,/	>B ; %&" /#336>+;K #{2 +,< =I$(M"("5K;'!+0$)&/"#I; /..2ii.D-E FP!Q	   '2!nO +O <I$(MLLI!? ".$7O~<],;) !&B 33 >**95>u$<u(< 	 //"/!0<LMH-5* '*::/N("'7*!!),+

 "(--N GHH, 
L 1 7 7 9 ? ? A>RJ!#O^D
!j&9&9#)r../@/F/F/H/N/N/PQ E"(b--o>EBD 		)R06B7==?E%%..*=

uQr{B7==?EIIi4:;AACJJNN5"5;;=E2-%*]d&0&9&9!*	 "++i4=";;z26M*:M:M";;z26
 (-=%G'H)/8DH2-!* "++i4";;z26";;z26	.*&G kk)R0G{{:r2H{{:r2H
    0?s||||HH+ ,H5N!!%ox@ -YHI
-%

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

8,b%)! o    &%	&2 R  	F(4DEL	F`  %.."  
(!$3#1'*+3qc3	 	
0  		P  
-i[A3?	


s   00j= /k kAk$ A"l  A l  A m >m =	k
	k
kk$k=<k= ll 	m	)m>m	m		mm	m=%m88m=raw_pool_presentc                   	 ddl m}  ||       }|j                         r|j                         S 	 |S # t        $ r Y |S w xY w)az  Return whether *provider* has a credential that can be selected now.

    ``auth.json`` historically allowed opaque token-style pool values that do
    not deserialize into ``PooledCredential`` entries. Preserve visibility for
    those legacy values, but when a real pool exists its availability state is
    authoritative: an all-exhausted/dead pool is not authenticated.
    r   	load_pool)agent.credential_poolr  has_credentialshas_availablerG   )r   r  r  pools       r'   _credential_pool_is_usabler  "  sV    3"!%%'' "   s   -3 	A A c                `    t        | t              si S ddlm}  || j	                  d            S )Nr   )normalize_extra_headersextra_headers)r   r/   rD   r  r2   )rN   r  s     r'   _extra_headers_from_configr  5  s(    eT"	9"599_#=>>r)   c                     t         j                         ryt         j                          dd} t        j                  | dd      }|j                          |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.
    Nc            	        	 ddl m}   |        }t        |j                  |j                  |j
                  |j                  |j                  |j                  xs g        y # t        $ r t        j                  dd       Y y w xY w)Nr   )load_picker_context)r   r]   r]  r  r9   excluded_providerszpicker cache prewarm failedT)exc_info)hermes_cli.inventoryr  r  r   r]   r]  r  r9   r  rG   rV  rW  )r  r  s     r'   _warmz)prewarm_picker_cache_async.<locals>._warmT  s|    	G@%'C )!$!5!5!$!5!5!//"11!$!5!5#&#9#9#?R  	GLL6LF	Gs   AA! ! BBTzpicker-cache-prewarm)r#  daemonr+   r,   r-   )_picker_prewarm_doneis_setr.   
_threadingThreadr   )r  ts     r'   prewarm_picker_cache_asyncr  =  sJ    & ""$G( 	t:PQAGGIHr)   )force_fresh_nous_tierr  r]  refreshprobe_custom_providersprobe_current_custom_provider
for_pickerr  c               7   	 ddl ddlm}m}m} ddlm} ddlm}m	}m
}m}m}m}m} |r	  |        g }t!               }t#         xs d      j%                         j'                         }t#        xs d      j%                         j)                  d      j'                         dm	fd}|xs g D ch c],  }|st#        |      j%                         j'                         . }}t!               dnd
dofd}dpfddq fd} |       }t+        |      } |D !"cg c]  \  }!}"|!	 c}"}!| d<    |       | d<   d| vrddlm}#  |#       | d<   d| vrቪj.                  j1                  d      s<j.                  j1                  d      s! j%                         j'                         dk(  rddlm}$ ddlm}%  j%                         j'                         dk(  }&j.                  j1                  d      xs |&rrndxs d}'	  |$j.                  j1                  dd      |'d      }(|(s|&r|r|g}(|(| d<   ddlm}) ddlm}* ddlm}+ |j?                         D ]  \  },}-|+j1                  |,      }.|.r
|.|,k7  r|.|)v r$|,}/	 ddl m!}0  |0|,      }1|1|1jD                  }/|/|,k7  rI|,}2|2j'                         |v r^|,j'                         |v s|-j'                         |v r|j1                  |-      }3tG        |3t*              s|j1                  |,      }4|4r|4jH                  dk7  rdd lm%}5  |5|,      s|4r"|4jL                  rtO        |4jL                        }6n$|3j1                  d!g       }6tG        |6tN              stQ        fd"|6D              }7|7sK	 dd#lm)}8  |8       }9tU        |9xr! |9j1                  d$i       j1                  |,            }:|:rtW        |,d%&      }7|7s ||,      };|;s| j1                  |,g       };|,|v r	 ||,|;      };g }<tG        |t*              r;|j1                  |,      }=tG        |=t*              rtY        |=j1                  d'            }<tO        t*        j[                  g |<|;            };t]        |;      }>|,t^        v r|;}?n	||;d| n|;}? ||-      }@|4r|4jD                  r|4jD                  n@r@jD                  n|-}A|ja                  |2|A|2 k(  xs |, k(  xs |- k(  d(|?|>d)d*       |jc                  |2j'                                 ||2        dd+lm2}B ddlm}C |j?                         D DEci c]  \  }D}E|E|D
 }F}D}EBj?                         D ]O  \  }G}H|Gj'                         |v rFj1                  G|G      }I|Ij'                         |v r?Gj'                         |v sIj'                         |v rdd(}7HjH                  d,k(  r	 |I      }7n*Hjf                  rtQ        fd-Hjf                  D              }7|7s[HjH                  dk(  rLGIfD ]E  }JCj1                  |J      }K|KsKjL                  s$tQ        fd.KjL                  D              sCd%}7 n |7s,	 dd#lm)}8  |8       }9|9j1                  d/i       }L|9r
GLv sILv rd%}7|7s2	 tW        I      rd%}7n#|
r!	 dd1l6m7}N  |NI      }O|Ojq                         rd%}7|7sDId3k(  r?	 dd4l9m:}Pm;}Q  |Q       }R |P       }S|RrRj1                  d5      sSrSj1                  d5      rd%}7|7sId7v r
 |I      };n HjH                  d,k(  r4	  |I      }T|TrTn%| j1                  Ig       xs | j1                  Gg       };nIdk(  r{| j1                  dg       };	 dd8lm<}Um=}Vm>}Wm?}X dd9lm@}Y  |Ud      xs i }Zd}[	  Yd      xs i }\|\j1                  d:d      xs d}[ V|;      r W|;Z[      \  };}"n X|;Z[      \  };}"n= |I      };|;s3| j1                  Ig       xs | j1                  Gg       };I|v r	 |I|;      };t]        |;      }>It^        v r|;}?n	||;d| n|;}?|ja                  It        |I      |I k(  xs G k(  d(|?|>d<d*       |jc                  Gj'                                |jc                  Ij'                                 ||I       R 	 dd=lmB}] ]D ]  }^|^j                  j'                         |v r!^j                  j'                         |v r>Cj1                  ^j                        }_d(}`|_r*_jL                  rtQ        fd>_jL                  D              }``s2	 dd#lm)}8  |8       }a|aj1                  d/i       }b|ar^j                  bv rd%}``s	 tW        ^j                        rd%}``s$_r"t        _d?d      d,k(  r |^j                        }``s_rDt        _d?d      d,k(  r4	  |^j                        }T|TrTn| j1                  ^j                  g       }cn0 |^j                        }c|cs| j1                  ^j                  g       }ct]        c      }d|cd| nc}e|ja                  ^j                  |^j                  |^j                   k(  d(|edd@d*       |jc                  |^j                  j'                                 ||^j                          t!               }f|rtG        |t*              rddAlGmH}g ddBlImJ}h  |g       }i|j?                         D ]  \  }j}ktG        |kt*              s hk      s!jj'                         |v r4kj1                  dCd      xs j}Akj1                  dDd      xs* kj1                  dEd      xs kj1                  d	d      xs d}lt#        kj1                  dFd      xs d      j%                         }mt#        kj1                  dd      xs d      j%                         }nt#        kj1                  dG      xs kj1                  dH      xs d      j%                         j'                         }onrnnmrdIm nd}pt#        l      j%                         j)                  d      j'                         }qt        k      }rt        t        |rj?                                     }s|q|po|sf}t|kj1                  dJd      xs kj1                  dKd      }ug }v|urvja                  u       tY        kj1                  d'g             D ]  }w|wvvsvja                  w        tivrA}xdLD ]*  }y|yxv sxj                  y      d   j%                         }x n xj                         }zd}{t        |z      D ]/  \  }|}}|}j%                  dM      }~|~stQ        dN ~D              s-|}{ n {'{dOk\  r"dPj                  zd{       j%                         }xj}|xxs Alg kg dQit<   vD ]'  }|sit   d'   vsit   d'   ja                         ) it   dR   ja                  A        ij                         D ]  }|dS   }k|d   }j|dC   }A|dT   }ltO        |d'         }|sMt#        l      j%                         j'                         }dU|v r"| j1                  dV      xs g }|rtO              }t#        kj1                  dd      xs d      j%                         }|s[t#        kj1                  dFd      xs d      j%                         }m|mr*j.                  j1                  md      j%                         nd}kj1                  dWd%      }tG        |t"              rj'                         dXv}tU              }t#        l      j%                         j)                  d      j'                         }t#        j      j%                         j'                         }t        A      j'                         }||k(  xs  |k(  xs |dYk(  xr tU              xr k(  } ||Z      xr! tU        l      xr xr tU              xs  }|r#	 dd[lmS}  |lt        k      xs d\      }|r}|ja                  jAd%|rt]              ndd]ld^       |jc                  jj'                                |jc                  t        A      j'                                t#        |l      j%                         j)                  d      j'                         }j1                  dR      xs AgD ]p  }t#        |      j%                         j'                         f}|d   s2d_   s8fjc                         |jc                  t              j'                                r t#        A      j%                         j'                         f}|d   sd_   sfjc                          |dYk(  rĉrdY|vrtQ        fd`|xs g D              s|r|gng }|s	r:	 dd[lmS}  |dt#              j%                         j)                  d            }|r}|ja                  dYdad%d%|d| nt]              dbt#              j%                         j)                  d      d^       |jc                  dY       |rtG        |tN              rddAlGmH}  |       }|D ]  }tG        |t*              sj1                  dC      xs dj%                         }j1                  dDd      xs* j1                  d	d      xs j1                  dEd      xs dj%                         j)                  d      }lrlsj1                  d      xs dj%                         }nj1                  dF      xs dj%                         }mnxs. mr*j.                  j1                  md      j%                         nd}t#        j1                  dG      xs j1                  dH      xs d      j%                         j'                         }onrnnmrdIm nd}pj1                  dWd%      }tG        |t"              rj'                         dXv}t              }rt        t        |rj?                                     }s}dLD ]*  }y|yv sj                  y      d   j%                         } n lposj'                         f}t|tvr xs }At        |A      }2|2|Alg d(rdct<   n(rt   j1                  d      st   d<   sd(t   dW<   j1                  dK      xs dj%                         }u|ur!ut   d'   vrt   d'   ja                  u       tY        j1                  d'i             }|rd%t   dd<   D ]$  }w|wt   d'   vst   d'   ja                  w       &  t!               }t        fdej                         D              }|j                         D ]O  }|dT   }l|j1                  dd      }|d   }2|2j'                         |v r|2j'                         vrE|2j'                         v rD|2}dO} df j'                         |v rd_z  } df| j'                         |v r df }2|2d<   t#        dC         j%                         j'                         t#        |dT         j%                         j)                  d      j'                         f}|d   rd_   rfv rd_   }|rv r|2j'                         |k(  xs  |dYk(  xr tU              xr k(  xr d_k(  } ||Z      xr@ tU        l      xr3 tU              xs j1                  dd       xr j1                  dWd%      }|rF	 dd[lmS}  |lj1                  dg      xs d\      }|rd'<   t]        |      |dh<   t        l|       |ja                  |2dC   d%|d'   t]        |d'         d]|dT   d^       |jc                  |2j'                                jc                  |2j'                                R 	 ddBlImJ}h tG        |t*              r|j?                         D ch c]D  \  }}tG        |t*              r/ h      s't#              j%                         j'                         F }}}|r|D cg c]v  }t#        |j1                  did            j%                         j'                         vr;t#        j1                  dd            j%                         j'                         vrx }}|r\|D ]W  }|j1                  dj      sj1                  d'      xs g }||vr)|gd'<   |j1                  dht]        |            d_z   |dh<    n |j                  dk l       |S # t        $ r Y xw xY wc c}w c c}"}!w # |%$ r g }(Y w xY w# t        $ r Y 9w xY w# t        $ r Y w xY wc c}E}Dw # t        $ r"}Mth        jk                  d0GM       Y d}M~Md}M~Mww xY w# t        $ r Y iw xY w# t        $ r"}Mth        jk                  d2IM       Y d}M~Md}M~Mww xY w# t        $ r!}Mth        jk                  d6M       Y d}M~Mzd}M~Mww xY w# t        $ r* | j1                  Ig       xs | j1                  Gg       };Y w xY w# t        $ r d}[Y w xY w# t        $ r Y w xY w# t        $ r g }]Y ;w xY w# t        $ r Y w xY w# t        $ r Y w xY w# t        $ r  | j1                  ^j                  g       }cY w xY w# t        $ r Y 
w xY w# t        $ r Y 	-w xY w# t        $ r Y w xY wc c}}w c c}w # t        $ r Y Xw xY w)ru  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.

    ``probe_current_custom_provider`` is the middle ground for GUI picker
    opens: probe only the currently-selected custom endpoint so its model list
    matches the active provider without blocking on every saved/offline custom
    endpoint.
    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_idsr<   r:   c                *    t        xs xr |       S r   )rh   )row_is_currentr  r  s    r'   _can_probe_custom_providerz@list_authenticated_providers.<locals>._can_probe_custom_provider  s    *`/L/_Q_aar)   r=   c                v    t        | xs d      j                         j                  d      j                         S )Nr<   r:   r   r   rE   r   )r=   s    r'   	_norm_urlz/list_authenticated_providers.<locals>._norm_url  s-    39"~##%,,S17799r)   r  c                *   	 ddl m} |j                  |       }|syd}t	        |dd      r*j
                  j                  |j                  d      xs d}|st	        |dd      xs d} |      }|rj                  |       yy# t        $ r Y yw xY w)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  Nr<   base_url_env_varinference_base_url)hermes_cli.authr  rG   r2   getattrr\  r  r    )r  _regpcfgr=   normed_builtin_endpointsr  rZ  s        r'   _record_builtin_endpointz>list_authenticated_providers.<locals>._record_builtin_endpoint  s    	A xx~4+R0**..!6!6;ArC$ 4b9?RC3""6*   		s   B 	BBc                 ,    j                   j                  dd      j                         ry j                   j                  dd      j                         r+ j                   j                  dd      j                         ry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_BEDROCKr<   TAWS_ACCESS_KEY_IDAWS_SECRET_ACCESS_KEYc              3  r   K   | ].  }j                   j                  |d       j                          0 yw)r<   N)r\  r2   r   )r@   r+   rZ  s     r'   rB   zQlist_authenticated_providers.<locals>._has_fast_aws_sdk_signal.<locals>.<genexpr>  s1      
 JJNN4$**,
s   47)AWS_PROFILE&AWS_CONTAINER_CREDENTIALS_RELATIVE_URI"AWS_CONTAINER_CREDENTIALS_FULL_URIAWS_WEB_IDENTITY_TOKEN_FILE)r\  r2   r   rF   )rZ  s   r'   _has_fast_aws_sdk_signalz>list_authenticated_providers.<locals>._has_fast_aws_sdk_signal  s{     ::>>4b9??AJJNN.399;

6;AAC 

 
 	
r)   c                   t        | xs d      j                         j                         }t        xs d      j                         j                         }        ry||k7  ry	 ddlm} t         |             S # t        $ r Y yw xY w)z@Credential check for AWS SDK providers in non-runtime discovery.r<   TFr   )has_aws_credentials)r   r   r   agent.bedrock_adapterr  rh   rG   )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    
O))+113	+1r288:@@B#%$	A+-.. 		s   'A= =	B	B	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/v1g      ?)rW   r;   timeoutr5  )_PROVIDER_ALIASESr7  )get_provider_profilerW   )is_runtime_provider_routableenvc              3  T   K   | ]  }j                   j                  |       ! y wr   r\  r2   r@   evrZ  s     r'   rB   z/list_authenticated_providers.<locals>.<genexpr>  s     >r

r*>   %()_load_auth_storecredential_poolTr  r>   Fzbuilt-in)r  r+   
is_currentis_user_definedr>   total_modelsr\   )HERMES_OVERLAYSaws_sdkc              3  T   K   | ]  }j                   j                  |       ! y wr   r  r  s     r'   rB   z/list_authenticated_providers.<locals>.<genexpr>  s     P2BJJNN2.Pr  c              3  T   K   | ]  }j                   j                  |       ! y wr   r  r  s     r'   rB   z/list_authenticated_providers.<locals>.<genexpr>  s     N"2::>>"-Nr  rK   z"Auth store check failed for %s: %sr  z'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-codexrP  )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  T   K   | ]  }j                   j                  |       ! y wr   r  r  s     r'   rB   z/list_authenticated_providers.<locals>.<genexpr>  s     Yr

r 2Yr  r[   	canonical)OrderedDictrL  r+   r;   apirC  r   rY   zenv:r%  r   )u   —z - z.,()c              3  <   K   | ]  }|j                           y wr   )r   )r@   cs     r'   rB   z/list_authenticated_providers.<locals>.<genexpr>	  s     "<1199;"<rC   r   r   )r  r+   rH   r>   ep_cfg	raw_namesr  r  rH   zapi.openai.comr   discover_models>   0nofalserS   )r  )fetch_api_models)headerszuser-config)r  r+   r  r  r>   r  r\   rH   r   c              3    K   | ]  }t        |t              xr t        |j                  d d      xs& |j                  dd      xs |j                  dd            j	                         j                  d      j                         t              j	                         j                  d      j                         k(    yw)r;   r<   r=   r  r:   N)r   r/   r   r2   r   rE   r   )r@   _cpr]   s     r'   rB   z/list_authenticated_providers.<locals>.<genexpr>	  s      	
  sD! A
B' &775"%&775"% egffSk%%'#$**,33C8>>@AA	
s   CCrT   rX   )r  r+   rH   rW   r>   has_explicit_modelsr  r  r  c              3     K   | ]D  }r@t        |d          j                         j                  d      j                         k(  rd F yw)rH   r:   r   Nr  )r@   _grp_current_base_url_norms     r'   rB   z/list_authenticated_providers.<locals>.<genexpr>0
  sK      ,
%DO$**,33C8>>@DZZ ,
s   A
Ar   r  r  provider_idr  c                    | d    | d    fS )Nr  r  rV   )rs    r'   r   z.list_authenticated_providers.<locals>.<lambda>
  s    AlO 3a6G5GH r)   r   )r  rh   r,   rh   )r=   r   r,   r   )r  r   r,   r-   )r,   rh   )r  r   r,   rh   )WrZ  agent.models_devr  r  r  r  r  r   r  r   r  r  r  r  r  rG   r.   r   r   r   rE   r/   r  r\  r2   r  r  r6  r  rU  r8  r   rK   r  r+   r   r[   r  rZ   r0   rF   r  rh   r  r5   fromkeysrb   r   r!   r    r  extra_env_varsrV  rW  r  r  r  agent.anthropic_adapterr  r  r  r   r  r  r  r   r  ImportErrorr  r  labelcollectionsr
  rD   rM  r  r1   rY  r   	enumerater   valuesr
   r  sumrQ   r   )r   r]   r  r9   r  r  r]  r  r  r  r  r  r  r  _mdev_pinfor  r  r   r  r  r  r  r  results
seen_slugs_current_provider_normr  r  	_excludedr  r  datacuratedr  ro  r  r  r  is_current_lmstudiolm_baselivere  _CANON_ALIASESrf  	hermes_idmdev_idrh  
_canonical_gpp_profr  pdatapconfigr  env_vars	has_credsr  storer  rI   configured_models
configuredtotaltoppinfodisplay_namer  _auth_registrykv_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_st_canon_provsr  
_cp_config_cp_has_creds	_cp_store_cp_providers_store_cp_model_ids	_cp_total_cp_top_section3_emitted_pairs_OD3rM  	ep_groupsep_namer  rH   rC  inline_api_keyr   credential_identityapi_url_normentry_extra_headersheaders_identity	group_keyr%  r  r#   grp_displaysep_toks_cut_at_i_t_tlgrp_slug_mgrpmodels_list	url_lowerfbrW   discoverr  _ep_url_norm_ep_slug_norm_ep_custom_slug_norm_ep_is_currentshould_prober  live_models_url_norm_for_pair	_raw_name_pair_models_live_modelsr
  groupsrN   raw_name_display_prefixdeclared_models_section4_emitted_slugs_current_base_url_group_count	base_slugr   	_pair_key_grp_url_norm_grp_is_currentr+   rJ   _disabled_slugsr  _rowr  r  r  r  rZ  s   ``      ``                                                                                                                                                            @@@@@r'   r  r  m  sl   f  
 2   	')
 GeJ !1!7R8>>@FFH !1!7R8>>@GGLRRTb 8J7ORVTUc!flln**,VIV "e:+,
2 D %))9$:G/@AVS!SAGL 12GFOW$?";"=  


|$

}(EIYI_I_IaIgIgIimwIw;-.446<<>*LJJNN=) *$7<L RV*) 	
	(

|R8 D +!?D"
 JEE4::< '	7 .11)<*/ 
	>OE "ZZ
 " ::<:%??	)W]]_	-I!%&
 $''	2 w((I5 	A+I6w//G445Hyy+Hh- >X>>	<(*#'Meii(92>BB9M$  $ :!D!I
  .i8	Ir2I1129iH	 (*nd+'++I6J*d+$7
x8P$Q!'G):'GY'GHI	I22C,6,B)KZ(	CG$'.7<<w||TYejj_f (( / 00/..$! 
 	 	tzz|$ &'D 5C
 )?(D(D(FG1q!tGOG'--/ e.W99;*$ &))#s3*,99;)#{'8'8':i'G 		)6{CI##P9O9OPPIW..);k* %))$/D11N8M8MNN$(	 M<(*"'))K"<c_48V $I Z-k: $IC )+ 6 002(,I [K7O  =>79 \%5%5m%Dm!< $I DD 2+>I )+Q0=$(Dw{{;/K/cw{{[^`bOc	 F"  FB/I  S(06B!%f-3C!gg&7<BG *?@#.y(G#LLIq#.y(G#LLIq 2+>I#KKR8PGKKR<P	"77 6{I NII44C,6,B)KZ(	Ck*%)99TSDT=T$!
 	 	syy{#{((*+ -Ke.VI  ?+88>>z)88>>y( $''1
*55YZ=X=XYYM<,.	&/mmK&D#-@!@$(M -chh7$(M 
KQS0TXa0a:388DM '*k2>)K:0:(,'++chh2K
 6chh?M  'CHHb 9&	0:0F-,MHHII((&66$%!
 	 	sxx~~'( *?+P $'5*^T: 	49)-	-335 Y	COGVfd+ 'v.}}*,!::fb1<WL

:r* ::eR(::eR( 	  &**Y39r:@@BG Ir!:!@bAGGIN

:& ::k* egeeg	  " *1WI&r  
 w<--/66s;AACL #=V"D$V,?,E,E,G%HI%':HFVWI #JJ;Vvzz'SU?VM
 "$L##M2/

8R0HI 2</ ''12 	) +) Ck)&1&7&7&<Q&?&E&E&G $))+'. FB((6*Cs"<"<<"$	
 &7a<"%((5'?";"A"A"CK"$'7<& $!#(	)$ # >"Ii$8$BBi(299"=> i -44\BsY	Cv ##% [	3C]F&kGv;L)nGs8}-K L..0668	#y0 X.4"B&*2h &**Y39r:@@BGfjjB7=2>DDFAH"**.."5;;=bzz"3T:H(C(#>>+3GG"&{"3w<--/66s;AACLL..0668M#7#E#K#K#M !77 '+AA +h6 ?34?$(>>  6^T Y]^eYf ks W8%8!8  
B"2 :6 B Jd#K
 #&1 NN$,#'%4?K 0Q'"	 	 NN7==?+NN/=CCEF
 "%W!3!3!5!<!<S!A!G!G!I WW[1Cl^ L		N((*002& 8a+//6NN#7	#B#H#H#JKL L!'')//1"E QxE!H'++E2w[	3L 	(*J& 	
 ).B	
 	
 &3=/3>/C8H4I4O4O4Q4X4XY\4]^*G 	%#.8.Dgkz*'L$+,224;;C@	
 		 	x  J'7>+ .9]% ]	AEeT*		&)/R668H		*b) 99UB'99UB' egffSk  7#ii	28b??ANyy+1r88:G$ 7>

w+113B  		*% 99[) egeeg	  " *1WI&r   yy!2D9H(C(#>>+3GG #=U"C$V,?,E,E,G%HI 'O% /)&5&;&;C&@&C&I&I&KO
 !"5xAQSbShShSjkI&  /:(+L9 (&& +0'/%8	%y! 6)#4#8#8#C3:F9%i0
  ;@F9%&78 #YYw/52<<>MfY6G6Q!Qy!(+22=A1%))Hb2IJO;?y!"78+ A6)#4X#>>9%h/66x@Aw]	A~ (+u(+ ,
,
 )
% ==? n	6C)nGggi,Gv;D zz|z)djjlBY.Y
 zz|66 	"1QC(..0J>FA #1QC(..0J>#AaS)"F CK &&(..0C	N#))+2237==?I |	!>U1U &aLM2D!D8 #jjl.DD &(2 7/07!%;;7 2Q6	  +/J 5M5']H#''2G*H&H5 GG-t4	  B"2 # 8 @D#K
 #(3H.1+.>N+ :#[
 NNF-#'h- #CM 2'y>	 	 NN4::<(#''

5]n	6j9nd+ "0!5!5!7D#c4(1DS1I D	!'')O 
 &15534::<BBDO[AEE&"-.446<<>oU     	D88L)hhx(.BGG+"/!:'!:X'+xxG'MPQ'Q^$	 LLHLINy   		  WX BD  	D	L  		j  n HL  MA3LLM, %  ZFUXYYZ(  OH#NNO(  Q#KKR8PGKKR<P	Q8 ! ! G!   	N  ,    $  : 'CHHb 9:L ! D  Z ! .
  s?  Af& :Af6)Af6'Af;
%Ag AgA
Ag?Ag/'+Ag5Ah3& Ah#>Ai!.2Aj9 Ak"Ak<$Ak3Ak& 1Ak89Al2Al@1"AmG:9Am ^AAm$`;)Am? a$A	Am4b-	Am? b6A;Am:d1Am? f&	Af3f2Af3gAggAgg	AggAgg	Ag,g+Ag,g5	Ah g>AhhAh h#	Ah0h,Ah3h/Ah0h0Ah3h3	Aih<AiiAii!	Aji*AjjAjj/Akk AkkAkkAkkAkkAkk	Ak#k"Ak#k&Ak5k4Ak5k8	AllAll	AllAll%Amm Amm	AmmAmm	Am!m Am!m$	Am1m0Am1m4Am? m?	AnnAnc           
         	 ddl m}  ||      }|| S |g| D cg c]1  }t        |j                  dd            j	                         dk7  s0|3 c}z   S c c}w # 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_rowr  r<   r1  )r  r  r   r2   r   rG   )rK   r   r  moa_rowr  s        r'   _prepend_moa_picker_providerr  
  sq    :#$45?yy\!Cfb8I4J4P4P4RV[4[A\\\\ s-   A A 1AAA A A('A(c           
     r   ddl m} t        | |||||d|      }	|rt        |	|       }	g }
|	D ]  }t	        |j                  dd            j                         }|dk(  rA	  |       }|D cg c]  \  }}|	 }}}t        |      }||d
| n||d	<   t        |      |d<   t        |j                  d	            }t        |j                  d            xr t        |j                  d            }|s|s|
j                  |        |
S c c}}w # t        $ r t        |j                  d	g             }Y w xY w)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_modelsT)r   r]   r  r9   r  r]  r  r  )r   r  r<   r  r>   Nr  r  rH   )r   r  r  r  r   r2   r   rG   r0   r/   rb   rh   r!   )r   r]   r  r9   r  r]  include_moar  r  rK   r   r  r  r2  r  ro  live_ids
has_modelsis_custom_endpoints                      r'   list_picker_providersr  
  sH   : :,))%)#-	I 0M]^	H 155$%++-<5.0.23FCC33 QA3=3I(;J/xAhK #HAn!%%/*
!!%%(9":;VQUU9EU@V"4!$ O 4 5h 345s$   D+D	7D	D$D65D6)r3   r   r,   	list[str])rH   r   rI   r  r,   r-   )r]   r   r,   zOptional[ProviderDef])rc   r   r,   r   )rc   r   r,   rh   )r,   r   r  )r   r   r,   r   )r   r   r,   z!tuple[str, str, bool, bool, bool])Fr<   )
r   rh   r   rh   r   rh   r   r   r,   rh   )r#   r   rd   r   r,   r1   )r   r   r   r   r,   Optional[tuple[str, str, str]])r<   NN)r   r   r  r/   r9   list | Noner,   r  )rV   )r   r   r  r  r,   r  )r<   r<   NNN)r   r   r   r   r;   r   rW   r   r   r   r9   r  r  
int | Noner,   zOptional[int])rc   r   r  zOptional[dict]r9   zOptional[list]r,   dict[str, str])r<   r<   Fr<   NN)r   r   r   r   r]  r   r]   r   r^  r   r   rh   r   r   r  r/   r9   r  r,   r   )r   r   r  rh   r,   rh   )rN   r   r,   r  )r,   zOptional['_threading.Thread'])r<   r<   NN)r   r   r]   r   r  r/   r9   r  r  rh   r  r  r]  r   r  rh   r  rh   r  rh   r  rh   r  r  r,   
List[dict])r<   )rK   r  r   r   r,   r  )r<   r<   NNNr<   FN)r   r   r]   r   r  r/   r9   r  r  r  r]  r   r  rh   r  r  r,   r  )Jrx   
__future__r   loggingr   dataclassesr   typingr   r   r   r   rU  r	   r
   r   r   r   r   r   hermes_cli.model_normalizer   r  r   r   r   r   r   	frozensetr   ry   	getLoggerru   rV  r5   rQ   r^   rn   compile
IGNORECASEri   r_   rf   rl   ro   rq   r   r   r   r   r   r   r   r   r   r   r   r   r  r  r  r   r(  r  	threadingr  Eventr  r  r  r  r  r  r  rV   r)   r'   <module>r     s  ( #  	 ! 2 2    .77V-W N W			8	$-`55&5	5p,-   )bjj-MM .+  
.	@J 1+{O<1+ {M:1+ {N;	1+
 {H51+ x11+ 
x/1+ x11+ 	x.1+ 	x.1+ x21+$ z?;%1+* vv.+1+0 |W511+6 vv.71+< y)4=1+B x4C1+H |V4I1+N vu- y&1 x0 z95a1+' 1x*  35 / 4 *,& +@F	6    & $  CL& 	+++ + 	+
 
+dm/`J/J/J/ $J/\ $( " 	0 *,& $( &*$((,*** * 	*
 $* "* &* *d=="= %= 	=P $(\	\	\	 \	 	\	
 \	 \	 \	 \	 "\	 \	L 'z'')  KP &?-b $(	d #(!#'*/&*ddd d "	d  d d d d !d $(d d $d dN#* $(!&*??? ? "	?
 ? ? ? $? ?r)   