mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-05-30 06:41:51 +00:00
fix(runtime): treat 'ollama'/'vllm'/'llamacpp' aliases like 'custom' for base_url trust (#27132)
When config.yaml has provider: ollama (or vllm/llamacpp/llama-cpp) with a
non-loopback base_url, auth.py's resolve_provider() correctly normalises
the alias to 'custom' at the top level, but two sites in runtime_provider.py
were still comparing the *original* string against the literal 'custom':
- _config_base_url_trustworthy_for_bare_custom() rejected non-loopback
URLs because cfg_provider_norm was 'ollama', not 'custom'.
- _resolve_openrouter_runtime() only entered the trust branch when
requested_norm == 'custom'.
Both sites now consult resolve_provider() and treat any alias that
resolves to 'custom' identically. Result: provider: ollama + LAN IP no
longer silently falls through to OpenRouter (HTTP 401), matching the
behaviour of provider: custom with the same base_url.
E2E verified across 6 cases (ollama/vllm/llamacpp/custom + LAN; ollama +
loopback; openrouter + cloud) — all route to the configured endpoint;
'frobnicate' + LAN still rejects with AuthError as before.
Also adds scripts/release.py AUTHOR_MAP entry for @stepanov1975
(PR #22074 — wizard config picker preservation, cherry-picked into the
preceding commit).
This commit is contained in:
parent
e13f242f01
commit
6a159be7ca
3 changed files with 111 additions and 1 deletions
|
|
@ -47,7 +47,8 @@ def _config_base_url_trustworthy_for_bare_custom(cfg_base_url: str, cfg_provider
|
|||
"""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``,
|
||||
previous provider. Reject non-loopback URLs unless the YAML provider is already ``custom``
|
||||
(or one of the local-server aliases that resolve to ``custom`` — ollama, vllm, llamacpp, …),
|
||||
so a stale OpenRouter/Z.ai base_url cannot hijack local ``custom`` sessions.
|
||||
"""
|
||||
cfg_provider_norm = (cfg_provider or "").strip().lower()
|
||||
|
|
@ -56,6 +57,17 @@ def _config_base_url_trustworthy_for_bare_custom(cfg_base_url: str, cfg_provider
|
|||
return False
|
||||
if cfg_provider_norm == "custom":
|
||||
return True
|
||||
# GitHub #27132: provider aliases that resolve to "custom" at runtime
|
||||
# (ollama, vllm, llamacpp, …) should be trusted the same way "custom"
|
||||
# is, otherwise a legit LAN/WireGuard ollama endpoint silently falls
|
||||
# through to OpenRouter.
|
||||
try:
|
||||
from hermes_cli.auth import resolve_provider as _resolve_provider
|
||||
|
||||
if _resolve_provider(cfg_provider_norm) == "custom":
|
||||
return True
|
||||
except Exception:
|
||||
pass
|
||||
if base_url_host_matches(bu, "openrouter.ai"):
|
||||
return False
|
||||
return _loopback_hostname(base_url_hostname(bu))
|
||||
|
|
@ -547,7 +559,20 @@ def _resolve_named_custom_runtime(
|
|||
# Bare `provider="custom"` with an explicit base_url (e.g. propagated
|
||||
# from a `model_aliases:` direct-alias resolution) — build a runtime
|
||||
# directly so the alias's base_url actually takes effect.
|
||||
#
|
||||
# GitHub #27132: provider aliases that resolve to "custom" at runtime
|
||||
# (ollama, vllm, llamacpp, …) are treated identically here, so a YAML
|
||||
# `provider: ollama` with a LAN/WireGuard `base_url` doesn't silently
|
||||
# fall through to OpenRouter.
|
||||
requested_norm = (requested_provider or "").strip().lower()
|
||||
if requested_norm and requested_norm != "custom":
|
||||
try:
|
||||
from hermes_cli.auth import resolve_provider as _resolve_provider
|
||||
|
||||
if _resolve_provider(requested_norm) == "custom":
|
||||
requested_norm = "custom"
|
||||
except Exception:
|
||||
pass
|
||||
if requested_norm == "custom" and explicit_base_url:
|
||||
base_url = explicit_base_url.strip().rstrip("/")
|
||||
# Check credential pool first — mirrors the named-custom-provider path
|
||||
|
|
@ -638,6 +663,19 @@ def _resolve_openrouter_runtime(
|
|||
break
|
||||
requested_norm = (requested_provider or "").strip().lower()
|
||||
cfg_provider = cfg_provider.strip().lower()
|
||||
# GitHub #27132: provider aliases that resolve to "custom" (ollama,
|
||||
# vllm, llamacpp, …) follow the same base_url trust + routing rules
|
||||
# as a bare `provider: custom`. Normalising here keeps every check
|
||||
# below — `requested_norm == "custom"`, the trust check, the pool
|
||||
# gate up the stack — alias-aware without duplicating the alias map.
|
||||
if requested_norm and requested_norm != "custom":
|
||||
try:
|
||||
from hermes_cli.auth import resolve_provider as _resolve_provider
|
||||
|
||||
if _resolve_provider(requested_norm) == "custom":
|
||||
requested_norm = "custom"
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
env_openrouter_base_url = os.getenv("OPENROUTER_BASE_URL", "").strip()
|
||||
env_custom_base_url = os.getenv("CUSTOM_BASE_URL", "").strip()
|
||||
|
|
|
|||
|
|
@ -1146,6 +1146,7 @@ AUTHOR_MAP = {
|
|||
"192385615+LifeJiggy@users.noreply.github.com": "LifeJiggy", # stale salvage commit alias (PR #28315)
|
||||
"beastant1@gmail.com": "nekwo", # PR #26481 (PS5.1 UTF-8 BOM)
|
||||
"43717185+nekwo@users.noreply.github.com": "nekwo",
|
||||
"9785479+stepanov1975@users.noreply.github.com": "stepanov1975", # PR #22074 (setup config picker writes)
|
||||
"67979730+flooryyyy@users.noreply.github.com": "flooryyyy", # PR #26374 (tool_trace error detection)
|
||||
"188585318+dgians@users.noreply.github.com": "dgians", # PR #26034 (.ts/.py/.sh docs types)
|
||||
"zealy@tz.co": "dgians", # PR #26034 (bot-committed by zealy-tzco under dgians' PR)
|
||||
|
|
|
|||
|
|
@ -2321,3 +2321,74 @@ def test_minimax_oauth_pool_forces_anthropic_messages_despite_stale_config(monke
|
|||
assert resolved["provider"] == "minimax-oauth"
|
||||
assert resolved["api_mode"] == "anthropic_messages"
|
||||
assert resolved["base_url"] == "https://api.minimax.io/anthropic"
|
||||
|
||||
|
||||
# ----------------------------------------------------------------------
|
||||
# GitHub #27132 — provider aliases (ollama/vllm/llamacpp/llama-cpp) must
|
||||
# follow the same base_url trust + routing rules as bare `provider: custom`.
|
||||
# Without this, a YAML `provider: ollama` with a LAN/WireGuard `base_url`
|
||||
# silently falls through to OpenRouter (HTTP 401).
|
||||
# ----------------------------------------------------------------------
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"alias,base_url",
|
||||
[
|
||||
("ollama", "http://192.168.0.103:11434/v1"),
|
||||
("vllm", "http://192.168.0.103:8000/v1"),
|
||||
("llamacpp", "http://192.168.0.103:8080/v1"),
|
||||
("llama-cpp", "http://192.168.0.103:8080/v1"),
|
||||
],
|
||||
)
|
||||
def test_custom_aliases_with_lan_base_url_route_to_custom_not_openrouter(
|
||||
monkeypatch, alias, base_url
|
||||
):
|
||||
"""provider: ollama|vllm|llamacpp + LAN IP must NOT fall through to OpenRouter."""
|
||||
monkeypatch.setattr(
|
||||
rp,
|
||||
"_get_model_config",
|
||||
lambda: {"provider": alias, "base_url": base_url},
|
||||
)
|
||||
# Pretend OPENROUTER_API_KEY is set so the openrouter fallback would
|
||||
# otherwise succeed — we want to prove the alias short-circuits before
|
||||
# reaching it.
|
||||
monkeypatch.setenv("OPENROUTER_API_KEY", "sk-or-fake-test")
|
||||
# No custom credential pool — exercise the bare-alias path.
|
||||
monkeypatch.setattr(rp, "load_pool", lambda provider: None)
|
||||
|
||||
resolved = rp.resolve_runtime_provider()
|
||||
|
||||
assert resolved["provider"] == "custom", (
|
||||
f"alias {alias!r} with LAN base_url should resolve to provider=custom, "
|
||||
f"got {resolved['provider']!r}"
|
||||
)
|
||||
assert resolved["base_url"] == base_url.rstrip("/"), (
|
||||
f"base_url should be the configured LAN endpoint, got {resolved['base_url']!r}"
|
||||
)
|
||||
|
||||
|
||||
def test_custom_alias_with_loopback_base_url_routes_to_custom(monkeypatch):
|
||||
"""provider: ollama + loopback should also route to custom (regression guard)."""
|
||||
monkeypatch.setattr(
|
||||
rp,
|
||||
"_get_model_config",
|
||||
lambda: {"provider": "ollama", "base_url": "http://localhost:11434/v1"},
|
||||
)
|
||||
monkeypatch.setenv("OPENROUTER_API_KEY", "sk-or-fake-test")
|
||||
monkeypatch.setattr(rp, "load_pool", lambda provider: None)
|
||||
|
||||
resolved = rp.resolve_runtime_provider()
|
||||
|
||||
assert resolved["provider"] == "custom"
|
||||
assert resolved["base_url"] == "http://localhost:11434/v1"
|
||||
|
||||
|
||||
def test_trustworthy_check_accepts_custom_aliases():
|
||||
"""_config_base_url_trustworthy_for_bare_custom() must accept aliases for custom."""
|
||||
fn = rp._config_base_url_trustworthy_for_bare_custom
|
||||
for alias in ("ollama", "vllm", "llamacpp", "llama-cpp", "llama.cpp"):
|
||||
assert fn("http://192.168.0.103:11434/v1", alias) is True, (
|
||||
f"alias {alias!r} should be trusted with non-loopback base_url"
|
||||
)
|
||||
# Unrelated provider name should still be rejected with non-loopback URL.
|
||||
assert fn("http://192.168.0.103:11434/v1", "openrouter") is False
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue