From 88951215d36882c8df0cd98bb6302c0636ef7790 Mon Sep 17 00:00:00 2001 From: teknium1 Date: Sat, 14 Mar 2026 11:24:29 -0700 Subject: [PATCH] fix: avoid custom provider shadowing built-in providers Follow up on salvaged PR #1012. Prevents raw custom-provider names from intercepting built-in provider ids, and keeps the regression coverage focused on current-main behavior. --- hermes_cli/runtime_provider.py | 14 +++++++++ tests/test_runtime_provider_resolution.py | 36 +++++++++++++++-------- 2 files changed, 37 insertions(+), 13 deletions(-) diff --git a/hermes_cli/runtime_provider.py b/hermes_cli/runtime_provider.py index fb487f45..fead6800 100644 --- a/hermes_cli/runtime_provider.py +++ b/hermes_cli/runtime_provider.py @@ -5,6 +5,7 @@ from __future__ import annotations import os from typing import Any, Dict, Optional +from hermes_cli import auth as auth_mod from hermes_cli.auth import ( AuthError, PROVIDER_REGISTRY, @@ -56,6 +57,19 @@ def _get_named_custom_provider(requested_provider: str) -> Optional[Dict[str, An if not requested_norm or requested_norm == "custom": return None + # Raw names should only map to custom providers when they are not already + # valid built-in providers or aliases. Explicit menu keys like + # ``custom:local`` always target the saved custom provider. + if requested_norm == "auto": + return None + if not requested_norm.startswith("custom:"): + try: + auth_mod.resolve_provider(requested_norm) + except AuthError: + pass + else: + return None + config = load_config() custom_providers = config.get("custom_providers") if not isinstance(custom_providers, list): diff --git a/tests/test_runtime_provider_resolution.py b/tests/test_runtime_provider_resolution.py index 3ff1066c..a53c716a 100644 --- a/tests/test_runtime_provider_resolution.py +++ b/tests/test_runtime_provider_resolution.py @@ -226,27 +226,37 @@ def test_named_custom_provider_falls_back_to_openai_api_key(monkeypatch): assert resolved["requested_provider"] == "custom:local-llm" -def test_resolve_runtime_provider_nous_api(monkeypatch): - """Nous Portal API key provider resolves via the api_key path.""" - monkeypatch.setattr(rp, "resolve_provider", lambda *a, **k: "nous-api") +def test_named_custom_provider_does_not_shadow_builtin_provider(monkeypatch): monkeypatch.setattr( rp, - "resolve_api_key_provider_credentials", - lambda pid: { - "provider": "nous-api", - "api_key": "nous-test-key", + "load_config", + lambda: { + "custom_providers": [ + { + "name": "nous", + "base_url": "http://localhost:1234/v1", + "api_key": "shadow-key", + } + ] + }, + ) + monkeypatch.setattr( + rp, + "resolve_nous_runtime_credentials", + lambda **kwargs: { "base_url": "https://inference-api.nousresearch.com/v1", - "source": "NOUS_API_KEY", + "api_key": "nous-runtime-key", + "source": "portal", + "expires_at": None, }, ) - resolved = rp.resolve_runtime_provider(requested="nous-api") + resolved = rp.resolve_runtime_provider(requested="nous") - assert resolved["provider"] == "nous-api" - assert resolved["api_mode"] == "chat_completions" + assert resolved["provider"] == "nous" assert resolved["base_url"] == "https://inference-api.nousresearch.com/v1" - assert resolved["api_key"] == "nous-test-key" - assert resolved["requested_provider"] == "nous-api" + assert resolved["api_key"] == "nous-runtime-key" + assert resolved["requested_provider"] == "nous" def test_explicit_openrouter_skips_openai_base_url(monkeypatch):