mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-06-30 11:52:04 +00:00
fix: propagate key_env from custom_providers into ProviderDef
resolve_custom_provider() previously returned api_key_env_vars=() for every custom provider entry, silently dropping the configured key_env field. This caused 401 errors for any custom provider that required an API key via environment variable (e.g. Xiaomi MiMo Token Plan, self-hosted OpenAI-compatible servers). The key_env field is already documented in _VALID_CUSTOM_PROVIDER_FIELDS and normalized by normalize_custom_provider_entry(), so this was just an oversight in the ProviderDef construction. Also adds a regression test that verifies key_env is properly propagated into the resolved ProviderDef.
This commit is contained in:
parent
9f97915163
commit
fa11b11cf5
2 changed files with 60 additions and 5 deletions
|
|
@ -639,7 +639,7 @@ def resolve_custom_provider(
|
|||
# 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
|
||||
first_valid: Optional[Tuple[str, str, Tuple[str, ...]]] = None
|
||||
|
||||
for entry in custom_providers:
|
||||
if not isinstance(entry, dict):
|
||||
|
|
@ -655,9 +655,14 @@ def resolve_custom_provider(
|
|||
if not display_name or not api_url:
|
||||
continue
|
||||
|
||||
key_env = (entry.get("key_env") or "").strip()
|
||||
env_vars: List[str] = []
|
||||
if key_env:
|
||||
env_vars.append(key_env)
|
||||
|
||||
# Stash the first valid entry for bare-"custom" fallback
|
||||
if first_valid is None:
|
||||
first_valid = (display_name, api_url)
|
||||
first_valid = (display_name, api_url, tuple(env_vars))
|
||||
|
||||
slug = custom_provider_slug(display_name)
|
||||
if requested not in {display_name.lower(), slug}:
|
||||
|
|
@ -667,7 +672,7 @@ def resolve_custom_provider(
|
|||
id=slug,
|
||||
name=display_name,
|
||||
transport="openai_chat",
|
||||
api_key_env_vars=(),
|
||||
api_key_env_vars=tuple(env_vars),
|
||||
base_url=api_url,
|
||||
is_aggregator=False,
|
||||
auth_type="api_key",
|
||||
|
|
@ -676,13 +681,13 @@ def resolve_custom_provider(
|
|||
|
||||
# Self-heal: bare "custom" matched nothing — return first valid entry
|
||||
if bare_custom_fallback and first_valid:
|
||||
dname, aurl = first_valid
|
||||
dname, aurl, denv = first_valid
|
||||
slug = custom_provider_slug(dname)
|
||||
return ProviderDef(
|
||||
id=slug,
|
||||
name=dname,
|
||||
transport="openai_chat",
|
||||
api_key_env_vars=(),
|
||||
api_key_env_vars=denv,
|
||||
base_url=aurl,
|
||||
is_aggregator=False,
|
||||
auth_type="api_key",
|
||||
|
|
|
|||
|
|
@ -337,6 +337,7 @@ def test_list_dedupes_dict_model_matching_singular_default(monkeypatch):
|
|||
|
||||
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# #9210: group custom_providers by (base_url, api_key) in /model picker
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
|
|
@ -791,3 +792,52 @@ def test_custom_providers_discover_models_false_string_is_normalised(monkeypatch
|
|||
assert gateway_prov is not None
|
||||
assert calls == [], "string 'false' must disable live discovery"
|
||||
assert gateway_prov["models"] == ["only-model"]
|
||||
|
||||
|
||||
def test_resolve_custom_provider_passes_key_env():
|
||||
"""resolve_custom_provider should propagate key_env into api_key_env_vars.
|
||||
|
||||
Regression: previously api_key_env_vars was always (), silently dropping
|
||||
the configured env var and causing 401s on every request.
|
||||
"""
|
||||
from hermes_cli.providers import resolve_custom_provider
|
||||
|
||||
resolved = resolve_custom_provider(
|
||||
"custom:token-plan",
|
||||
custom_providers=[
|
||||
{
|
||||
"name": "token-plan",
|
||||
"base_url": "https://token-plan-sgp.xiaomimimo.com/v1",
|
||||
"key_env": "XIAOMI_MIMO_API_KEY",
|
||||
"model": "mimo-v2-pro",
|
||||
}
|
||||
],
|
||||
)
|
||||
|
||||
assert resolved is not None
|
||||
assert resolved.api_key_env_vars == ("XIAOMI_MIMO_API_KEY",)
|
||||
assert resolved.base_url == "https://token-plan-sgp.xiaomimimo.com/v1"
|
||||
|
||||
|
||||
def test_resolve_custom_provider_bare_custom_self_heal_passes_key_env():
|
||||
"""The bare-'custom' self-heal path must also propagate key_env.
|
||||
|
||||
A corrupt stored provider of the bare string 'custom' falls back to the
|
||||
first valid entry; that fallback previously hardcoded api_key_env_vars=(),
|
||||
dropping the env var just like the named-match path did.
|
||||
"""
|
||||
from hermes_cli.providers import resolve_custom_provider
|
||||
|
||||
resolved = resolve_custom_provider(
|
||||
"custom",
|
||||
custom_providers=[
|
||||
{
|
||||
"name": "token-plan",
|
||||
"base_url": "https://token-plan-sgp.xiaomimimo.com/v1",
|
||||
"key_env": "XIAOMI_MIMO_API_KEY",
|
||||
}
|
||||
],
|
||||
)
|
||||
|
||||
assert resolved is not None
|
||||
assert resolved.api_key_env_vars == ("XIAOMI_MIMO_API_KEY",)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue