@@ -641,12 +641,15 @@ def test_vision_returns_none_without_any_credentials(self):
641641 assert client is None
642642 assert model is None
643643
644- def test_vision_auto_includes_anthropic_when_configured (self , monkeypatch ):
645- monkeypatch .setenv ("ANTHROPIC_API_KEY" , "sk-ant-api03-key" )
644+ def test_vision_auto_includes_active_provider_when_configured (self , monkeypatch ):
645+ """Active provider appears in available backends when credentials exist."""
646+ monkeypatch .setenv ("ANTHROPIC_API_KEY" , "***" )
646647 with (
647648 patch ("agent.auxiliary_client._read_nous_auth" , return_value = None ),
649+ patch ("agent.auxiliary_client._read_main_provider" , return_value = "anthropic" ),
650+ patch ("agent.auxiliary_client._read_main_model" , return_value = "claude-sonnet-4" ),
648651 patch ("agent.anthropic_adapter.build_anthropic_client" , return_value = MagicMock ()),
649- patch ("agent.anthropic_adapter.resolve_anthropic_token" , return_value = "sk-ant-api03-key " ),
652+ patch ("agent.anthropic_adapter.resolve_anthropic_token" , return_value = "*** " ),
650653 ):
651654 backends = get_available_vision_backends ()
652655
@@ -719,88 +722,50 @@ def test_resolve_provider_client_copilot_uses_runtime_credentials(self, monkeypa
719722 assert call_kwargs ["base_url" ] == "https://api.githubcopilot.com"
720723 assert call_kwargs ["default_headers" ]["Editor-Version" ]
721724
722- def test_vision_auto_uses_anthropic_when_no_higher_priority_backend (self , monkeypatch ):
723- monkeypatch .setenv ("ANTHROPIC_API_KEY" , "sk-ant-api03-key" )
725+ def test_vision_auto_uses_active_provider_as_fallback (self , monkeypatch ):
726+ """When no OpenRouter/Nous available, vision auto falls back to active provider."""
727+ monkeypatch .setenv ("ANTHROPIC_API_KEY" , "***" )
724728 with (
725729 patch ("agent.auxiliary_client._read_nous_auth" , return_value = None ),
730+ patch ("agent.auxiliary_client._read_main_provider" , return_value = "anthropic" ),
731+ patch ("agent.auxiliary_client._read_main_model" , return_value = "claude-sonnet-4" ),
726732 patch ("agent.anthropic_adapter.build_anthropic_client" , return_value = MagicMock ()),
727- patch ("agent.anthropic_adapter.resolve_anthropic_token" , return_value = "sk-ant-api03-key " ),
733+ patch ("agent.anthropic_adapter.resolve_anthropic_token" , return_value = "*** " ),
728734 ):
729735 client , model = get_vision_auxiliary_client ()
730736
731737 assert client is not None
732738 assert client .__class__ .__name__ == "AnthropicAuxiliaryClient"
733- assert model == "claude-haiku-4-5-20251001"
734739
735- def test_selected_anthropic_provider_is_preferred_for_vision_auto (self , monkeypatch ):
740+ def test_vision_auto_prefers_openrouter_over_active_provider (self , monkeypatch ):
741+ """OpenRouter is tried before the active provider in vision auto."""
736742 monkeypatch .setenv ("OPENROUTER_API_KEY" , "or-key" )
737- monkeypatch .setenv ("ANTHROPIC_API_KEY" , "sk-ant-api03-key" )
738-
739- def fake_load_config ():
740- return {"model" : {"provider" : "anthropic" , "default" : "claude-sonnet-4-6" }}
743+ monkeypatch .setenv ("ANTHROPIC_API_KEY" , "***" )
741744
742745 with (
743746 patch ("agent.auxiliary_client._read_nous_auth" , return_value = None ),
744- patch ("agent.anthropic_adapter.build_anthropic_client " , return_value = MagicMock () ),
745- patch ("agent.anthropic_adapter.resolve_anthropic_token " , return_value = "sk-ant-api03-key " ),
747+ patch ("agent.auxiliary_client._read_main_provider " , return_value = "anthropic" ),
748+ patch ("agent.auxiliary_client._read_main_model " , return_value = "claude-sonnet-4 " ),
746749 patch ("agent.auxiliary_client.OpenAI" ) as mock_openai ,
747- patch ("hermes_cli.config.load_config" , fake_load_config ),
748- ):
749- client , model = get_vision_auxiliary_client ()
750-
751- assert client is not None
752- assert client .__class__ .__name__ == "AnthropicAuxiliaryClient"
753- assert model == "claude-haiku-4-5-20251001"
754-
755- def test_selected_codex_provider_short_circuits_vision_auto (self , monkeypatch ):
756- def fake_load_config ():
757- return {"model" : {"provider" : "openai-codex" , "default" : "gpt-5.2-codex" }}
758-
759- codex_client = MagicMock ()
760- with (
761- patch ("hermes_cli.config.load_config" , fake_load_config ),
762- patch ("agent.auxiliary_client._try_codex" , return_value = (codex_client , "gpt-5.2-codex" )) as mock_codex ,
763- patch ("agent.auxiliary_client._try_openrouter" ) as mock_openrouter ,
764- patch ("agent.auxiliary_client._try_nous" ) as mock_nous ,
765- patch ("agent.auxiliary_client._try_anthropic" ) as mock_anthropic ,
766- patch ("agent.auxiliary_client._try_custom_endpoint" ) as mock_custom ,
767750 ):
768751 provider , client , model = resolve_vision_provider_client ()
769752
770- assert provider == "openai-codex"
771- assert client is codex_client
772- assert model == "gpt-5.2-codex"
773- mock_codex .assert_called_once ()
774- mock_openrouter .assert_not_called ()
775- mock_nous .assert_not_called ()
776- mock_anthropic .assert_not_called ()
777- mock_custom .assert_not_called ()
778-
779- def test_vision_auto_includes_codex (self , codex_auth_dir ):
780- """Codex supports vision (gpt-5.3-codex), so auto mode should use it."""
781- with patch ("agent.auxiliary_client._read_nous_auth" , return_value = None ), \
782- patch ("agent.auxiliary_client.OpenAI" ):
783- client , model = get_vision_auxiliary_client ()
784- from agent .auxiliary_client import CodexAuxiliaryClient
785- assert isinstance (client , CodexAuxiliaryClient )
786- assert model == "gpt-5.2-codex"
787-
788- def test_vision_auto_falls_back_to_custom_endpoint (self , monkeypatch ):
789- """Custom endpoint is used as fallback in vision auto mode.
753+ # OpenRouter should win over anthropic active provider
754+ assert provider == "openrouter"
790755
791- Many local models (Qwen-VL, LLaVA, etc.) support vision.
792- When no OpenRouter/Nous/Codex is available, try the custom endpoint.
793- """
756+ def test_vision_auto_uses_named_custom_as_active_provider (self , monkeypatch ):
757+ """Named custom provider works as active provider fallback in vision auto."""
794758 monkeypatch .delenv ("OPENROUTER_API_KEY" , raising = False )
795759 monkeypatch .delenv ("ANTHROPIC_API_KEY" , raising = False )
796760 with patch ("agent.auxiliary_client._read_nous_auth" , return_value = None ), \
797761 patch ("agent.auxiliary_client._select_pool_entry" , return_value = (False , None )), \
798- patch ("agent.auxiliary_client._read_codex_access_token" , return_value = None ), \
799- patch ("agent.auxiliary_client._resolve_custom_runtime" ,
800- return_value = ("http://localhost:1234/v1" , "local-key" )), \
801- patch ("agent.auxiliary_client.OpenAI" ) as mock_openai :
802- client , model = get_vision_auxiliary_client ()
803- assert client is not None # Custom endpoint picked up as fallback
762+ patch ("agent.auxiliary_client._read_main_provider" , return_value = "custom:local" ), \
763+ patch ("agent.auxiliary_client._read_main_model" , return_value = "my-local-model" ), \
764+ patch ("agent.auxiliary_client.resolve_provider_client" ,
765+ return_value = (MagicMock (), "my-local-model" )) as mock_resolve :
766+ provider , client , model = resolve_vision_provider_client ()
767+ assert client is not None
768+ assert provider == "custom:local"
804769
805770 def test_vision_direct_endpoint_override (self , monkeypatch ):
806771 monkeypatch .setenv ("OPENROUTER_API_KEY" , "or-key" )
@@ -888,7 +853,14 @@ def test_vision_forced_main_returns_none_without_creds(self, monkeypatch):
888853 monkeypatch .setenv ("AUXILIARY_VISION_PROVIDER" , "main" )
889854 monkeypatch .delenv ("OPENAI_BASE_URL" , raising = False )
890855 monkeypatch .delenv ("OPENAI_API_KEY" , raising = False )
856+ # Clear client cache to avoid stale entries from previous tests
857+ from agent .auxiliary_client import _client_cache
858+ _client_cache .clear ()
891859 with patch ("agent.auxiliary_client._read_nous_auth" , return_value = None ), \
860+ patch ("agent.auxiliary_client._read_main_provider" , return_value = "" ), \
861+ patch ("agent.auxiliary_client._read_main_model" , return_value = "" ), \
862+ patch ("agent.auxiliary_client._select_pool_entry" , return_value = (False , None )), \
863+ patch ("agent.auxiliary_client._resolve_custom_runtime" , return_value = (None , None )), \
892864 patch ("agent.auxiliary_client._read_codex_access_token" , return_value = None ), \
893865 patch ("agent.auxiliary_client._resolve_api_key_provider" , return_value = (None , None )):
894866 client , model = get_vision_auxiliary_client ()
0 commit comments