Files
oai-web/server/providers/registry.py
2026-04-08 12:43:24 +02:00

88 lines
3.4 KiB
Python

"""
providers/registry.py — Provider factory.
Keys are resolved from:
1. Per-user setting (user_settings table) — if user_id is provided
2. Global credential_store (system:anthropic_api_key / system:openrouter_api_key / system:openai_api_key)
API keys are never read from .env — configure them via Settings → Credentials.
"""
from __future__ import annotations
from .base import AIProvider
async def _resolve_key(provider: str, user_id: str | None = None) -> str:
"""Resolve the API key for a provider: user setting → global credential store."""
from ..database import credential_store, user_settings_store
if user_id:
user_key = await user_settings_store.get(user_id, f"{provider}_api_key")
if user_key:
return user_key
return await credential_store.get(f"system:{provider}_api_key") or ""
async def get_provider(user_id: str | None = None) -> AIProvider:
"""Return the default provider, with keys resolved for the given user."""
from ..config import settings
return await get_provider_for_name(settings.default_provider, user_id=user_id)
async def get_provider_for_name(name: str, user_id: str | None = None) -> AIProvider:
"""Return a provider instance configured with the resolved key."""
key = await _resolve_key(name, user_id=user_id)
if not key:
raise RuntimeError(
f"No API key configured for provider '{name}'. "
"Set it in Settings → General or via environment variable."
)
if name == "anthropic":
from .anthropic_provider import AnthropicProvider
return AnthropicProvider(api_key=key)
elif name == "openrouter":
from .openrouter_provider import OpenRouterProvider
return OpenRouterProvider(api_key=key, app_name="oAI-Web")
elif name == "openai":
from .openai_provider import OpenAIProvider
return OpenAIProvider(api_key=key)
else:
raise RuntimeError(
f"Unknown provider '{name}'. Valid values: 'anthropic', 'openrouter', 'openai'"
)
async def get_provider_for_model(model_str: str, user_id: str | None = None) -> tuple[AIProvider, str]:
"""
Parse a "provider:model" string and return (provider_instance, bare_model_id).
If the model string has no provider prefix, the default provider is used.
Examples:
"anthropic:claude-sonnet-4-6" → (AnthropicProvider, "claude-sonnet-4-6")
"openrouter:openai/gpt-4o" → (OpenRouterProvider, "openai/gpt-4o")
"claude-sonnet-4-6" → (default_provider, "claude-sonnet-4-6")
"""
from ..config import settings
_known = {"anthropic", "openrouter", "openai"}
if ":" in model_str:
prefix, bare = model_str.split(":", 1)
if prefix in _known:
return await get_provider_for_name(prefix, user_id=user_id), bare
# No recognised prefix — use default provider, full string as model ID
return await get_provider_for_name(settings.default_provider, user_id=user_id), model_str
async def get_available_providers(user_id: str | None = None) -> list[str]:
"""Return names of providers that have a valid API key for the given user."""
available = []
if await _resolve_key("anthropic", user_id=user_id):
available.append("anthropic")
if await _resolve_key("openrouter", user_id=user_id):
available.append("openrouter")
if await _resolve_key("openai", user_id=user_id):
available.append("openai")
return available