[github] Fix Oauth login

This commit is contained in:
Jeroen Evens 2026-02-17 22:50:39 +01:00
parent b161fa4f9a
commit 16127d49f9
2 changed files with 82 additions and 37 deletions

View File

@ -915,12 +915,36 @@ app.add_typer(provider_app, name="provider")
@provider_app.command("login") @provider_app.command("login")
def provider_login( def provider_login(
provider: str = typer.Argument(..., help="OAuth provider to authenticate with (e.g., 'openai-codex')"), provider: str = typer.Argument(..., help="OAuth provider to authenticate with (e.g., 'openai-codex', 'github-copilot')"),
): ):
"""Authenticate with an OAuth provider.""" """Authenticate with an OAuth provider."""
console.print(f"{__logo__} OAuth Login - {provider}\n") from nanobot.providers.registry import PROVIDERS
if provider == "openai-codex": # Normalize: "github-copilot" → "github_copilot"
provider_key = provider.replace("-", "_")
# Validate against the registry — only OAuth providers support login
spec = None
for s in PROVIDERS:
if s.name == provider_key and s.is_oauth:
spec = s
break
if not spec:
oauth_names = [s.name.replace("_", "-") for s in PROVIDERS if s.is_oauth]
console.print(f"[red]Unknown OAuth provider: {provider}[/red]")
console.print(f"[yellow]Supported providers: {', '.join(oauth_names)}[/yellow]")
raise typer.Exit(1)
console.print(f"{__logo__} OAuth Login - {spec.display_name}\n")
if spec.name == "openai_codex":
_login_openai_codex()
elif spec.name == "github_copilot":
_login_github_copilot()
def _login_openai_codex() -> None:
"""Authenticate with OpenAI Codex via oauth_cli_kit."""
try: try:
from oauth_cli_kit import get_token, login_oauth_interactive from oauth_cli_kit import get_token, login_oauth_interactive
token = None token = None
@ -946,9 +970,37 @@ def provider_login(
except Exception as e: except Exception as e:
console.print(f"[red]Authentication error: {e}[/red]") console.print(f"[red]Authentication error: {e}[/red]")
raise typer.Exit(1) raise typer.Exit(1)
else:
console.print(f"[red]Unknown OAuth provider: {provider}[/red]")
console.print("[yellow]Supported providers: openai-codex[/yellow]") def _login_github_copilot() -> None:
"""Authenticate with GitHub Copilot via LiteLLM's device flow.
LiteLLM handles the full OAuth device flow (device code poll token
storage) internally when a github_copilot/ model is first called.
We trigger that flow by sending a minimal completion request.
"""
import asyncio
console.print("[cyan]Starting GitHub Copilot device flow via LiteLLM...[/cyan]")
console.print("You will be prompted to visit a URL and enter a device code.\n")
async def _trigger_device_flow() -> None:
from litellm import acompletion
await acompletion(
model="github_copilot/gpt-4o",
messages=[{"role": "user", "content": "hi"}],
max_tokens=1,
)
try:
asyncio.run(_trigger_device_flow())
console.print("\n[green]✓ Successfully authenticated with GitHub Copilot![/green]")
except Exception as e:
error_msg = str(e)
# A successful device flow still returns a valid response;
# any exception here means the flow genuinely failed.
console.print(f"[red]Authentication error: {error_msg}[/red]")
console.print("[yellow]Ensure you have a GitHub Copilot subscription.[/yellow]")
raise typer.Exit(1) raise typer.Exit(1)

View File

@ -38,9 +38,6 @@ class LiteLLMProvider(LLMProvider):
# api_key / api_base are fallback for auto-detection. # api_key / api_base are fallback for auto-detection.
self._gateway = find_gateway(provider_name, api_key, api_base) self._gateway = find_gateway(provider_name, api_key, api_base)
# Detect GitHub Copilot (uses OAuth device flow, no API key)
self.is_github_copilot = "github_copilot" in default_model
# Configure environment variables # Configure environment variables
if api_key: if api_key:
self._setup_env(api_key, api_base, default_model) self._setup_env(api_key, api_base, default_model)
@ -79,10 +76,6 @@ class LiteLLMProvider(LLMProvider):
def _resolve_model(self, model: str) -> str: def _resolve_model(self, model: str) -> str:
"""Resolve model name by applying provider/gateway prefixes.""" """Resolve model name by applying provider/gateway prefixes."""
# GitHub Copilot models pass through directly
if self.is_github_copilot:
return model
if self._gateway: if self._gateway:
# Gateway mode: apply gateway prefix, skip provider-specific prefixes # Gateway mode: apply gateway prefix, skip provider-specific prefixes
prefix = self._gateway.litellm_prefix prefix = self._gateway.litellm_prefix