mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-06-15 09:21:36 +00:00
fix(model_switch): section 3 base_url/model/dedup follow-up
On top of the salvaged PR #12505 (Jason/farion1231, which adds dict-format models: enumeration to both sections), three section-3 refinements from competing PR #11534 (YangManBOBO): - accept base_url as canonical (matches Hermes's writer and custom_providers entries); keep api/url as fallbacks for legacy/hand-edited configs - accept singular model as a default_model synonym, matching custom_providers - add seen_slugs guard so the same provider slug appearing in both providers: dict and custom_providers: list emits exactly one picker row (providers: dict wins since section 3 runs first) Two regression tests cover the new behavior. AUTHOR_MAP entry added for farion1231 so CI doesn't reject the cherry-picked commit.
This commit is contained in:
parent
bca03eab20
commit
5a23f3291a
3 changed files with 95 additions and 2 deletions
|
|
@ -1039,9 +1039,23 @@ def list_authenticated_providers(
|
|||
for ep_name, ep_cfg in user_providers.items():
|
||||
if not isinstance(ep_cfg, dict):
|
||||
continue
|
||||
# Skip if this slug was already emitted (e.g. canonical provider
|
||||
# with the same name) or will be picked up by section 4.
|
||||
if ep_name.lower() in seen_slugs:
|
||||
continue
|
||||
display_name = ep_cfg.get("name", "") or ep_name
|
||||
api_url = ep_cfg.get("api", "") or ep_cfg.get("url", "") or ""
|
||||
default_model = ep_cfg.get("default_model", "")
|
||||
# ``base_url`` is Hermes's canonical write key (matches
|
||||
# custom_providers and _save_custom_provider); ``api`` / ``url``
|
||||
# remain as fallbacks for hand-edited / legacy configs.
|
||||
api_url = (
|
||||
ep_cfg.get("base_url", "")
|
||||
or ep_cfg.get("api", "")
|
||||
or ep_cfg.get("url", "")
|
||||
or ""
|
||||
)
|
||||
# ``default_model`` is the legacy key; ``model`` matches what
|
||||
# custom_providers entries use, so accept either.
|
||||
default_model = ep_cfg.get("default_model", "") or ep_cfg.get("model", "")
|
||||
|
||||
# Build models list from both default_model and full models array
|
||||
models_list = []
|
||||
|
|
@ -1073,6 +1087,7 @@ def list_authenticated_providers(
|
|||
"source": "user-config",
|
||||
"api_url": api_url,
|
||||
})
|
||||
seen_slugs.add(ep_name.lower())
|
||||
|
||||
# --- 4. Saved custom providers from config ---
|
||||
# Each ``custom_providers`` entry represents one model under a named
|
||||
|
|
|
|||
|
|
@ -177,6 +177,7 @@ AUTHOR_MAP = {
|
|||
"duerzy@gmail.com": "duerzy",
|
||||
"emozilla@nousresearch.com": "emozilla",
|
||||
"fancydirty@gmail.com": "fancydirty",
|
||||
"farion1231@gmail.com": "farion1231",
|
||||
"floptopbot33@gmail.com": "flobo3",
|
||||
"fontana.pedro93@gmail.com": "pefontana",
|
||||
"francis.x.fitzpatrick@gmail.com": "fxfitz",
|
||||
|
|
|
|||
|
|
@ -227,6 +227,83 @@ def test_list_authenticated_providers_fallback_to_default_only(monkeypatch):
|
|||
assert user_prov["models"] == ["single-model"]
|
||||
|
||||
|
||||
def test_list_authenticated_providers_accepts_base_url_and_singular_model(monkeypatch):
|
||||
"""providers: dict entries written in canonical Hermes shape
|
||||
(``base_url`` + singular ``model``) should resolve the same as the
|
||||
legacy ``api`` + ``default_model`` shape.
|
||||
|
||||
Regression: section 3 previously only read ``api``/``url`` and
|
||||
``default_model``, so new-shape entries written by Hermes's own writer
|
||||
surfaced with empty ``api_url`` and no default.
|
||||
"""
|
||||
monkeypatch.setattr("agent.models_dev.fetch_models_dev", lambda: {})
|
||||
monkeypatch.setattr("hermes_cli.providers.HERMES_OVERLAYS", {})
|
||||
|
||||
user_providers = {
|
||||
"custom": {
|
||||
"base_url": "http://example.com/v1",
|
||||
"model": "gpt-5.4",
|
||||
"models": {
|
||||
"gpt-5.4": {},
|
||||
"grok-4.20-beta": {},
|
||||
"minimax-m2.7": {},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
providers = list_authenticated_providers(
|
||||
current_provider="custom",
|
||||
user_providers=user_providers,
|
||||
custom_providers=[],
|
||||
max_models=50,
|
||||
)
|
||||
|
||||
custom = next((p for p in providers if p["slug"] == "custom"), None)
|
||||
assert custom is not None
|
||||
assert custom["api_url"] == "http://example.com/v1"
|
||||
assert custom["models"] == ["gpt-5.4", "grok-4.20-beta", "minimax-m2.7"]
|
||||
assert custom["total_models"] == 3
|
||||
|
||||
|
||||
def test_list_authenticated_providers_dedupes_when_user_and_custom_overlap(monkeypatch):
|
||||
"""When the same slug appears in both ``providers:`` dict and
|
||||
``custom_providers:`` list, emit exactly one row (providers: dict wins
|
||||
since it is processed first).
|
||||
|
||||
Regression: section 3 previously had no ``seen_slugs`` check, so
|
||||
overlapping entries produced two picker rows for the same provider.
|
||||
"""
|
||||
monkeypatch.setattr("agent.models_dev.fetch_models_dev", lambda: {})
|
||||
monkeypatch.setattr("hermes_cli.providers.HERMES_OVERLAYS", {})
|
||||
|
||||
providers = list_authenticated_providers(
|
||||
current_provider="custom",
|
||||
user_providers={
|
||||
"custom": {
|
||||
"base_url": "http://example.com/v1",
|
||||
"model": "gpt-5.4",
|
||||
"models": {
|
||||
"gpt-5.4": {},
|
||||
"grok-4.20-beta": {},
|
||||
},
|
||||
}
|
||||
},
|
||||
custom_providers=[
|
||||
{
|
||||
"name": "custom",
|
||||
"base_url": "http://example.com/v1",
|
||||
"model": "legacy-only-model",
|
||||
}
|
||||
],
|
||||
max_models=50,
|
||||
)
|
||||
|
||||
matches = [p for p in providers if p["slug"] == "custom"]
|
||||
assert len(matches) == 1
|
||||
# providers: dict wins — legacy-only-model is suppressed.
|
||||
assert matches[0]["models"] == ["gpt-5.4", "grok-4.20-beta"]
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# Tests for _get_named_custom_provider with providers: dict
|
||||
# =============================================================================
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue