mirror of
https://github.com/furyhawk/ai_agent.git
synced 2026-07-21 10:15:44 +00:00
- Implemented `conversation-store` for managing conversations and messages. - Created `file-preview-store` to handle file preview state. - Added `sidebar-store` for sidebar visibility management. - Developed `theme-store` for theme persistence and management. - Introduced `kb-selection-store` for managing active knowledge base selections with persistence. chore: define API and chat types - Added types for API responses, authentication, chat messages, conversations, and projects. - Defined interfaces for various entities including users, sessions, and message ratings. build: configure TypeScript and testing setup - Set up `tsconfig.json` for TypeScript configuration. - Created `vitest.config.ts` for testing configuration with Vitest. - Added `vitest.setup.ts` for global test setup including mocks for Next.js router and media queries. - Configured Vercel deployment settings in `vercel.json`.
109 lines
3.2 KiB
Python
109 lines
3.2 KiB
Python
"""Exception handlers for FastAPI application.
|
|
|
|
These handlers convert domain exceptions to proper HTTP responses.
|
|
WebSocket connections that raise an ``AppException`` before ``accept()`` are
|
|
handled too — Starlette closes the socket with 403 and we just log the
|
|
incident; we cannot return an HTTP body for a non-HTTP scope.
|
|
"""
|
|
|
|
import logging
|
|
from typing import Any
|
|
|
|
from fastapi import FastAPI
|
|
from fastapi.responses import JSONResponse
|
|
from starlette.requests import HTTPConnection
|
|
|
|
from app.core.exceptions import AppException
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
def _connection_meta(conn: HTTPConnection) -> dict[str, Any]:
|
|
"""Common log fields shared by HTTP requests and WebSocket connections.
|
|
|
|
``method`` exists only on HTTP ``Request`` — for WebSockets we surface the
|
|
scope type so log filters can still distinguish the two.
|
|
"""
|
|
return {
|
|
"path": conn.url.path,
|
|
"method": getattr(conn, "method", None) or conn.scope.get("type", "unknown"),
|
|
}
|
|
|
|
|
|
def _is_websocket(conn: HTTPConnection) -> bool:
|
|
return conn.scope.get("type") == "websocket"
|
|
|
|
|
|
async def app_exception_handler(request: HTTPConnection, exc: AppException) -> JSONResponse | None:
|
|
"""Handle application exceptions for both HTTP and WebSocket scopes.
|
|
|
|
Logs 5xx errors as errors and 4xx as warnings. Returns a JSON response
|
|
for HTTP scopes; returns ``None`` for WebSocket scopes (Starlette will
|
|
close the socket on its own).
|
|
"""
|
|
log_extra = {
|
|
"error_code": exc.code,
|
|
"status_code": exc.status_code,
|
|
"details": exc.details,
|
|
**_connection_meta(request),
|
|
}
|
|
|
|
if exc.status_code >= 500:
|
|
logger.error(f"{exc.code}: {exc.message}", extra=log_extra)
|
|
else:
|
|
logger.warning(f"{exc.code}: {exc.message}", extra=log_extra)
|
|
|
|
if _is_websocket(request):
|
|
return None
|
|
|
|
headers: dict[str, str] = {}
|
|
if exc.status_code == 401:
|
|
headers["WWW-Authenticate"] = "Bearer"
|
|
|
|
return JSONResponse(
|
|
status_code=exc.status_code,
|
|
content={
|
|
"error": {
|
|
"code": exc.code,
|
|
"message": exc.message,
|
|
"details": exc.details or None,
|
|
}
|
|
},
|
|
headers=headers,
|
|
)
|
|
|
|
|
|
async def unhandled_exception_handler(
|
|
request: HTTPConnection, exc: Exception
|
|
) -> JSONResponse | None:
|
|
"""Handle unexpected exceptions.
|
|
|
|
Logs the full exception but returns a generic error to the client
|
|
to avoid leaking sensitive information.
|
|
"""
|
|
logger.exception("Unhandled exception", extra=_connection_meta(request))
|
|
|
|
if _is_websocket(request):
|
|
return None
|
|
|
|
return JSONResponse(
|
|
status_code=500,
|
|
content={
|
|
"error": {
|
|
"code": "INTERNAL_ERROR",
|
|
"message": "An unexpected error occurred",
|
|
"details": None,
|
|
}
|
|
},
|
|
)
|
|
|
|
|
|
def register_exception_handlers(app: FastAPI) -> None:
|
|
"""Register all exception handlers on the FastAPI app.
|
|
|
|
Call this after creating the FastAPI application instance.
|
|
"""
|
|
app.add_exception_handler(AppException, app_exception_handler)
|
|
# Uncomment to catch all unhandled exceptions:
|
|
# app.add_exception_handler(Exception, unhandled_exception_handler)
|