fix(model_switch): correct user_providers override for private models

The switch_model override logic incorrectly iterated over user_providers
as if it were a list of dicts, but it's actually a dict mapping
provider_slug -> config. This meant private models defined in a provider's
`models:` section (e.g. nahcrof-dedicated with discover_models: false)
were never accepted when the API /models list didn't include them.

Fix: iterate over user_providers.items(), match by slug, and handle both
dict and list forms of the models config.
This commit is contained in:
Mind-Dragon 2026-04-30 15:31:57 +02:00 committed by Teknium
parent 1e5a23fa64
commit 5ad8281885

View File

@ -891,14 +891,19 @@ def switch_model(
if not validation.get("accepted"): if not validation.get("accepted"):
override = False override = False
if user_providers: if user_providers:
for up in user_providers: # user_providers is a dict: {provider_slug: config_dict}
if isinstance(up, dict) and up.get("provider") == target_provider: for slug, cfg in user_providers.items():
cfg_models = up.get("models", []) if slug == target_provider:
if new_model in cfg_models or any( cfg_models = cfg.get("models", {})
m.get("name") == new_model for m in cfg_models if isinstance(m, dict) # Direct membership works for dict (keys) and list (strings)
): if new_model in cfg_models:
override = True override = True
break break
# Also accept if models is a list of dicts with 'name' field
if isinstance(cfg_models, list):
if any(m.get("name") == new_model for m in cfg_models if isinstance(m, dict)):
override = True
break
if override: if override:
validation = {"accepted": True, "persist": True, "recognized": False, "message": validation.get("message", "")} validation = {"accepted": True, "persist": True, "recognized": False, "message": validation.get("message", "")}
else: else:
@ -1412,14 +1417,17 @@ def list_authenticated_providers(
models_list = list(fb) models_list = list(fb)
# Prefer the endpoint's live /models list when credentials are # Prefer the endpoint's live /models list when credentials are
# available. This keeps OpenAI-compatible relays (for example CRS) # available, unless the provider explicitly opts out via
# in sync when the server catalog changes without requiring the # discover_models: false (e.g. dedicated endpoints that expose
# user to mirror every model into config.yaml. # the entire aggregator catalog via /models).
api_key = str(ep_cfg.get("api_key", "") or "").strip() api_key = str(ep_cfg.get("api_key", "") or "").strip()
if not api_key: if not api_key:
key_env = str(ep_cfg.get("key_env", "") or "").strip() key_env = str(ep_cfg.get("key_env", "") or "").strip()
api_key = os.environ.get(key_env, "").strip() if key_env else "" api_key = os.environ.get(key_env, "").strip() if key_env else ""
if api_url and api_key: discover = ep_cfg.get("discover_models", True)
if isinstance(discover, str):
discover = discover.lower() not in ("false", "no", "0")
if api_url and api_key and discover:
try: try:
from hermes_cli.models import fetch_api_models from hermes_cli.models import fetch_api_models
live_models = fetch_api_models(api_key, api_url) live_models = fetch_api_models(api_key, api_url)