mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-04-28 06:51:16 +08:00
fix(auth): preserve 'custom' provider instead of silently remapping to 'openrouter'
resolve_provider('custom') was silently returning 'openrouter', causing
users who set provider: custom in config.yaml to unknowingly route
through OpenRouter instead of their local/custom endpoint. The display
showed 'via openrouter' even when the user explicitly chose custom.
Changes:
- auth.py: Split the conditional so 'custom' returns 'custom' as-is
- runtime_provider.py: _resolve_named_custom_runtime now returns
provider='custom' instead of 'openrouter'
- runtime_provider.py: _resolve_openrouter_runtime returns
provider='custom' when that was explicitly requested
- Add 'no-key-required' placeholder for keyless local servers
- Update existing test + add 5 new tests covering the fix
Fixes #2562
This commit is contained in:
@@ -690,8 +690,10 @@ def resolve_provider(
|
||||
}
|
||||
normalized = _PROVIDER_ALIASES.get(normalized, normalized)
|
||||
|
||||
if normalized in {"openrouter", "custom"}:
|
||||
if normalized == "openrouter":
|
||||
return "openrouter"
|
||||
if normalized == "custom":
|
||||
return "custom"
|
||||
if normalized in PROVIDER_REGISTRY:
|
||||
return normalized
|
||||
if normalized != "auto":
|
||||
|
||||
@@ -198,7 +198,7 @@ def _resolve_named_custom_runtime(
|
||||
api_key = next((candidate for candidate in api_key_candidates if has_usable_secret(candidate)), "")
|
||||
|
||||
return {
|
||||
"provider": "openrouter",
|
||||
"provider": "custom",
|
||||
"api_mode": custom_provider.get("api_mode")
|
||||
or _detect_api_mode_for_url(base_url)
|
||||
or "chat_completions",
|
||||
@@ -279,8 +279,16 @@ def _resolve_openrouter_runtime(
|
||||
|
||||
source = "explicit" if (explicit_api_key or explicit_base_url) else "env/config"
|
||||
|
||||
# When "custom" was explicitly requested, preserve that as the provider
|
||||
# name instead of silently relabeling to "openrouter" (#2562).
|
||||
# Also provide a placeholder API key for local servers that don't require
|
||||
# authentication — the OpenAI SDK requires a non-empty api_key string.
|
||||
effective_provider = "custom" if requested_norm == "custom" else "openrouter"
|
||||
if effective_provider == "custom" and not api_key and not _is_openrouter_url:
|
||||
api_key = "no-key-required"
|
||||
|
||||
return {
|
||||
"provider": "openrouter",
|
||||
"provider": effective_provider,
|
||||
"api_mode": _parse_api_mode(model_cfg.get("api_mode"))
|
||||
or _detect_api_mode_for_url(base_url)
|
||||
or "chat_completions",
|
||||
|
||||
@@ -267,7 +267,7 @@ def test_named_custom_provider_uses_saved_credentials(monkeypatch):
|
||||
|
||||
resolved = rp.resolve_runtime_provider(requested="local")
|
||||
|
||||
assert resolved["provider"] == "openrouter"
|
||||
assert resolved["provider"] == "custom"
|
||||
assert resolved["api_mode"] == "chat_completions"
|
||||
assert resolved["base_url"] == "http://1.2.3.4:1234/v1"
|
||||
assert resolved["api_key"] == "local-provider-key"
|
||||
@@ -579,3 +579,81 @@ def test_named_custom_provider_anthropic_api_mode(monkeypatch):
|
||||
|
||||
assert resolved["api_mode"] == "anthropic_messages"
|
||||
assert resolved["base_url"] == "https://proxy.example.com/anthropic"
|
||||
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# fix #2562 — resolve_provider("custom") must not remap to "openrouter"
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
|
||||
def test_resolve_provider_custom_returns_custom():
|
||||
"""resolve_provider('custom') must return 'custom', not 'openrouter'."""
|
||||
from hermes_cli.auth import resolve_provider
|
||||
assert resolve_provider("custom") == "custom"
|
||||
|
||||
|
||||
def test_resolve_provider_openrouter_unchanged():
|
||||
"""resolve_provider('openrouter') must still return 'openrouter'."""
|
||||
from hermes_cli.auth import resolve_provider
|
||||
assert resolve_provider("openrouter") == "openrouter"
|
||||
|
||||
|
||||
def test_custom_provider_runtime_preserves_provider_name(monkeypatch):
|
||||
"""resolve_runtime_provider with provider='custom' must return provider='custom'."""
|
||||
monkeypatch.delenv("OPENAI_API_KEY", raising=False)
|
||||
monkeypatch.delenv("OPENROUTER_API_KEY", raising=False)
|
||||
monkeypatch.delenv("OPENAI_BASE_URL", raising=False)
|
||||
monkeypatch.delenv("OPENROUTER_BASE_URL", raising=False)
|
||||
monkeypatch.setattr(
|
||||
rp,
|
||||
"load_config",
|
||||
lambda: {
|
||||
"model": {
|
||||
"provider": "custom",
|
||||
"base_url": "http://localhost:8080/v1",
|
||||
"api_key": "test-key-123",
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
resolved = rp.resolve_runtime_provider(requested="custom")
|
||||
assert resolved["provider"] == "custom", (
|
||||
f"Expected provider='custom', got provider='{resolved['provider']}'"
|
||||
)
|
||||
assert resolved["base_url"] == "http://localhost:8080/v1"
|
||||
assert resolved["api_key"] == "test-key-123"
|
||||
|
||||
|
||||
def test_custom_provider_no_key_gets_placeholder(monkeypatch):
|
||||
"""Local server with no API key should get 'no-key-required' placeholder."""
|
||||
monkeypatch.delenv("OPENAI_API_KEY", raising=False)
|
||||
monkeypatch.delenv("OPENROUTER_API_KEY", raising=False)
|
||||
monkeypatch.delenv("OPENAI_BASE_URL", raising=False)
|
||||
monkeypatch.delenv("OPENROUTER_BASE_URL", raising=False)
|
||||
monkeypatch.setattr(
|
||||
rp,
|
||||
"load_config",
|
||||
lambda: {
|
||||
"model": {
|
||||
"provider": "custom",
|
||||
"base_url": "http://localhost:8080/v1",
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
resolved = rp.resolve_runtime_provider(requested="custom")
|
||||
assert resolved["provider"] == "custom"
|
||||
assert resolved["api_key"] == "no-key-required"
|
||||
assert resolved["base_url"] == "http://localhost:8080/v1"
|
||||
|
||||
|
||||
def test_openrouter_provider_not_affected_by_custom_fix(monkeypatch):
|
||||
"""Fixing custom must not change openrouter behavior."""
|
||||
monkeypatch.delenv("OPENAI_API_KEY", raising=False)
|
||||
monkeypatch.delenv("OPENAI_BASE_URL", raising=False)
|
||||
monkeypatch.delenv("OPENROUTER_BASE_URL", raising=False)
|
||||
monkeypatch.setenv("OPENROUTER_API_KEY", "test-or-key")
|
||||
monkeypatch.setattr(rp, "load_config", lambda: {})
|
||||
|
||||
resolved = rp.resolve_runtime_provider(requested="openrouter")
|
||||
assert resolved["provider"] == "openrouter"
|
||||
|
||||
Reference in New Issue
Block a user