
    
Bj                   ^   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
 ddl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j2                  e      Zd	Z ej:                  d
ej<                        ZdKdZ dLd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"d%d$      d& e"d&d&      d' e"d'd'      d( e"d)d(      d* e"d+d*       e"d,d-       e"d.d/       e"d0d1       e"d2d3      d4Z#d5e$d6<    G d7 d8e	      Z%i Z&d9e$d:<   i Z'd9e$d;<   dMd<Z(dNd=Z)e G d> d?             Z*e G d@ dA             Z+dOdBZ,dPdCZ-	 	 	 	 	 	 dQdDZ.	 	 	 dR	 	 	 	 	 	 	 dSdEZ/	 dT	 	 	 	 	 dUdFZ0	 	 	 	 	 dV	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 dWdGZ1	 	 	 	 	 	 dX	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 dYdHZ2	 	 	 	 	 	 dZ	 	 	 	 	 	 	 	 	 	 	 	 	 d[dIZ3	 	 	 	 	 	 dZ	 	 	 	 	 	 	 	 	 	 	 	 	 d[dJZ4y)\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)List
NamedTupleOptional)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_modelszNous 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](?:[-_.:]|$)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
model_names    </home/ubuntu/.hermes/hermes-agent/hermes_cli/model_switch.pyis_nous_hermes_non_agenticr   K   s!     +22:>??    c                &    t        |       rt        S y)zHReturn a warning string if *model_name* is a Nous Hermes 3/4 chat model. )r   _HERMES_MODEL_WARNINGr   s    r   _check_hermes_model_warningr   W   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.strvendorfamilyN__name__
__module____qualname____doc____annotations__ r   r   r!   r!   c   s    DKKr   r!   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)rB   rD   rF   rG   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providerbase_urlNr%   r+   r   r   rJ   rJ      s    ?JMMr   rJ   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   )load_configmodel_aliasesrK   r   rL   customrM   )rK   rL   rM   aliases/   )dictrO   hermes_cli.configrR   get
isinstanceitemsrJ   striplowerr"   split	Exception)mergedrR   cfguser_aliasesnameentryrK   rL   rM   model_sectionsimple_aliasescurrent_providervaluekeyvals                  r   _load_direct_aliasesrl      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)rP   updaterl   r+   r   r   _ensure_direct_aliasesro      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.r   successr   r"   	new_modeltarget_providerFprovider_changedapi_keyrM   api_modeerror_messagewarning_messageprovider_labelresolved_via_aliasNzOptional[ModelCapabilities]capabilitiesOptional[ModelInfo]
model_info	is_global)r&   r'   r(   r)   r*   rs   rt   ru   rv   rM   rw   rx   ry   rz   r{   r|   r~   r   r+   r   r   rq   rq     s    +MIsOS"d"GSHcHcM3OSNC  04L-4&*J#*Itr   rq   c                  T    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	<   y
)CustomAutoResultz?Result of switching to bare 'custom' provider with auto-detect.r   rr   r   r"   rK   rM   rv   rx   N)	r&   r'   r(   r)   r*   rK   rM   rv   rx   r+   r   r   r   r     s1    IME3OHcGSM3r   r   c                   d}d}ddl }|j                  dd|       } d| v r"d}| j                  dd      j                         } | j	                         }d}g }|t        |      k  rO||   d	k(  r|d
z   t        |      k  r||d
z      }|dz  }n|j                  ||          |d
z  }|t        |      k  rOdj                  |      j                         }|||fS )a  Parse --provider and --global flags from /model command args.

    Returns (model_input, explicit_provider, is_global).

    Examples::

        "sonnet"                         -> ("sonnet", "", False)
        "sonnet --global"                -> ("sonnet", "", True)
        "sonnet --provider anthropic"    -> ("sonnet", "anthropic", False)
        "--provider my-ollama"           -> ("", "my-ollama", False)
        "sonnet --provider anthropic --global" -> ("sonnet", "anthropic", True)
    Fr   r   Nz+[\u2012\u2013\u2014\u2015](provider|global)z--\1z--globalTz
--providerrW       )resubreplacer]   r_   lenappendjoin)raw_argsr   explicit_provider_repartsifilteredmodel_inputs           r   parse_model_flagsr   )  s     I wwEwPXYH X	##J399; NNE	AH
c%j.8|#AE
(: %a!eFAOOE!H%FA c%j. ((8$**,K*I66r   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}
|
j                  |d      }|	||fz   S # t        $ r Y Sw 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')
    NrV   rW   -r   startvV
in_versionz-_.	in_suffix.betweenc              3  "   K   | ]  }|  	 y wNr+   ).0ns     r   	<genexpr>z"_model_sort_key.<locals>.<genexpr>  s     )q)s   r   )promaxplusturbo)r   
startswithlstripr]   isdigitr   floatrstrip
ValueErrorr^   tuplerZ   )model_idprefixrestnums
suffix_bufstatenum_bufchsuffixversion_key_SUFFIX_RANKsuffix_ranks               r   _model_sort_keyr   W  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A>L""61-K+v...m &  &  & .  		sH   ()H!)H()H8)I 	H%$H%(	H54H58	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_MODELSrV   c                    t        |       S r   )r   )mprefix_for_sorts    r   <lambda>zresolve_alias.<locals>.<lambda>  s    q/B r   rj   )r]   r^   ro   rP   rZ   rL   rK   r\   rH   r   hermes_cli.modelsr   r   r`   r   r   sort)	raw_inputrh   rj   direct
alias_namedaidentityr#   r$   catalogr   staticr   seen
aggregatorr   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   )rh   user_providerscustom_providers
max_modelsslug)list_authenticated_providersr`   )rh   r   r   	providers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_providersr   rL   results        r   _resolve_alias_fallbackr   %  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)rM   rv   rL   r   config_context_length)agent.model_metadatar   intr`   context_window)	rK   rL   rM   rv   r~   r   r   r   ctxs	            r   resolve_display_context_lengthr   6  s~    4A&^Mr%-"7
 s8O  *";";:,,--	  s   ,A 	A A c	                ,  @ ddl m}	m}
m}m} ddlm} d}| j                         @|}|rt        |||      }|Ed| d}	 ddl	m
}  |       }|r!|d	z  }|dd
 D ]  }|d|j                   z  } t        d||      S |j                  }@s|j                  rTddlm}  ||j                        }|r|@nat        d||j"                  |d|j"                   d|j                   d|       S t        d||j"                  |d|j"                   d|       S t%        @|      }||\  }@}nt%        | |      }| |\  }@}t&        j)                  d|@|       n| j                         j+                         }|t,        v rpt/        |||      }t1        | |      }||\  }@}t&        j)                  d|@|       nt,        |   }t        d|d| d|j2                   d|j4                   d      S | j7                  d      }|dkD  rhd| vrdt9        |      rY| d| j                         j+                         }| |dz   d j                         }|r |r| d| @t&        j)                  d| @       d} t9        |      ry|swt;        |      }!|!rj@j+                         }"|!D ]  }#|#j+                         |"k(  s|#@d}  n; |!D ]5  }#d|#v s|#j=                  dd      \  }}$|$j+                         |"k(  s1|#@d}  n |xs d}%|d v xs
 d!|%v xs d"|%v }&||k(  r|&s|s| s |
@|      }|r|\  }@||k7  }'t?        |      }(|jA                  d#      rt        |||      })|)|)j"                  }(|}*|}+d},|'s|rB	  ||@$      }-|-jC                  d%d      }*|-jC                  d&d      }+|-jC                  d'd      },nA	  ||@$      }-|-jC                  d%d      }*|-jC                  d&d      }+|-jC                  d'd      },|r?tE                tF        jC                  |      }/|/|/j                  r|/j                  }+d},|*sd*}*tI        @|      @	  |@||*|+|,xs d+      }0|0jC                  d/      sLd}1|ra|jK                         D ]N  \  }2}3|2|k(  s|3jC                  d0i       }4@|4v rd}1 n+tM        |4tN              s7tQ        @fd1|4D              sLd}1 n |1s|rtM        |tN              r|D ]  }5tM        |5tR              s|5jC                  d2d      }6|6rd#|6 nd}7|5jC                  d&d      }8|7|k(  s|8|+k(  sL|5jC                  d3d      }9|5jC                  d0i       }:@|9k(  rd}1 ntM        |:tR              s@|:v sd}1 n |1rddd|0jC                  d4d      d.}0n#|0jC                  d4d5      };t        d@||(||;6      S |0jC                  d7      r|0d7   @|d8v r
 |	@|*9      },|d:v r	 ||@      },|,stU        ||+      },|,d;k(  r-|d<v r)tM        |+tV              r|+rtY        jZ                  d=d|+      }+t]        |@      }<t_        |@      }=g }>|0jC                  d4      r|>ja                  |0d4          tc        @      }?|?r|>ja                  |?       t        d@||'|*|+|,|>rd>je                  |>      nd|(||<|=|?      S # t        $ r Y Aw xY w# t        $ r }.t        d||(|d(|( d)|.       cY d}.~.S d}.~.ww xY w# t        $ r Y w xY w# t        $ r}.dddd,@ d-|. d.}0Y d}.~.d}.~.ww xY w)@a  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   Nz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   
  • F)rr   r   rx   )_auto_detect_local_modelzNo model detected on z (z@). Specify the model explicitly: /model <model-name> --provider )rr   rt   rz   r   rx   z
Provider 'zN' has no base URL configured. Specify a model: /model <model-name> --provider zAlias '%s' resolved to %s on %s)rh   r   r   z,Alias '%s' resolved via fallback to %s on %szAlias 'z
' maps to rV   z] but no matching model was found in any provider catalog. Try specifying the full model name.:rW   z3Converted vendor:model '%s' to aggregator slug '%s'T>   localrT   	localhostz	127.0.0.1zcustom:)	requestedtarget_modelrv   rM   rw   z,Could not resolve credentials for provider 'z': zno-key-required)rv   rM   rw   zCould not validate `z`: )acceptedpersist
recognizedmessager   modelsc              3  h   K   | ])  }t        |t              s|j                  d       k(   + yw)rd   N)r[   rX   rZ   )r   r   rs   s     r   r   zswitch_model.<locals>.<genexpr>  s*     eaQ[\]_cQdquuV}	9es   22rd   rK   r   zInvalid model)rr   rs   rt   rz   r   rx   corrected_model>   github-copilotcopilot)rv   >   opencodeopencode-goopencode-zenanthropic_messages>   r   r   z/v1/?$z | )rr   rs   rt   ru   rv   rM   rw   ry   rz   r{   r|   r~   r   )3r   r   r   r   r   hermes_cli.runtime_providerr   r]   r   rY   r   r   r`   rq   idrM   r   rd   r   loggerdebugr^   rH   r   r   r#   r$   findr   r   r_   r
   r   rZ   ro   rP   r   r\   r[   listanyrX   r	   r"   r   r   r   r   r   r   r   )Ar   rh   current_modelcurrent_base_urlcurrent_api_keyr   r   r   r   r   r   r   r   r   resolved_aliasrt   pdef_switch_errr   _cfg_issues_cir   detectedalias_result_rj   authedfallback_resultr   	colon_posleftrightresolved_in_current_catalogr   new_model_lowerr   bare_base	is_customru   rz   custom_pdefrv   rM   rw   runtimee_da
validationoverrider   rb   
cfg_modelsre   
entry_name
entry_slug	entry_urlentry_modelentry_modelsmsgr|   r~   warningshermes_warnrs   sA                                                                   @r   switch_modelr+  g  s   `  EN!I&O
 $

 <$%6$7 85 6 G79#XXK*2A @##++'??@ %#)  '' }}P3DMMB (I, %(7'+yy"+3DII;b P\\m[np	 	 )!$3#'99'$TYYK 0KK\J]_	 	 %Y@#+7(Ay. %Y0@A#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 ; %& !&B$(;; 
5 8K5$8 	
 //"/0<LMH-5* '*::/N!!),+

 "(--N GHH,	.)&G kk)R0G{{:r2H{{:r2H	.*&G kk)R0G{{:r2H{{:r2H
    0?s||||HH+ -YHI
-%

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

8,b%)! O
  X  
	$ /-#&'s1#/	 	
	0  		2  
-i[A3?	


s\   0Z& .A Z6 0A [" >[2 &	Z32Z36	[?[[["	[/.[/2	\;\\c                l!   hijk ddl kddlm}m}m} ddlm}	 ddlm}
m	}m
}m}m}m} g }t               }t               }t               hdOdjdPhjkfd	}dQkfd
idRi fd} |       }t        |      }|
D cg c]  \  }}|	 c}}|d<    |       |d<   d|vrddlm}  |       |d<   d|vrkj"                  j%                  d      s<kj"                  j%                  d      s! j'                         j)                         dk(  rddlm} ddlm}  j'                         j)                         dk(  }kj"                  j%                  d      xs |r|r|ndxs d}	  |kj"                  j%                  dd      |d      }|s|r|r|g}||d<   |j/                         D ]z  \  }} | |v r|j%                  |       }!t1        |!t              s.|	j%                  |      }"|"r|"j2                  dk7  rQ|"r"|"j4                  rt7        |"j4                        }#n#|!j%                  dg       }#t1        |#t6              st9        kfd|#D              }$|$s&	 ddlm}%  |%       }&|&r||&j%                  di       v rd}$|$s|j%                  |g       }'||v r	 |||'      }'t?        |'      }(|'d| })|}* ||       }+|+r|+j@                  n| },|jC                  |*|,|* k(  xs |  k(  d|)|(d d!       |jE                  |*j)                                |jE                  |         ||*       } dd"l#m$}- ddlm}. |j/                         D /0ci c]  \  }/}0|0|/
 }1}/}0|-j/                         D ]  \  }2}3|2j)                         |v r|1j%                  |2|2      }4|4j)                         |v r?d}$|3j2                  d#k(  r	 ||4      }$n*|3jJ                  rt9        kfd$|3jJ                  D              }$|$s[|3j2                  dk(  rL|2|4fD ]E  }5|.j%                  |5      }6|6s|6j4                  s$t9        kfd%|6j4                  D              sCd}$ n |$s,	 ddlm}%  |%       }&|&j%                  d&i       }7|&r
|2|7v s|4|7v rd}$|$s!	 dd(l(m)}9  |9|4      }:|:jU                         rd}$|$sD|4d*k(  r?	 dd+l+m,};m-}<  |<       }= |;       }>|=r|=j%                  d,      s|>r|>j%                  d,      rd}$|$s{|4d.v r	 ||4      }'n{|3j2                  d#k(  r9	 dd/l.m/}?  |?       }@|@@n%|j%                  |4g       xs |j%                  |2g       }'n3|j%                  |4g       xs |j%                  |2g       }'|4|v r	 ||4|'      }'t?        |'      }(|'d| })|jC                  |4ta        |4      |4 k(  xs |2 k(  d|)|(d0d!       |jE                  |2j)                                |jE                  |4j)                                 ||4        	 dd1lm1}A AD ]  }B|Bjf                  j)                         |v r!|.j%                  Bjf                        }Cd}D|Cr*Cj4                  rt9        kfd2Cj4                  D              }DDs2	 ddlm}%  |%       }E|Ej%                  d&i       }F|ErBjf                  Fv rd}DDs+	 dd(l(m)}9  |9Bjf                        }G|GjU                         rd}DDs$Cr"ti        Cd3d      d#k(  r |Bjf                        }DDsCr?ti        Cd3d      d#k(  r/	 dd/l.m/}?  |?       }@|@@n|j%                  Bjf                  g       }Hn|j%                  Bjf                  g       }Ht?        H      }I|Hd| }J|jC                  Bjf                  |Bjj                  |Bjf                   k(  d|J|Id4d!       |jE                  |Bjf                  j)                                 ||Bjf                          t               }K|ret1        |t              rT|j/                         D ]@  \  }L}Mt1        |Mt              sLj)                         |v r+Mj%                  d5d      xs L},Mj%                  d6d      xs* Mj%                  d7d      xs Mj%                  dd      xs d}NMj%                  d8d      xs Mj%                  d9d      }Og }P|OrPjC                  O       Mj%                  d:g       }Qt1        |Qt              r!QD ]  }R|RsRPvsPjC                  R        n0t1        Qt6              r QD ]  }R|RsRPvsPjC                  R        PsMtm        N      j'                         j)                         }Sd;|Sv r"|j%                  d<      xs g }T|Trt7        T      }Ptm        Mj%                  dd      xs d      j'                         }U|Us[tm        Mj%                  d=d      xs d      j'                         }V|Vr*kj"                  j%                  Vd      j'                         nd}UMj%                  d>d      }Wt1        |Wtl              rWj)                         d?v}WNrUrWr	 dd@lm7}X  |XUN      }Y|YrY}P|jC                  L|,|L k(  dP|Prt?        P      nddANdB       |jE                  Lj)                                |jE                  tq        |,      j)                                tm        |,      j'                         j)                         tm        |N      j'                         js                  dC      j)                         f}Z|Zd   s)ZdD   s0KjE                  Z       C |rt1        |t6              rddEl:m;}[  |[       }\|D ]  }]t1        |]t              s]j%                  d5      xs dj'                         }^]j%                  d6d      xs* ]j%                  dd      xs ]j%                  d7d      xs dj'                         js                  dC      }N^rNs]j%                  d      xs dj'                         }UN|Uf}_|_\vr^},dFD ]*  }`|`|,v s|,jy                  `      d   j'                         }, n |,s^},|r7N|j'                         js                  dC      k(  r r dGk7  r n
tq        |,      }*ntq        |,      }*|*|,Ng dH\_<   ]j%                  d9      xs dj'                         }O|Or!O\_   d:   vr\_   d:   jC                  O       ]j%                  d:i       }Qt1        |Qt              r.QD ]'  }R|RsR\_   d:   vs\_   d:   jC                  R       ) t1        Qt6              sQD ]'  }R|RsR\_   d:   vs\_   d:   jC                  R       )  t               }a\j/                         D ]  \  }b}c|b\  }N}U|cd   }*|*j)                         |v r|*j)                         avr6|*j)                         av rD|*}ddI}ed dJe j)                         |v redDz  }ed dJ|e j)                         |v rd dJe }*|*cd<   tm        cd5         j'                         j)                         tm        |cdK         j'                         js                  dC      j)                         f}f|fd   r
fdD   rfKv rfdD   }g|grghv rNr'Ur%	 dd@lm7}X  |XUN      }Y|YrYcd:<   t?        |Y      |cdL<   |jC                  |*cd5   |* k(  d|cd:   t?        |cd:         dA|cdK   dB       |jE                  |*j)                                ajE                  |*j)                                 |j{                  dM N       |S c c}}w # |$ r g }Y w xY w# t<        $ r Y w xY wc c}0}/w # t<        $ r"}8tL        jO                  d'|2|8       Y d}8~84d}8~8ww xY w# t<        $ r"}8tL        jO                  d)|4|8       Y d}8~8?d}8~8ww xY w# t<        $ r!}8tL        jO                  d-|8       Y d}8~8&d}8~8ww xY w# t<        $ r* |j%                  |4g       xs |j%                  |2g       }'Y 
w xY w# td        $ r g }AY 
Zw xY w# t<        $ r Y 	w xY w# t<        $ r Y 	w xY w# t<        $ r  |j%                  Bjf                  g       }HY 	Sw xY w# t<        $ r Y Yw xY w# t<        $ r Y w xY w)Su  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.
    r   N)PROVIDER_TO_MODELS_DEVfetch_models_devget_provider_infoPROVIDER_REGISTRY)OPENROUTER_MODELSr   _MODELS_DEV_PREFERRED_merge_with_models_devprovider_model_idsget_curated_nous_model_idsurlc                v    t        | xs d      j                         j                  d      j                         S )Nr   rV   )r"   r]   r   r^   )r7  s    r   	_norm_urlz/list_authenticated_providers.<locals>._norm_urlG  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   r0  Nr   base_url_env_varinference_base_url)hermes_cli.authr1  r`   rZ   getattrenvironr;  add)r   _regpcfgr7  normed_builtin_endpointsr9  oss        r   _record_builtin_endpointz>list_authenticated_providers.<locals>._record_builtin_endpointJ  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?  rZ   r]   )r   rd   rE  s     r   r   zQlist_authenticated_providers.<locals>._has_fast_aws_sdk_signal.<locals>.<genexpr>o  s1      
 JJNN4$**,
s   47)AWS_PROFILE&AWS_CONTAINER_CREDENTIALS_RELATIVE_URI"AWS_CONTAINER_CREDENTIALS_FULL_URIAWS_WEB_IDENTITY_TOKEN_FILE)r?  rZ   r]   r  )rE  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_adapterrR  r   r`   )r   	slug_normcurrent_normrR  rP  rh   s       r   _has_aws_sdk_creds_for_listingzDlist_authenticated_providers.<locals>._has_aws_sdk_creds_for_listingy  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/v1r   g      ?)rv   rM   timeoutrv   envc              3  T   K   | ]  }j                   j                  |       ! y wr   r?  rZ   r   evrE  s     r   r   z/list_authenticated_providers.<locals>.<genexpr>  s     >r

r*>   %()_load_auth_storecredential_poolTFzbuilt-in)r   rd   
is_currentis_user_definedr   total_modelssource)HERMES_OVERLAYSaws_sdkc              3  T   K   | ]  }j                   j                  |       ! y wr   r`  ra  s     r   r   z/list_authenticated_providers.<locals>.<genexpr>  s     P2BJJNN2.Prc  c              3  T   K   | ]  }j                   j                  |       ! y wr   r`  ra  s     r   r   z/list_authenticated_providers.<locals>.<genexpr>  s     N"2::>>"-Nrc  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-codexr   )bedrock_model_ids_or_nonehermes)CANONICAL_PROVIDERSc              3  T   K   | ]  }j                   j                  |       ! y wr   r`  ra  s     r   r   z/list_authenticated_providers.<locals>.<genexpr>~  s     Yr

r 2Yrc  	auth_type	canonicalrd   rM   apidefault_modelrK   r   zapi.openai.comr2   key_envdiscover_models>   0nofalse)fetch_api_modelszuser-config)r   rd   rf  rg  r   rh  ri  api_urlrV   rW   )OrderedDict)u   —z - rT   )r   rd   r  r   r   r   r  rh  c                    | d    | d    fS )Nrf  rh  r+   )rs    r   r   z.list_authenticated_providers.<locals>.<lambda>  s    AlO 3a6G5GH r   r   )r7  r"   returnr"   )r   r"   r  None)r  r   )r   r"   r  r   )>rE  agent.models_devr-  r.  r/  r=  r1  r   r2  r   r3  r4  r5  r6  setrX   rW  r?  rZ   r]   r^   r[  r\  r\   r[   rx  api_key_env_varsr  r  rd  r`   r   rd   r   r@  hermes_cli.providersrj  extra_env_varsr  r  agent.credential_poolrn  has_credentialsagent.anthropic_adapterro  rp  rS  rt  r
   rv  ImportErrorr   r>  labelr"   r  r   r   collectionsr  r_   r   )lrh   r  r   r   r   r  r-  r.  _mdev_pinfor1  r2  r   r3  r4  r5  r6  results
seen_slugsseen_mdev_idsrF  rV  datacuratedr   r  rW  r[  r\  is_current_lmstudiolm_baselive	hermes_idmdev_idpdatapconfigenv_vars	has_credsrd  store	model_idstotaltopr   pinfodisplay_namerj  _auth_registrykv_mdev_to_hermespidoverlayhermes_slug_keyrB  providers_storeexcrn  poolro  rp  hermes_credscc_credsrt  _ids_canon_provs_cp
_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"  r   	url_lowerfbrv   r|  discoverr  live_models_pairr  groupsre   raw_name	group_keysep_section4_emitted_slugsgrp_keygrp	base_slugr   	_pair_key_grp_url_normrD  rP  r9  rE  sl   `                                                                                                       @@@@r   r   r     s   2  
 2  GeJM "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"
 5::< ?'	7 m#!%&
 $''	2 w((I5w//G445Hyy+Hh- >X>>	<(*Y%))4Er*JJ $I  KK	2.	--.y)DII$G$%*uzz "22QgAQ6Q$! 
 	 	tzz|$'" &?'D 5C
 )?(D(D(FG1q!tGOG'--/ m.W99;*$ &))#s3*, 		)6{CI##P9O9OPPIW..);k* %))$/D11N8M8MNN$(	 M<(*"'))K"<c_48V $I Z; -'') $I [K7O  =>79 \%5%5m%Dm!< $I DD +;7I )+QK02$($4D7;;{TV;W;o[b[f[fgjln[o	
  K4LC8LI332;	J	I$k*%)99TSDT=T$!
 	 	syy{#{((*+ -[m.fI  =+88>>z) $''1
*55YZ=X=XYYM<,.	&/mmK&D#-@!@$(M ;$SXX.++-$(M 
KQS0TXa0a:388DM '*k2>)K:K02(,(8gkk#((TV>W $KK"5M&	,HHII((&66$%!
 	 	sxx~~'( *{=+L $'5*^T:-335 U	3OGVfd+ }}*,!::fb1<WL
 

:r* ::eR(::eR( 	  #JJ;Vvzz'SU?VM K""=1
  Hb1J*d+# .AQk1#**1-. J-# .AQk1#**1-. 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7xB"27G"DK"&1 NN$%)99#'%4?K 0Q'"	 	 NN7==?+NN/=CCEFL!'')//1G""$++C0668E QxE!H'++E2kU	3J J'7>+
 .9]% E	>EeT*		&)/R668H		*b) 99UB'99UB' egffSk  7yy+1r88:G '*I&
  () Cl*'3'9'9#'>q'A'G'G'I $#+L
 %#3#9#9#;#B#B3#GG ,0@H0L )1,?  0=D (& 	%y! #YYw/52<<>MfY6G6Q!Qy!(+22=A8R0J*d+# >AQfY&7&AAy)(3::1=> J-# >AQfY&7&AAy)(3::1=>GE	>N (+u"LLN >	6LGS&GW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 7B"27G"DK"(3H.1+.>N+ NNF"&66#'h- #CM 2'y>	 	 NN4::<(#''

5}>	6B LLHLINU BD  	D	P  N HH  MA3LLM  ZFUXYYZ(  OH#NNO*  Q#KKR8PGKKR<P	Q<  (    &  : 'CHHb 9:z ! H ! s  :}%} %}#=}3 +}9. ~'>57A@&A@8 1AA
*AA5-AA*6AB#$AB&} } #	}0/}09	~$~~$'	0	?::?@/A@5@4A@5@8AAAAAA
	AAAAAA	AA'A&AA'A*%ABBABB	AB#B"AB#B&	AB3B2AB3c                H   ddl m} t        | |||||      }g }|D ]  }	t        |	j	                  dd            j                         }
|
dk(  r=	  |       }|D cg c]  \  }}|	 }}}t        |	      }	|d| |	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_models)rh   r  r   r   r   r  r   r   r   r   Nrh  rg  r  )r   r  r   r"   rZ   r^   r`   r  rX   r   r   r   )rh   r  r   r   r   r  r  r   r   r   r   r  r   r  live_ids
has_modelsis_custom_endpoints                    r   list_picker_providersr    s+   6 :,))%)#I H 155$%++-<5.0.23FCC33 QA";J/AhK #HAn!%%/*
!!%%(9":;VQUU9EU@V"4!$ O 4 5h 345s$   C:C4&C:4C::$D! D!)r   r"   r  r   )r   r"   r  r"   )r  rN   )r  r  )r   r"   r  ztuple[str, str, bool])r   r"   r   r"   r  r   )r   r"   rh   r"   r  Optional[tuple[str, str, str]])r   NN)rh   r"   r   rX   r   list | Noner  	list[str])r+   )r   r"   r   r  r  r  )r   r   NNN)rK   r"   rL   r"   rM   r"   rv   r"   r~   r}   r   r  r   z
int | Noner  zOptional[int])r   r   Fr   NN)r   r"   rh   r"   r  r"   r  r"   r	  r"   r   r   r   r"   r   rX   r   r  r  rq   )r   r   NN   r   )rh   r"   r  r"   r   rX   r   r  r   r   r  r"   r  z
List[dict])5r)   
__future__r   loggingr   dataclassesr   typingr   r   r   r  r   r	   r
   r   r   hermes_cli.model_normalizer   r  r   r   r   r   r   	getLoggerr&   r  r   compile
IGNORECASEr   r   r   r!   rH   r*   rJ   rO   rP   rl   ro   rq   r   r   r   r   r   r   r   r+  r   r  r+   r   r   <module>r     s  ( #  	 ! - -   
		8	$-   )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    &   '7\h/VJ/J/J/ $J/\ $( " 	0 *,& $( &*$((,*** * 	*
 $* "* &* *j $(iii i 	i
 i i i i "i ib $(^
^
^
 ^
 "	^

 ^
 ^
 ^
D $(999 9 "	9
 9 9 9r   