{"repo":"NousResearch/hermes-agent","public_id":"8857","display_label":"Propagating api_mode in vision provider resolution","summary":"The three members center on the vision auxiliary client not passing api_mode through provider resolution, with one issue describing a crash for custom providers. Two pull requests share an identical title and appear to address the same area, but their contents and outcomes are not shown.","subsystem":null,"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":9001,"merged_at":"2026-04-13T12:02:54+00:00"},"resolution":{"state":"resolved","by":"pr-9001"},"freshness":{"state":"stale","last_verified_at":"2026-04-13T12:02:54+00:00","is_stale":true},"grouping":{"backbone":"STRUCTURAL_SPANNING","link_density":0.6666666666666666},"fix_edges":[{"issue_number":8857,"pr_number":9001,"tier":"verified","basis":"closing_ref","confidence":0.95,"recoverable_from_timeline":true,"pr_merged_at":"2026-04-13T12:02:54+00:00"}],"members":[{"kind":"issue","number":8857,"title":"Vision auxiliary client drops api_mode for custom providers, causing crash","url":null,"state":"closed"},{"kind":"pr","number":8871,"title":"fix(agent): propagate api_mode to vision provider resolution","url":null,"state":"closed","close_reason":{"refs":[{"kind":"pr","value":"#9001","number":9001}],"category":"merged_via","implication":"superseded"},"merged":false},{"kind":"pr","number":9001,"title":"fix(agent): propagate api_mode to vision provider resolution","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_named_custom_providers.py"],"fix_hunks":[{"file":"agent/auxiliary_client.py","hunks":["@@ -71,13 +71,13 @@\n }\n \n \n-def _normalize_aux_provider(provider: Optional[str], *, for_vision: bool = False) -> str:\n+def _normalize_aux_provider(provider: Optional[str]) -> str:\n     normalized = (provider or \"auto\").strip().lower()\n     if normalized.startswith(\"custom:\"):\n         suffix = normalized.split(\":\", 1)[1].strip()\n         if not suffix:\n             return \"custom\"\n-        normalized = suffix if not for_vision else \"custom\"\n+        normalized = suffix\n     if normalized == \"codex\":\n         return \"openai-codex\"\n     if normalized == \"main\":","@@ -1603,7 +1603,7 @@ def get_async_text_auxiliary_client(task: str = \"\", *, main_runtime: Optional[Di\n \n \n def _normalize_vision_provider(provider: Optional[str]) -> str:\n-    return _normalize_aux_provider(provider, for_vision=True)\n+    return _normalize_aux_provider(provider)\n \n \n def _resolve_strict_vision_backend(provider: str) -> Tuple[Optional[Any], Optional[str]]:","@@ -1686,6 +1686,7 @@ def _finalize(resolved_provider: str, sync_client: Any, default_model: Optional[\n             async_mode=async_mode,\n             explicit_base_url=resolved_base_url,\n             explicit_api_key=[redacted:secret-assignment]\n+            api_mode=resolved_api_mode,\n         )\n         if client is None:\n             return \"custom\", None, None","@@ -1710,7 +1711,8 @@ def _finalize(resolved_provider: str, sync_client: Any, default_model: Optional[\n                 # Use provider-specific vision model if available, otherwise main model.\n                 vision_model = _PROVIDER_VISION_MODELS.get(main_provider, main_model)\n                 rpc_client, rpc_model = resolve_provider_client(\n-                    main_provider, vision_model)\n+                    main_provider, vision_model,\n+                    api_mode=resolved_api_mode)\n                 if rpc_client is not None:\n                     logger.info(\n                         \"Vision auto-detect: using active provider %s (%s)\",","@@ -1734,7 +1736,8 @@ def _finalize(resolved_provider: str, sync_client: Any, default_model: Optional[\n         sync_client, default_model = _resolve_strict_vision_backend(requested)\n         return _finalize(requested, sync_client, default_model)\n \n-    client, final_model = _get_cached_client(requested, resolved_model, async_mode)\n+    client, final_model = _get_cached_client(requested, resolved_model, async_mode,\n+                                             api_mode=resolved_api_mode)\n     if client is None:\n         return requested, None, None\n     return requested, client, final_model"]},{"file":"tests/agent/test_auxiliary_named_custom_providers.py","hunks":["@@ -58,6 +58,10 @@ def test_bare_provider_name_unchanged(self):\n         assert _normalize_vision_provider(\"beans\") == \"beans\"\n         assert _normalize_vision_provider(\"deepseek\") == \"deepseek\"\n \n+    def test_custom_colon_named_provider_preserved(self):\n+        from agent.auxiliary_client import _normalize_vision_provider\n+        assert _normalize_vision_provider(\"custom:beans\") == \"beans\"\n+\n     def test_codex_alias_still_works(self):\n         from agent.auxiliary_client import _normalize_vision_provider\n         assert _normalize_vision_provider(\"codex\") == \"openai-codex\"","@@ -229,3 +233,22 @@ def test_vision_auto_strips_matching_main_provider_prefix(self, tmp_path):\n         assert provider == \"zai\"\n         assert client is not None\n         assert model == \"glm-5.1\"\n+\n+\n+class TestVisionPathApiMode:\n+    \"\"\"Vision path should propagate api_mode to _get_cached_client.\"\"\"\n+\n+    def test_explicit_provider_passes_api_mode(self, tmp_path):\n+        _write_config(tmp_path, {\n+            \"model\": {\"default\": \"test-model\"},\n+            \"auxiliary\": {\"vision\": {\"api_mode\": \"chat_completions\"}},\n+        })\n+        with patch(\"agent.auxiliary_client._get_cached_client\") as mock_gcc:\n+            mock_gcc.return_value = (MagicMock(), \"test-model\")\n+            from agent.auxiliary_client import resolve_vision_provider_client\n+\n+            provider, client, model = resolve_vision_provider_client(provider=\"deepseek\")\n+\n+        mock_gcc.assert_called_once()\n+        _, kwargs = mock_gcc.call_args\n+        assert kwargs.get(\"api_mode\") == \"chat_completions\"\n"]}]}],"trust":{"default_tier":"verified","includes_suggested":false}}