From 2ae195673e5009d4d1c2436a1da463047d6ae98d Mon Sep 17 00:00:00 2001 From: kshitij Date: Mon, 20 Jul 2026 11:57:45 +0530 Subject: [PATCH] fix: widen metadata-preserve guard to list-of-dicts models form The dict-form guard from PR #67878 only covered the mapping shape ({model: {context_length: ...}}). The list-of-dicts shape ([{id: model, context_length: ...}]) is also a supported config form (per _declared_model_ids) and was still being replaced with a flat list of strings, destroying per-model metadata. Sibling site for #67841. --- hermes_cli/model_switch.py | 9 ++++- .../test_model_switch_custom_providers.py | 38 +++++++++++++++++++ 2 files changed, 45 insertions(+), 2 deletions(-) diff --git a/hermes_cli/model_switch.py b/hermes_cli/model_switch.py index eb7b237cf2e5..f3fd20875890 100644 --- a/hermes_cli/model_switch.py +++ b/hermes_cli/model_switch.py @@ -134,10 +134,15 @@ def _save_discovered_models_to_config( continue existing = entry.get("models") # Preserve per-model metadata: when ``models`` is a mapping - # (e.g. ``{"model-a": {"context_length": 8192}}``), the user - # has curated metadata per model — do not replace it. + # (e.g. ``{"model-a": {"context_length": 8192}}``) or a list of + # dicts (e.g. ``[{"id": "model-a", "context_length": 8192}]``), + # the user has curated metadata per model — do not replace it. if isinstance(existing, dict): continue + if isinstance(existing, list) and any( + isinstance(m, dict) for m in existing + ): + continue # Only update when models are stale — avoids unnecessary # config writes on every picker open. if isinstance(existing, list) and existing == model_ids: diff --git a/tests/hermes_cli/test_model_switch_custom_providers.py b/tests/hermes_cli/test_model_switch_custom_providers.py index 05f0edf7b4f5..47e65a25d283 100644 --- a/tests/hermes_cli/test_model_switch_custom_providers.py +++ b/tests/hermes_cli/test_model_switch_custom_providers.py @@ -1516,3 +1516,41 @@ def test_save_discovered_models_preserves_dict_form(monkeypatch): assert save_calls == [], ( "Dict-form models must not be replaced with a flat list" ) + + +def test_save_discovered_models_preserves_list_of_dicts_form(monkeypatch): + """``_save_discovered_models_to_config`` must not replace a list-of-dicts + ``models`` form (per-model metadata via ``[{id: ...}]``) with a flat list + of strings (#67841 sibling site).""" + from hermes_cli.model_switch import _save_discovered_models_to_config + + save_calls = [] + + def fake_save(config): + save_calls.append(dict(config)) + + monkeypatch.setattr("hermes_cli.config.save_config", fake_save) + monkeypatch.setattr( + "hermes_cli.config.load_config", + lambda: { + "custom_providers": [ + { + "name": "my-gateway", + "base_url": "https://gateway.example.com/v1", + "models": [ + {"id": "configured-model", "context_length": 8192}, + {"id": "other-model", "context_length": 4096}, + ], + } + ] + }, + ) + + # List-of-dicts models must NOT be overwritten by discovered models + _save_discovered_models_to_config( + "https://gateway.example.com/v1", + ["configured-model", "discovered-model"], + ) + assert save_calls == [], ( + "List-of-dicts models must not be replaced with a flat list" + )