fix(cli): same-provider /model on a session-only custom endpoint stays on it (#74143)

A bare `custom`/`local` session whose base_url is not the trusted config
`model.base_url` re-resolved credentials from config on a same-provider
switch and fell through to the OpenRouter default: the next turn hit
openrouter.ai with an empty (or the custom) key. Keep the session endpoint
and key when resolution comes back empty or on OpenRouter; a config-backed
custom URL still wins so key/endpoint rotation is not pinned to a stale
session.

Diagnosed by fangliquanflq in #74143 / #71693; this is the minimal form of
that fix at the same-provider credential step.
This commit is contained in:
Teknium
2026-09-10 03:37:28 -07:00
parent 2466684db5
commit 70ed17e3d8
2 changed files with 32 additions and 1 deletions
+12 -1
View File
@@ -18,7 +18,7 @@ from hermes_cli.providers import (
from hermes_cli.model_normalize import normalize_model_for_provider
from agent.models_dev import (
ModelCapabilities, ModelInfo, get_model_capabilities, get_model_info, list_provider_models)
from utils import base_url_hostname, base_url_origin
from utils import base_url_host_matches, base_url_hostname, base_url_origin
# Re-exported: callers/tests patch hermes_cli.model_switch.<name>.
from hermes_cli.model_switch_providers import list_authenticated_providers
@@ -1309,6 +1309,17 @@ def _creds_for_current_provider(st: _Switch) -> None:
st.resolve_runtime(requested=st.current_provider)
except Exception:
pass
# Bare ``custom``/``local`` sessions whose base_url is session-only (not a trusted config
# ``model.base_url``) re-resolve to the OpenRouter DEFAULT — a host the user never picked
# (#74143). Keep the session endpoint + key then; a config-backed custom URL still wins so
# key/endpoint rotation is not pinned to a stale session.
if (
st.current_provider in {"custom", "local"} and st.current_base_url
and (not st.base_url or base_url_host_matches(st.base_url, "openrouter.ai"))
and not base_url_host_matches(st.current_base_url, "openrouter.ai")
):
st.base_url, st.api_key = st.current_base_url, st.current_api_key
st.api_mode = determine_api_mode(st.current_provider, st.base_url)
def _resolve_switch_credentials(st: _Switch) -> Optional[ModelSwitchResult]:
@@ -2220,3 +2220,23 @@ def test_legacy_sentinel_catalog_still_resolves_and_migrates(tmp_path, monkeypat
assert list(saved["models"]) == _LOCAL_CATALOG
assert "__discovered_model_catalog__" not in saved["models"]
assert "__explicit_model_allowlist__" not in saved["models"]
def test_same_provider_switch_on_session_only_custom_endpoint_keeps_endpoint(monkeypatch):
"""#74143: a same-provider ``/model`` on a bare ``custom`` session whose base_url is NOT the
trusted config ``model.base_url`` must stay on that endpoint with its key — re-resolving from
config fell through to the OpenRouter default and moved the next turn to a host the user never
picked."""
for var in ("OPENROUTER_API_KEY", "OPENROUTER_BASE_URL", "CUSTOM_BASE_URL", "CUSTOM_API_KEY", "OPENAI_API_KEY"):
monkeypatch.delenv(var, raising=False)
monkeypatch.setattr("hermes_cli.model_switch.load_config", lambda: {"model": {"provider": "openrouter", "default": "x"}}, raising=False)
monkeypatch.setattr(
"hermes_cli.models.probe_api_models",
lambda api_key, base_url, **kw: {"models": ["m-a", "m-b"], "url": base_url + "/models", "base_url": base_url,
"suggested_base_url": None, "used_fallback": False})
result = switch_model("m-b", "custom", "m-a", "http://10.0.0.5:8000/v1", "session-secret")
assert result.success
assert result.base_url == "http://10.0.0.5:8000/v1"
assert result.api_key == "session-secret"