Create and configure the FastAPI application.
(*, lifespan_enabled: bool = True)
| 85 | |
| 86 | |
| 87 | def create_app(*, lifespan_enabled: bool = True) -> FastAPI: |
| 88 | """Create and configure the FastAPI application.""" |
| 89 | settings = get_settings() |
| 90 | log_path = Path(os.getenv("LOG_FILE", server_log_path())) |
| 91 | configure_logging(log_path, verbose_third_party=settings.log_raw_api_payloads) |
| 92 | |
| 93 | app_kwargs: dict[str, Any] = { |
| 94 | "title": "Claude Code Proxy", |
| 95 | "version": "2.1.0", |
| 96 | } |
| 97 | if lifespan_enabled: |
| 98 | app_kwargs["lifespan"] = lifespan |
| 99 | app = FastAPI(**app_kwargs) |
| 100 | |
| 101 | @app.middleware("http") |
| 102 | async def trace_http_correlation(request: Request, call_next): |
| 103 | """Attach HTTP identifiers and optional Claude session id to logs.""" |
| 104 | claude_sid = extract_claude_session_id_from_headers(request.headers) |
| 105 | with logger.contextualize( |
| 106 | http_method=request.method, |
| 107 | http_path=request.url.path, |
| 108 | claude_session_id=claude_sid, |
| 109 | ): |
| 110 | response = await call_next(request) |
| 111 | return response |
| 112 | |
| 113 | # Register routes |
| 114 | app.include_router(admin_router) |
| 115 | app.include_router(router) |
| 116 | |
| 117 | # Exception handlers |
| 118 | @app.exception_handler(RequestValidationError) |
| 119 | async def validation_error_handler(request: Request, exc: RequestValidationError): |
| 120 | """Log request shape for 422 debugging without content values.""" |
| 121 | body: Any |
| 122 | try: |
| 123 | body = await request.json() |
| 124 | except Exception as e: |
| 125 | body = {"_json_error": type(e).__name__} |
| 126 | |
| 127 | message_summary, tool_names = summarize_request_validation_body(body) |
| 128 | |
| 129 | trace_event( |
| 130 | stage="ingress", |
| 131 | event="server.request.validation_failed", |
| 132 | source="api", |
| 133 | path=request.url.path, |
| 134 | query=dict(request.query_params), |
| 135 | error_locs=[list(error.get("loc", ())) for error in exc.errors()], |
| 136 | error_types=[str(error.get("type", "")) for error in exc.errors()], |
| 137 | message_summary=message_summary, |
| 138 | tool_names=tool_names, |
| 139 | ) |
| 140 | return await request_validation_exception_handler(request, exc) |
| 141 | |
| 142 | @app.exception_handler(ProviderError) |
| 143 | async def provider_error_handler(request: Request, exc: ProviderError): |
| 144 | """Handle provider-specific errors and return Anthropic format.""" |