diff --git a/libs/code/deepagents_code/tui/widgets/auth.py b/libs/code/deepagents_code/tui/widgets/auth.py index 1b46e35cf64..8bf2b6a0979 100644 --- a/libs/code/deepagents_code/tui/widgets/auth.py +++ b/libs/code/deepagents_code/tui/widgets/auth.py @@ -61,7 +61,6 @@ ProviderAuthState, ProviderAuthStatus, clear_caches, - get_available_models, get_base_url_env_var, get_base_url_env_vars, get_credential_env_var, @@ -1879,19 +1878,28 @@ def _build_options_with_warning(self) -> tuple[list[Option], str | None]: name for name, cfg in config.providers.items() if cfg.get("api_key_env") } - # Only show well-known providers whose LangChain package is actually - # installed. `get_available_models` returns providers it could - # successfully import profiles for, so it doubles as an install - # gate. Stored and config-defined providers are always shown — even - # if the package was later uninstalled — so a stale credential can - # still be cleaned up and an explicitly-declared provider stays - # visible. - installed = set(get_available_models().keys()) - well_known_installed = set(PROVIDER_API_KEY_ENV) & installed + # Show well-known providers whenever their LangChain package is + # installed, even if none of the package's model profiles pass dcode's + # tool-calling filter. Stored and config-defined providers are always + # shown too, so stale credentials can still be cleaned up and an + # explicitly-declared provider stays visible. + from deepagents_code.config_manifest import is_provider_package_installed + + well_known_installed = { + provider + for provider in PROVIDER_API_KEY_ENV + if config.is_provider_enabled(provider) + and is_provider_package_installed(provider) + } # `openai_codex` is gated on `langchain-openai` being installed (we - # surface it whenever `openai` was discovered) rather than on + # surface it whenever `openai` is available) rather than on # `PROVIDER_API_KEY_ENV`, since it has no env var of its own. - codex_installed = {CODEX_PROVIDER} if "openai" in installed else set() + codex_installed = ( + {CODEX_PROVIDER} + if "openai" in well_known_installed + and config.is_provider_enabled(CODEX_PROVIDER) + else set() + ) shown = well_known_installed | codex_installed | stored | config_providers # Surface well-known providers whose package isn't installed yet as diff --git a/libs/code/tests/unit_tests/tui/widgets/test_auth_widgets.py b/libs/code/tests/unit_tests/tui/widgets/test_auth_widgets.py index c3524be96e9..8d1aeb49954 100644 --- a/libs/code/tests/unit_tests/tui/widgets/test_auth_widgets.py +++ b/libs/code/tests/unit_tests/tui/widgets/test_auth_widgets.py @@ -2038,10 +2038,6 @@ async def test_configured_providers_sort_to_top( "DEEPAGENTS_CODE_ANTHROPIC_API_KEY", ): monkeypatch.delenv(var, raising=False) - monkeypatch.setattr( - "deepagents_code.tui.widgets.auth.get_available_models", - lambda: {"openai": ["gpt-5.4"], "anthropic": ["claude-opus-4-7"]}, - ) monkeypatch.setattr( "deepagents_code.config_manifest.is_provider_package_installed", lambda _provider: True, @@ -2068,10 +2064,6 @@ async def test_configured_services_sort_with_configured_providers( "DEEPAGENTS_CODE_TAVILY_API_KEY", ): monkeypatch.delenv(var, raising=False) - monkeypatch.setattr( - "deepagents_code.tui.widgets.auth.get_available_models", - lambda: {"anthropic": ["claude-opus-4-7"]}, - ) monkeypatch.setattr( "deepagents_code.config_manifest.is_provider_package_installed", lambda _provider: True, @@ -2109,10 +2101,6 @@ async def test_uninstalled_providers_stay_below_configured( "DEEPAGENTS_CODE_GROQ_API_KEY", ): monkeypatch.delenv(var, raising=False) - monkeypatch.setattr( - "deepagents_code.tui.widgets.auth.get_available_models", - lambda: {"openai": ["gpt-5.4"], "anthropic": ["claude-opus-4-7"]}, - ) monkeypatch.setattr( "deepagents_code.config_manifest.is_provider_package_installed", lambda provider: provider != "groq", @@ -2148,10 +2136,6 @@ async def test_refresh_options_resorts_after_saving_key( "DEEPAGENTS_CODE_ANTHROPIC_API_KEY", ): monkeypatch.delenv(var, raising=False) - monkeypatch.setattr( - "deepagents_code.tui.widgets.auth.get_available_models", - lambda: {"openai": ["gpt-5.4"], "anthropic": ["claude-opus-4-7"]}, - ) monkeypatch.setattr( "deepagents_code.config_manifest.is_provider_package_installed", lambda _provider: True, @@ -2199,15 +2183,6 @@ async def test_configured_group_preserves_alphabetical_order( "DEEPAGENTS_CODE_MISTRAL_API_KEY", ): monkeypatch.delenv(var, raising=False) - monkeypatch.setattr( - "deepagents_code.tui.widgets.auth.get_available_models", - lambda: { - "openai": ["gpt-5.4"], - "anthropic": ["claude-opus-4-7"], - "cohere": ["command"], - "mistralai": ["mistral-large"], - }, - ) monkeypatch.setattr( "deepagents_code.config_manifest.is_provider_package_installed", lambda _provider: True, @@ -2244,10 +2219,6 @@ async def test_env_configured_provider_floats( "DEEPAGENTS_CODE_ANTHROPIC_API_KEY", ): monkeypatch.delenv(var, raising=False) - monkeypatch.setattr( - "deepagents_code.tui.widgets.auth.get_available_models", - lambda: {"openai": ["gpt-5.4"], "anthropic": ["claude-opus-4-7"]}, - ) monkeypatch.setattr( "deepagents_code.config_manifest.is_provider_package_installed", lambda _provider: True, @@ -2343,34 +2314,27 @@ async def test_only_installed_well_known_providers_listed( ) -> None: """Installed providers show as live rows; uninstalled ones grey out. - When `openai` is "installed", `openai_codex` rides along — it shares - the same `langchain-openai` package, so the manager surfaces the - OAuth-backed twin alongside the API-key entry. With every known - package reported installed, no greyed install-on-select rows appear, - so the listing equals the installed set. + When `openai` is installed, `openai_codex` rides along because it shares + the same `langchain-openai` package. Every other known provider remains + a greyed install-on-select row below the live entries. """ - # Pretend only `openai` and `anthropic` are installed. - monkeypatch.setattr( - "deepagents_code.tui.widgets.auth.get_available_models", - lambda: {"openai": ["gpt-5.4"], "anthropic": ["claude-opus-4-7"]}, - ) - # Report every known package installed so no greyed-out - # install-on-select rows are appended to the listing. monkeypatch.setattr( "deepagents_code.config_manifest.is_provider_package_installed", - lambda _provider: True, + lambda provider: provider in {"openai", "anthropic"}, ) app = _AuthHostApp() async with app.run_test() as pilot: app.show_manager() await pilot.pause() - options = app.screen.query_one("#auth-manager-options", OptionList) + screen = cast("AuthManagerScreen", app.screen) + options = screen.query_one("#auth-manager-options", OptionList) ids = { options.get_option_at_index(i).id for i in range(options.option_count) } + live_ids = ids - set(screen._install_extras) # Non-model services (Tavily search, LangSmith tracing) are always # listed for key entry. - assert ids == { + assert live_ids == { "openai", "openai_codex", "anthropic", @@ -2387,8 +2351,8 @@ async def test_selecting_service_opens_prompt_for_its_env_var( would look up a credential env var the service doesn't have. """ monkeypatch.setattr( - "deepagents_code.tui.widgets.auth.get_available_models", - lambda: {"openai": ["gpt-5.4"]}, + "deepagents_code.config_manifest.is_provider_package_installed", + lambda provider: provider == "openai", ) app = _AuthHostApp() async with app.run_test() as pilot: @@ -2413,8 +2377,8 @@ async def test_service_row_shows_stored_badge( """ auth_store.set_stored_key("tavily", "k") monkeypatch.setattr( - "deepagents_code.tui.widgets.auth.get_available_models", - lambda: {"openai": ["gpt-5.4"]}, + "deepagents_code.config_manifest.is_provider_package_installed", + lambda provider: provider == "openai", ) app = _AuthHostApp() async with app.run_test() as pilot: @@ -2438,8 +2402,8 @@ async def test_stored_provider_shown_even_when_uninstalled( """ auth_store.set_stored_key("groq", "k") monkeypatch.setattr( - "deepagents_code.tui.widgets.auth.get_available_models", - lambda: {"openai": ["gpt-5.4"]}, + "deepagents_code.config_manifest.is_provider_package_installed", + lambda provider: provider == "openai", ) app = _AuthHostApp() async with app.run_test() as pilot: @@ -2461,10 +2425,6 @@ async def test_uninstalled_known_provider_shown_greyed( so `groq` is surfaced as a `[not installed]` install-on-select entry for discoverability rather than being hidden. """ - monkeypatch.setattr( - "deepagents_code.tui.widgets.auth.get_available_models", - lambda: {"openai": ["gpt-5.4"], "anthropic": ["claude-opus-4-7"]}, - ) monkeypatch.setattr( "deepagents_code.config_manifest.is_provider_package_installed", lambda provider: provider in {"openai", "anthropic"}, @@ -2492,10 +2452,6 @@ async def test_selecting_uninstalled_provider_prompts_install( InstallProviderConfirmScreen, ) - monkeypatch.setattr( - "deepagents_code.tui.widgets.auth.get_available_models", - lambda: {"openai": ["gpt-5.4"]}, - ) monkeypatch.setattr( "deepagents_code.config_manifest.is_provider_package_installed", lambda provider: provider == "openai", @@ -2514,10 +2470,6 @@ async def test_confirming_install_records_extra_and_dismisses( self, monkeypatch: pytest.MonkeyPatch ) -> None: """Confirming the install records the extra and dismisses the manager.""" - monkeypatch.setattr( - "deepagents_code.tui.widgets.auth.get_available_models", - lambda: {"openai": ["gpt-5.4"]}, - ) monkeypatch.setattr( "deepagents_code.config_manifest.is_provider_package_installed", lambda provider: provider == "openai", @@ -2534,40 +2486,34 @@ async def test_confirming_install_records_extra_and_dismisses( assert screen.pending_install_extra == "groq" assert screen.pending_install_provider == "groq" - async def test_reopening_manager_highlights_initial_provider( + async def test_reopening_highlights_installed_provider_without_compatible_models( self, monkeypatch: pytest.MonkeyPatch ) -> None: - """Reopening after an install highlights the just-installed provider. + """An installed provider stays live even when its profiles are filtered out. - Simulates the post-install reopen: `groq` is now installed and listed, - and passing it as `initial_provider` lands the cursor on its row rather - than resetting to index 0. + Perplexity currently declares no tool-calling models, so model discovery + excludes it. The auth manager must use package availability instead and + highlight it when reopening after the extra install. """ - monkeypatch.setattr( - "deepagents_code.tui.widgets.auth.get_available_models", - lambda: {"openai": ["gpt-5.4"], "groq": ["llama-3"]}, - ) monkeypatch.setattr( "deepagents_code.config_manifest.is_provider_package_installed", - lambda provider: provider in {"openai", "groq"}, + lambda provider: provider in {"openai", "perplexity"}, ) app = _AuthHostApp() async with app.run_test() as pilot: - app.show_manager(initial_provider="groq") + app.show_manager(initial_provider="perplexity") await pilot.pause() screen = cast("AuthManagerScreen", app.screen) options = screen.query_one("#auth-manager-options", OptionList) assert options.highlighted is not None - assert options.get_option_at_index(options.highlighted).id == "groq" + selected = options.get_option_at_index(options.highlighted) + assert selected.id == "perplexity" + assert "not installed" not in str(selected.prompt) async def test_reopening_manager_ignores_unknown_initial_provider( self, monkeypatch: pytest.MonkeyPatch ) -> None: """An initial provider absent from the list leaves the cursor at the top.""" - monkeypatch.setattr( - "deepagents_code.tui.widgets.auth.get_available_models", - lambda: {"openai": ["gpt-5.4"]}, - ) monkeypatch.setattr( "deepagents_code.config_manifest.is_provider_package_installed", lambda provider: provider == "openai", @@ -2584,10 +2530,6 @@ async def test_cancelling_install_leaves_manager_without_pending_extra( self, monkeypatch: pytest.MonkeyPatch ) -> None: """Declining the install records nothing and keeps the user on the manager.""" - monkeypatch.setattr( - "deepagents_code.tui.widgets.auth.get_available_models", - lambda: {"openai": ["gpt-5.4"]}, - ) monkeypatch.setattr( "deepagents_code.config_manifest.is_provider_package_installed", lambda provider: provider == "openai", @@ -2621,10 +2563,6 @@ async def test_disabled_known_provider_not_offered_for_install( """) monkeypatch.setattr(model_config, "DEFAULT_CONFIG_PATH", config_path) model_config.clear_caches() - monkeypatch.setattr( - "deepagents_code.tui.widgets.auth.get_available_models", - lambda: {"openai": ["gpt-5.4"], "anthropic": ["claude-opus-4-7"]}, - ) monkeypatch.setattr( "deepagents_code.config_manifest.is_provider_package_installed", lambda provider: provider in {"openai", "anthropic"}, @@ -2651,10 +2589,6 @@ async def test_known_provider_without_extra_not_offered_for_install( so the provider must not appear as an install-on-select row pointing at a `None` extra. """ - monkeypatch.setattr( - "deepagents_code.tui.widgets.auth.get_available_models", - lambda: {"openai": ["gpt-5.4"]}, - ) monkeypatch.setattr( "deepagents_code.config_manifest.is_provider_package_installed", lambda provider: provider != "groq", @@ -2737,6 +2671,34 @@ async def test_codex_option_visible_when_openai_installed(self) -> None: } assert "openai_codex" in ids + async def test_disabled_codex_option_hidden( + self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch + ) -> None: + """A disabled `openai_codex` provider stays out of the auth manager.""" + config_path = tmp_path / "config.toml" + config_path.write_text(""" +[models.providers.openai_codex] +enabled = false +""") + monkeypatch.setattr(model_config, "DEFAULT_CONFIG_PATH", config_path) + model_config.clear_caches() + monkeypatch.setattr( + "deepagents_code.config_manifest.is_provider_package_installed", + lambda provider: provider == "openai", + ) + + app = _AuthHostApp() + async with app.run_test() as pilot: + app.show_manager() + await pilot.pause() + options = app.screen.query_one("#auth-manager-options", OptionList) + ids = { + options.get_option_at_index(i).id for i in range(options.option_count) + } + + assert "openai" in ids + assert "openai_codex" not in ids + async def test_codex_badge_reflects_signed_out_state( self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch ) -> None: