From 58cce14a0721690280b1ae323f13fe48f6fa3b81 Mon Sep 17 00:00:00 2001 From: hamb1y Date: Sun, 28 Jun 2026 10:11:24 +0530 Subject: [PATCH] fix(cli): allow oauth login to set main provider --- nanobot/cli/commands.py | 49 ++++++++++++++++++++++++++++ tests/cli/test_commands.py | 65 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 114 insertions(+) diff --git a/nanobot/cli/commands.py b/nanobot/cli/commands.py index 499c9753..d471b746 100644 --- a/nanobot/cli/commands.py +++ b/nanobot/cli/commands.py @@ -1744,6 +1744,11 @@ _PROVIDER_DISPLAY: dict[str, str] = { "github_copilot": "GitHub Copilot", } +_OAUTH_PROVIDER_DEFAULT_MODELS: dict[str, str] = { + "openai_codex": "openai-codex/gpt-5.1-codex", + "github_copilot": "github-copilot/gpt-4.1", +} + def _register_login(name: str): """Register an OAuth login handler.""" @@ -1775,9 +1780,51 @@ def _resolve_oauth_provider(provider: str): return spec +def _set_oauth_provider_as_main( + provider_name: str, + *, + model: str | None = None, + config_path: str | None = None, +) -> None: + """Persist an OAuth provider as the active agent provider.""" + from nanobot.config.loader import get_config_path, load_config, save_config, set_config_path + + resolved_config_path = Path(config_path).expanduser().resolve() if config_path else None + if resolved_config_path is not None: + set_config_path(resolved_config_path) + console.print(f"[dim]Using config: {resolved_config_path}[/dim]") + + config = load_config(resolved_config_path) + selected_model = (model or "").strip() or _OAUTH_PROVIDER_DEFAULT_MODELS[provider_name] + config.agents.defaults.model_preset = None + config.agents.defaults.provider = provider_name + config.agents.defaults.model = selected_model + save_config(config, resolved_config_path) + + saved_path = resolved_config_path or get_config_path() + console.print( + f"[green]✓ Set {provider_name.replace('_', '-')} as the main provider[/green] " + f"[dim]{selected_model}[/dim]" + ) + console.print(f"[dim]Saved: {saved_path}[/dim]") + + @provider_app.command("login") def provider_login( provider: str = typer.Argument(..., help="OAuth provider (e.g. 'openai-codex', 'github-copilot')"), + set_main: bool = typer.Option( + False, + "--set-main", + "--main", + help="Set this OAuth provider as the active agent provider after login", + ), + model: str | None = typer.Option( + None, + "--model", + "-m", + help="Model to use when setting this provider as the active provider", + ), + config: str | None = typer.Option(None, "--config", "-c", help="Path to config file"), ): """Authenticate with an OAuth provider.""" spec = _resolve_oauth_provider(provider) @@ -1789,6 +1836,8 @@ def provider_login( console.print(f"{__logo__} OAuth Login - {spec.label}\n") handler() + if set_main or model: + _set_oauth_provider_as_main(spec.name, model=model, config_path=config) @provider_app.command("logout") diff --git a/tests/cli/test_commands.py b/tests/cli/test_commands.py index e265d1c2..817ea38a 100644 --- a/tests/cli/test_commands.py +++ b/tests/cli/test_commands.py @@ -435,6 +435,71 @@ def test_provider_login_rejects_unknown_provider(): assert "Unknown OAuth provider" in result.stdout +def test_provider_login_can_set_openai_codex_as_main_provider(tmp_path): + config_path = tmp_path / "config.json" + called = False + original = cli_commands._LOGIN_HANDLERS["openai_codex"] + + def fake_login() -> None: + nonlocal called + called = True + + cli_commands._LOGIN_HANDLERS["openai_codex"] = fake_login + try: + result = runner.invoke( + app, + [ + "provider", + "login", + "openai-codex", + "--set-main", + "--config", + str(config_path), + ], + ) + finally: + cli_commands._LOGIN_HANDLERS["openai_codex"] = original + + assert result.exit_code == 0 + assert called is True + assert "Set openai-codex as the main provider" in result.stdout + + saved = Config.model_validate(json.loads(config_path.read_text(encoding="utf-8"))) + assert saved.agents.defaults.provider == "openai_codex" + assert saved.agents.defaults.model == "openai-codex/gpt-5.1-codex" + assert saved.agents.defaults.model_preset is None + assert make_provider(saved).__class__.__name__ == "OpenAICodexProvider" + + +def test_provider_login_model_implies_set_main_provider(tmp_path): + config_path = tmp_path / "config.json" + original = cli_commands._LOGIN_HANDLERS["github_copilot"] + cli_commands._LOGIN_HANDLERS["github_copilot"] = lambda: None + try: + result = runner.invoke( + app, + [ + "provider", + "login", + "github-copilot", + "--model", + "github-copilot/gpt-5.4-mini", + "--config", + str(config_path), + ], + ) + finally: + cli_commands._LOGIN_HANDLERS["github_copilot"] = original + + assert result.exit_code == 0 + assert "Set github-copilot as the main provider" in result.stdout + + saved = Config.model_validate(json.loads(config_path.read_text(encoding="utf-8"))) + assert saved.agents.defaults.provider == "github_copilot" + assert saved.agents.defaults.model == "github-copilot/gpt-5.4-mini" + assert make_provider(saved).__class__.__name__ == "GitHubCopilotProvider" + + def test_provider_login_openai_codex_passes_configured_proxy(monkeypatch): proxy = "http://127.0.0.1:23458" monkeypatch.setattr(