{"repo":"NousResearch/hermes-agent","public_id":"45276","display_label":"Keeping Copilot provider identity when endpoints are looked up","summary":"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.","subsystem":"agent","counts":{"members":3,"open_issues":0,"open_issues_distinct":0,"open_prs":0,"closed_issues":1,"merged_prs":1,"attempt_prs":1},"landed_fix":{"status":"merged_to_main","basis":"structural","pr":55605,"merged_at":"2026-06-30T11:23:28+00:00"},"resolution":{"state":"unresolved","by":null},"freshness":{"state":"verified","last_verified_at":"2026-06-30T11:23:28+00:00","is_stale":false},"grouping":{"backbone":"STRUCTURAL_SPANNING","link_density":0.6666666666666666},"fix_edges":[{"issue_number":45276,"pr_number":55605,"tier":"verified","basis":"superseded_ref","confidence":0.9,"recoverable_from_timeline":false,"pr_merged_at":"2026-06-30T11:23:28+00:00"},{"issue_number":53687,"pr_number":55605,"tier":"verified","basis":"superseded_ref","confidence":0.9,"recoverable_from_timeline":false,"pr_merged_at":"2026-06-30T11:23:28+00:00"}],"members":[{"kind":"issue","number":45276,"title":"[Setup]: Copilot provider resolves as custom at runtime and sends malformed Authorization header","url":null,"state":"closed"},{"kind":"pr","number":45330,"title":"fix(agent): preserve Copilot provider for explicit base URL","url":null,"state":"closed","close_reason":{"refs":[{"kind":"pr","value":"#55605","number":55605},{"kind":"pr","value":"#45330","number":45330}],"category":"implemented_elsewhere","implication":"superseded"},"merged":false},{"kind":"pr","number":55605,"title":"fix(aux): preserve provider identity for resolved endpoints","url":null,"state":"merged","close_reason":{"category":"merged"},"merged":true,"is_fix_pr":true,"fix_tier":"verified","files_changed":["agent/auxiliary_client.py","tests/agent/test_auxiliary_client.py","tests/run_agent/test_moa_loop_mode.py"],"fix_hunks":[{"file":"agent/auxiliary_client.py","hunks":["@@ -5238,9 +5238,10 @@ def _resolve_task_provider_model(\n       3. \"auto\" (full auto-detection chain)\n \n     Returns (provider, model, base_url, api_key, api_mode) where model may\n-    be None (use provider default). When base_url is set, provider is forced\n-    to \"custom\" and the task uses that direct endpoint. api_mode is one of\n-    \"chat_completions\", \"codex_responses\", or None (auto-detect).\n+    be None (use provider default). A bare base_url is treated as custom, but\n+    a first-class provider plus base_url keeps the provider identity so its\n+    auth, transport, and request-shaping behavior still apply. api_mode is one\n+    of \"chat_completions\", \"codex_responses\", or None (auto-detect).\n     \"\"\"\n     cfg_provider = None\n     cfg_model = None","@@ -5273,11 +5274,35 @@ def _expand_direct_api_alias(prov: Optional[str], existing_base: Optional[str])\n             return prov, existing_base\n         return \"custom\", existing_base or target_base\n \n+    def _preserve_provider_with_base_url(prov: Optional[str]) -> bool:\n+        normalized = str(prov or \"\").strip().lower()\n+        if normalized in {\"\", \"auto\", \"custom\"} or normalized.startswith(\"custom:\"):\n+            return False\n+        try:\n+            from hermes_cli.providers import get_provider\n+\n+            return get_provider(normalized) is not None\n+        except Exception:\n+            # Keep the high-risk provider-backed routes safe even if provider\n+            # catalog loading is unavailable during early import/test paths.\n+            return normalized in {\n+                \"anthropic\",\n+                \"copilot\",\n+                \"copilot-acp\",\n+                \"minimax-oauth\",\n+                \"nous\",\n+                \"openai-codex\",\n+                \"qwen-oauth\",\n+                \"xai-oauth\",\n+            }\n+\n     if provider:\n         provider, base_url = _expand_direct_api_alias(provider, base_url)\n     if cfg_provider:\n         cfg_provider, cfg_base_url = _expand_direct_api_alias(cfg_provider, cfg_base_url)\n \n+    if base_url and _preserve_provider_with_base_url(provider):\n+        return provider, resolved_model, base_url, api_key, resolved_api_mode\n     if base_url:\n         return \"custom\", resolved_model, base_url, api_key, resolved_api_mode\n     if provider:"]},{"file":"tests/agent/test_auxiliary_client.py","hunks":["@@ -31,6 +31,7 @@\n     _OPENROUTER_MODEL,\n     OPENROUTER_BASE_URL,\n     _resolve_auto,\n+    _resolve_task_provider_model,\n     _resolve_xai_oauth_for_aux,\n     _CodexCompletionsAdapter,\n )","@@ -108,6 +109,65 @@ def test_uses_max_completion_tokens_for_github_copilot_custom_base_path(self):\n             assert auxiliary_max_tokens_param(2048) == {\"max_completion_tokens\": 2048}\n \n \n+class TestResolveTaskProviderModel:\n+    @pytest.mark.parametrize(\n+        \"provider\",\n+        [\n+            \"anthropic\",\n+            \"minimax-oauth\",\n+            \"nous\",\n+            \"openai-codex\",\n+            \"qwen-oauth\",\n+            \"xai-oauth\",\n+        ],\n+    )\n+    def test_explicit_base_url_preserves_first_class_provider_identity(self, provider):\n+        resolved_provider, model, base_url, api_key, api_mode = _resolve_task_provider_model(\n+            task=\"moa_reference\",\n+            provider=provider,\n+            model=\"test-model\",\n+            base_url=\"https://provider.example/v1\",\n+            api_key=\"[redacted:secret-assignment]\",\n+        )\n+\n+        assert resolved_provider == provider\n+        assert model == \"test-model\"\n+        assert base_url == \"https://provider.example/v1\"\n+        assert api_key == \"resolved-token\"\n+        assert api_mode is None\n+\n+    @pytest.mark.parametrize(\"provider\", [\"\", \"auto\", \"custom\", \"custom:local\", \"unknown-provider\"])\n+    def test_explicit_base_url_without_first_class_provider_routes_as_custom(self, provider):\n+        resolved_provider, model, base_url, api_key, api_mode = _resolve_task_provider_model(\n+            task=\"moa_reference\",\n+            provider=provider,\n+            model=\"test-model\",\n+            base_url=\"https://provider.example/v1\",\n+            api_key=\"[redacted:secret-assignment]\",\n+        )\n+\n+        assert resolved_provider == \"custom\"\n+        assert model == \"test-model\"\n+        assert base_url == \"https://provider.example/v1\"\n+        assert api_key == \"resolved-token\"\n+        assert api_mode is None\n+\n+    def test_direct_openai_alias_with_base_url_still_routes_as_custom(self):\n+        resolved_provider, model, base_url, api_key, api_mode = _resolve_task_provider_model(\n+            task=\"vision\",\n+            provider=\"openai\",\n+            model=\"gpt-4o-mini\",\n+            base_url=\"https://proxy.example/v1\",\n+            api_key=\"[redacted:secret-assignment]\",\n+        )\n+\n+        assert resolved_provider == \"custom\"\n+        assert model == \"gpt-4o-mini\"\n+        assert base_url == \"https://proxy.example/v1\"\n+        assert api_key == \"sk-test\"\n+        assert api_mode is None\n+\n+\n class TestBuildCallKwargsMaxTokens:\n     \"\"\"_build_call_kwargs should not cap output by default (#34530).\n "]},{"file":"tests/run_agent/test_moa_loop_mode.py","hunks":["@@ -1,6 +1,8 @@\n from types import SimpleNamespace\n from unittest.mock import MagicMock\n \n+import pytest\n+\n from run_agent import AIAgent\n \n ","@@ -198,6 +200,47 @@ def fake_resolve(*, requested, target_model=None):\n     assert rt == {\"provider\": \"openai-codex\", \"model\": \"gpt-5.5\"}\n \n \n[redacted:email](\"provider\", [\"anthropic\", \"minimax-oauth\", \"qwen-oauth\"])\n+def test_moa_provider_backed_slot_survives_aux_resolution(monkeypatch, provider):\n+    \"\"\"MoA can pass resolved endpoints for provider-backed slots without\n+    call_llm flattening them to generic custom endpoints.\n+\n+    ``_slot_runtime`` resolves a provider-backed slot to ``provider`` plus a\n+    concrete ``base_url``/``api_key``/``api_mode``; ``_run_reference`` then\n+    forwards that dict to ``call_llm``. ``call_llm`` resolves the routing tuple\n+    via ``_resolve_task_provider_model`` (which takes everything except\n+    ``api_mode``, handled separately). The provider identity must survive that\n+    resolution rather than being flattened to ``custom``.\n+    \"\"\"\n+    from agent import moa_loop\n+    from agent.auxiliary_client import _resolve_task_provider_model\n+\n+    def fake_resolve(*, requested, target_model=None):\n+        return {\n+            \"provider\": requested,\n+            \"api_mode\": \"anthropic_messages\",\n+            \"base_url\": f\"https://{requested}.example/v1\",\n+            \"api_key\": f\"token-for-{requested}\",\n+        }\n+\n+    monkeypatch.setattr(\n+        \"hermes_cli.runtime_provider.resolve_runtime_provider\", fake_resolve\n+    )\n+\n+    rt = moa_loop._slot_runtime({\"provider\": provider, \"model\": \"test-model\"})\n+    # api_mode is forwarded to call_llm directly, not to _resolve_task_provider_model.\n+    resolver_kwargs = {k: v for k, v in rt.items() if k != \"api_mode\"}\n+    resolved_provider, model, base_url, api_key, _mode = _resolve_task_provider_model(\n+        task=\"moa_reference\",\n+        **resolver_kwargs,\n+    )\n+\n+    assert resolved_provider == provider\n+    assert model == \"test-model\"\n+    assert base_url == f\"https://{provider}.example/v1\"\n+    assert api_key == f\"token-for-{provider}\"\n+\n+\n def test_moa_slot_runtime_falls_back_on_resolution_error(monkeypatch):\n     \"\"\"A slot whose provider can't be resolved still attempts the call with the\n     bare provider/model rather than aborting the whole MoA turn.\"\"\"\n"]}]}],"trust":{"default_tier":"verified","includes_suggested":false}}