# NousResearch/hermes-agent · Keeping Copilot provider identity when endpoints are looked up

## Open now

- Nothing open on GitHub.

These items appear to concern the Copilot provider being treated as a custom provider at runtime, with one report mentioning a malformed Authorization header. Two pull requests seem to address keeping the provider identity when a base URL or endpoint is explicitly set or looked up, but the titles alone don't confirm outcomes.

## TL;DR

- 3 member(s): 0 open issue(s), 1 closed issue(s), 0 open PR(s), 1 merged PR(s), 1 PR(s) closed without merging.
- A fix was merged to main in PR #55605. Verify it addresses your case.
- **Recommended:** read PR #55605 (and any competing PRs below) before filing. Confirm the merged change covers your symptom.

## Maintainer resolution verdict

- Internal-tracker verdict over this cluster's issue members: `unresolved`. This is distinct from the fix status above. Weigh it yourself.

## How this cluster was grouped

- Link evidence (`STRUCTURAL_SPANNING`): every member is connected by structural evidence: shared changed files, or a closing/superseded reference.
- Pairwise link density `0.67`: the share of member pairs that carry a link, counting a structural link in full and a relevance-only link at 0.36. `1.00` means every pair is linked.

## Closed / duplicate issues

- #45276: \[Setup\]: Copilot provider resolves as custom at runtime and sends malformed Authorization header

## Prior & competing PRs

- **#55605: fix(aux): preserve provider identity for resolved endpoints**. merged to main · fix tier `verified`
  - maintainer direction: category `merged`
  - files: `agent/auxiliary_client.py`, `tests/agent/test_auxiliary_client.py`, `tests/run_agent/test_moa_loop_mode.py`
  - fix hunks:
  - `agent/auxiliary_client.py`

    ```diff
    @@ -5238,9 +5238,10 @@ def _resolve_task_provider_model(
           3. "auto" (full auto-detection chain)
     
         Returns (provider, model, base_url, api_key, api_mode) where model may
    -    be None (use provider default). When base_url is set, provider is forced
    -    to "custom" and the task uses that direct endpoint. api_mode is one of
    -    "chat_completions", "codex_responses", or None (auto-detect).
    +    be None (use provider default). A bare base_url is treated as custom, but
    +    a first-class provider plus base_url keeps the provider identity so its
    +    auth, transport, and request-shaping behavior still apply. api_mode is one
    +    of "chat_completions", "codex_responses", or None (auto-detect).
         """
         cfg_provider = None
         cfg_model = None
    ```

    ```diff
    @@ -5273,11 +5274,35 @@ def _expand_direct_api_alias(prov: Optional[str], existing_base: Optional[str])
                 return prov, existing_base
             return "custom", existing_base or target_base
     
    +    def _preserve_provider_with_base_url(prov: Optional[str]) -> bool:
    +        normalized = str(prov or "").strip().lower()
    +        if normalized in {"", "auto", "custom"} or normalized.startswith("custom:"):
    +            return False
    +        try:
    +            from hermes_cli.providers import get_provider
    +
    +            return get_provider(normalized) is not None
    +        except Exception:
    +            # Keep the high-risk provider-backed routes safe even if provider
    +            # catalog loading is unavailable during early import/test paths.
    +            return normalized in {
    +                "anthropic",
    +                "copilot",
    +                "copilot-acp",
    +                "minimax-oauth",
    +                "nous",
    +                "openai-codex",
    +                "qwen-oauth",
    +                "xai-oauth",
    +            }
    +
         if provider:
             provider, base_url = _expand_direct_api_alias(provider, base_url)
         if cfg_provider:
             cfg_provider, cfg_base_url = _expand_direct_api_alias(cfg_provider, cfg_base_url)
     
    +    if base_url and _preserve_provider_with_base_url(provider):
    +        return provider, resolved_model, base_url, api_key, resolved_api_mode
         if base_url:
             return "custom", resolved_model, base_url, api_key, resolved_api_mode
         if provider:
    ```
  - `tests/agent/test_auxiliary_client.py`

    ```diff
    @@ -31,6 +31,7 @@
         _OPENROUTER_MODEL,
         OPENROUTER_BASE_URL,
         _resolve_auto,
    +    _resolve_task_provider_model,
         _resolve_xai_oauth_for_aux,
         _CodexCompletionsAdapter,
     )
    ```

    ```diff
    @@ -108,6 +109,65 @@ def test_uses_max_completion_tokens_for_github_copilot_custom_base_path(self):
                 assert auxiliary_max_tokens_param(2048) == {"max_completion_tokens": 2048}
     
     
    +class TestResolveTaskProviderModel:
    +    @pytest.mark.parametrize(
    +        "provider",
    +        [
    +            "anthropic",
    +            "minimax-oauth",
    +            "nous",
    +            "openai-codex",
    +            "qwen-oauth",
    +            "xai-oauth",
    +        ],
    +    )
    +    def test_explicit_base_url_preserves_first_class_provider_identity(self, provider):
    +        resolved_provider, model, base_url, api_key, api_mode = _resolve_task_provider_model(
    +            task="moa_reference",
    +            provider=provider,
    +            model="test-model",
    +            base_url="https://provider.example/v1",
    +            api_key="[redacted:secret-assignment]",
    +        )
    +
    +        assert resolved_provider == provider
    +        assert model == "test-model"
    +        assert base_url == "https://provider.example/v1"
    +        assert api_key == "resolved-token"
    +        assert api_mode is None
    +
    +    @pytest.mark.parametrize("provider", ["", "auto", "custom", "custom:local", "unknown-provider"])
    +    def test_explicit_base_url_without_first_class_provider_routes_as_custom(self, provider):
    +        resolved_provider, model, base_url, api_key, api_mode = _resolve_task_provider_model(
    +            task="moa_reference",
    +            provider=provider,
    +            model="test-model",
    +            base_url="https://provider.example/v1",
    +            api_key="[redacted:secret-assignment]",
    +        )
    +
    +        assert resolved_provider == "custom"
    +        assert model == "test-model"
    +        assert base_url == "https://provider.example/v1"
    +        assert api_key == "resolved-token"
    +        assert api_mode is None
    +
    +    def test_direct_openai_alias_with_base_url_still_routes_as_custom(self):
    +        resolved_provider, model, base_url, api_key, api_mode = _resolve_task_provider_model(
    +            task="vision",
    +            provider="openai",
    +            model="gpt-4o-mini",
    +            base_url="https://proxy.example/v1",
    +            api_key="[redacted:secret-assignment]",
    +        )
    +
    +        assert resolved_provider == "custom"
    +        assert model == "gpt-4o-mini"
    +        assert base_url == "https://proxy.example/v1"
    +        assert api_key == "sk-test"
    +        assert api_mode is None
    +
    +
     class TestBuildCallKwargsMaxTokens:
         """_build_call_kwargs should not cap output by default (#34530).
     
    ```
  - `tests/run_agent/test_moa_loop_mode.py`

    ```diff
    @@ -1,6 +1,8 @@
     from types import SimpleNamespace
     from unittest.mock import MagicMock
     
    +import pytest
    +
     from run_agent import AIAgent
     
     
    ```

    ```diff
    @@ -198,6 +200,47 @@ def fake_resolve(*, requested, target_model=None):
         assert rt == {"provider": "openai-codex", "model": "gpt-5.5"}
     
     
    [redacted:email]("provider", ["anthropic", "minimax-oauth", "qwen-oauth"])
    +def test_moa_provider_backed_slot_survives_aux_resolution(monkeypatch, provider):
    +    """MoA can pass resolved endpoints for provider-backed slots without
    +    call_llm flattening them to generic custom endpoints.
    +
    +    ``_slot_runtime`` resolves a provider-backed slot to ``provider`` plus a
    +    concrete ``base_url``/``api_key``/``api_mode``; ``_run_reference`` then
    +    forwards that dict to ``call_llm``. ``call_llm`` resolves the routing tuple
    +    via ``_resolve_task_provider_model`` (which takes everything except
    +    ``api_mode``, handled separately). The provider identity must survive that
    +    resolution rather than being flattened to ``custom``.
    +    """
    +    from agent import moa_loop
    +    from agent.auxiliary_client import _resolve_task_provider_model
    +
    +    def fake_resolve(*, requested, target_model=None):
    +        return {
    +            "provider": requested,
    +            "api_mode": "anthropic_messages",
    +            "base_url": f"https://{requested}.example/v1",
    +            "api_key": f"token-for-{requested}",
    +        }
    +
    +    monkeypatch.setattr(
    +        "hermes_cli.runtime_provider.resolve_runtime_provider", fake_resolve
    +    )
    +
    +    rt = moa_loop._slot_runtime({"provider": provider, "model": "test-model"})
    +    # api_mode is forwarded to call_llm directly, not to _resolve_task_provider_model.
    +    resolver_kwargs = {k: v for k, v in rt.items() if k != "api_mode"}
    +    resolved_provider, model, base_url, api_key, _mode = _resolve_task_provider_model(
    +        task="moa_reference",
    +        **resolver_kwargs,
    +    )
    +
    +    assert resolved_provider == provider
    +    assert model == "test-model"
    +    assert base_url == f"https://{provider}.example/v1"
    +    assert api_key == f"token-for-{provider}"
    +
    +
     def test_moa_slot_runtime_falls_back_on_resolution_error(monkeypatch):
         """A slot whose provider can't be resolved still attempts the call with the
         bare provider/model rather than aborting the whole MoA turn."""
    ```
- **#45330: fix(agent): preserve Copilot provider for explicit base URL**. closed without merge
  - maintainer direction: category `implemented_elsewhere`; implication `superseded`; refs `#55605`, `#45330`

## Fix status

- Merged to main in PR #55605 (cited above). No release data is tracked. Verify downstream.
- Fix last verified (merged) `2026-06-30`.
- Subsystem: `agent`
