# NousResearch/hermes-agent · Propagating api_mode in vision provider resolution

## Open now

- Nothing open on GitHub.

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.

## 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 #9001. Verify it addresses your case.
- **Recommended:** read PR #9001 (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: `resolved` · deciding PR(s): `pr-9001`. 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

- #8857: Vision auxiliary client drops api_mode for custom providers, causing crash

## Prior & competing PRs

- **#9001: fix(agent): propagate api_mode to vision provider resolution**. merged to main · fix tier `verified`
  - maintainer direction: category `merged`
  - files: `agent/auxiliary_client.py`, `tests/agent/test_auxiliary_named_custom_providers.py`
  - fix hunks:
  - `agent/auxiliary_client.py`

    ```diff
    @@ -71,13 +71,13 @@
     }
     
     
    -def _normalize_aux_provider(provider: Optional[str], *, for_vision: bool = False) -> str:
    +def _normalize_aux_provider(provider: Optional[str]) -> str:
         normalized = (provider or "auto").strip().lower()
         if normalized.startswith("custom:"):
             suffix = normalized.split(":", 1)[1].strip()
             if not suffix:
                 return "custom"
    -        normalized = suffix if not for_vision else "custom"
    +        normalized = suffix
         if normalized == "codex":
             return "openai-codex"
         if normalized == "main":
    ```

    ```diff
    @@ -1603,7 +1603,7 @@ def get_async_text_auxiliary_client(task: str = "", *, main_runtime: Optional[Di
     
     
     def _normalize_vision_provider(provider: Optional[str]) -> str:
    -    return _normalize_aux_provider(provider, for_vision=True)
    +    return _normalize_aux_provider(provider)
     
     
     def _resolve_strict_vision_backend(provider: str) -> Tuple[Optional[Any], Optional[str]]:
    ```

    ```diff
    @@ -1686,6 +1686,7 @@ def _finalize(resolved_provider: str, sync_client: Any, default_model: Optional[
                 async_mode=async_mode,
                 explicit_base_url=resolved_base_url,
                 explicit_api_key=[redacted:secret-assignment]
    +            api_mode=resolved_api_mode,
             )
             if client is None:
                 return "custom", None, None
    ```

    ```diff
    @@ -1710,7 +1711,8 @@ def _finalize(resolved_provider: str, sync_client: Any, default_model: Optional[
                     # Use provider-specific vision model if available, otherwise main model.
                     vision_model = _PROVIDER_VISION_MODELS.get(main_provider, main_model)
                     rpc_client, rpc_model = resolve_provider_client(
    -                    main_provider, vision_model)
    +                    main_provider, vision_model,
    +                    api_mode=resolved_api_mode)
                     if rpc_client is not None:
                         logger.info(
                             "Vision auto-detect: using active provider %s (%s)",
    ```

    ```diff
    @@ -1734,7 +1736,8 @@ def _finalize(resolved_provider: str, sync_client: Any, default_model: Optional[
             sync_client, default_model = _resolve_strict_vision_backend(requested)
             return _finalize(requested, sync_client, default_model)
     
    -    client, final_model = _get_cached_client(requested, resolved_model, async_mode)
    +    client, final_model = _get_cached_client(requested, resolved_model, async_mode,
    +                                             api_mode=resolved_api_mode)
         if client is None:
             return requested, None, None
         return requested, client, final_model
    ```
  - `tests/agent/test_auxiliary_named_custom_providers.py`

    ```diff
    @@ -58,6 +58,10 @@ def test_bare_provider_name_unchanged(self):
             assert _normalize_vision_provider("beans") == "beans"
             assert _normalize_vision_provider("deepseek") == "deepseek"
     
    +    def test_custom_colon_named_provider_preserved(self):
    +        from agent.auxiliary_client import _normalize_vision_provider
    +        assert _normalize_vision_provider("custom:beans") == "beans"
    +
         def test_codex_alias_still_works(self):
             from agent.auxiliary_client import _normalize_vision_provider
             assert _normalize_vision_provider("codex") == "openai-codex"
    ```

    ```diff
    @@ -229,3 +233,22 @@ def test_vision_auto_strips_matching_main_provider_prefix(self, tmp_path):
             assert provider == "zai"
             assert client is not None
             assert model == "glm-5.1"
    +
    +
    +class TestVisionPathApiMode:
    +    """Vision path should propagate api_mode to _get_cached_client."""
    +
    +    def test_explicit_provider_passes_api_mode(self, tmp_path):
    +        _write_config(tmp_path, {
    +            "model": {"default": "test-model"},
    +            "auxiliary": {"vision": {"api_mode": "chat_completions"}},
    +        })
    +        with patch("agent.auxiliary_client._get_cached_client") as mock_gcc:
    +            mock_gcc.return_value = (MagicMock(), "test-model")
    +            from agent.auxiliary_client import resolve_vision_provider_client
    +
    +            provider, client, model = resolve_vision_provider_client(provider="deepseek")
    +
    +        mock_gcc.assert_called_once()
    +        _, kwargs = mock_gcc.call_args
    +        assert kwargs.get("api_mode") == "chat_completions"
    ```
- **#8871: fix(agent): propagate api_mode to vision provider resolution**. closed without merge
  - maintainer direction: category `merged_via`; implication `superseded`; refs `#9001`

## Fix status

- Merged to main in PR #9001 (cited above). No release data is tracked. Verify downstream.
- Fix last verified (merged) `2026-04-13`. This confirmation is old and may be out of date (a revert/reopen would not have invalidated it here); re-verify.
