Files
FusionAGI/fusionagi/api/error_codes.py

155 lines
5.2 KiB
Python
Raw Permalink Normal View History

"""Structured error codes for machine-readable error taxonomy.
Every API error includes a unique code, human-readable message,
and optional details for programmatic handling.
"""
from __future__ import annotations
from enum import Enum
from typing import Any
class ErrorCode(str, Enum):
"""Machine-readable error codes for the FusionAGI API."""
# Auth errors (1xxx)
AUTH_MISSING = "FAGI-1001"
AUTH_INVALID = "FAGI-1002"
AUTH_EXPIRED = "FAGI-1003"
AUTH_INSUFFICIENT = "FAGI-1004"
# Rate limiting (2xxx)
RATE_LIMIT_IP = "FAGI-2001"
RATE_LIMIT_TENANT = "FAGI-2002"
# Session errors (3xxx)
SESSION_NOT_FOUND = "FAGI-3001"
SESSION_EXPIRED = "FAGI-3002"
SESSION_LIMIT = "FAGI-3003"
# Prompt/input errors (4xxx)
PROMPT_EMPTY = "FAGI-4001"
PROMPT_TOO_LONG = "FAGI-4002"
INPUT_INVALID = "FAGI-4003"
FILE_TOO_LARGE = "FAGI-4004"
# Orchestration errors (5xxx)
ORCHESTRATOR_UNAVAILABLE = "FAGI-5001"
HEAD_TIMEOUT = "FAGI-5002"
WITNESS_FAILURE = "FAGI-5003"
CONSENSUS_FAILURE = "FAGI-5004"
# Adapter errors (6xxx)
LLM_UNAVAILABLE = "FAGI-6001"
LLM_TIMEOUT = "FAGI-6002"
LLM_RATE_LIMIT = "FAGI-6003"
LLM_CONTEXT_LENGTH = "FAGI-6004"
# Governance errors (7xxx)
GOVERNANCE_ADVISORY = "FAGI-7001"
SAFETY_FLAG = "FAGI-7002"
PII_DETECTED = "FAGI-7003"
# Infrastructure errors (8xxx)
DB_UNAVAILABLE = "FAGI-8001"
CACHE_UNAVAILABLE = "FAGI-8002"
STORAGE_FULL = "FAGI-8003"
# Tenant errors (9xxx)
TENANT_NOT_FOUND = "FAGI-9001"
TENANT_SUSPENDED = "FAGI-9002"
# General (0xxx)
INTERNAL_ERROR = "FAGI-0001"
NOT_IMPLEMENTED = "FAGI-0002"
VERSION_UNSUPPORTED = "FAGI-0003"
# Human-readable descriptions
_DESCRIPTIONS: dict[ErrorCode, str] = {
ErrorCode.AUTH_MISSING: "Authentication required. Provide a Bearer token.",
ErrorCode.AUTH_INVALID: "Invalid API key or token.",
ErrorCode.AUTH_EXPIRED: "API key has expired. Rotate via /v1/admin/keys/rotate.",
ErrorCode.AUTH_INSUFFICIENT: "Insufficient permissions for this operation.",
ErrorCode.RATE_LIMIT_IP: "IP-level rate limit exceeded.",
ErrorCode.RATE_LIMIT_TENANT: "Tenant-level rate limit exceeded.",
ErrorCode.SESSION_NOT_FOUND: "Session not found. Create one via POST /v1/sessions.",
ErrorCode.SESSION_EXPIRED: "Session has expired.",
ErrorCode.SESSION_LIMIT: "Maximum concurrent sessions reached.",
ErrorCode.PROMPT_EMPTY: "Prompt cannot be empty.",
ErrorCode.PROMPT_TOO_LONG: "Prompt exceeds maximum length.",
ErrorCode.INPUT_INVALID: "Request body validation failed.",
ErrorCode.FILE_TOO_LARGE: "Uploaded file exceeds size limit.",
ErrorCode.ORCHESTRATOR_UNAVAILABLE: "Orchestrator is not initialized.",
ErrorCode.HEAD_TIMEOUT: "One or more heads timed out during processing.",
ErrorCode.WITNESS_FAILURE: "Witness synthesis failed.",
ErrorCode.CONSENSUS_FAILURE: "Head consensus could not be reached.",
ErrorCode.LLM_UNAVAILABLE: "LLM provider is unavailable.",
ErrorCode.LLM_TIMEOUT: "LLM request timed out.",
ErrorCode.LLM_RATE_LIMIT: "LLM provider rate limit hit.",
ErrorCode.LLM_CONTEXT_LENGTH: "Input exceeds LLM context window.",
ErrorCode.GOVERNANCE_ADVISORY: "Governance advisory triggered.",
ErrorCode.SAFETY_FLAG: "Safety pipeline flagged the output.",
ErrorCode.PII_DETECTED: "Potential PII detected in output.",
ErrorCode.DB_UNAVAILABLE: "Database backend is unavailable.",
ErrorCode.CACHE_UNAVAILABLE: "Cache backend is unavailable.",
ErrorCode.STORAGE_FULL: "Storage capacity reached.",
ErrorCode.TENANT_NOT_FOUND: "Tenant not found.",
ErrorCode.TENANT_SUSPENDED: "Tenant account is suspended.",
ErrorCode.INTERNAL_ERROR: "An unexpected internal error occurred.",
ErrorCode.NOT_IMPLEMENTED: "This feature is not yet implemented.",
ErrorCode.VERSION_UNSUPPORTED: "Requested API version is not supported.",
}
def error_response(
code: ErrorCode,
detail: str | None = None,
extra: dict[str, Any] | None = None,
) -> dict[str, Any]:
"""Build a structured error response dict.
Args:
code: ErrorCode enum value.
detail: Optional human-readable detail (overrides default).
extra: Optional additional context.
Returns:
Structured error dict with code, message, and optional details.
"""
resp: dict[str, Any] = {
"error": {
"code": code.value,
"message": detail or _DESCRIPTIONS.get(code, "Unknown error"),
},
}
if extra:
resp["error"]["details"] = extra
return resp
def error_json_response(
code: ErrorCode,
status_code: int = 400,
detail: str | None = None,
extra: dict[str, Any] | None = None,
) -> Any:
"""Build a FastAPI JSONResponse with structured error.
Args:
code: ErrorCode enum value.
status_code: HTTP status code.
detail: Optional override message.
extra: Optional additional context.
Returns:
JSONResponse with structured error body.
"""
from starlette.responses import JSONResponse
return JSONResponse(
content=error_response(code, detail, extra),
status_code=status_code,
)