2026-05-20 22:03:57 -05:00
|
|
|
"""Provider credential records — API keys and endpoints for AI providers.
|
|
|
|
|
|
|
|
|
|
Each row stores one named account for a single provider (Anthropic, OpenAI,
|
|
|
|
|
Ollama, etc.). An organization can have multiple rows per provider, enabling
|
|
|
|
|
separate cost tracking for e.g. "work" and "personal" OpenAI accounts.
|
|
|
|
|
|
|
|
|
|
Token storage follows the same pattern as ForgejoConnection and Gateway:
|
|
|
|
|
plaintext in the trusted DB, last-four chars available for verification without
|
|
|
|
|
exposing the full key.
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
from __future__ import annotations
|
|
|
|
|
|
|
|
|
|
from datetime import datetime
|
|
|
|
|
from uuid import UUID, uuid4
|
|
|
|
|
|
|
|
|
|
from sqlmodel import Field
|
|
|
|
|
|
|
|
|
|
from app.core.time import utcnow
|
|
|
|
|
from app.models.base import QueryModel
|
|
|
|
|
|
|
|
|
|
RUNTIME_ANNOTATION_TYPES = (datetime,)
|
|
|
|
|
|
|
|
|
|
SUPPORTED_PROVIDERS = frozenset({"anthropic", "openai", "ollama", "google", "other"})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class ProviderCredential(QueryModel, table=True):
|
|
|
|
|
"""One named AI-provider account for an organisation."""
|
|
|
|
|
|
|
|
|
|
__tablename__ = "provider_credentials" # pyright: ignore[reportAssignmentType]
|
|
|
|
|
|
|
|
|
|
id: UUID = Field(default_factory=uuid4, primary_key=True)
|
|
|
|
|
organization_id: UUID = Field(foreign_key="organizations.id", index=True)
|
|
|
|
|
|
|
|
|
|
# Provider identity
|
|
|
|
|
provider: str = Field(index=True) # "anthropic", "openai", "ollama", …
|
|
|
|
|
account_key: str = Field(index=True) # user label: "work", "personal", "local"
|
|
|
|
|
display_name: str = Field(default="") # human-readable name shown in the UI
|
|
|
|
|
|
|
|
|
|
# Credentials (not all providers need both)
|
|
|
|
|
api_key: str | None = Field(default=None) # full key — never returned in API
|
|
|
|
|
api_key_last_four: str | None = Field(default=None) # shown in UI for verification
|
|
|
|
|
base_url: str | None = Field(default=None) # Ollama, Azure, custom endpoints
|
|
|
|
|
|
2026-05-21 03:29:35 -05:00
|
|
|
# Session / OAuth token for subscription-usage endpoints.
|
|
|
|
|
# Anthropic: claude.ai sessionKey cookie (sk-ant-sid-...)
|
|
|
|
|
# OpenAI Codex: ChatGPT bearer token from browser session
|
|
|
|
|
# This is a different credential from the API key — it authenticates against
|
|
|
|
|
# the provider's subscription system rather than the generation API.
|
|
|
|
|
session_key: str | None = Field(default=None) # full token — never returned in API
|
|
|
|
|
session_key_last_four: str | None = Field(default=None)
|
|
|
|
|
|
2026-05-20 22:03:57 -05:00
|
|
|
active: bool = Field(default=True, index=True)
|
|
|
|
|
|
|
|
|
|
created_at: datetime = Field(default_factory=utcnow)
|
|
|
|
|
updated_at: datetime = Field(default_factory=utcnow)
|