
    `gjJf                       d Z ddlmZ ddlmZmZ ddlmZ  ed       G d d             Zdd	Z	d
d
d
d
d
d
d
d
dd
dd	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 ddZ
ddZd
d	 	 	 	 	 	 	 ddZddZddZddZddZd
d	 	 	 	 	 ddZd d!dZy)"u  Provider/model inventory context — shared substrate for the dashboard
``/api/model/options``, the TUI ``model.options``/``model.save_key``
JSON-RPC handlers, and the interactive picker.

Before this module the three call-sites each duplicated:

1. The 17-LOC config-slice that pulls ``model.{default,name,provider,base_url}``,
   ``providers:``, and ``custom_providers:`` out of ``load_config()``;
2. The call into ``list_authenticated_providers`` with the resulting kwargs;
3. (TUI only) a 45-LOC post-pass that merges authenticated rows with
   unconfigured ``CANONICAL_PROVIDERS`` rows and emits ``authenticated``/
   ``auth_type``/``key_env``/``warning`` hints for the picker UI.

Consolidating those three steps into one entry point eliminates two bugs
the duplicates were hiding:

- The dashboard read ``cfg.get("custom_providers")`` directly, missing the
  v12+ keyed ``providers:`` form (which the TUI handled via
  ``get_compatible_custom_providers``).
- The TUI's canonical-merge keyed on ``is_user_defined`` to decide
  ordering. Section 3 of ``list_authenticated_providers`` sets
  ``is_user_defined=True`` even for canonical slugs that appear in the
  ``providers:`` config dict, which silently demoted them to the tail of
  the picker. ``_reorder_canonical`` keys on slug membership instead.

Substrate facts (verified May 2026):
- ``list_authenticated_providers`` already populates each row's
  ``models`` from the curated catalog (same source as the picker). Do
  NOT call ``provider_model_ids()`` per row to "freshen" — that bypasses
  curation and pulls in non-agentic models (Nous /models returns ~400
  IDs including TTS, embeddings, rerankers, image/video generators).
    )annotations)	dataclassreplace)OptionalT)frozenc                  r    e Zd ZU dZded<   ded<   ded<   ded<   ded	<   d
Zded<   d
d
d
d	 	 	 	 	 	 	 ddZy
)ConfigContextzSnapshot of the model + provider config every inventory caller
    needs. Built once via ``load_picker_context()``; the TUI overlays
    live agent state via ``with_overrides()`` before passing through.
    strcurrent_providercurrent_modelcurrent_base_urldictuser_providerslistcustom_providersNexcluded_providers)r   r   r   c               P    i }|r||d<   |r||d<   |r||d<   |rt        | fi |S | S )u   Return a copy with truthy overrides applied.

        Truthy-only because the TUI reads agent attributes that may be
        empty strings before an agent is spawned — empties must NOT
        clobber the disk-config values.
        r   r   r   )r   )selfr   r   r   kws        G/root/.hermes/venv/lib/python3.12/site-packages/hermes_cli/inventory.pywith_overrideszConfigContext.with_overrides9   sI     %5B!""/B%5B!"&(wt"r"2d2    )r   Optional[str]r   r   r   r   returnz'ConfigContext')__name__
__module____qualname____doc____annotations__r   r    r   r   r	   r	   +   sj    
 ##
 +/'+*.3 (3 %	3
 (3 
3r   r	   c                    ddl m} m}  |       }|j                  di       }t	        |t
              rS|j                  d|j                  dd            xs d}|j                  dd      xs d}|j                  dd      xs d}n|rt        |      nd}d}d}|j                  d	      }|j                  d
i       j                  d      xs g }t        |||t	        |t
              r|ni  | |      t	        |t              r|      S g       S )u   Load the disk-config snapshot every consumer needs.

    Replaces the inline 17-LOC config-slice that ``web_server.py`` and
    ``tui_gateway/server.py`` (×2 sites) used to do.
    r   )get_compatible_custom_providersload_configmodeldefaultname providerbase_url	providersmodel_catalogr   )r   r   r   r   r   r   )	hermes_cli.configr"   r#   get
isinstancer   r
   r	   r   )	r"   r#   cfg	model_cfgr   r   r   rawexcludeds	            r   load_picker_contextr3   P   s    O
-C$I)T"!ivr1JKQr$==R8>B$==R8>B +4I
''+
Cww+//0DEKH)#)(d3s8='1(D'A8  HJ r   FN)explicit_onlyinclude_unconfiguredpicker_hintscanonical_orderpricingcapabilitiesforce_fresh_nous_tierrefreshprobe_custom_providersprobe_current_custom_provider
max_modelsc                  ddl m}  || j                  | j                  | j                  | j
                  | j                  ||||	|
| j                  xs g       }t        | j                        }|A|g|D cg c]1  }t        |j                  dd            j                         dk7  s0|3 c}z   }|r&t        ||       }t        |      t        || d	      z   }	 dd
lm} |t%               }|D ]?  }|j                  d      s|j'                  d |j                  d      xs g D               A |r|D ]  }|j                  d      r|j                  dd      } ||      s0|j                  d      xs g }|D cg c]  }|j                         |vs| }}t)        |      t)        |      k  s||d<   t)        |      |d<    |rSt        |      t        ||       D cg c]1  }t        |j                  dd            j                         dk7  s0|3 c}z   }|rt+        |       |rt-        |      }|rt/        ||       |rt1        |       || j                  | j                  dS c c}w # t"        $ r d}Y w xY wc c}w c c}w )u|
  Build the ``{providers, model, provider}`` shape every consumer
    needs from a single substrate call.

    Flags:
    - ``explicit_only``: keep only providers the user explicitly configured
      (current provider, providers from config, or providers backed by
      provider-specific env vars). This hides ambient / auto-seeded
      credentials from desktop chat pickers.
    - ``include_unconfigured``: append ``CANONICAL_PROVIDERS`` rows that
      ``list_authenticated_providers`` didn't emit (TUI uses this to show
      the full provider universe in the picker).
    - ``picker_hints``: add ``authenticated``/``auth_type``/``key_env``/
      ``warning`` per row (TUI ``ModelPickerDialog`` shape).
    - ``canonical_order``: reorder canonical-slug rows to
      ``CANONICAL_PROVIDERS`` declaration order; truly-custom rows go
      last (TUI display order).
    - ``pricing``: enrich each row with formatted per-model pricing and,
      for Nous, ``free_tier``/``unavailable_models`` so the GUI picker can
      show $/Mtok columns and gate paid models on free accounts —
      mirroring the ``hermes model`` CLI picker. Adds network calls
      (pricing fetch + Nous tier check); only set for interactive pickers.
    - ``capabilities``: add a per-row ``capabilities`` map
      ``{model: {fast, reasoning}}`` so pickers can gate the model-options
      controls (fast toggle / reasoning) to what each model actually
      supports, instead of offering knobs the backend would reject.
    - ``force_fresh_nous_tier``: bypass the short Nous free-tier cache when
      selecting Portal-recommended Nous models and applying tier gating. Keep
      this false for UI picker opens; explicit auth/model flows can opt in
      when they need freshly-purchased credits to show up immediately.
    - ``refresh``: bust the per-provider model-id disk cache so every row
      re-fetches its live catalog. Set only for an explicit user-triggered
      "refresh models" action; normal picker opens leave it false to stay
      snappy on the 1h cache.
    - ``probe_custom_providers``: allow saved custom/provider endpoints to
      run live ``/models`` discovery while building the payload. GUI picker
      opens should leave this false unless the user explicitly refreshes; the
      row can still render its configured model immediately, and slow/offline
      local endpoints no longer block the dialog.
    - ``probe_current_custom_provider``: when ``probe_custom_providers`` is
      false, still live-probe the current custom endpoint. This keeps normal
      GUI/TUI picker opens fast while making the active custom provider's model
      list match the classic CLI picker.
    r   )list_authenticated_providers)r   r   r   r   r   r:   r>   r;   r<   r=   r   Nslugr'   moaTcurrent_only)is_routing_aggregatoris_user_definedc              3  <   K   | ]  }|j                           y wN)lower).0ms     r   	<genexpr>z'build_models_payload.<locals>.<genexpr>   s     "P1779"Ps   modelstotal_modelsr:   )r*   r$   r(   )hermes_cli.model_switchr@   r   r   r   r   r   r   _moa_provider_rowr
   r-   rI   _filter_explicit_provider_rowsr   _append_unconfigured_rowshermes_cli.providersrE   	Exceptionsetupdatelen_apply_picker_hints_reorder_canonical_apply_pricing_apply_capabilities)ctxr4   r5   r6   r7   r8   r9   r:   r;   r<   r=   r>   r@   rowsmoa_rowr_is_routing_aggregatoruser_modelsrowrA   originalrK   filtereds                          r   build_models_payloadrf   r   s   t E'----''))--35&C117RD   4 45GytW!s1553D/E/K/K/MQV/VAWW-dC8 Dz5#D
 
&X ) # 	QCww()"""Pswwx7H7NB"PP	Q  8 77,-wwvr* .d3778,2'/P!1779K3OAPPx=3x=0$,CM*-h-C'/82 Dz(A$(Lx1PSTUTYTYZ`bdTePfPlPlPnrwPwQxxD!!$'t3HID! ""(( K X0  &!%&: Q ys6   71I.)I.I3 >JJ#1J
J
3JJc                X   ddl m} 	 ddlm} | D ]v  }|j                  d      xs d}i }|j                  d      xs g D ]@  }d}|#|r!	  |||      }|t        |j                        }t         ||            |d	||<   B ||d
<   x y# t        $ r d}Y w xY w# t        $ r d}Y ?w xY w)u  Attach a ``{model: {fast, reasoning}}`` map to each provider row.

    `fast` mirrors ``model_supports_fast_mode`` (the same gate the runtime
    enforces). `reasoning` comes from the models.dev catalog when known and
    defaults to True otherwise — the effort dial is broadly accepted and a
    no-op on models that ignore it, whereas hiding it from a capable-but-
    uncatalogued model is the worse failure.
    r   )model_supports_fast_mode)get_model_capabilitiesNrA   r'   rM   T)fast	reasoningr9   )hermes_cli.modelsrh   agent.models_devri   rU   r-   boolsupports_reasoning)	r^   rh   ri   rc   rA   capsr$   rk   metas	            r   r\   r\   
  s     ;&;  #wwv$"+-WWX&," 	EI%1d%1$>D'$()@)@$A	
 5e<=&DK	 #N'#  &!%& ! % $I%s#   B
 	 B
BBB)(B)rC   c                  ddl m} ddlm}m} | D ch c]  }|d   j                          }}|j                  xs dj                         }t        |j                  xs d      j                         }	g }
|D ]n  }|j                  j                         |v r!|r|j                  j                         |k7  rA|j                  j                         |k(  r|j                  |j                        }|r|j                  nd}|r|j                  r|j                  d   nd}|dk(  r|rd| dnd	}|
j                  |j                  |j                  |j                  |j                        d
d|	r|	gng |	rdnddd|||d       |
j                  |j                  |j                  |j                  |j                        |j                  j                         |k(  dg ddd       q |
S c c}w )a  Build fallback rows for canonical providers missing from ``rows``.

    Most missing canonical providers become empty setup skeletons. The one
    exception is the *current* configured provider: if config.yaml still points
    at it but credentials are presently unavailable, keep a visible row carrying
    the saved model so GUI pickers don't silently snap to some other provider.
    r   PROVIDER_REGISTRY)CANONICAL_PROVIDERS_PROVIDER_LABELSrA   r'   api_keyz6Configured provider missing usable credentials; paste z- to reactivate. Showing the saved model only.ziConfigured provider is not authenticated; run `hermes model` to reactivate. Showing the saved model only.TF   zconfigured-current)rA   r&   
is_currentrF   rM   rN   sourceauthenticated	auth_typekey_envwarning	canonical)rA   r&   ry   rF   rM   rN   rz   )hermes_cli.authrt   rl   ru   rv   rI   r   r
   r   striprA   r-   r|   api_key_env_varsappendlabel)r^   r]   rD   rt   ru   rv   r`   seencur	cur_modelextrasentryr/   r|   r}   r~   s                   r   rS   rS   3  s    2G'+,!AfIOO,D,%2
,
,
.CC%%+,224IF$ .
::%EJJ,,.#5::$#''

3C),)I C00 $$Q'  	)g I	 R0 00	  MM!JJ,00U[[I"&',-6ykB)2A2%*!*&& 

(,,UZZE#jj..0C7#( !%
	
I.
^ Mg -s   G,c                   ddl m} t        |j                  xs d      j	                         j                         }g }| D ]  }t        |j                  dd            j	                         j                         }|s=|j                  d      r|j                  |       `|r||k(  r|j                  |       y|dk(  rt               r|j                  |        ||      s|j                  |        |S )a'  Keep only rows backed by explicit user configuration.

    ``list_authenticated_providers`` intentionally discovers ambient / auto-
    seeded credentials (for example GitHub CLI -> Copilot). Desktop chat model
    pickers want the narrower subset the user explicitly configured for Hermes.
    r   )!is_provider_explicitly_configuredr'   rA   rF   rB   )	r   r   r
   r   r   rI   r-   r   "_raw_config_has_enabled_moa_preset)r^   r]   r   current_slugkeptrc   rA   s          r   rR   rR   y  s     Bs++1r288:@@BLD 37762&'--/55777$%KKDL0KK5= 23C ,T2KK)* Kr   c                 
   	 ddl m}   |        }t        |t              sy|j                  d      t        t              syj                  d      }t        |t              r]|j                         D ]I  \  }}t        |xs d      j                         s$t        |t              s y|j                  dd      sI y yh d	}t        fd
|D              xr t        j                  dd            S # t        $ r Y yw xY w)a~  Return True when the user's raw config explicitly enables MoA.

    ``load_config()`` includes ``DEFAULT_CONFIG["moa"].presets.default`` for
    everyone. Explicit-only model pickers must not treat that default as a user
    choice, but they should keep MoA visible once the user has saved at least
    one enabled preset (or an older flat MoA config) in their own config.yaml.
    r   )read_raw_configFrB   presetsr'   Tenabled>   fanout
aggregator
max_tokensreference_modelsreference_max_tokensreference_temperatureaggregator_temperaturec              3  &   K   | ]  }|v  
 y wrH   r    )rJ   keyrB   s     r   rL   z5_raw_config_has_enabled_moa_preset.<locals>.<genexpr>  s     1cscz1s   )r,   r   rU   r.   r   r-   itemsr
   r   anyrn   )r   r1   r   r&   presetlegacy_keysrB   s         @r   r   r     s    5 c4 
''%.Cc4 ggi G'4 #MMO 	LD&tzr?((*fd+zz)T*	 K 1[11Td3779d;S6TT;  s   C6 6	DDc                r   ddl m} | D ]  }d|v r|j                  d      dk(  xr |j                  d       }| |d<   |r|j                  d      rJ|j                  |d         }|r|j                  nd	}|r|j                  r|j                  d   nd
}||d<   ||d<   |d	k(  r|rd| dnd| d|d<    y)a0  Add ``authenticated``/``auth_type``/``key_env``/``warning`` per row.

    Mutates ``rows`` in-place. Rows already from
    ``list_authenticated_providers`` are marked ``authenticated=True``;
    the unconfigured skeleton rows from ``_append_unconfigured_rows`` get
    the picker's setup-hint shape.
    r   rs   r{   rz   r   rM   rF   rA   rw   r'   r|   r}   zpaste z to activatez!run `hermes model` to configure ()r~   N)r   rt   r-   r|   r   )r^   rt   rc   is_skeletonr/   r|   r}   s          r   rY   rY     s     2 
c! ggh';6Pswwx?P;P#.Ocgg&78##CK0%(CMMi	 ,,   # 	
 %K I I%' WI\*4YKqA 	I+
r   c                    ddl m} t        |      D ci c]  \  }}|j                  | c}}t	        fd| D        fd      }| D cg c]  }|d   vs| }}||z   S c c}}w c c}w )u  Canonical slugs in ``CANONICAL_PROVIDERS`` declaration order;
    truly-custom rows last.

    Keys on slug membership, NOT ``is_user_defined`` — section 3 of
    ``list_authenticated_providers`` sets ``is_user_defined=True`` on
    rows from the ``providers:`` config dict even when the slug is
    canonical. Keying on the flag would silently demote canonical
    providers configured via the new keyed schema.
    r   )ru   c              3  2   K   | ]  }|d    v s|  yw)rA   Nr    )rJ   r`   orders     r   rL   z%_reorder_canonical.<locals>.<genexpr>  s     /qAfI./s   c                    | d      S )NrA   r    )r`   r   s    r   <lambda>z$_reorder_canonical.<locals>.<lambda>  s    eAfI& r   )r   rA   )rl   ru   	enumeraterA   sorted)r^   ru   iecanonr`   r   r   s          @r   rZ   rZ     st     6#,-@#AB41aQVVQYBE/D/&E 8A6%!7a8F86> C
 9s   A&A,A,rO   c                  ddl m}m}m}m} d}| D ]9  }t        |j                  dd            j                         }|j                  d      xs g }	|	sE	  ||      xs i }
|
sUi }|	D ]  }|
j                  |      }|s|j                  dd      }|j                  dd      }|j                  d	d      }|dk7  r ||      nd}|dk7  r ||      nd}|r ||      nd}|d
k(  xr |d
k(  xs |dk(  }||||d||<    |r||d<   |dk(  s	 |	 ||      }t        |      |d<   |r |t        |	      |
d      \  }}||d<   ng |d<   < y# t        $ r i }
Y w xY w# t        $ r d|d<   g |d<   Y ew xY w)u  Enrich each provider row with per-model pricing + Nous tier gating.

    Mutates ``rows`` in-place. For every row whose provider supports live
    pricing (openrouter / nous / novita) adds::

        row["pricing"] = {model_id: {"input": "$3.00", "output": "$15.00",
                                     "cache": "$0.30" | None, "free": bool}}

    For Nous additionally adds::

        row["free_tier"] = bool            # current account is free-tier
        row["unavailable_models"] = [...]  # paid models a free user can't pick

    Prices are pre-formatted via ``_format_price_per_mtok`` so the GUI just
    renders strings — identical formatting to the CLI picker. All failures
    are swallowed (best-effort): a row simply gets no ``pricing`` key.
    r   )_format_price_per_mtokcheck_nous_free_tierget_pricing_for_providerpartition_nous_models_by_tierNrA   r'   rM   prompt
completioninput_cache_readfree)inputoutputcacher   r8   nous)force_fresh	free_tierT)r   unavailable_modelsF)rl   r   r   r   r   r
   r-   rI   rU   rn   r   )r^   r:   r   r   r   r   nous_free_tierrc   rA   rM   raw_pricing	formattedmidpinp_rawout_raw	cache_rawinpoutr   is_free_selectableunavailables                          r   r[   r[     s   ,  &*N 5/37762&'--/"(b	248>BK %'	 	C$AeeHb)GeeL"-G0"5I5<](1C5<](1C9B*95EVmD)C#)G	IcN	& &C	N6>/!)%9$9&N $(#7K !/LVkT0,K 1<C,-02C,-a5/  	K	T  / $)K ,.()	/s$   E=E EE E76E7c                (   	 ddl m} ddlm}  | |       j	                  d      xs i       }t        |j	                  di       j                               }|sydd| xs dj                         dk(  d	|t        |      d
dd
dd
S # t        $ r Y yw xY w)a+  Build the virtual ``moa`` provider row for model pickers.

    Shared by the CLI inventory (:func:`build_models_payload`) and the gateway
    picker path (:func:`hermes_cli.model_switch.list_picker_providers`) so the
    row shape stays in one place. Returns ``None`` when no MoA presets exist.
    r   )r#   )normalize_moa_configrB   r   NzMixture of Agentsr'   FvirtualTzTAggregator acts as the selected model; references provide analysis before each call.)
rA   r&   ry   rF   rM   rN   rz   r{   r|   r~   )
r,   r#   hermes_cli.moa_configr   r-   r   keysrI   rX   rU   )r   r#   r   r/   rM   s        r   rQ   rQ   [  s    1>";=#4#4U#;#ArBcggi,1134'+1r88:eC$K!"m
 	
  s   AB *B 	BB)r   r	   )r]   r	   r4   rn   r5   rn   r6   rn   r7   rn   r8   rn   r9   rn   r:   rn   r;   rn   r<   rn   r=   rn   r>   z
int | Noner   r   )r^   
list[dict]r   None)r^   r   r]   r	   rD   rn   r   r   )r^   r   r]   r	   r   r   )r   rn   )r^   r   r   r   )r^   r   r:   rn   r   r   )r'   )r   r
   r   zdict | None)r   
__future__r   dataclassesr   r   typingr   r	   r3   rf   r\   rS   rR   r   rY   rZ   r[   rQ   r    r   r   <module>r      st  B # *  $!3 !3 !3HJ  !&!"'#'*/!U	U U 	U
 U U U U  U U !U $(U U 
Up##Z 	C
C	C 	C
 CL F)UX#
L0 #(U/
U/  U/ 
	U/pr   