mirror of
https://github.com/Alishahryar1/free-claude-code.git
synced 2026-04-28 03:20:01 +00:00
Some checks are pending
CI / checks (push) Waiting to run
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.
119 lines
3.8 KiB
Python
119 lines
3.8 KiB
Python
"""FastAPI application factory and configuration."""
|
|
|
|
import traceback
|
|
from contextlib import asynccontextmanager
|
|
from typing import Any
|
|
|
|
from fastapi import FastAPI, Request
|
|
from fastapi.exception_handlers import request_validation_exception_handler
|
|
from fastapi.exceptions import RequestValidationError
|
|
from fastapi.responses import JSONResponse
|
|
from loguru import logger
|
|
|
|
from config.logging_config import configure_logging
|
|
from config.settings import get_settings
|
|
from providers.exceptions import ProviderError
|
|
|
|
from .routes import router
|
|
from .runtime import AppRuntime
|
|
from .validation_log import summarize_request_validation_body
|
|
|
|
|
|
@asynccontextmanager
|
|
async def lifespan(app: FastAPI):
|
|
"""Application lifespan manager."""
|
|
runtime = AppRuntime.for_app(app, settings=get_settings())
|
|
await runtime.startup()
|
|
|
|
yield
|
|
|
|
await runtime.shutdown()
|
|
|
|
|
|
def create_app() -> FastAPI:
|
|
"""Create and configure the FastAPI application."""
|
|
settings = get_settings()
|
|
configure_logging(
|
|
settings.log_file, verbose_third_party=settings.log_raw_api_payloads
|
|
)
|
|
|
|
app = FastAPI(
|
|
title="Claude Code Proxy",
|
|
version="2.0.0",
|
|
lifespan=lifespan,
|
|
)
|
|
|
|
# Register routes
|
|
app.include_router(router)
|
|
|
|
# Exception handlers
|
|
@app.exception_handler(RequestValidationError)
|
|
async def validation_error_handler(request: Request, exc: RequestValidationError):
|
|
"""Log request shape for 422 debugging without content values."""
|
|
body: Any
|
|
try:
|
|
body = await request.json()
|
|
except Exception as e:
|
|
body = {"_json_error": type(e).__name__}
|
|
|
|
message_summary, tool_names = summarize_request_validation_body(body)
|
|
|
|
logger.debug(
|
|
"Request validation failed: path={} query={} error_locs={} error_types={} message_summary={} tool_names={}",
|
|
request.url.path,
|
|
str(request.url.query),
|
|
[list(error.get("loc", ())) for error in exc.errors()],
|
|
[str(error.get("type", "")) for error in exc.errors()],
|
|
message_summary,
|
|
tool_names,
|
|
)
|
|
return await request_validation_exception_handler(request, exc)
|
|
|
|
@app.exception_handler(ProviderError)
|
|
async def provider_error_handler(request: Request, exc: ProviderError):
|
|
"""Handle provider-specific errors and return Anthropic format."""
|
|
err_settings = get_settings()
|
|
if err_settings.log_api_error_tracebacks:
|
|
logger.error(
|
|
"Provider Error: error_type={} status_code={} message={}",
|
|
exc.error_type,
|
|
exc.status_code,
|
|
exc.message,
|
|
)
|
|
else:
|
|
logger.error(
|
|
"Provider Error: error_type={} status_code={}",
|
|
exc.error_type,
|
|
exc.status_code,
|
|
)
|
|
return JSONResponse(
|
|
status_code=exc.status_code,
|
|
content=exc.to_anthropic_format(),
|
|
)
|
|
|
|
@app.exception_handler(Exception)
|
|
async def general_error_handler(request: Request, exc: Exception):
|
|
"""Handle general errors and return Anthropic format."""
|
|
settings = get_settings()
|
|
if settings.log_api_error_tracebacks:
|
|
logger.error("General Error: {}", exc)
|
|
logger.error(traceback.format_exc())
|
|
else:
|
|
logger.error(
|
|
"General Error: path={} method={} exc_type={}",
|
|
request.url.path,
|
|
request.method,
|
|
type(exc).__name__,
|
|
)
|
|
return JSONResponse(
|
|
status_code=500,
|
|
content={
|
|
"type": "error",
|
|
"error": {
|
|
"type": "api_error",
|
|
"message": "An unexpected error occurred.",
|
|
},
|
|
},
|
|
)
|
|
|
|
return app
|