§
    øžyj–Ž  ã                  ó.  — 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œd>d„Z
dddd œd?d!„Zd"d"d"dd#œd@d)„Zd"d"d*œdAd-„ZdBd/„Zd0ZdBd1„Zdd2œdCd4„ZdDd5„ZdEd6„ZdBd7„ZdFd8„Zdd9œdGd:„ZdHdId<„ZdS )Juž  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                  óf   — 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d
S )ÚConfigContextzÏSnapshot 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   úOptional[str]Úreturnú'ConfigContext'c               óR   — i }|r||d<   |r||d<   |r||d<   |rt          | fi |¤Žn| 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        ú:/home/ragecks/.hermes/hermes-agent/hermes_cli/inventory.pyÚwith_overrideszConfigContext.with_overrides9   sa   € ð ˆØð 	6Ø%5ˆBÐ!Ñ"Øð 	0Ø"/ˆBˆÑØð 	6Ø%5ˆBÐ!Ñ"Ø&(Ð2�w�tÐ"Ð"˜rÐ"Ð"Ð"¨dÐ2ó    )r   r   r   r   r   r   r   r   )Ú__name__Ú
__module__Ú__qualname__Ú__doc__Ú__annotations__r   r   © r   r   r	   r	   +   s™   € € € € € € ðð ð
 ÐÐÑØÐÐÑØÐÐÑØÐÐÑØÐÐÑØ#ÐÐ#Ð#Ð#Ñ#ð
 +/Ø'+Ø*.ð3ð 3ð 3ð 3ð 3ð 3ð 3ð 3r   r	   r   c                 ó|  — ddl m} m}  |¦   «         }|                     di ¦  «        }t	          |t
          ¦  «        r]|                     d|                     dd¦  «        ¦  «        pd}|                     dd¦  «        pd}|                     dd¦  «        pd}n|rt          |¦  «        nd}d}d}|                     d	¦  «        }|                     d
i ¦  «                             d¦  «        pg }t          |||t	          |t
          ¦  «        r|ni  | |¦  «        t	          |t          ¦  «        r|n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_contextr5   P   s\  € ð OÐNÐNÐNÐNÐNÐNÐNà
ˆ+‰-Œ-€CØ—’˜ Ñ$Ô$€IÝ�)�TÑ"Ô"ð Ø!Ÿš i°·²¸vÀrÑ1JÔ1JÑKÔKÐQÈrˆØ$Ÿ=š=¨°RÑ8Ô8Ð>¸BÐØ$Ÿ=š=¨°RÑ8Ô8Ð>¸BÐÐð +4Ð;�˜I™œ˜¸ˆØÐØÐØ
�'Š'�+Ñ
Ô
€CØ�wŠw�¨Ñ+Ô+×/Ò/Ð0DÑEÔEÐKÈ€HÝØ)Ø#Ø)Ý(¨­dÑ3Ô3Ð;�s�s¸Ø8Ð8¸Ñ=Ô=Ý'1°(½DÑ'AÔ'AÐI˜8˜8Àrðñ ô ð r   FN)Úexplicit_onlyÚinclude_unconfiguredÚpicker_hintsÚcanonical_orderÚpricingÚcapabilitiesÚfeaturedÚforce_fresh_nous_tierÚrefreshÚprobe_custom_providersÚprobe_current_custom_providerÚ
for_pickerÚ
max_modelsÚctxr6   Úboolr7   r8   r9   r:   r;   r<   r=   r>   r?   r@   rA   rB   ú
int | Noner   c               ó~  ‡— ddl m}  || j        | j        | j        | j        | j        |||	|
||| j        pg ¬¦  «        }t          | j        ¦  «        }|�|gd„ |D ¦   «         z   }|r2t          || ¦  «        }t          |¦  «        t          || d¬¦  «        z   }	 ddlm} n# t          $ r d}Y nw xY w|�÷t          ¦   «         Š|D ]K}|                     d	¦  «        r4‰                     d
„ |                     d¦  «        pg D ¦   «         ¦  «         ŒL‰r™|D ]–}|                     d	¦  «        rŒ|                     dd¦  «        } ||¦  «        sŒ:|                     d¦  «        pg }ˆfd„|D ¦   «         }t%          |¦  «        t%          |¦  «        k     r||d<   t%          |¦  «        |d<   Œ—|r*t          |¦  «        d„ t          || ¦  «        D ¦   «         z   }|rt'          |¦  «         |rt)          |¦  «        }|rt+          ||¬¦  «         |rt-          |¦  «         |rt/          |¦  «         || j        | j        dœS )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.
    - ``featured``: add a per-row ``featured_models`` list â€” the newest few
      models per lab (by models.dev release_date, ranked within the row's own
      models; see ``_FEATURED_PER_LAB``) for aggregator providers that serve
      dozens of models across many labs. Pickers default their visible set to
      these; the rest of ``models`` stays reachable via search / show-all. Empty
      for single-lab providers (callers fall back to top-N). Derived live from
      models.dev â€” no allowlist.
    - ``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.
    - ``for_picker``: interactive-picker visibility. Keeps providers whose
      credential pool exists but is entirely rate-limited (exhausted) in the
      list. Rate limits are per-model, so a different model under the same
      provider may still work; hiding the provider strands the user. Set for
      any surface a human is choosing from, not for programmatic resolution.
    r   )Úlist_authenticated_providers)r   r   r   r   r   r=   rB   r>   r?   r@   rA   r   Nc                ó„   — g | ]=}t          |                     d d¦  «        ¦  «                             ¦   «         dk    ¯;|‘Œ>S ©Úslugr)   Úmoa©r
   r/   Úlower©Ú.0Úrs     r   ú
<listcomp>z(build_models_payload.<locals>.<listcomp>Í   sE   € ÐWÐWÐW !­s°1·5²5¸ÀÑ3DÔ3DÑ/EÔ/E×/KÒ/KÑ/MÔ/MÐQVÒ/VÐ/V˜AÐ/VÐ/VÐ/Vr   T©Úcurrent_only)Úis_routing_aggregatorÚis_user_definedc              3  ó>   K  — | ]}|                      ¦   «         V — Œd S ©N©rM   )rO   Úms     r   ú	<genexpr>z'build_models_payload.<locals>.<genexpr>ì   s*   è è € Ð"PÐ"P° 1§7¢7¡9¤9Ð"PÐ"PÐ"PÐ"PÐ"PÐ"Pr   ÚmodelsrJ   r)   c                ó@   •— g | ]}|                      ¦   «         ‰v¯|‘ŒS r"   rX   )rO   rY   Úuser_modelss     €r   rQ   z(build_models_payload.<locals>.<listcomp>  s+   ø€ ÐPÐPÐP !°1·7²7±9´9ÀKÐ3OÐ3O˜AÐ3OÐ3OÐ3Or   Útotal_modelsc                ó„   — g | ]=}t          |                     d d¦  «        ¦  «                             ¦   «         dk    ¯;|‘Œ>S rI   rL   rN   s     r   rQ   z(build_models_payload.<locals>.<listcomp>  sK   € ÐxÐxÐx 1ÕPSÐTU×TYÒTYÐZ`ÐbdÑTeÔTeÑPfÔPf×PlÒPlÑPnÔPnÐrwÒPwÐPw˜QÐPwÐPwÐPwr   ©r=   )r,   r&   r*   )Úhermes_cli.model_switchrG   r   r   r   r   r   r   Ú_moa_provider_rowÚ_filter_explicit_provider_rowsr   Ú_append_unconfigured_rowsÚhermes_cli.providersrT   Ú	ExceptionÚsetr/   ÚupdateÚlenÚ_apply_picker_hintsÚ_reorder_canonicalÚ_apply_pricingÚ_apply_capabilitiesÚ_apply_featured)rC   r6   r7   r8   r9   r:   r;   r<   r=   r>   r?   r@   rA   rB   rG   ÚrowsÚmoa_rowÚ_is_routing_aggregatorÚrowrJ   ÚoriginalÚfilteredr]   s                         @r   Úbuild_models_payloadru   r   s  ø€ ðP EÐDÐDÐDÐDÐDà'Ð'ØÔ-ØÔ-ØÔ'ØÔ)ØÔ-Ø3ØØØ5Ø&CØØÔ1Ð7°Rðñ ô €Dõ   Ô 4Ñ5Ô5€GØÐØˆyÐWÐW tÐWÑWÔWÑWˆàð 	
Ý-¨d°CÑ8Ô8ˆõ �D‰zŒzÕ5Ø�# Dð
ñ 
ô 
ñ 
ˆð&ØXÐXÐXÐXÐXÐXÐXøÝð &ð &ð &Ø!%ÐÐÐð&øøøð Ð)Ý #¡¤ˆØð 	Qð 	QˆCØ�wŠwÐ(Ñ)Ô)ð QØ×"Ò"Ð"PÐ"P°s·w²w¸xÑ7HÔ7HÐ7NÈBÐ"PÑ"PÔ"PÑPÔPÐPøØð 	8Øð 8ð 8�ð —7’7Ð,Ñ-Ô-ð ØØ—w’w˜v rÑ*Ô*�ð .Ð-¨dÑ3Ô3ð ØØŸ7š7 8Ñ,Ô,Ð2°�ØPÐPÐPÐP xÐPÑPÔP�Ý�x‘=”=¥3 x¡=¤=Ò0Ð0Ø$,�C˜‘MÝ*-¨h©-¬-�C˜Ñ'øàð yÝ�D‰zŒzÐxÐxÕ(AÀ$ÈÑ(LÔ(LÐxÑxÔxÑxˆØð "Ý˜DÑ!Ô!Ð!Øð (Ý! $Ñ'Ô'ˆØð JÝ�tÐ3HÐIÑIÔIÐIØð "Ý˜DÑ!Ô!Ð!Øð Ý˜ÑÔÐð ØÔ"ØÔ(ðð ð s   ÂB! Â!B0Â/B0)r6   r7   r>   c               óŠ   — t          |¦  «        }t          | t          |¦  «        t          |¦  «        ddddd||| ¬¦  «        S )aÆ  Build the shared API-server/dashboard/TUI model-options payload.

    This wraps ``build_models_payload`` with the stable picker shape and the
    safe custom-provider probe policy used for normal GUI/TUI opens:

    - normal open: probe only the current custom provider so offline saved
      endpoints do not block the picker
    - explicit refresh: probe every custom provider while busting the model
      cache so live catalogs repopulate fully
    T)
r6   r7   r8   r9   r:   r;   r<   r>   r?   r@   )rD   ru   )rC   r6   r7   r>   s       r   Úbuild_model_options_payloadrw     sZ   € õ" �7‰mŒm€GÝØÝ˜=Ñ)Ô)Ý!Ð"6Ñ7Ô7ØØØØØØØ&Ø*1 kðñ ô ð r   r)   )r   r   r   rB   r   r
   r   r   ú
list[dict]c                ó–   — t          ¦   «                              | ||¬¦  «        }t          |ddd|¬¦  «        d         }d„ |D ¦   «         S )uc  Provider rows for any auxiliary-task picker (vision, compression, â€¦).

    THE entry point for every aux picker â€” present and future. Call this
    instead of ``list_authenticated_providers()`` directly.

    Aux pickers kept re-deriving their own kwargs and each one silently
    dropped a different slice of the user's configuration. Two independent
    contributor PRs landed against the same two call sites for exactly this:
    #52642 (user ``providers:`` / ``custom_providers:`` entries never
    appeared) and #66624 (providers with an exhausted credential pool were
    hidden). Both were per-site kwarg patches, so the next aux picker would
    have reintroduced the same gap. Routing through one function makes the
    correct behaviour the default that a new caller cannot forget:

    - user-defined ``providers:`` and saved ``custom_providers:`` entries
    - ``model_catalog.excluded_providers`` honoured, matching ``/model``
    - exhausted-credential-pool providers stay visible (``for_picker``)
    - the active custom endpoint is probed, offline saved ones are not, so
      the picker never blocks on a dead local server

    The virtual ``moa`` row is excluded: auxiliary tasks must not run the
    MoA reference fan-out, and ``auxiliary_client`` unwraps a ``moa``
    provider to its aggregator slot anyway (see ``_resolve_auto``), so
    offering it here would be a choice silently rewritten behind the user's
    back. Mirrors the same filter in ``hermes_cli/moa_cmd.py``.

    Rows are the standard ``list_authenticated_providers`` shape. Pair with
    :func:`format_aux_picker_entries` to render them.
    r   TF)rA   r?   r@   rB   r,   c                óª   — g | ]P}t          |                     d ¦  «        pd¦  «                             ¦   «                              ¦   «         dk    ¯N|‘ŒQS rI   )r
   r/   ÚstriprM   rN   s     r   rQ   z)build_aux_picker_rows.<locals>.<listcomp>o  sQ   € ÐQÐQÐQ�!�s 1§5¢5¨¡=¤=Ð#6°BÑ7Ô7×=Ò=Ñ?Ô?×EÒEÑGÔGÈ5ÒPÐPˆAÐPÐPÐPr   )r5   r   ru   )r   r   r   rB   rC   ro   s         r   Úbuild_aux_picker_rowsr|   ?  st   € õH Ñ
Ô
×
.Ò
.Ø)Ø#Ø)ð /ñ ô €Cõ
  ØØØ$Ø&*Øðñ ô ð ô€Dð RÐQ�tÐQÑQÔQÐQr   )r   r   ro   ú list[tuple[str, str, list[str]]]c          
     óš  — g }t          |pd¦  «                             ¦   «                              ¦   «         }t          t          |pd¦  «                             ¦   «         ¦  «        }| D ]à}t          |                     d¦  «        pd¦  «        }|                     d¦  «        p|}|                     d¦  «        p#t          |                     d¦  «        pg ¦  «        }	|	rd|	› d�nd}
|                     ¦   «         |k    r|r|sdnd}|                     ||› |
› |› �t          |                     d¦  «        pg ¦  «        f¦  «         Œá|S )	u#  Render aux-picker rows as ``(slug, label, models)`` menu entries.

    Owns the label text and the ``â†� current`` marker so every aux picker
    presents providers identically. Callers add their own leading/trailing
    entries (``auto``, ``Custom endpoint``, ``Back``) around this list.

    A custom endpoint set via a raw ``base_url`` is "current" only through
    that URL â€” never through a provider slug â€” so when ``current_base_url``
    is set no provider row is marked, matching the pre-existing behaviour of
    both call sites.
    r)   rJ   r(   r^   r[   u    â€” z modelsu     â†� current)r
   r{   rM   rD   r/   ri   Úappendr   )ro   r   r   ÚentriesÚcurrent_slugÚhas_base_urlrr   rJ   r(   ÚtotalÚ
model_hintÚmarkers               r   Úformat_aux_picker_entriesr†   r  sa  € ð" 13€GÝÐ'Ð-¨2Ñ.Ô.×4Ò4Ñ6Ô6×<Ò<Ñ>Ô>€LÝ�Ð,Ð2°Ñ3Ô3×9Ò9Ñ;Ô;Ñ<Ô<€LØð 
]ð 
]ˆÝ�3—7’7˜6‘?”?Ð( bÑ)Ô)ˆØ�wŠw�v‰ŒÐ& $ˆØ—’˜Ñ'Ô'ÐG­3¨s¯wªw°xÑ/@Ô/@Ð/FÀBÑ+GÔ+GˆØ/4Ð<Ð+˜UÐ+Ð+Ð+Ð+¸"ˆ
ð �zŠz‰|Œ|˜|Ò+Ð+°Ð+À\Ð+ð ˆOàð 	ð
 	�Š˜ Ð; zÐ;°6Ð;Ð;½TÀ#Ç'Â'È(ÑBSÔBSÐBYÐWYÑ=ZÔ=ZÐ[Ñ\Ô\Ð\Ð\Ø€Nr   ÚNonec                ót  — ddl m} 	 ddlm} n# t          $ r d}Y nw xY w| D ]”}|                     d¦  «        pd}i }|                     d¦  «        pg D ]\}d}|�8|r6	  |||¦  «        }|�t          |j        ¦  «        }n# t          $ r d}Y nw xY wt           ||¦  «        ¦  «        |d	œ||<   Œ]||d
<   Œ•dS )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_capabilitiesNrJ   r)   r[   T)ÚfastÚ	reasoningr;   )Úhermes_cli.modelsr‰   Úagent.models_devrŠ   rf   r/   rD   Úsupports_reasoning)	ro   r‰   rŠ   rr   rJ   Úcapsr&   rŒ   Úmetas	            r   rm   rm   ”  sJ  € ð ;Ð:Ð:Ð:Ð:Ð:ð&Ø;Ð;Ð;Ð;Ð;Ð;Ð;øÝð &ð &ð &Ø!%ÐÐÐð&øøøð ð #ð #ˆØ�wŠw�v‰ŒÐ$ "ˆØ+-ˆà—W’W˜XÑ&Ô&Ð,¨"ð 	ð 	ˆEØˆIØ%Ð1°dÐ1ð%Ø1Ð1°$¸Ñ>Ô>�DØÐ'Ý$(¨Ô)@Ñ$AÔ$A˜	øøÝ ð %ð %ð %Ø $�I�I�Ið%øøøõ Ð5Ð5°eÑ<Ô<Ñ=Ô=Ø&ðð ˆD�‰KˆKð
 #ˆˆNÑÐð'#ð #s   ˆ ��Á"BÂBÂBé   c                ó„  ‡— 	 ddl m} n# t          $ r d}Y nw xY w| D �] }t          |                     d¦  «        pd¦  «                             ¦   «                              ¦   «         }|                     d¦  «        pg }i }t          |¦  «        D ]Š\  }}d|v r|                     dd¦  «        d         nd}|si } n^d}	|�- |||¦  «        p |d	|¦  «        }
|
rt          |
d
d¦  «        nd}	| 
                    |g ¦  «                             ||	|f¦  «         Œ‹t          |¦  «        dk     rg |d<   �Œg }|                     ¦   «         D ]A}t          |d„ d¬¦  «        }|                     d„ |dt           …         D ¦   «         ¦  «         ŒBd„ t          |¦  «        D ¦   «         Št          |ˆfd„¬¦  «        |d<   �Œ¢dS )u@  Attach a ``featured_models`` shortlist to each aggregator provider row.

    Aggregator providers (nous, openrouter) serve dozens of models across many
    labs, so a flat "top-N" default would drop whole labs from the picker.
    Instead we surface the ``_FEATURED_PER_LAB`` newest models per lab (the
    vendor segment of a ``vendor/model`` id), ranked by models.dev
    ``release_date`` among that row's OWN models â€” never against the current
    date, so the choice is stable as models age. Same-date ties (and labs whose
    models lack a date) fall back to the row's curated order, which is already
    flagship-first, so a lab keeps its headliners rather than an arbitrary slice.

    Derived live from the models.dev catalog already loaded on this path (same
    source as pricing/capabilities) â€” there is no hand-maintained allowlist to
    keep in sync. Non-aggregator providers (a single lab, local endpoints,
    custom proxies) get an empty list and callers fall back to their existing
    top-N behaviour; splitting one lab into a shortlist would just hide models.
    r   )Úget_model_infoNrJ   r)   r[   ú/é   Ú
openrouterÚrelease_dateé   Úfeatured_modelsc                ó$   — | d         | d          fS )Nr–   r   r"   )Úes    r   ú<lambda>z!_apply_featured.<locals>.<lambda>õ  s   € °A°a´D¸1¸Q¼4¸%°=€ r   T)ÚkeyÚreversec              3  ó"   K  — | ]
\  }}}|V — Œd S rW   r"   )rO   Ú_posÚ_dater&   s       r   rZ   z"_apply_featured.<locals>.<genexpr>ö  s)   è è € ÐWÐWÑ&8 d¨E°5˜EÐWÐWÐWÐWÐWÐWr   c                ó   — i | ]\  }}||“Œ	S r"   r"   )rO   ÚirY   s      r   ú
<dictcomp>z#_apply_featured.<locals>.<dictcomp>ø  s   € Ð4Ð4Ð4™$˜!˜Q��AÐ4Ð4Ð4r   c                ó   •— ‰|          S rW   r"   )rY   Úorders    €r   r�   z!_apply_featured.<locals>.<lambda>ù  s   ø€ ÀÀaÄ€ r   ©rž   )rŽ   r”   rf   r
   r/   r{   rM   Ú	enumerateÚsplitÚgetattrÚ
setdefaultr   ri   ÚvaluesÚsortedÚextendÚ_FEATURED_PER_LAB)ro   r”   rr   rJ   r[   Úby_labÚposr&   ÚlabÚdateÚinfor<   r€   Úrankedr§   s                 @r   rn   rn   Á  sF  ø€ ð$Ø3Ð3Ð3Ð3Ð3Ð3Ð3øÝð ð ð Øˆˆˆðøøøð ð !Jñ !JˆÝ�3—7’7˜6‘?”?Ð( bÑ)Ô)×/Ò/Ñ1Ô1×7Ò7Ñ9Ô9ˆØ—’˜Ñ"Ô"Ð( bˆð 9;ˆÝ# FÑ+Ô+ð 	Bð 	B‰JˆC�Ø,/°5¨L¨L�%—+’+˜c 1Ñ%Ô% aÔ(Ð(¸bˆCØð ð �Ø�ØˆDØÐ)Ø%�~ d¨EÑ2Ô2ÐY°n°nÀ\ÐSXÑ6YÔ6Y�Ø<@ÐH•w˜t ^°RÑ8Ô8Ð8Àb�Ø×Ò˜c 2Ñ&Ô&×-Ò-¨s°D¸%Ð.@ÑAÔAÐAÐAõ ˆv‰;Œ;˜Š?ˆ?Ø%'ˆCÐ!Ñ"Ùà ˆØ—}’}‘”ð 	Xð 	XˆGõ ˜GÐ)@Ð)@È$ÐOÑOÔOˆFØ�OŠOÐWÐW¸FÐCUÕDUÐCUÔ<VÐWÑWÔWÑWÔWÐWÐWà4Ð4¥)¨FÑ"3Ô"3Ð4Ñ4Ô4ˆÝ!'¨Ð6HÐ6HÐ6HÐ6HÐ!IÑ!IÔ!IˆÐÑÑðC!Jð !Js   ƒ
 Š˜rR   rS   c               óp  — ddl m} ddlm}m} d„ | D ¦   «         }|j        pd                     ¦   «         }t          |j        pd¦  «         	                    ¦   «         }g }	|D �]R}
|
j
                             ¦   «         |v rŒ|r|
j
                             ¦   «         |k    rŒ?|
j
                             ¦   «         |k    r›|                     |
j
        ¦  «        }|r|j        nd}|r|j        r|j        d         nd}|dk    r|rd|› d�nd	}|	                     |
j
        |                     |
j
        |
j        ¦  «        d
d|r|gng |rdnddd|||dœ¦  «         Œ÷|	                     |
j
        |                     |
j
        |
j        ¦  «        |
j
                             ¦   «         |k    dg dddœ¦  «         �ŒT|	S )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_LABELSc                óB   — h | ]}|d                                ¦   «         ’ŒS ©rJ   rX   rN   s     r   ú	<setcomp>z,_append_unconfigured_rows.<locals>.<setcomp>  s&   € Ð,Ð,Ð, !ˆAˆfŒI�OŠOÑÔÐ,Ð,Ð,r   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.TFr–   zconfigured-current)rJ   r(   Ú
is_currentrU   r[   r^   ÚsourceÚauthenticatedÚ	auth_typeÚkey_envÚwarningÚ	canonical)rJ   r(   rÀ   rU   r[   r^   rÁ   )Úhermes_cli.authr¹   r�   rº   r»   r   rM   r
   r   r{   rJ   r/   rÃ   Úapi_key_env_varsr   Úlabel)ro   rC   rS   r¹   rº   r»   ÚseenÚcurÚ	cur_modelÚextrasÚentryr1   rÃ   rÄ   rÅ   s                  r   rd   rd   ÿ  sI  € ð 2Ð1Ð1Ð1Ð1Ð1ØGÐGÐGÐGÐGÐGÐGÐGà,Ð, tÐ,Ñ,Ô,€DØÔÐ% 2×
,Ò
,Ñ
.Ô
.€CÝ�CÔ%Ð+¨Ñ,Ô,×2Ò2Ñ4Ô4€IØ€FØ$ð .
ñ .
ˆØŒ:×ÒÑÔ Ð%Ð%ØØð 	˜EœJ×,Ò,Ñ.Ô.°#Ò5Ð5ØØŒ:×ÒÑÔ Ò$Ð$Ø#×'Ò'¨¬
Ñ3Ô3ˆCØ),Ð;˜œ˜°)ˆIð ðØÔ0ð�Ô$ QÔ'Ð'àð ð  	Ò)Ð)¨gÐ)ð0Èð 0ð 0ð 0ð 0ð0ð	 ð �MŠMà!œJØ,×0Ò0°´¸U¼[ÑIÔIØ"&Ø',Ø-6Ð>˜y˜k˜k¸BØ)2Ð$9 A A¸Ø2Ø%*Ø!*Ø&Ø&ðð ñô ð ð Ø�Šàœ
Ø(×,Ò,¨U¬Z¸¼ÑEÔEØ#œj×.Ò.Ñ0Ô0°CÒ7Ø#(ØØ !Ø%ðð ñ
	
ô 
	
ð 
	
ñ 
	
ð €Mr   c                óN  — ddl m} t          |j        pd¦  «                             ¦   «                              ¦   «         }g }| D ]ß}t          |                     dd¦  «        ¦  «                             ¦   «                              ¦   «         }|sŒL|                     d¦  «        r|                     |¦  «         Œw|r||k    r|                     |¦  «         Œ•|dk    r$t          ¦   «         r|                     |¦  «         Œ¿ ||¦  «        r|                     |¦  «         Œà|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)   rJ   rU   rK   )	rÇ   rÐ   r
   r   r{   rM   r/   r   Ú"_raw_config_has_enabled_moa_preset)ro   rC   rÐ   r�   Úkeptrr   rJ   s          r   rc   rc   E  s@  € ð BÐAÐAÐAÐAÐAå�sÔ+Ð1¨rÑ2Ô2×8Ò8Ñ:Ô:×@Ò@ÑBÔB€LØ€DØð ð ˆÝ�3—7’7˜6 2Ñ&Ô&Ñ'Ô'×-Ò-Ñ/Ô/×5Ò5Ñ7Ô7ˆØð 	ØØ�7Š7Ð$Ñ%Ô%ð 	Ø�KŠK˜ÑÔÐØØð 	˜D LÒ0Ð0Ø�KŠK˜ÑÔÐØØ�5Š=ˆ=õ 2Ñ3Ô3ð !Ø—’˜CÑ Ô Ð ØØ,Ð,¨TÑ2Ô2ð 	Ø�KŠK˜ÑÔÐøØ€Kr   c                 óŠ  ‡— 	 ddl m}   | ¦   «         }n# t          $ r Y dS w xY wt          |t          ¦  «        sdS |                     d¦  «        Št          ‰t          ¦  «        sdS ‰                     d¦  «        }t          |t          ¦  «        rq|                     ¦   «         D ]Z\  }}t          |pd¦  «                             ¦   «         sŒ)t          |t          ¦  «        s dS |                     dd¦  «        r dS Œ[dS h d	£}t          ˆfd
„|D ¦   «         ¦  «        o"t          ‰                     dd¦  «        ¦  «        S )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_configFrK   Úpresetsr)   TÚenabled>   ÚfanoutÚ
aggregatorÚ
max_tokensÚreference_modelsÚreference_max_tokensÚreference_temperatureÚaggregator_temperaturec              3  ó    •K  — | ]}|‰v V — Œ	d S rW   r"   )rO   rž   rK   s     €r   rZ   z5_raw_config_has_enabled_moa_preset.<locals>.<genexpr>‘  s'   øè è € Ð1Ð1˜cˆs�cˆzÐ1Ð1Ð1Ð1Ð1Ð1r   )r.   rÔ   rf   r0   r   r/   Úitemsr
   r{   ÚanyrD   )rÔ   r3   rÕ   r(   ÚpresetÚlegacy_keysrK   s         @r   rÑ   rÑ   h  sz  ø€ ðØ5Ð5Ð5Ð5Ð5Ð5àˆoÑÔˆˆøÝð ð ð Øˆuˆuðøøøõ �c�4Ñ Ô ð ØˆuØ
�'Š'�%‰.Œ.€CÝ�c�4Ñ Ô ð Øˆuà�gŠg�iÑ Ô €GÝ�'�4Ñ Ô ð Ø#ŸMšM™OœOð 	ð 	‰LˆD�&Ý�t�z˜r‘?”?×(Ò(Ñ*Ô*ð ØÝ˜f¥dÑ+Ô+ð Ø�t�tØ�zŠz˜) TÑ*Ô*ð Ø�t�tðàˆuðð ð €Kõ Ð1Ð1Ð1Ð1 [Ð1Ñ1Ô1Ñ1Ô1ÐTµd¸3¿7º7À9ÈdÑ;SÔ;SÑ6TÔ6TÐTs   ƒ ”
"¡"c                ó~  — ddl m} | D ]³}d|v rŒ|                     d¦  «        dk    o|                     d¦  «         }| |d<   |r|                     d¦  «        rŒT|                     |d         ¦  «        }|r|j        nd	}|r|j        r|j        d         nd
}||d<   ||d<   |d	k    r|rd|› d�nd|› d�|d<   Œ´dS )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   r¸   rÂ   rÁ   rÆ   r[   rU   rJ   r¿   r)   rÃ   rÄ   zpaste z to activatez!run `hermes model` to configure (ú)rÅ   N)rÇ   r¹   r/   rÃ   rÈ   )ro   r¹   rr   Úis_skeletonr1   rÃ   rÄ   s          r   rj   rj   ”  s8  € ð 2Ð1Ð1Ð1Ð1Ð1àð 
ð 
ˆØ˜cÐ!Ð!Øð —g’g˜hÑ'Ô'¨;Ò6ÐP¸s¿wºwÀxÑ?PÔ?PÐ;PˆØ#.˜ˆˆOÑØð 	˜cŸgšgÐ&7Ñ8Ô8ð 	ØØ×#Ò# C¨¤KÑ0Ô0ˆØ%(Ð7�C”M�M¨iˆ	ð ðØÔ,ðˆCÔ  Ô#Ð#àð 	ð
 %ˆˆKÑØ ˆˆI‰ð ˜IÒ%Ð%¨'Ð%ð +�WÐ*Ð*Ð*Ð*àA°YÐAÐAÐAð 	ˆI‰ˆð+
ð 
r   c                ó¨   ‡— ddl m} d„ t          |¦  «        D ¦   «         Št          ˆfd„| D ¦   «         ˆfd„¬¦  «        }ˆfd„| D ¦   «         }||z   S )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   )rº   c                ó$   — i | ]\  }}|j         |“ŒS r"   r½   )rO   r¤   rœ   s      r   r¥   z&_reorder_canonical.<locals>.<dictcomp>Æ  s    € ÐBÐBÐB™4˜1˜aˆQŒV�QÐBÐBÐBr   c              3  ó0   •K  — | ]}|d          ‰v ¯|V — ŒdS )rJ   Nr"   ©rO   rP   r§   s     €r   rZ   z%_reorder_canonical.<locals>.<genexpr>È  s1   øè è € Ð/Ð/ˆq˜A˜fœI¨Ð.Ð.ˆÐ.Ð.Ð.Ð.Ð/Ð/r   c                ó    •— ‰| d                  S )NrJ   r"   )rP   r§   s    €r   r�   z$_reorder_canonical.<locals>.<lambda>É  s   ø€ �e˜A˜fœIÔ&€ r   r¨   c                ó(   •— g | ]}|d          ‰v¯|‘ŒS r½   r"   ré   s     €r   rQ   z&_reorder_canonical.<locals>.<listcomp>Ë  s'   ø€ Ð8Ð8Ð8�A  6¤°%Ð!7Ð!7ˆaÐ!7Ð!7Ð!7r   )r�   rº   r©   r®   )ro   rº   ÚcanonrÍ   r§   s       @r   rk   rk   º  s�   ø€ ð 6Ð5Ð5Ð5Ð5Ð5àBÐB¥9Ð-@Ñ#AÔ#AÐBÑBÔB€EÝØ/Ð/Ð/Ð/�DÐ/Ñ/Ô/Ø&Ð&Ð&Ð&ðñ ô €Eð 9Ð8Ð8Ð8˜Ð8Ñ8Ô8€FØ�6‰>Ðr   r`   c          	     ó,  — ddl m}m}m}m}m} d}| D �]ÿ}t          |                     dd¦  «        ¦  «                             ¦   «         }	|                     d¦  «        pg }
|
sŒR	  ||	¦  «        pi }n# t          $ r i }Y nw xY w|sŒvi }|
D �]}|                     |¦  «        }|sŒ|                     dd¦  «        }|                     dd¦  «        }|                     d	d¦  «        }|dk    r ||¦  «        nd}|dk    r ||¦  «        nd}|r ||¦  «        nd}|d
k    o|d
k    p|dk    }||||dœ}|	dk    rW|sU ||||                     d¦  «        ¦  «        }|�3|\  }}}||d<   |dk    r ||¦  «        |d<   |dk    r ||¦  «        |d<   |||<   �Œ|r||d<   |	dk    ri	 |€ ||¬¦  «        }t          |¦  «        |d<   |r$ |t          |
¦  «        |d¬¦  «        \  }}||d<   ng |d<   �Œä# t          $ r d|d<   g |d<   Y �Œûw xY w�ŒdS )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Úcompute_sale_discountÚget_pricing_for_providerÚpartition_nous_models_by_tierNrJ   r)   r[   ÚpromptÚ
completionÚinput_cache_readÚfree)ÚinputÚoutputÚcacherö   Únousrs   Údiscount_percentÚ	was_inputÚ
was_outputr:   )Úforce_freshÚ	free_tierT)rÿ   Úunavailable_modelsF)r�   rî   rï   rð   rñ   rò   r
   r/   rM   rf   rD   r   )ro   r=   rî   rï   rð   rñ   rò   Únous_free_tierrr   rJ   r[   Úraw_pricingÚ	formattedÚmidÚpÚinp_rawÚout_rawÚ	cache_rawÚinpÚoutrù   Úis_freerÎ   Úsalerû   Úwas_prompt_rawÚwas_out_rawÚ_selectableÚunavailables                                r   rl   rl   Ï  s~  € ð,ð ð ð ð ð ð ð ð ð ð ð ð ð ð &*€Nàð I/ñ I/ˆÝ�3—7’7˜6 2Ñ&Ô&Ñ'Ô'×-Ò-Ñ/Ô/ˆØ—’˜Ñ"Ô"Ð( bˆØð 	Øð	Ø2Ð2°4Ñ8Ô8Ð>¸BˆKˆKøÝð 	ð 	ð 	ØˆKˆKˆKð	øøøàð 	Øà%'ˆ	Øð %	#ñ %	#ˆCØ—’ Ñ$Ô$ˆAØð ØØ—e’e˜H bÑ)Ô)ˆGØ—e’e˜L¨"Ñ-Ô-ˆGØŸšÐ0°"Ñ5Ô5ˆIØ5<À²]°]Ð(Ð(¨Ñ1Ô1Ð1ÈˆCØ5<À²]°]Ð(Ð(¨Ñ1Ô1Ð1ÈˆCØ9BÐLÐ*Ð*¨9Ñ5Ô5Ð5ÈˆEà˜V’mÐD¨°ªÐ)C¸#Àº)ˆGàØØØð	ð ˆEð �vŠ~ˆ~ gˆ~Ø,Ð,Ø˜W a§e¢e¨JÑ&7Ô&7ñô �ð Ð#ØDHÑAÐ$ n°kØ0@�EÐ,Ñ-Ø%¨Ò+Ð+Ø-CÐ-CØ*ñ.ô .˜˜kÑ*ð # bÒ(Ð(Ø.DÐ.DØ'ñ/ô /˜˜lÑ+ð #ˆI�c‰N‰Nàð 	'Ø&ˆC�	‰Nà�6Š>ˆ>ð/Ø!Ð)Ø%9Ð%9Ø$9ð&ñ &ô &�Nõ $(¨Ñ#7Ô#7��KÑ Ø!ð 3Ø/LÐ/LÝ˜V™œ k¸Tð0ñ 0ô 0Ñ,�K ð 1<�CÐ,Ñ-Ð-à02�CÐ,Ñ-ùøÝð /ð /ð /ð $)��KÑ Ø,.�Ð(Ñ)Ð)Ñ)ð	/øøøñ ðoI/ð I/s%   Á&A4Á4BÂBÆ*AG7Ç7HÈHúdict | Nonec                ód  — 	 ddl m} ddlm}  | |¦   «                              d¦  «        pi ¦  «        }t          |                     di ¦  «                             ¦   «         ¦  «        }|sdS dd| pd                     ¦   «         dk    d	|t          |¦  «        d
dd
ddœ
S # t          $ r Y dS 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_configrK   rÕ   NzMixture of Agentsr)   FÚvirtualTzTAggregator acts as the selected model; references provide analysis before each call.)
rJ   r(   rÀ   rU   r[   r^   rÁ   rÂ   rÃ   rÅ   )
r.   r%   Úhermes_cli.moa_configr  r/   r   ÚkeysrM   ri   rf   )r   r%   r  r1   r[   s        r   rb   rb   <  sõ   € ðØ1Ð1Ð1Ð1Ð1Ð1Ø>Ð>Ð>Ð>Ð>Ð>à"Ð" ; ;¡=¤=×#4Ò#4°UÑ#;Ô#;Ð#A¸rÑBÔBˆÝ�c—g’g˜i¨Ñ,Ô,×1Ò1Ñ3Ô3Ñ4Ô4ˆØð 	Ø�4àØ'Ø+Ð1¨r×8Ò8Ñ:Ô:¸eÒCØ$ØÝ ™KœKØØ!Ø"Ømð
ð 
ð 	
øõ ð ð ð Øˆtˆtðøøøs   ‚A+B! Á/1B! Â!
B/Â.B/)r   r	   )rC   r	   r6   rD   r7   rD   r8   rD   r9   rD   r:   rD   r;   rD   r<   rD   r=   rD   r>   rD   r?   rD   r@   rD   rA   rD   rB   rE   r   r   )
rC   r	   r6   rD   r7   rD   r>   rD   r   r   )
r   r
   r   r
   r   r
   rB   rE   r   rx   )ro   rx   r   r
   r   r
   r   r}   )ro   rx   r   r‡   )ro   rx   rC   r	   rS   rD   r   rx   )ro   rx   rC   r	   r   rx   )r   rD   )ro   rx   r   rx   )ro   rx   r=   rD   r   r‡   )r)   )r   r
   r   r  )r    Ú
__future__r   Údataclassesr   r   Útypingr   r	   r5   ru   rw   r|   r†   rm   r°   rn   rd   rc   rÑ   rj   rk   rl   rb   r"   r   r   ú<module>r     s|  ððð ðB #Ð "Ð "Ð "Ð "Ð "à *Ð *Ð *Ð *Ð *Ð *Ð *Ð *Ø Ð Ð Ð Ð Ð ð €�$ÐÑÔð!3ð !3ð !3ð !3ð !3ñ !3ô !3ñ Ôð!3ðHð ð ð ðJ  Ø!&ØØ!ØØØØ"'ØØ#'Ø*/ØØ!ðfð fð fð fð fð fðX  Ø!&Øðð ð ð ð ð ðL ØØØ!ð0Rð 0Rð 0Rð 0Rð 0Rð 0Rðl Øð	ð ð ð ð ð ðD##ð ##ð ##ð ##ðT Ð ð8Jð 8Jð 8Jð 8JðD ð	Cð Cð Cð Cð Cð CðL ð  ð  ð  ðF)Uð )Uð )Uð )UðX#
ð #
ð #
ð #
ðLð ð ð ð0 #(ðj/ð j/ð j/ð j/ð j/ð j/ðZð ð ð ð ð ð r   