mirror of
https://github.com/Alishahryar1/free-claude-code.git
synced 2026-04-26 10:31:07 +00:00
- Add local web_search/web_fetch SSE handling and optional tool schemas - Extend HeuristicToolParser for JSON-style WebFetch/WebSearch text - Consolidate provider defaults, ids, and exception typing; stream contracts - Messaging: typed options, voice config injection, platform contract cleanup - Tests for web server tools, converters, parsers, contracts; ignore debug-*.log
131 lines
4.7 KiB
Python
131 lines
4.7 KiB
Python
"""Dependency injection for FastAPI."""
|
|
|
|
from fastapi import Depends, HTTPException, Request
|
|
from loguru import logger
|
|
from starlette.applications import Starlette
|
|
|
|
from config.settings import Settings
|
|
from config.settings import get_settings as _get_settings
|
|
from core.anthropic import get_user_facing_error_message
|
|
from providers.base import BaseProvider
|
|
from providers.exceptions import AuthenticationError, UnknownProviderTypeError
|
|
from providers.registry import PROVIDER_DESCRIPTORS, ProviderRegistry
|
|
|
|
# Process-level cache: only for :func:`get_provider_for_type` / :func:`get_provider`
|
|
# when there is no ``Request``/``app`` (unit tests, scripts). HTTP handlers must pass
|
|
# ``app`` to :func:`resolve_provider` so the app-scoped registry is used.
|
|
_providers: dict[str, BaseProvider] = {}
|
|
|
|
|
|
def get_settings() -> Settings:
|
|
"""Get application settings via dependency injection."""
|
|
return _get_settings()
|
|
|
|
|
|
def resolve_provider(
|
|
provider_type: str,
|
|
*,
|
|
app: Starlette | None,
|
|
settings: Settings,
|
|
) -> BaseProvider:
|
|
"""Resolve a provider using the app-scoped registry when ``app`` is set.
|
|
|
|
When ``app`` is not ``None``, the app-owned :attr:`app.state.provider_registry`
|
|
is always used. If the registry is missing (e.g. a test app without
|
|
:class:`~api.runtime.AppRuntime` startup), a new :class:`ProviderRegistry`
|
|
is installed on ``app.state`` so the process cache is never mixed with
|
|
per-request app identity.
|
|
|
|
When ``app`` is ``None`` (no HTTP context), uses the process-level
|
|
:data:`_providers` cache only.
|
|
"""
|
|
if app is not None:
|
|
reg = getattr(app.state, "provider_registry", None)
|
|
if reg is None:
|
|
reg = ProviderRegistry()
|
|
app.state.provider_registry = reg
|
|
return _resolve_with_registry(reg, provider_type, settings)
|
|
return _resolve_with_registry(ProviderRegistry(_providers), provider_type, settings)
|
|
|
|
|
|
def _resolve_with_registry(
|
|
registry: ProviderRegistry, provider_type: str, settings: Settings
|
|
) -> BaseProvider:
|
|
should_log_init = not registry.is_cached(provider_type)
|
|
try:
|
|
provider = registry.get(provider_type, settings)
|
|
except AuthenticationError as e:
|
|
raise HTTPException(
|
|
status_code=503, detail=get_user_facing_error_message(e)
|
|
) from e
|
|
except UnknownProviderTypeError:
|
|
logger.error(
|
|
"Unknown provider_type: '{}'. Supported: {}",
|
|
provider_type,
|
|
", ".join(f"'{key}'" for key in PROVIDER_DESCRIPTORS),
|
|
)
|
|
raise
|
|
if should_log_init:
|
|
logger.info("Provider initialized: {}", provider_type)
|
|
return provider
|
|
|
|
|
|
def get_provider_for_type(provider_type: str) -> BaseProvider:
|
|
"""Get or create a provider in the process-level cache (no ``app``/Request).
|
|
|
|
For server requests, use :func:`resolve_provider` with the active
|
|
:attr:`request.app` so the app-scoped provider registry is used.
|
|
"""
|
|
return resolve_provider(provider_type, app=None, settings=get_settings())
|
|
|
|
|
|
def require_api_key(
|
|
request: Request, settings: Settings = Depends(get_settings)
|
|
) -> None:
|
|
"""Require a server API key (Anthropic-style).
|
|
|
|
Checks `x-api-key` header or `Authorization: Bearer ...` against
|
|
`Settings.anthropic_auth_token`. If `ANTHROPIC_AUTH_TOKEN` is empty, this is a no-op.
|
|
"""
|
|
anthropic_auth_token = settings.anthropic_auth_token
|
|
if not anthropic_auth_token:
|
|
# No API key configured -> allow
|
|
return
|
|
|
|
header = (
|
|
request.headers.get("x-api-key")
|
|
or request.headers.get("authorization")
|
|
or request.headers.get("anthropic-auth-token")
|
|
)
|
|
if not header:
|
|
raise HTTPException(status_code=401, detail="Missing API key")
|
|
|
|
# Support both raw key in X-API-Key and Bearer token in Authorization
|
|
token = header
|
|
if header.lower().startswith("bearer "):
|
|
token = header.split(" ", 1)[1]
|
|
|
|
# Strip anything after the first colon to handle tokens with appended model names
|
|
if token and ":" in token:
|
|
token = token.split(":", 1)[0]
|
|
|
|
if token != anthropic_auth_token:
|
|
raise HTTPException(status_code=401, detail="Invalid API key")
|
|
|
|
|
|
def get_provider() -> BaseProvider:
|
|
"""Get or create the default provider (``MODEL`` / ``provider_type``).
|
|
|
|
Process-cache helper for scripts, unit tests, and non-FastAPI callers. HTTP
|
|
handlers must use :func:`resolve_provider` with :attr:`request.app` so the
|
|
app-scoped :class:`~providers.registry.ProviderRegistry` is used.
|
|
"""
|
|
return get_provider_for_type(get_settings().provider_type)
|
|
|
|
|
|
async def cleanup_provider():
|
|
"""Cleanup all provider resources."""
|
|
global _providers
|
|
await ProviderRegistry(_providers).cleanup()
|
|
_providers = {}
|
|
logger.debug("Provider cleanup completed")
|