Skip to content

fix(provider): ACP sessions with provider="custom" fail to resolve correct credential #13489

@antyiwei

Description

@antyiwei

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

  1. Configure a custom provider in config.yaml (e.g., dashscope with its own base_url and api_key)
  2. Have an ACP client send provider="custom" with base_url pointing to that custom provider's endpoint
  3. The session's model_config stores {"provider": "custom", "base_url": "https://dashscope.aliyuncs.com/compatible-mode/v1"}
  4. On session restore, _make_agent() calls resolve_runtime_provider(requested="custom")
  5. _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

Metadata

Metadata

Assignees

No one assigned

    Labels

    area/configConfig system, migrations, profilescomp/acpAgent Communication Protocol adaptercomp/cliCLI entry point, hermes_cli/, setup wizardtype/bugSomething isn't working

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions