""" 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