Initial commit
This commit is contained in:
87
server/providers/registry.py
Normal file
87
server/providers/registry.py
Normal file
@@ -0,0 +1,87 @@
|
||||
"""
|
||||
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
|
||||
Reference in New Issue
Block a user