mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-04-25 00:51:20 +00:00
fix(runtime): resolve bare custom provider to loopback or CUSTOM_BASE_URL
When /model selects Custom but model.provider in YAML still reflects a prior provider, trust model.base_url only for loopback hosts or when provider is custom. Consult CUSTOM_BASE_URL before OpenRouter defaults (#14676).
This commit is contained in:
parent
2f39dbe471
commit
1dca2e0a28
2 changed files with 94 additions and 1 deletions
|
|
@ -36,6 +36,29 @@ def _normalize_custom_provider_name(value: str) -> str:
|
|||
return value.strip().lower().replace(" ", "-")
|
||||
|
||||
|
||||
def _loopback_hostname(host: str) -> bool:
|
||||
h = (host or "").lower().rstrip(".")
|
||||
return h in {"localhost", "127.0.0.1", "::1", "0.0.0.0"}
|
||||
|
||||
|
||||
def _config_base_url_trustworthy_for_bare_custom(cfg_base_url: str, cfg_provider: str) -> bool:
|
||||
"""Decide whether ``model.base_url`` may back bare ``custom`` runtime resolution.
|
||||
|
||||
GitHub #14676: the model picker can select Custom while ``model.provider`` still reflects a
|
||||
previous provider. Reject non-loopback URLs unless the YAML provider is already ``custom``,
|
||||
so a stale OpenRouter/Z.ai base_url cannot hijack local ``custom`` sessions.
|
||||
"""
|
||||
cfg_provider_norm = (cfg_provider or "").strip().lower()
|
||||
bu = (cfg_base_url or "").strip()
|
||||
if not bu:
|
||||
return False
|
||||
if cfg_provider_norm == "custom":
|
||||
return True
|
||||
if base_url_host_matches(bu, "openrouter.ai"):
|
||||
return False
|
||||
return _loopback_hostname(base_url_hostname(bu))
|
||||
|
||||
|
||||
def _detect_api_mode_for_url(base_url: str) -> Optional[str]:
|
||||
"""Auto-detect api_mode from the resolved base URL.
|
||||
|
||||
|
|
@ -472,6 +495,7 @@ def _resolve_openrouter_runtime(
|
|||
cfg_provider = cfg_provider.strip().lower()
|
||||
|
||||
env_openrouter_base_url = os.getenv("OPENROUTER_BASE_URL", "").strip()
|
||||
env_custom_base_url = os.getenv("CUSTOM_BASE_URL", "").strip()
|
||||
|
||||
# Use config base_url when available and the provider context matches.
|
||||
# OPENAI_BASE_URL env var is no longer consulted — config.yaml is
|
||||
|
|
@ -481,11 +505,14 @@ def _resolve_openrouter_runtime(
|
|||
if requested_norm == "auto":
|
||||
if not cfg_provider or cfg_provider == "auto":
|
||||
use_config_base_url = True
|
||||
elif requested_norm == "custom" and cfg_provider == "custom":
|
||||
elif requested_norm == "custom" and _config_base_url_trustworthy_for_bare_custom(
|
||||
cfg_base_url, cfg_provider
|
||||
):
|
||||
use_config_base_url = True
|
||||
|
||||
base_url = (
|
||||
(explicit_base_url or "").strip()
|
||||
or env_custom_base_url
|
||||
or (cfg_base_url.strip() if use_config_base_url else "")
|
||||
or env_openrouter_base_url
|
||||
or OPENROUTER_BASE_URL
|
||||
|
|
|
|||
|
|
@ -536,6 +536,72 @@ def test_custom_endpoint_explicit_custom_prefers_config_key(monkeypatch):
|
|||
assert resolved["api_key"] == "sk-vllm-key"
|
||||
|
||||
|
||||
def test_bare_custom_uses_loopback_model_base_url_when_provider_not_custom(monkeypatch):
|
||||
"""Regression for #14676: /model can select Custom while YAML still lists another provider."""
|
||||
monkeypatch.setattr(rp, "resolve_provider", lambda *a, **k: "openrouter")
|
||||
monkeypatch.setattr(
|
||||
rp,
|
||||
"_get_model_config",
|
||||
lambda: {
|
||||
"provider": "openrouter",
|
||||
"base_url": "http://127.0.0.1:8082/v1",
|
||||
"default": "my-local-model",
|
||||
},
|
||||
)
|
||||
monkeypatch.delenv("CUSTOM_BASE_URL", raising=False)
|
||||
monkeypatch.delenv("OPENROUTER_BASE_URL", raising=False)
|
||||
monkeypatch.delenv("OPENAI_BASE_URL", raising=False)
|
||||
monkeypatch.setenv("OPENROUTER_API_KEY", "or-key")
|
||||
monkeypatch.setenv("OPENAI_API_KEY", "openai-key")
|
||||
|
||||
resolved = rp.resolve_runtime_provider(requested="custom")
|
||||
|
||||
assert resolved["provider"] == "custom"
|
||||
assert resolved["base_url"] == "http://127.0.0.1:8082/v1"
|
||||
assert resolved["api_key"] == "openai-key"
|
||||
|
||||
|
||||
def test_bare_custom_custom_base_url_env_overrides_remote_yaml(monkeypatch):
|
||||
monkeypatch.setattr(rp, "resolve_provider", lambda *a, **k: "openrouter")
|
||||
monkeypatch.setattr(
|
||||
rp,
|
||||
"_get_model_config",
|
||||
lambda: {
|
||||
"provider": "openrouter",
|
||||
"base_url": "https://api.openrouter.ai/api/v1",
|
||||
},
|
||||
)
|
||||
monkeypatch.setenv("CUSTOM_BASE_URL", "http://localhost:9999/v1")
|
||||
monkeypatch.delenv("OPENROUTER_BASE_URL", raising=False)
|
||||
monkeypatch.setenv("OPENROUTER_API_KEY", "or-key")
|
||||
|
||||
resolved = rp.resolve_runtime_provider(requested="custom")
|
||||
|
||||
assert resolved["provider"] == "custom"
|
||||
assert resolved["base_url"] == "http://localhost:9999/v1"
|
||||
|
||||
|
||||
def test_bare_custom_does_not_trust_non_loopback_when_provider_not_custom(monkeypatch):
|
||||
monkeypatch.setattr(rp, "resolve_provider", lambda *a, **k: "openrouter")
|
||||
monkeypatch.setattr(
|
||||
rp,
|
||||
"_get_model_config",
|
||||
lambda: {
|
||||
"provider": "openrouter",
|
||||
"base_url": "https://remote.example.com/v1",
|
||||
},
|
||||
)
|
||||
monkeypatch.delenv("CUSTOM_BASE_URL", raising=False)
|
||||
monkeypatch.delenv("OPENROUTER_BASE_URL", raising=False)
|
||||
monkeypatch.setenv("OPENROUTER_API_KEY", "or-key")
|
||||
|
||||
resolved = rp.resolve_runtime_provider(requested="custom")
|
||||
|
||||
assert resolved["provider"] == "custom"
|
||||
assert "openrouter.ai" in resolved["base_url"]
|
||||
assert "remote.example.com" not in resolved["base_url"]
|
||||
|
||||
|
||||
def test_named_custom_provider_uses_saved_credentials(monkeypatch):
|
||||
monkeypatch.delenv("OPENAI_API_KEY", raising=False)
|
||||
monkeypatch.delenv("OPENROUTER_API_KEY", raising=False)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue