Bug Description
When an ACP client creates a session with provider="custom" and a specific base_url (e.g., a DashScope-compatible endpoint), _get_named_custom_provider() returns None because "custom" is treated as a generic identifier:
https://github.com/NousResearch/hermes-agent/blob/main/hermes_cli/runtime_provider.py#L283
def _get_named_custom_provider(requested_provider: str) -> Optional[Dict[str, Any]]:
requested_norm = _normalize_custom_provider_name(requested_provider or "")
if not requested_norm or requested_norm == "custom":
return None # <-- always returns None for "custom"
This causes resolve_runtime_provider() to fall through to resolve_provider("custom"), which resolves to "openrouter" as the default. The wrong API key is then sent to the target endpoint, resulting in HTTP 401 errors.
Reproduction
- Configure a custom provider in
config.yaml (e.g., dashscope with its own base_url and api_key)
- Have an ACP client send
provider="custom" with base_url pointing to that custom provider's endpoint
- The session's
model_config stores {"provider": "custom", "base_url": "https://dashscope.aliyuncs.com/compatible-mode/v1"}
- On session restore,
_make_agent() calls resolve_runtime_provider(requested="custom")
_get_named_custom_provider("custom") returns None → falls back to OpenRouter credentials → 401 from the target provider
Expected Behavior
When provider="custom" is paired with a base_url that matches a configured custom provider, the correct credential from that provider should be used.
Suggested Fix
The fix should be in _get_named_custom_provider() in runtime_provider.py. When requested_provider == "custom", instead of returning None, try to match against configured custom providers by base_url:
def _get_named_custom_provider(
requested_provider: str,
base_url: str | None = None,
) -> Optional[Dict[str, Any]]:
requested_norm = _normalize_custom_provider_name(requested_provider or "")
if not requested_norm:
return None
if requested_norm == "custom":
# When provider is generic "custom", try matching by base_url
if base_url:
matched = _match_custom_provider_by_base_url(base_url)
if matched:
return matched
return None
Where _match_custom_provider_by_base_url() iterates over configured custom_providers and returns the first entry whose base_url matches (prefix or host match). This way:
- Any custom provider works automatically — no hardcoded URL patterns
- The fix lives in the correct layer (provider resolution, not ACP adapter)
- Callers that already pass
base_url (e.g., ACP _make_agent()) benefit immediately
The function signature change is backward-compatible since base_url defaults to None, preserving existing behavior when not provided.
Environment
- hermes-agent v0.10.0
- macOS Darwin 25.4.0
- ACP client: external desktop agent manager
Bug Description
When an ACP client creates a session with
provider="custom"and a specificbase_url(e.g., a DashScope-compatible endpoint),_get_named_custom_provider()returnsNonebecause"custom"is treated as a generic identifier:https://github.com/NousResearch/hermes-agent/blob/main/hermes_cli/runtime_provider.py#L283
This causes
resolve_runtime_provider()to fall through toresolve_provider("custom"), which resolves to"openrouter"as the default. The wrong API key is then sent to the target endpoint, resulting in HTTP 401 errors.Reproduction
config.yaml(e.g.,dashscopewith its own base_url and api_key)provider="custom"withbase_urlpointing to that custom provider's endpointmodel_configstores{"provider": "custom", "base_url": "https://dashscope.aliyuncs.com/compatible-mode/v1"}_make_agent()callsresolve_runtime_provider(requested="custom")_get_named_custom_provider("custom")returnsNone→ falls back to OpenRouter credentials → 401 from the target providerExpected Behavior
When
provider="custom"is paired with abase_urlthat matches a configured custom provider, the correct credential from that provider should be used.Suggested Fix
The fix should be in
_get_named_custom_provider()inruntime_provider.py. Whenrequested_provider == "custom", instead of returningNone, try to match against configured custom providers bybase_url:Where
_match_custom_provider_by_base_url()iterates over configuredcustom_providersand returns the first entry whosebase_urlmatches (prefix or host match). This way:base_url(e.g., ACP_make_agent()) benefit immediatelyThe function signature change is backward-compatible since
base_urldefaults toNone, preserving existing behavior when not provided.Environment