Skip to content

feat: hermes models / hermes providers — non-interactive inventory CLI (#23359)#23369

Closed
kshitijk4poor wants to merge 1 commit into
NousResearch:mainfrom
kshitijk4poor:feat/cli-inventory
Closed

feat: hermes models / hermes providers — non-interactive inventory CLI (#23359)#23369
kshitijk4poor wants to merge 1 commit into
NousResearch:mainfrom
kshitijk4poor:feat/cli-inventory

Conversation

@kshitijk4poor

@kshitijk4poor kshitijk4poor commented May 10, 2026

Copy link
Copy Markdown
Collaborator

What this PR does

Adds three scriptable, non-interactive CLI surfaces and consolidates four existing inventory call-sites onto a single source of truth:

hermes models list   [--provider NAME] [--json] [--all] [--no-live] [--offline]
hermes models status [--provider NAME] [--json]                     [--offline]
hermes providers list                  [--json] [--all]             [--offline]

Closes #23359 (jointly with #3503 once that PR migrates to consume the shared formatter).

The new hermes_cli/inventory.py module (600 LOC) is the single source of truth for inventory traversal. The dashboard (/api/model/options), TUI (model.options JSON-RPC), and TUI (model.save_key JSON-RPC) have been migrated to consume it, dropping ~140 lines of duplicated config-load / canonical-merge / picker-hint logic across the three consumer files. --offline mode synthesises from in-repo static data so cron and CI callers don't block on the network.

A reachability-probe surface (--probe for models status) is tracked separately in #23614provider_model_ids() falls back to curated lists, so a true reachability check needs an explicit endpoint probe that isn't wired through the existing model-discovery wrapper.

Why

Hermes had four independent ways to list providers/models — each interactive (TTY required) or HTTP (gateway running), each reproducing the same five-step config dance, and each post-processing the result a slightly different way. There was no scriptable / cron-friendly entry point.

Surface Before After
Interactive picker (cli.py) list_authenticated_providers directly (unchanged)
Dashboard (/api/model/options) Inline 17-line config dance + raw cfg.get("custom_providers") (which misses v12+ keyed providers — bug fix) + list_authenticated_providers inventory.build_payload("models", ctx=load_picker_context(), max_models=50)
TUI (model.options JSON-RPC) Inline 17-line config dance + list_authenticated_providers + 45-line canonical-merge / picker-hint post-processing inventory.build_payload("models", ctx, picker_hints=True, canonical_order=True) with with_overrides() for agent-session state
TUI (model.save_key JSON-RPC) Inline list_authenticated_providers + slug match inventory.build_payload("models", ctx, picker_hints=True) then slug match
NEW: scriptable CLI (didn't exist) hermes models list/status + hermes providers list

Five open PRs have each reinvented the same traversal for one narrow purpose: #3503 (/models slash), #21695 (plugin provider runtime resolver), #19526 (model.picker_mode), #17994 (/free-models), #21785 (hermes doctor skip-by-endpoint). Each adds ~50-150 LOC of inventory walking. Now they have a single helper to consume.

Full motivation, pain points, and ecosystem evidence in tracking issue #23359.

What this PR is NOT doing

Modes / network behaviour

Flag models.dev HTTP Per-provider live HTTP
(default) ✓ via cache ✓ for configured providers
--no-live (models list only) ✓ via cache
--offline

--offline bypasses list_authenticated_providers entirely and synthesises inventory from CANONICAL_PROVIDERS (universe of ~35) + _PROVIDER_MODELS (curated) + OPENROUTER_MODELS (local constant) + profile.fallback_models. Auth state is derived from environment variables alone — profile.env_varsPROVIDER_REGISTRY[slug].api_key_env_varsHERMES_OVERLAYS[slug].extra_env_vars. No consultation of auth.json, credential pool, Claude Code creds, or Hermes OAuth files.

Public API exposed by hermes_cli/inventory.py

load_picker_context() -> ConfigContext
    # Replaces the 17-LOC config dance in dashboard / TUI / picker.

ConfigContext.with_overrides(*, current_provider, current_model, current_base_url) -> ConfigContext
    # Layer agent-session state on top of disk config (TUI override path).

build_payload(kind, *, ctx=None, provider=None, include_unconfigured=False,
              live=True, offline=False, picker_hints=False,
              canonical_order=False, max_models=50) -> dict
    # kind ∈ {"models", "status", "providers"}

Optional flags:

  • ctx — pre-loaded context, bypassing load_picker_context() (TUI override path).
  • picker_hints=True — adds authenticated per row, plus auth_type / key_env / warning for unconfigured canonical rows (the dashboard frontend's ModelPickerDialog shape).
  • canonical_order=True — reorder rows to CANONICAL_PROVIDERS declaration order (TUI display order).

authenticated is only emitted under picker_hints=True. Default scriptable shape uses auth_state: "configured" | "unconfigured" exclusively — clean schema for cron/CI scripts.

JSON contract (default scriptable shape)

{
  "schema_version": 1,
  "current": {"provider": "openrouter", "model": "anthropic/claude-sonnet-4.6"},
  "providers": [
    {
      "slug": "anthropic",
      "name": "Anthropic",
      "is_current": false,
      "is_user_defined": false,
      "auth_state": "configured",
      "api_mode": "anthropic_messages",
      "auth_type": "api_key",
      "base_url": "https://api.anthropic.com",
      "env_vars": ["ANTHROPIC_API_KEY", "ANTHROPIC_TOKEN", "CLAUDE_CODE_OAUTH_TOKEN"],
      "env_vars_present": [true, false, false],
      "models": ["claude-opus-4-7", "..."],
      "total_models": 8,
      "source": "canonical",
      "model_source": "curated"
    }
  ]
}

env_vars lists variable names (including HERMES_OVERLAYS bridges — e.g. OpenRouter accepts OPENAI_API_KEY). env_vars_present is the corresponding bool array. Values are never included in the payload — explicit invariant; canary-tested.

Substrate gotchas the implementation respects

These are documented in references/provider-inventory-surface.md (hermes-agent-dev skill) and were each verified against main before implementation:

  1. Four registries don't fully overlap. Inventory iterates CANONICAL_PROVIDERS (the universe, ~35).
  2. profile.fetch_models() returns models the runtime rejects. OpenRouter raw=367 vs picker=34. Module never calls it directly. Canary test enforces.
  3. Custom providers split across two config shapes. Module reads via get_compatible_custom_providers(config) — fixes a latent bug in dashboard's /api/model/options (was missing v12+ keyed entries).
  4. OpenRouter offline gotcha. _PROVIDER_MODELS["openrouter"] is None; module uses OPENROUTER_MODELS constant. ~31 entries, no HTTP. Canary asserts equality against the constant length.
  5. CLI exit-code propagation. hermes_cli/main.py:11688 does args.func(args) and discards the return value. Handlers use parser.error() / required=True / SystemExit(2) instead of return 2.
  6. TUI contract preserved: model.options does NOT call provider_model_ids per-row in the live path. That would pull non-agentic models (TTS, embeddings). Canary test (test_live_path_does_not_call_models_for) enforces.
  7. model.save_key migration verified. build_payload("models", picker_hints=True) returns the freshly-keyed slug with populated models. Canary test (test_save_key_migration_returns_populated_row) enforces.
  8. Anthropic credential discovery has 5+ paths. A clean-env E2E asserting "anthropic is unconfigured" can fail on dev machines with Claude Code installed. E2E uses arcee (single env var, no extras) as the canonical clean-baseline target.

Tests

File Tests What
tests/hermes_cli/test_inventory.py 32 Three concept canaries (no models.dev / no provider HTTP / status HTTP-free) + H2-H6 + W1 regressions + alias canonicalisation + universe assertions + schema/mutex + consumer-shape parity (web_server + TUI) + TUI live-path canary + save_key migration canary
tests/hermes_cli/test_inventory_cli.py 9 Subprocess E2E via hermes console script
tests/test_tui_gateway_server.py::test_model_options_does_not_overwrite_curated_models 1 Existing test, still passes (TUI contract gate)

Concept B canary monkeypatches every per-provider live-fetch helper (fetch_anthropic_models, fetch_openrouter_models, fetch_ai_gateway_models, fetch_ollama_cloud_models, fetch_lmstudio_models, _fetch_github_models, fetch_api_models, fetch_nous_models, fetch_models_dev, model_ids, get_curated_nous_model_ids, bedrock_model_ids_or_none, list_authenticated_providers) to fail-on-call, then asserts none fire when --offline is passed.

The TUI contract canary monkeypatches _models_for and asserts it fires zero times in the live path with picker_hints=True, canonical_order=True (the TUI's exact call shape) — guaranteeing build_payload doesn't pull non-agentic models when consumed via model.options.

The save_key migration canary monkeypatches list_authenticated_providers to return a fake row, then asserts the saved slug is found in build_payload's output with populated models — locking the contract tui_gateway/server.py:5247-5256 relies on.

$ pytest tests/hermes_cli/test_inventory.py tests/hermes_cli/test_inventory_cli.py \
        tests/test_tui_gateway_server.py::test_model_options_does_not_overwrite_curated_models -q
42 passed in 5.8s

Full tests/hermes_cli/ + tests/test_tui_gateway_server.py: 13 pre-existing failures verified to fail identically on origin/main (gateway systemd / WSL / kanban / plugins / web-server auth / update hangup / browser launch hint — none related to this PR).

Lint diff

ruff — Total: 0 on HEAD, 0 on base (➖ 0)  🆕 New issues: none
ty   — 0 issues in hermes_cli/inventory.py

Files changed

hermes_cli/inventory.py                | 600 +++++++++++++++++++ (new, single source of truth)
hermes_cli/main.py                     |  89 ++ (subparsers + 3 handlers + #23614 TODO marker)
hermes_cli/web_server.py               |  36 +- (-29 net: migrated to inventory)
tui_gateway/server.py                  | 155 +- (migrated model.options + model.save_key)
tests/hermes_cli/test_inventory.py     | 678 ++++ (new)
tests/hermes_cli/test_inventory_cli.py | 136 ++ (new)
website/docs/reference/cli-commands.md |  84 +++
7 files changed, 1638 insertions(+), 140 deletions(-)

Of the 140 lines deleted from web_server.py + tui_gateway/server.py, ~125 were literal duplicates of the config dance + canonical merge, now living in one place.

No setup.py files. No env-var-value logging (canary-tested). No changes to list_authenticated_providers, provider_model_ids, or any provider plugin.

How to verify

# Universe + offline correctness
hermes providers list --offline --all --json | jq '.providers | length'           # 35
hermes models list --provider=openrouter --offline --json | jq '.providers[0].total_models'   # ≈31

# Default mode (live, same as picker)
hermes providers list --json | jq '.providers | length'                            # configured providers count

# OpenRouter regression check
hermes models list --provider=openrouter --json | jq '.providers[0].total_models'  # ≤50, NOT 367

# Mutex + exit codes
hermes models list --provider=does-not-exist; echo $?   # 2
hermes models foo; echo $?    # 2
hermes providers; echo $?     # 2

# Dashboard / TUI integration: their existing endpoints work unchanged
curl http://localhost:7172/api/model/options | jq '.providers | length'   # ≥1
# (TUI model.options + model.save_key exercised by tests/test_tui_gateway_server.py)

Out of scope (tracked separately)

Refs

@alt-glitch alt-glitch added type/feature New feature or request comp/cli CLI entry point, hermes_cli/, setup wizard P2 Medium — degraded but workaround exists labels May 10, 2026
@kshitijk4poor
kshitijk4poor force-pushed the feat/cli-inventory branch 2 times, most recently from f922f9b to b7f3921 Compare May 11, 2026 06:21
@kshitijk4poor
kshitijk4poor force-pushed the feat/cli-inventory branch 2 times, most recently from 9643efc to ed624a4 Compare May 11, 2026 07:10
Consolidates the duplicated provider/model traversal across dashboard
/api/model/options, TUI model.options, and TUI model.save_key onto a
single hermes_cli/inventory.py module (600 LOC). The 3 consumer files
delete 150 LOC of inline config-slice + post-processing.

Also adds three scriptable CLI surfaces for cron / CI / agents
introspecting their own environment:

  hermes models list   [--provider NAME] [--json] [--all] [--no-live] [--offline]
  hermes models status [--provider NAME] [--json]            [--offline]
  hermes providers list                  [--json] [--all]              [--offline]

--offline is fully HTTP-free (no models.dev, no provider live API, no
remote catalog fetches, no credential-pool probing); auth state derived
from environment variables alone via profile.env_vars ∪
PROVIDER_REGISTRY.api_key_env_vars ∪ HERMES_OVERLAYS.extra_env_vars.

Reachability-probe surface (--probe) is tracked separately in NousResearch#23614.

Closes NousResearch#23359.
@kshitijk4poor

Copy link
Copy Markdown
Collaborator Author

Closing in favour of #23666, which is the cut-to-the-bone refactor extracted from this PR.

This PR (#23369) bundled two concerns:

  1. The consolidation — three call-sites (dashboard /api/model/options, TUI model.options, TUI model.save_key) each duplicating a 17-LOC config-slice + list_authenticated_providers + post-processing. Real win.
  2. A new scriptable CLI surfacehermes models list/status + hermes providers list with offline mode, JSON contract, text renderers. No external user demand — none of the cited "five PRs reinventing this traversal" actually need or would consume the new helper, and no other issue in the tracker asks for hermes models list --json.

Honest accounting on this PR was +1638 / -140 with a 0.23 consolidation ratio (additions in the new module vs deletions in consumers) — well below the 0.5 threshold for legitimate consolidation framing. ~1400 of those LOC served (1) — speculative feature work I retroactively justified as part of the consolidation argument.

#23666 keeps only (1):

  • 240 LOC module (vs 600 here)
  • 378 LOC tests (vs 814 here)
  • +666 / -145 total (vs +1638 / -140 here)
  • Consolidation ratio 0.60 — above threshold

The scriptable CLI surface is deferred until an external user actually asks for it.

Both latent bugs this PR found are preserved in #23666:

  • Dashboard missing v12+ keyed providers (cfg.get("custom_providers") vs get_compatible_custom_providers).
  • TUI demoting canonical providers configured via the keyed providers: schema (canonical-order keying on slug membership, not is_user_defined).

teknium1 pushed a commit that referenced this pull request May 14, 2026
Three call-sites in the codebase each duplicated the same config-slice
+ list_authenticated_providers + post-processing pattern:

- hermes_cli/web_server.py /api/model/options
- tui_gateway/server.py model.options JSON-RPC
- tui_gateway/server.py model.save_key JSON-RPC

This consolidates them onto hermes_cli/inventory.py:

  load_picker_context() -> ConfigContext
      Replaces the 17-LOC config-slice (model.{default,name,provider,
      base_url}, providers:, custom_providers:) every consumer did
      inline.

  ConfigContext.with_overrides(*, current_provider=, current_model=,
                               current_base_url=) -> ConfigContext
      Truthy-only overlay for TUI agent-session state on top of disk
      config. Empty getattr(agent, ...) attrs MUST NOT clobber disk.

  build_models_payload(ctx, *, include_unconfigured, picker_hints,
                       canonical_order, max_models) -> dict
      Single payload builder. Delegates curation to
      list_authenticated_providers (does not call provider_model_ids
      per row \u2014 that pulls non-agentic models). picker_hints +
      canonical_order produce the TUI ModelPickerDialog shape;
      defaults match the dashboard's existing /api/model/options
      contract.

Two latent bugs fixed by consolidation:

1. The dashboard read cfg.get('custom_providers') directly, missing
   the v12+ keyed providers: form. Now both surfaces go through
   get_compatible_custom_providers().

2. The TUI's canonical-merge keyed on is_user_defined to decide order.
   Section 3 of list_authenticated_providers sets is_user_defined=True
   on rows from the providers: config dict even when the slug is
   canonical \u2014 that silently demoted them to the picker tail.
   _reorder_canonical now keys on slug membership instead.

Stats: +666 / -145 (net +521). Module 240 LOC; 18 behavior tests.

This PR replaces the rejected #23369 (which bundled the consolidation
with new scriptable CLI surfaces \u2014 hermes models list/status, hermes
providers list \u2014 and a JSON contract that have no external user
demand). Just the refactor; the CLI surface is deferred to a separate
PR gated on actual demand.

Refs #23359.
jsboige pushed a commit to jsboige/hermes-agent that referenced this pull request May 14, 2026
Three call-sites in the codebase each duplicated the same config-slice
+ list_authenticated_providers + post-processing pattern:

- hermes_cli/web_server.py /api/model/options
- tui_gateway/server.py model.options JSON-RPC
- tui_gateway/server.py model.save_key JSON-RPC

This consolidates them onto hermes_cli/inventory.py:

  load_picker_context() -> ConfigContext
      Replaces the 17-LOC config-slice (model.{default,name,provider,
      base_url}, providers:, custom_providers:) every consumer did
      inline.

  ConfigContext.with_overrides(*, current_provider=, current_model=,
                               current_base_url=) -> ConfigContext
      Truthy-only overlay for TUI agent-session state on top of disk
      config. Empty getattr(agent, ...) attrs MUST NOT clobber disk.

  build_models_payload(ctx, *, include_unconfigured, picker_hints,
                       canonical_order, max_models) -> dict
      Single payload builder. Delegates curation to
      list_authenticated_providers (does not call provider_model_ids
      per row \u2014 that pulls non-agentic models). picker_hints +
      canonical_order produce the TUI ModelPickerDialog shape;
      defaults match the dashboard's existing /api/model/options
      contract.

Two latent bugs fixed by consolidation:

1. The dashboard read cfg.get('custom_providers') directly, missing
   the v12+ keyed providers: form. Now both surfaces go through
   get_compatible_custom_providers().

2. The TUI's canonical-merge keyed on is_user_defined to decide order.
   Section 3 of list_authenticated_providers sets is_user_defined=True
   on rows from the providers: config dict even when the slug is
   canonical \u2014 that silently demoted them to the picker tail.
   _reorder_canonical now keys on slug membership instead.

Stats: +666 / -145 (net +521). Module 240 LOC; 18 behavior tests.

This PR replaces the rejected NousResearch#23369 (which bundled the consolidation
with new scriptable CLI surfaces \u2014 hermes models list/status, hermes
providers list \u2014 and a JSON contract that have no external user
demand). Just the refactor; the CLI surface is deferred to a separate
PR gated on actual demand.

Refs NousResearch#23359.
AlexFoxD pushed a commit to AlexFoxD/hermes-agent that referenced this pull request May 21, 2026
Three call-sites in the codebase each duplicated the same config-slice
+ list_authenticated_providers + post-processing pattern:

- hermes_cli/web_server.py /api/model/options
- tui_gateway/server.py model.options JSON-RPC
- tui_gateway/server.py model.save_key JSON-RPC

This consolidates them onto hermes_cli/inventory.py:

  load_picker_context() -> ConfigContext
      Replaces the 17-LOC config-slice (model.{default,name,provider,
      base_url}, providers:, custom_providers:) every consumer did
      inline.

  ConfigContext.with_overrides(*, current_provider=, current_model=,
                               current_base_url=) -> ConfigContext
      Truthy-only overlay for TUI agent-session state on top of disk
      config. Empty getattr(agent, ...) attrs MUST NOT clobber disk.

  build_models_payload(ctx, *, include_unconfigured, picker_hints,
                       canonical_order, max_models) -> dict
      Single payload builder. Delegates curation to
      list_authenticated_providers (does not call provider_model_ids
      per row \u2014 that pulls non-agentic models). picker_hints +
      canonical_order produce the TUI ModelPickerDialog shape;
      defaults match the dashboard's existing /api/model/options
      contract.

Two latent bugs fixed by consolidation:

1. The dashboard read cfg.get('custom_providers') directly, missing
   the v12+ keyed providers: form. Now both surfaces go through
   get_compatible_custom_providers().

2. The TUI's canonical-merge keyed on is_user_defined to decide order.
   Section 3 of list_authenticated_providers sets is_user_defined=True
   on rows from the providers: config dict even when the slug is
   canonical \u2014 that silently demoted them to the picker tail.
   _reorder_canonical now keys on slug membership instead.

Stats: +666 / -145 (net +521). Module 240 LOC; 18 behavior tests.

This PR replaces the rejected NousResearch#23369 (which bundled the consolidation
with new scriptable CLI surfaces \u2014 hermes models list/status, hermes
providers list \u2014 and a JSON contract that have no external user
demand). Just the refactor; the CLI surface is deferred to a separate
PR gated on actual demand.

Refs NousResearch#23359.
gweeteve pushed a commit to gweeteve/hermes-agent that referenced this pull request Jun 2, 2026
Three call-sites in the codebase each duplicated the same config-slice
+ list_authenticated_providers + post-processing pattern:

- hermes_cli/web_server.py /api/model/options
- tui_gateway/server.py model.options JSON-RPC
- tui_gateway/server.py model.save_key JSON-RPC

This consolidates them onto hermes_cli/inventory.py:

  load_picker_context() -> ConfigContext
      Replaces the 17-LOC config-slice (model.{default,name,provider,
      base_url}, providers:, custom_providers:) every consumer did
      inline.

  ConfigContext.with_overrides(*, current_provider=, current_model=,
                               current_base_url=) -> ConfigContext
      Truthy-only overlay for TUI agent-session state on top of disk
      config. Empty getattr(agent, ...) attrs MUST NOT clobber disk.

  build_models_payload(ctx, *, include_unconfigured, picker_hints,
                       canonical_order, max_models) -> dict
      Single payload builder. Delegates curation to
      list_authenticated_providers (does not call provider_model_ids
      per row \u2014 that pulls non-agentic models). picker_hints +
      canonical_order produce the TUI ModelPickerDialog shape;
      defaults match the dashboard's existing /api/model/options
      contract.

Two latent bugs fixed by consolidation:

1. The dashboard read cfg.get('custom_providers') directly, missing
   the v12+ keyed providers: form. Now both surfaces go through
   get_compatible_custom_providers().

2. The TUI's canonical-merge keyed on is_user_defined to decide order.
   Section 3 of list_authenticated_providers sets is_user_defined=True
   on rows from the providers: config dict even when the slug is
   canonical \u2014 that silently demoted them to the picker tail.
   _reorder_canonical now keys on slug membership instead.

Stats: +666 / -145 (net +521). Module 240 LOC; 18 behavior tests.

This PR replaces the rejected NousResearch#23369 (which bundled the consolidation
with new scriptable CLI surfaces \u2014 hermes models list/status, hermes
providers list \u2014 and a JSON contract that have no external user
demand). Just the refactor; the CLI surface is deferred to a separate
PR gated on actual demand.

Refs NousResearch#23359.
Seven74AI pushed a commit to Seven74AI/hermes-agent that referenced this pull request Jun 13, 2026
Three call-sites in the codebase each duplicated the same config-slice
+ list_authenticated_providers + post-processing pattern:

- hermes_cli/web_server.py /api/model/options
- tui_gateway/server.py model.options JSON-RPC
- tui_gateway/server.py model.save_key JSON-RPC

This consolidates them onto hermes_cli/inventory.py:

  load_picker_context() -> ConfigContext
      Replaces the 17-LOC config-slice (model.{default,name,provider,
      base_url}, providers:, custom_providers:) every consumer did
      inline.

  ConfigContext.with_overrides(*, current_provider=, current_model=,
                               current_base_url=) -> ConfigContext
      Truthy-only overlay for TUI agent-session state on top of disk
      config. Empty getattr(agent, ...) attrs MUST NOT clobber disk.

  build_models_payload(ctx, *, include_unconfigured, picker_hints,
                       canonical_order, max_models) -> dict
      Single payload builder. Delegates curation to
      list_authenticated_providers (does not call provider_model_ids
      per row \u2014 that pulls non-agentic models). picker_hints +
      canonical_order produce the TUI ModelPickerDialog shape;
      defaults match the dashboard's existing /api/model/options
      contract.

Two latent bugs fixed by consolidation:

1. The dashboard read cfg.get('custom_providers') directly, missing
   the v12+ keyed providers: form. Now both surfaces go through
   get_compatible_custom_providers().

2. The TUI's canonical-merge keyed on is_user_defined to decide order.
   Section 3 of list_authenticated_providers sets is_user_defined=True
   on rows from the providers: config dict even when the slug is
   canonical \u2014 that silently demoted them to the picker tail.
   _reorder_canonical now keys on slug membership instead.

Stats: +666 / -145 (net +521). Module 240 LOC; 18 behavior tests.

This PR replaces the rejected NousResearch#23369 (which bundled the consolidation
with new scriptable CLI surfaces \u2014 hermes models list/status, hermes
providers list \u2014 and a JSON contract that have no external user
demand). Just the refactor; the CLI surface is deferred to a separate
PR gated on actual demand.

Refs NousResearch#23359.
alt-glitch pushed a commit that referenced this pull request Jun 14, 2026
Three call-sites in the codebase each duplicated the same config-slice
+ list_authenticated_providers + post-processing pattern:

- hermes_cli/web_server.py /api/model/options
- tui_gateway/server.py model.options JSON-RPC
- tui_gateway/server.py model.save_key JSON-RPC

This consolidates them onto hermes_cli/inventory.py:

  load_picker_context() -> ConfigContext
      Replaces the 17-LOC config-slice (model.{default,name,provider,
      base_url}, providers:, custom_providers:) every consumer did
      inline.

  ConfigContext.with_overrides(*, current_provider=, current_model=,
                               current_base_url=) -> ConfigContext
      Truthy-only overlay for TUI agent-session state on top of disk
      config. Empty getattr(agent, ...) attrs MUST NOT clobber disk.

  build_models_payload(ctx, *, include_unconfigured, picker_hints,
                       canonical_order, max_models) -> dict
      Single payload builder. Delegates curation to
      list_authenticated_providers (does not call provider_model_ids
      per row \u2014 that pulls non-agentic models). picker_hints +
      canonical_order produce the TUI ModelPickerDialog shape;
      defaults match the dashboard's existing /api/model/options
      contract.

Two latent bugs fixed by consolidation:

1. The dashboard read cfg.get('custom_providers') directly, missing
   the v12+ keyed providers: form. Now both surfaces go through
   get_compatible_custom_providers().

2. The TUI's canonical-merge keyed on is_user_defined to decide order.
   Section 3 of list_authenticated_providers sets is_user_defined=True
   on rows from the providers: config dict even when the slug is
   canonical \u2014 that silently demoted them to the picker tail.
   _reorder_canonical now keys on slug membership instead.

Stats: +666 / -145 (net +521). Module 240 LOC; 18 behavior tests.

This PR replaces the rejected #23369 (which bundled the consolidation
with new scriptable CLI surfaces \u2014 hermes models list/status, hermes
providers list \u2014 and a JSON contract that have no external user
demand). Just the refactor; the CLI surface is deferred to a separate
PR gated on actual demand.

Refs #23359.
T02200059 pushed a commit to T02200059/hermes-agent that referenced this pull request Jun 18, 2026
Three call-sites in the codebase each duplicated the same config-slice
+ list_authenticated_providers + post-processing pattern:

- hermes_cli/web_server.py /api/model/options
- tui_gateway/server.py model.options JSON-RPC
- tui_gateway/server.py model.save_key JSON-RPC

This consolidates them onto hermes_cli/inventory.py:

  load_picker_context() -> ConfigContext
      Replaces the 17-LOC config-slice (model.{default,name,provider,
      base_url}, providers:, custom_providers:) every consumer did
      inline.

  ConfigContext.with_overrides(*, current_provider=, current_model=,
                               current_base_url=) -> ConfigContext
      Truthy-only overlay for TUI agent-session state on top of disk
      config. Empty getattr(agent, ...) attrs MUST NOT clobber disk.

  build_models_payload(ctx, *, include_unconfigured, picker_hints,
                       canonical_order, max_models) -> dict
      Single payload builder. Delegates curation to
      list_authenticated_providers (does not call provider_model_ids
      per row \u2014 that pulls non-agentic models). picker_hints +
      canonical_order produce the TUI ModelPickerDialog shape;
      defaults match the dashboard's existing /api/model/options
      contract.

Two latent bugs fixed by consolidation:

1. The dashboard read cfg.get('custom_providers') directly, missing
   the v12+ keyed providers: form. Now both surfaces go through
   get_compatible_custom_providers().

2. The TUI's canonical-merge keyed on is_user_defined to decide order.
   Section 3 of list_authenticated_providers sets is_user_defined=True
   on rows from the providers: config dict even when the slug is
   canonical \u2014 that silently demoted them to the picker tail.
   _reorder_canonical now keys on slug membership instead.

Stats: +666 / -145 (net +521). Module 240 LOC; 18 behavior tests.

This PR replaces the rejected NousResearch#23369 (which bundled the consolidation
with new scriptable CLI surfaces \u2014 hermes models list/status, hermes
providers list \u2014 and a JSON contract that have no external user
demand). Just the refactor; the CLI surface is deferred to a separate
PR gated on actual demand.

Refs NousResearch#23359.
liuchanchen pushed a commit to liuchanchen/hermes-agent that referenced this pull request Jul 3, 2026
Three call-sites in the codebase each duplicated the same config-slice
+ list_authenticated_providers + post-processing pattern:

- hermes_cli/web_server.py /api/model/options
- tui_gateway/server.py model.options JSON-RPC
- tui_gateway/server.py model.save_key JSON-RPC

This consolidates them onto hermes_cli/inventory.py:

  load_picker_context() -> ConfigContext
      Replaces the 17-LOC config-slice (model.{default,name,provider,
      base_url}, providers:, custom_providers:) every consumer did
      inline.

  ConfigContext.with_overrides(*, current_provider=, current_model=,
                               current_base_url=) -> ConfigContext
      Truthy-only overlay for TUI agent-session state on top of disk
      config. Empty getattr(agent, ...) attrs MUST NOT clobber disk.

  build_models_payload(ctx, *, include_unconfigured, picker_hints,
                       canonical_order, max_models) -> dict
      Single payload builder. Delegates curation to
      list_authenticated_providers (does not call provider_model_ids
      per row \u2014 that pulls non-agentic models). picker_hints +
      canonical_order produce the TUI ModelPickerDialog shape;
      defaults match the dashboard's existing /api/model/options
      contract.

Two latent bugs fixed by consolidation:

1. The dashboard read cfg.get('custom_providers') directly, missing
   the v12+ keyed providers: form. Now both surfaces go through
   get_compatible_custom_providers().

2. The TUI's canonical-merge keyed on is_user_defined to decide order.
   Section 3 of list_authenticated_providers sets is_user_defined=True
   on rows from the providers: config dict even when the slug is
   canonical \u2014 that silently demoted them to the picker tail.
   _reorder_canonical now keys on slug membership instead.

Stats: +666 / -145 (net +521). Module 240 LOC; 18 behavior tests.

This PR replaces the rejected NousResearch#23369 (which bundled the consolidation
with new scriptable CLI surfaces \u2014 hermes models list/status, hermes
providers list \u2014 and a JSON contract that have no external user
demand). Just the refactor; the CLI surface is deferred to a separate
PR gated on actual demand.

Refs NousResearch#23359.
liuchanchen pushed a commit to liuchanchen/hermes-agent that referenced this pull request Jul 3, 2026
Three call-sites in the codebase each duplicated the same config-slice
+ list_authenticated_providers + post-processing pattern:

- hermes_cli/web_server.py /api/model/options
- tui_gateway/server.py model.options JSON-RPC
- tui_gateway/server.py model.save_key JSON-RPC

This consolidates them onto hermes_cli/inventory.py:

  load_picker_context() -> ConfigContext
      Replaces the 17-LOC config-slice (model.{default,name,provider,
      base_url}, providers:, custom_providers:) every consumer did
      inline.

  ConfigContext.with_overrides(*, current_provider=, current_model=,
                               current_base_url=) -> ConfigContext
      Truthy-only overlay for TUI agent-session state on top of disk
      config. Empty getattr(agent, ...) attrs MUST NOT clobber disk.

  build_models_payload(ctx, *, include_unconfigured, picker_hints,
                       canonical_order, max_models) -> dict
      Single payload builder. Delegates curation to
      list_authenticated_providers (does not call provider_model_ids
      per row \u2014 that pulls non-agentic models). picker_hints +
      canonical_order produce the TUI ModelPickerDialog shape;
      defaults match the dashboard's existing /api/model/options
      contract.

Two latent bugs fixed by consolidation:

1. The dashboard read cfg.get('custom_providers') directly, missing
   the v12+ keyed providers: form. Now both surfaces go through
   get_compatible_custom_providers().

2. The TUI's canonical-merge keyed on is_user_defined to decide order.
   Section 3 of list_authenticated_providers sets is_user_defined=True
   on rows from the providers: config dict even when the slug is
   canonical \u2014 that silently demoted them to the picker tail.
   _reorder_canonical now keys on slug membership instead.

Stats: +666 / -145 (net +521). Module 240 LOC; 18 behavior tests.

This PR replaces the rejected NousResearch#23369 (which bundled the consolidation
with new scriptable CLI surfaces \u2014 hermes models list/status, hermes
providers list \u2014 and a JSON contract that have no external user
demand). Just the refactor; the CLI surface is deferred to a separate
PR gated on actual demand.

Refs NousResearch#23359.
kulikman pushed a commit to kulikman/hermes-agent that referenced this pull request Jul 16, 2026
Three call-sites in the codebase each duplicated the same config-slice
+ list_authenticated_providers + post-processing pattern:

- hermes_cli/web_server.py /api/model/options
- tui_gateway/server.py model.options JSON-RPC
- tui_gateway/server.py model.save_key JSON-RPC

This consolidates them onto hermes_cli/inventory.py:

  load_picker_context() -> ConfigContext
      Replaces the 17-LOC config-slice (model.{default,name,provider,
      base_url}, providers:, custom_providers:) every consumer did
      inline.

  ConfigContext.with_overrides(*, current_provider=, current_model=,
                               current_base_url=) -> ConfigContext
      Truthy-only overlay for TUI agent-session state on top of disk
      config. Empty getattr(agent, ...) attrs MUST NOT clobber disk.

  build_models_payload(ctx, *, include_unconfigured, picker_hints,
                       canonical_order, max_models) -> dict
      Single payload builder. Delegates curation to
      list_authenticated_providers (does not call provider_model_ids
      per row \u2014 that pulls non-agentic models). picker_hints +
      canonical_order produce the TUI ModelPickerDialog shape;
      defaults match the dashboard's existing /api/model/options
      contract.

Two latent bugs fixed by consolidation:

1. The dashboard read cfg.get('custom_providers') directly, missing
   the v12+ keyed providers: form. Now both surfaces go through
   get_compatible_custom_providers().

2. The TUI's canonical-merge keyed on is_user_defined to decide order.
   Section 3 of list_authenticated_providers sets is_user_defined=True
   on rows from the providers: config dict even when the slug is
   canonical \u2014 that silently demoted them to the picker tail.
   _reorder_canonical now keys on slug membership instead.

Stats: +666 / -145 (net +521). Module 240 LOC; 18 behavior tests.

This PR replaces the rejected NousResearch#23369 (which bundled the consolidation
with new scriptable CLI surfaces \u2014 hermes models list/status, hermes
providers list \u2014 and a JSON contract that have no external user
demand). Just the refactor; the CLI surface is deferred to a separate
PR gated on actual demand.

Refs NousResearch#23359.
Gravezzz pushed a commit to Gravezzz/hermes-agent that referenced this pull request Jul 21, 2026
Three call-sites in the codebase each duplicated the same config-slice
+ list_authenticated_providers + post-processing pattern:

- hermes_cli/web_server.py /api/model/options
- tui_gateway/server.py model.options JSON-RPC
- tui_gateway/server.py model.save_key JSON-RPC

This consolidates them onto hermes_cli/inventory.py:

  load_picker_context() -> ConfigContext
      Replaces the 17-LOC config-slice (model.{default,name,provider,
      base_url}, providers:, custom_providers:) every consumer did
      inline.

  ConfigContext.with_overrides(*, current_provider=, current_model=,
                               current_base_url=) -> ConfigContext
      Truthy-only overlay for TUI agent-session state on top of disk
      config. Empty getattr(agent, ...) attrs MUST NOT clobber disk.

  build_models_payload(ctx, *, include_unconfigured, picker_hints,
                       canonical_order, max_models) -> dict
      Single payload builder. Delegates curation to
      list_authenticated_providers (does not call provider_model_ids
      per row \u2014 that pulls non-agentic models). picker_hints +
      canonical_order produce the TUI ModelPickerDialog shape;
      defaults match the dashboard's existing /api/model/options
      contract.

Two latent bugs fixed by consolidation:

1. The dashboard read cfg.get('custom_providers') directly, missing
   the v12+ keyed providers: form. Now both surfaces go through
   get_compatible_custom_providers().

2. The TUI's canonical-merge keyed on is_user_defined to decide order.
   Section 3 of list_authenticated_providers sets is_user_defined=True
   on rows from the providers: config dict even when the slug is
   canonical \u2014 that silently demoted them to the picker tail.
   _reorder_canonical now keys on slug membership instead.

Stats: +666 / -145 (net +521). Module 240 LOC; 18 behavior tests.

This PR replaces the rejected NousResearch#23369 (which bundled the consolidation
with new scriptable CLI surfaces \u2014 hermes models list/status, hermes
providers list \u2014 and a JSON contract that have no external user
demand). Just the refactor; the CLI surface is deferred to a separate
PR gated on actual demand.

Refs NousResearch#23359.
waym0reom3ga added a commit to waym0reom3ga/autolycus-agent that referenced this pull request Jul 21, 2026
Three call-sites in the codebase each duplicated the same config-slice
+ list_authenticated_providers + post-processing pattern:

- hermes_cli/web_server.py /api/model/options
- tui_gateway/server.py model.options JSON-RPC
- tui_gateway/server.py model.save_key JSON-RPC

This consolidates them onto hermes_cli/inventory.py:

  load_picker_context() -> ConfigContext
      Replaces the 17-LOC config-slice (model.{default,name,provider,
      base_url}, providers:, custom_providers:) every consumer did
      inline.

  ConfigContext.with_overrides(*, current_provider=, current_model=,
                               current_base_url=) -> ConfigContext
      Truthy-only overlay for TUI agent-session state on top of disk
      config. Empty getattr(agent, ...) attrs MUST NOT clobber disk.

  build_models_payload(ctx, *, include_unconfigured, picker_hints,
                       canonical_order, max_models) -> dict
      Single payload builder. Delegates curation to
      list_authenticated_providers (does not call provider_model_ids
      per row \u2014 that pulls non-agentic models). picker_hints +
      canonical_order produce the TUI ModelPickerDialog shape;
      defaults match the dashboard's existing /api/model/options
      contract.

Two latent bugs fixed by consolidation:

1. The dashboard read cfg.get('custom_providers') directly, missing
   the v12+ keyed providers: form. Now both surfaces go through
   get_compatible_custom_providers().

2. The TUI's canonical-merge keyed on is_user_defined to decide order.
   Section 3 of list_authenticated_providers sets is_user_defined=True
   on rows from the providers: config dict even when the slug is
   canonical \u2014 that silently demoted them to the picker tail.
   _reorder_canonical now keys on slug membership instead.

Stats: +666 / -145 (net +521). Module 240 LOC; 18 behavior tests.

This PR replaces the rejected NousResearch#23369 (which bundled the consolidation
with new scriptable CLI surfaces \u2014 hermes models list/status, hermes
providers list \u2014 and a JSON contract that have no external user
demand). Just the refactor; the CLI surface is deferred to a separate
PR gated on actual demand.

Refs NousResearch#23359.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/cli CLI entry point, hermes_cli/, setup wizard P2 Medium — degraded but workaround exists type/feature New feature or request

Projects

None yet

2 participants