88 lines
3.4 KiB
Python
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
|