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:
parent
c1dc8d3f55
commit
08efe6ad3f
@ -173,20 +173,33 @@ This file stores important information that should persist across sessions.
|
|||||||
|
|
||||||
|
|
||||||
def _make_provider(config):
|
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.litellm_provider import LiteLLMProvider
|
||||||
from nanobot.providers.openai_codex_provider import OpenAICodexProvider
|
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
|
model = config.agents.defaults.model
|
||||||
if model.startswith("openai-codex/"):
|
model_lower = model.lower()
|
||||||
try:
|
|
||||||
_ = get_codex_token()
|
# Check for OAuth-based providers first (registry-driven)
|
||||||
except Exception:
|
for spec in PROVIDERS:
|
||||||
console.print("Please run: [cyan]nanobot login --provider openai-codex[/cyan]")
|
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)
|
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/"):
|
if not (p and p.api_key) and not model.startswith("bedrock/"):
|
||||||
console.print("[red]Error: No API key configured.[/red]")
|
console.print("[red]Error: No API key configured.[/red]")
|
||||||
console.print("Set one in ~/.nanobot/config.json under providers section")
|
console.print("Set one in ~/.nanobot/config.json under providers section")
|
||||||
|
|||||||
@ -132,15 +132,19 @@ class Config(BaseSettings):
|
|||||||
model_lower = (model or self.agents.defaults.model).lower()
|
model_lower = (model or self.agents.defaults.model).lower()
|
||||||
|
|
||||||
# Match by keyword (order follows PROVIDERS registry)
|
# 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:
|
for spec in PROVIDERS:
|
||||||
p = getattr(self.providers, spec.name, None)
|
p = getattr(self.providers, spec.name, None)
|
||||||
if p and any(kw in model_lower for kw in spec.keywords) and p.api_key:
|
if p and any(kw in model_lower for kw in spec.keywords):
|
||||||
return p
|
# 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)
|
# Fallback: gateways first, then others (follows registry order)
|
||||||
|
# OAuth providers are also valid fallbacks
|
||||||
for spec in PROVIDERS:
|
for spec in PROVIDERS:
|
||||||
p = getattr(self.providers, spec.name, None)
|
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 p
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|||||||
@ -51,6 +51,10 @@ class ProviderSpec:
|
|||||||
# per-model param overrides, e.g. (("kimi-k2.5", {"temperature": 1.0}),)
|
# per-model param overrides, e.g. (("kimi-k2.5", {"temperature": 1.0}),)
|
||||||
model_overrides: tuple[tuple[str, dict[str, Any]], ...] = ()
|
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
|
@property
|
||||||
def label(self) -> str:
|
def label(self) -> str:
|
||||||
return self.display_name or self.name.title()
|
return self.display_name or self.name.title()
|
||||||
@ -157,6 +161,8 @@ PROVIDERS: tuple[ProviderSpec, ...] = (
|
|||||||
default_api_base="https://chatgpt.com/backend-api",
|
default_api_base="https://chatgpt.com/backend-api",
|
||||||
strip_model_prefix=False,
|
strip_model_prefix=False,
|
||||||
model_overrides=(),
|
model_overrides=(),
|
||||||
|
is_oauth=True, # OAuth-based authentication
|
||||||
|
oauth_provider="openai-codex", # OAuth provider identifier
|
||||||
),
|
),
|
||||||
|
|
||||||
# DeepSeek: needs "deepseek/" prefix for LiteLLM routing.
|
# DeepSeek: needs "deepseek/" prefix for LiteLLM routing.
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user