mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-05-18 04:41:56 +00:00
fix(/model): surface Nous Portal models from remote catalog manifest (#23912)
The /model picker for Nous Portal users was returning the in-repo _PROVIDER_MODELS["nous"] snapshot — which only updates on Hermes releases — instead of the remote manifest published at https://hermes-agent.nousresearch.com/docs/api/model-catalog.json. OpenRouter already pulled from the manifest via fetch_openrouter_models; "nous" was the only curated provider where the existing manifest plumbing (get_curated_nous_model_ids → get_curated_nous_models) was defined but not wired into the picker pipeline. Switch the curated build in list_authenticated_providers to use it, with the same graceful fallback to the in-repo snapshot when the manifest is unreachable. Test: tests/hermes_cli/test_model_catalog.py exercises the picker with a patched manifest and asserts the manifest's nous list reaches list_picker_providers. Falls-back-to-static path was already covered by test_curated_nous_ids_falls_back_to_hardcoded_on_empty_catalog.
This commit is contained in:
parent
cc9e788c14
commit
8e2eb4b511
2 changed files with 53 additions and 3 deletions
|
|
@ -1079,6 +1079,7 @@ def list_authenticated_providers(
|
||||||
from hermes_cli.models import (
|
from hermes_cli.models import (
|
||||||
OPENROUTER_MODELS, _PROVIDER_MODELS,
|
OPENROUTER_MODELS, _PROVIDER_MODELS,
|
||||||
_MODELS_DEV_PREFERRED, _merge_with_models_dev, provider_model_ids,
|
_MODELS_DEV_PREFERRED, _merge_with_models_dev, provider_model_ids,
|
||||||
|
get_curated_nous_model_ids,
|
||||||
)
|
)
|
||||||
|
|
||||||
results: List[dict] = []
|
results: List[dict] = []
|
||||||
|
|
@ -1160,9 +1161,12 @@ def list_authenticated_providers(
|
||||||
# Build curated model lists keyed by hermes provider ID
|
# Build curated model lists keyed by hermes provider ID
|
||||||
curated: dict[str, list[str]] = dict(_PROVIDER_MODELS)
|
curated: dict[str, list[str]] = dict(_PROVIDER_MODELS)
|
||||||
curated["openrouter"] = [mid for mid, _ in OPENROUTER_MODELS]
|
curated["openrouter"] = [mid for mid, _ in OPENROUTER_MODELS]
|
||||||
# "nous" shares OpenRouter's curated list if not separately defined
|
# "nous" pulls from the remote model-catalog manifest published at
|
||||||
if "nous" not in curated:
|
# https://hermes-agent.nousresearch.com/docs/api/model-catalog.json so
|
||||||
curated["nous"] = curated["openrouter"]
|
# newly added Portal models surface in the /model picker without
|
||||||
|
# requiring a Hermes release. Falls back to the in-repo
|
||||||
|
# _PROVIDER_MODELS["nous"] snapshot when the manifest is unreachable.
|
||||||
|
curated["nous"] = get_curated_nous_model_ids()
|
||||||
# Ollama Cloud uses dynamic discovery (no static curated list)
|
# Ollama Cloud uses dynamic discovery (no static curated list)
|
||||||
if "ollama-cloud" not in curated:
|
if "ollama-cloud" not in curated:
|
||||||
from hermes_cli.models import fetch_ollama_cloud_models
|
from hermes_cli.models import fetch_ollama_cloud_models
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import json
|
import json
|
||||||
|
import os
|
||||||
import time
|
import time
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
|
|
@ -282,3 +283,48 @@ class TestIntegrationWithModelsModule:
|
||||||
result = get_curated_nous_model_ids()
|
result = get_curated_nous_model_ids()
|
||||||
|
|
||||||
assert result == ["anthropic/claude-opus-4.7", "moonshotai/kimi-k2.6"]
|
assert result == ["anthropic/claude-opus-4.7", "moonshotai/kimi-k2.6"]
|
||||||
|
|
||||||
|
def test_picker_nous_row_uses_manifest(self, tmp_path, monkeypatch):
|
||||||
|
"""The /model picker must surface the manifest's nous list, not the
|
||||||
|
in-repo _PROVIDER_MODELS["nous"] snapshot. Regression: before this
|
||||||
|
fix, list_authenticated_providers() built the curated dict from
|
||||||
|
_PROVIDER_MODELS only — so newly-added Portal models never reached
|
||||||
|
the slash-command picker until the next Hermes release.
|
||||||
|
"""
|
||||||
|
# We deliberately do NOT use the ``isolated_home`` fixture here:
|
||||||
|
# that fixture monkeypatches ``Path.home`` to ``tmp_path``, which
|
||||||
|
# trips the auth-store seat-belt in ``_auth_file_path()`` because
|
||||||
|
# ``HERMES_HOME / auth.json`` then resolves to the same path the
|
||||||
|
# seat-belt thinks is the "real" user store. Use the autouse
|
||||||
|
# ``_hermetic_environment`` HERMES_HOME directly instead.
|
||||||
|
import importlib
|
||||||
|
from hermes_cli import model_catalog
|
||||||
|
importlib.reload(model_catalog)
|
||||||
|
try:
|
||||||
|
from hermes_cli.model_switch import list_picker_providers
|
||||||
|
|
||||||
|
active_home = Path(os.environ["HERMES_HOME"])
|
||||||
|
(active_home / "auth.json").write_text(
|
||||||
|
json.dumps(
|
||||||
|
{
|
||||||
|
"providers": {"nous": {"access_token": "fake"}},
|
||||||
|
"credential_pool": {},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
with patch.object(
|
||||||
|
model_catalog, "_fetch_manifest", return_value=_valid_manifest()
|
||||||
|
):
|
||||||
|
picker = list_picker_providers(
|
||||||
|
current_provider="nous", max_models=99
|
||||||
|
)
|
||||||
|
finally:
|
||||||
|
model_catalog.reset_cache()
|
||||||
|
|
||||||
|
nous_row = next((r for r in picker if r["slug"] == "nous"), None)
|
||||||
|
assert nous_row is not None, "nous row must appear when authed"
|
||||||
|
assert nous_row["models"] == [
|
||||||
|
"anthropic/claude-opus-4.7",
|
||||||
|
"moonshotai/kimi-k2.6",
|
||||||
|
]
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue