diff --git a/agent/auxiliary_client.py b/agent/auxiliary_client.py index a32e3a293..57c3c1186 100644 --- a/agent/auxiliary_client.py +++ b/agent/auxiliary_client.py @@ -560,12 +560,16 @@ def get_vision_auxiliary_client() -> Tuple[Optional[OpenAI], Optional[str]]: forced = _get_auxiliary_provider("vision") if forced != "auto": return _resolve_forced_provider(forced) - # Auto: only multimodal-capable providers - for try_fn in (_try_openrouter, _try_nous, _try_codex): + # Auto: try providers known to support multimodal first, then fall + # back to the user's custom endpoint. Many local models (Qwen-VL, + # LLaVA, Pixtral, etc.) support vision — skipping them entirely + # caused silent failures for local-only users. + for try_fn in (_try_openrouter, _try_nous, _try_codex, + _try_custom_endpoint): client, model = try_fn() if client is not None: return client, model - logger.debug("Auxiliary vision client: none available (auto only tries OpenRouter/Nous/Codex)") + logger.debug("Auxiliary vision client: none available") return None, None diff --git a/tests/agent/test_auxiliary_client.py b/tests/agent/test_auxiliary_client.py index 66187d055..299d083f2 100644 --- a/tests/agent/test_auxiliary_client.py +++ b/tests/agent/test_auxiliary_client.py @@ -176,14 +176,18 @@ class TestVisionClientFallback: assert isinstance(client, CodexAuxiliaryClient) assert model == "gpt-5.3-codex" - def test_vision_auto_skips_custom_endpoint(self, monkeypatch): - """Custom endpoint is skipped in vision auto mode.""" + def test_vision_auto_falls_back_to_custom_endpoint(self, monkeypatch): + """Custom endpoint is used as fallback in vision auto mode. + + Many local models (Qwen-VL, LLaVA, etc.) support vision. + When no OpenRouter/Nous/Codex is available, try the custom endpoint. + """ monkeypatch.setenv("OPENAI_BASE_URL", "http://localhost:1234/v1") monkeypatch.setenv("OPENAI_API_KEY", "local-key") - with patch("agent.auxiliary_client._read_nous_auth", return_value=None): + with patch("agent.auxiliary_client._read_nous_auth", return_value=None), \ + patch("agent.auxiliary_client.OpenAI") as mock_openai: client, model = get_vision_auxiliary_client() - assert client is None - assert model is None + assert client is not None # Custom endpoint picked up as fallback def test_vision_uses_openrouter_when_available(self, monkeypatch): monkeypatch.setenv("OPENROUTER_API_KEY", "or-key")