mirror of
https://github.com/Alishahryar1/free-claude-code.git
synced 2026-07-03 14:05:26 +02:00
f3a7528d49
Consolidates the incremental refactor work into a single change set: modular web tools (api/web_tools), native Anthropic request building and SSE block policy, OpenAI conversion and error handling, provider transports and rate limiting, messaging handler and tree queue, safe logging, smoke tests, and broad test coverage.
122 lines
3.9 KiB
Python
122 lines
3.9 KiB
Python
"""Base provider interface - extend this to implement your own provider."""
|
|
|
|
from abc import ABC, abstractmethod
|
|
from collections.abc import AsyncIterator
|
|
from typing import Any
|
|
|
|
from pydantic import BaseModel
|
|
|
|
from config.constants import HTTP_CONNECT_TIMEOUT_DEFAULT
|
|
|
|
|
|
class ProviderConfig(BaseModel):
|
|
"""Configuration for a provider.
|
|
|
|
Base fields apply to all providers. Provider-specific parameters
|
|
(e.g. NIM temperature, top_p) are passed by the provider constructor.
|
|
"""
|
|
|
|
api_key: str
|
|
base_url: str | None = None
|
|
rate_limit: int | None = None
|
|
rate_window: int = 60
|
|
max_concurrency: int = 5
|
|
http_read_timeout: float = 300.0
|
|
http_write_timeout: float = 10.0
|
|
http_connect_timeout: float = HTTP_CONNECT_TIMEOUT_DEFAULT
|
|
enable_thinking: bool = True
|
|
proxy: str = ""
|
|
log_raw_sse_events: bool = False
|
|
log_api_error_tracebacks: bool = False
|
|
|
|
|
|
class BaseProvider(ABC):
|
|
"""Base class for all providers. Extend this to add your own."""
|
|
|
|
def __init__(self, config: ProviderConfig):
|
|
self._config = config
|
|
|
|
def _is_thinking_enabled(
|
|
self, request: Any, thinking_enabled: bool | None = None
|
|
) -> bool:
|
|
"""Return whether thinking should be enabled for this request."""
|
|
thinking = getattr(request, "thinking", None)
|
|
config_enabled = (
|
|
self._config.enable_thinking
|
|
if thinking_enabled is None
|
|
else thinking_enabled
|
|
)
|
|
request_enabled = True
|
|
if thinking is not None:
|
|
thinking_type = (
|
|
thinking.get("type")
|
|
if isinstance(thinking, dict)
|
|
else getattr(thinking, "type", None)
|
|
)
|
|
if thinking_type == "disabled":
|
|
request_enabled = False
|
|
|
|
enabled = (
|
|
thinking.get("enabled")
|
|
if isinstance(thinking, dict)
|
|
else getattr(thinking, "enabled", None)
|
|
)
|
|
if enabled is not None:
|
|
request_enabled = bool(enabled)
|
|
return config_enabled and request_enabled
|
|
|
|
def preflight_stream(
|
|
self, request: Any, *, thinking_enabled: bool | None = None
|
|
) -> None:
|
|
"""Eagerly validate/build the upstream request before opening an SSE stream.
|
|
|
|
Subclasses with ``_build_request_body`` (OpenAI and native) raise
|
|
:class:`providers.exceptions.InvalidRequestError` on conversion failures.
|
|
"""
|
|
build = getattr(self, "_build_request_body", None)
|
|
if build is None:
|
|
return
|
|
build(request, thinking_enabled=thinking_enabled)
|
|
|
|
def _log_stream_transport_error(
|
|
self, tag: str, req_tag: str, error: Exception
|
|
) -> None:
|
|
"""Log streaming transport failures (metadata-only unless verbose is enabled)."""
|
|
from loguru import logger
|
|
|
|
if self._config.log_api_error_tracebacks:
|
|
logger.error(
|
|
"{}_ERROR:{} {}: {}", tag, req_tag, type(error).__name__, error
|
|
)
|
|
return
|
|
response = getattr(error, "response", None)
|
|
status_code = (
|
|
getattr(response, "status_code", None) if response is not None else None
|
|
)
|
|
logger.error(
|
|
"{}_ERROR:{} exc_type={} http_status={}",
|
|
tag,
|
|
req_tag,
|
|
type(error).__name__,
|
|
status_code,
|
|
)
|
|
|
|
@abstractmethod
|
|
async def cleanup(self) -> None:
|
|
"""Release any resources held by this provider."""
|
|
|
|
@abstractmethod
|
|
async def stream_response(
|
|
self,
|
|
request: Any,
|
|
input_tokens: int = 0,
|
|
*,
|
|
request_id: str | None = None,
|
|
thinking_enabled: bool | None = None,
|
|
) -> AsyncIterator[str]:
|
|
"""Stream response in Anthropic SSE format."""
|
|
# Typing: abstract async generators need a yield for AsyncIterator[str]
|
|
# inference; this branch is never executed.
|
|
if False:
|
|
yield ""
|