fix: prevent bare 'custom' slug in model.provider (#17478)

When hermes model picker switches to a custom_providers entry, the slug
assignment can write the literal string 'custom' to model.provider if a
prior failed switch already left that value in config.yaml.

Two fixes:
1. model_switch.py: filter out bare 'custom' in slug assignment, always
   resolve to canonical custom:<name> form
2. providers.py: resolve_custom_provider() self-heals bare 'custom' by
   falling back to the first valid custom_providers entry

Closes #17478
This commit is contained in:
Andy 2026-04-29 22:15:56 +08:00 committed by Teknium
parent e0fa2cf972
commit 201f7caed8
2 changed files with 33 additions and 1 deletions

View File

@ -1503,7 +1503,14 @@ def list_authenticated_providers(
current_base_url
and api_url == current_base_url.strip().rstrip("/")
):
slug = current_provider or custom_provider_slug(display_name)
# Guard against bare "custom" slug left by a prior
# failed switch — always resolve to the canonical
# custom:<name> form. (GH #17478)
slug = (
current_provider
if current_provider and current_provider != "custom"
else custom_provider_slug(display_name)
)
else:
slug = custom_provider_slug(display_name)
groups[group_key] = {

View File

@ -585,6 +585,12 @@ def resolve_custom_provider(
if not requested:
return None
# If the stored provider is the bare string "custom" (corrupt state
# from a prior model-switch bug), fall back to the first custom
# provider entry so existing configs self-heal. (GH #17478)
bare_custom_fallback = requested == "custom"
first_valid = None
for entry in custom_providers:
if not isinstance(entry, dict):
continue
@ -599,6 +605,10 @@ def resolve_custom_provider(
if not display_name or not api_url:
continue
# Stash the first valid entry for bare-"custom" fallback
if first_valid is None:
first_valid = (display_name, api_url)
slug = custom_provider_slug(display_name)
if requested not in {display_name.lower(), slug}:
continue
@ -614,6 +624,21 @@ def resolve_custom_provider(
source="user-config",
)
# Self-heal: bare "custom" matched nothing — return first valid entry
if bare_custom_fallback and first_valid:
dname, aurl = first_valid
slug = custom_provider_slug(dname)
return ProviderDef(
id=slug,
name=dname,
transport="openai_chat",
api_key_env_vars=(),
base_url=aurl,
is_aggregator=False,
auth_type="api_key",
source="user-config",
)
return None