mirror of
https://github.com/Routstr/routstr-core.git
synced 2026-07-30 15:26:14 +00:00
A live upstream provider re-finds its own database row in two places — PPQ.AI's insufficient-balance self-disable and the base refresh_models_cache — with WHERE base_url == self.base_url AND api_key == self.api_key. That uses a rotatable secret as a self-handle: if the row's key rotates under a live object, it can no longer find itself. Carry the row's primary key on the instance as db_id, stamped centrally by from_db_row via a _build_from_row construction hook that subclasses override, and look the row up with session.get(UpstreamProviderRow, db_id). This also closes a latent gap where providers built outside the init path (auto-topup) never received db_id. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
73 lines
2.8 KiB
Python
73 lines
2.8 KiB
Python
from typing import TYPE_CHECKING
|
|
|
|
from ..payment.models import Model, async_fetch_openrouter_models
|
|
from .base import BaseUpstreamProvider
|
|
|
|
if TYPE_CHECKING:
|
|
from ..core.db import UpstreamProviderRow
|
|
|
|
|
|
class AnthropicUpstreamProvider(BaseUpstreamProvider):
|
|
"""Upstream provider specifically configured for Anthropic API."""
|
|
|
|
provider_type = "anthropic"
|
|
default_base_url = "https://api.anthropic.com/v1"
|
|
platform_url = "https://console.anthropic.com/settings/keys"
|
|
supports_anthropic_messages = True
|
|
litellm_provider_prefix = "anthropic/"
|
|
|
|
def __init__(self, api_key: str, provider_fee: float = 1.01):
|
|
super().__init__(
|
|
base_url=self.default_base_url,
|
|
api_key=api_key,
|
|
provider_fee=provider_fee,
|
|
)
|
|
|
|
@classmethod
|
|
def _build_from_row(
|
|
cls, provider_row: "UpstreamProviderRow"
|
|
) -> "AnthropicUpstreamProvider":
|
|
return cls(
|
|
api_key=provider_row.api_key,
|
|
provider_fee=provider_row.provider_fee,
|
|
)
|
|
|
|
@classmethod
|
|
def get_provider_metadata(cls) -> dict[str, object]:
|
|
return {
|
|
"id": cls.provider_type,
|
|
"name": "Anthropic",
|
|
"default_base_url": cls.default_base_url,
|
|
"fixed_base_url": True,
|
|
"platform_url": cls.platform_url,
|
|
}
|
|
|
|
def transform_model_name(self, model_id: str) -> str:
|
|
"""Strip 'anthropic/' prefix for Anthropic API compatibility and transform model names."""
|
|
if model_id.startswith("anthropic/"):
|
|
model_id = model_id[len("anthropic/") :]
|
|
fixed_transforms = {
|
|
"claude-haiku-4.5": "claude-haiku-4-5-20251001",
|
|
"claude-sonnet-4.5": "claude-sonnet-4-5-20250929",
|
|
"claude-opus-4.1": "claude-opus-4-1-20250805",
|
|
"claude-opus-4": "claude-opus-4-20250514",
|
|
"claude-sonnet-4": "claude-sonnet-4-20250514",
|
|
"claude-3.5-haiku": "claude-3-5-haiku-20241022",
|
|
"claude-3-haiku": "claude-3-haiku-20240307",
|
|
"claude-haiku-4-5": "claude-haiku-4-5-20251001",
|
|
"claude-sonnet-4-5": "claude-sonnet-4-5-20250929",
|
|
"claude-opus-4-1": "claude-opus-4-1-20250805",
|
|
"claude-3-5-haiku": "claude-3-5-haiku-20241022",
|
|
}
|
|
if model_id in fixed_transforms:
|
|
model_id = fixed_transforms[model_id]
|
|
return model_id
|
|
|
|
async def fetch_models(self) -> list[Model]:
|
|
"""Fetch Anthropic models from OpenRouter API filtered by anthropic source."""
|
|
models_data = await async_fetch_openrouter_models(source_filter="anthropic")
|
|
models = [Model(**model) for model in models_data] # type: ignore
|
|
for model in models:
|
|
model.alias_ids = [self.transform_model_name(model.id)]
|
|
return models
|