refactor: add OAuth support to provider registry system

- Add is_oauth and oauth_provider fields to ProviderSpec
- Update _make_provider() to use registry for OAuth provider detection
- Update get_provider() to support OAuth providers (no API key required)
- Mark OpenAI Codex as OAuth-based provider in registry

This improves the provider registry architecture to support OAuth-based
authentication flows, making it extensible for future OAuth providers.

Benefits:
- OAuth providers are now registry-driven (not hardcoded)
- Extensible design: new OAuth providers only need registry entry
- Backward compatible: existing API key providers unaffected
- Clean separation: OAuth logic centralized in registry
This commit is contained in:
pinhua33 2026-02-08 16:48:11 +08:00
parent c1dc8d3f55
commit 08efe6ad3f
3 changed files with 35 additions and 12 deletions

View File

@ -173,20 +173,33 @@ This file stores important information that should persist across sessions.
def _make_provider(config):
"""Create LiteLLMProvider from config. Exits if no API key found."""
"""Create provider from config. Exits if no credentials found."""
from nanobot.providers.litellm_provider import LiteLLMProvider
from nanobot.providers.openai_codex_provider import OpenAICodexProvider
from oauth_cli_kit import get_token as get_codex_token
from nanobot.providers.registry import PROVIDERS
from oauth_cli_kit import get_token as get_oauth_token
p = config.get_provider()
model = config.agents.defaults.model
if model.startswith("openai-codex/"):
try:
_ = get_codex_token()
except Exception:
console.print("Please run: [cyan]nanobot login --provider openai-codex[/cyan]")
model_lower = model.lower()
# Check for OAuth-based providers first (registry-driven)
for spec in PROVIDERS:
if spec.is_oauth and any(kw in model_lower for kw in spec.keywords):
# OAuth provider matched
try:
_ = get_oauth_token(spec.oauth_provider or spec.name)
except Exception:
console.print(f"Please run: [cyan]nanobot login --provider {spec.name}[/cyan]")
raise typer.Exit(1)
# Return appropriate OAuth provider class
if spec.name == "openai_codex":
return OpenAICodexProvider(default_model=model)
# Future OAuth providers can be added here
console.print(f"[red]Error: OAuth provider '{spec.name}' not fully implemented.[/red]")
raise typer.Exit(1)
return OpenAICodexProvider(default_model=model)
# Standard API key-based providers
p = config.get_provider()
if not (p and p.api_key) and not model.startswith("bedrock/"):
console.print("[red]Error: No API key configured.[/red]")
console.print("Set one in ~/.nanobot/config.json under providers section")

View File

@ -132,15 +132,19 @@ class Config(BaseSettings):
model_lower = (model or self.agents.defaults.model).lower()
# Match by keyword (order follows PROVIDERS registry)
# Note: OAuth providers don't require api_key, so we check is_oauth flag
for spec in PROVIDERS:
p = getattr(self.providers, spec.name, None)
if p and any(kw in model_lower for kw in spec.keywords) and p.api_key:
return p
if p and any(kw in model_lower for kw in spec.keywords):
# OAuth providers don't need api_key
if spec.is_oauth or p.api_key:
return p
# Fallback: gateways first, then others (follows registry order)
# OAuth providers are also valid fallbacks
for spec in PROVIDERS:
p = getattr(self.providers, spec.name, None)
if p and p.api_key:
if p and (spec.is_oauth or p.api_key):
return p
return None

View File

@ -51,6 +51,10 @@ class ProviderSpec:
# per-model param overrides, e.g. (("kimi-k2.5", {"temperature": 1.0}),)
model_overrides: tuple[tuple[str, dict[str, Any]], ...] = ()
# OAuth-based providers (e.g., OpenAI Codex) don't use API keys
is_oauth: bool = False # if True, uses OAuth flow instead of API key
oauth_provider: str = "" # OAuth provider name for token retrieval
@property
def label(self) -> str:
return self.display_name or self.name.title()
@ -157,6 +161,8 @@ PROVIDERS: tuple[ProviderSpec, ...] = (
default_api_base="https://chatgpt.com/backend-api",
strip_model_prefix=False,
model_overrides=(),
is_oauth=True, # OAuth-based authentication
oauth_provider="openai-codex", # OAuth provider identifier
),
# DeepSeek: needs "deepseek/" prefix for LiteLLM routing.