"""Shared HTTP primitives for the public OpenAI-compatible API.""" from __future__ import annotations import re from typing import Any, Literal from uuid import uuid4 from fastapi.responses import JSONResponse from fastapi.security import HTTPBearer from pydantic import BaseModel, ConfigDict from starlette.datastructures import Headers, MutableHeaders from starlette.types import ASGIApp, Message, Receive, Scope, Send PUBLIC_API_NAME = "OpenAI-compatible subset v1" REQUEST_ID_HEADER = "X-Request-ID" _VALID_REQUEST_ID = re.compile(r"^[A-Za-z0-9._:-]{1,128}$") PUBLIC_API_BEARER = HTTPBearer( auto_error=False, scheme_name="PublicAPIKey", description="Long-lived sk-rt-... API key or an endpoint-approved client secret.", ) REQUEST_ID_RESPONSE_HEADERS = { REQUEST_ID_HEADER: { "description": "Request identifier generated by the server or supplied by the caller.", "schema": {"type": "string"}, } } class OpenAIErrorDetail(BaseModel): """Error shape consumed by official OpenAI SDKs.""" model_config = ConfigDict(extra="forbid") type: str = "invalid_request_error" code: str message: str param: str | None = None class OpenAIErrorResponse(BaseModel): model_config = ConfigDict(extra="forbid") error: OpenAIErrorDetail class DeletedResource(BaseModel): model_config = ConfigDict(extra="forbid") id: str object: Literal["response"] = "response" deleted: Literal[True] = True PUBLIC_ERROR_RESPONSES: dict[int | str, dict[str, Any]] = { 400: { "model": OpenAIErrorResponse, "description": "The request is unsupported or semantically invalid.", }, 401: { "model": OpenAIErrorResponse, "description": "The API credential is missing, invalid, or expired.", }, 404: { "model": OpenAIErrorResponse, "description": "The resource does not exist or belongs to another API key.", }, 409: { "model": OpenAIErrorResponse, "description": "The requested state transition conflicts with current state.", }, 422: { "model": OpenAIErrorResponse, "description": "The JSON body or query parameters do not match the schema.", }, 429: { "model": OpenAIErrorResponse, "description": "A request or concurrency limit was exceeded.", }, 503: { "model": OpenAIErrorResponse, "description": "The service is temporarily unavailable.", }, 500: { "model": OpenAIErrorResponse, "description": "The response failed because of an internal error.", }, 504: { "model": OpenAIErrorResponse, "description": "The response exceeded its generation timeout.", }, } _ERROR_EXAMPLES = { 400: ("unsupported_parameter", "The requested parameter is not supported."), 401: ("invalid_api_key", "Invalid or expired API key."), 404: ("response_not_found", "Response not found."), 409: ("response_not_ready", "The requested state transition conflicts."), 422: ("invalid_request_error", "Request does not match the API schema."), 429: ("rate_limit_exceeded", "The request limit was exceeded."), 500: ("internal_error", "The response failed unexpectedly."), 503: ("service_unavailable", "The service is temporarily unavailable."), 504: ("response_timeout", "Response timed out."), } def error_payload( message: str, *, code: str, param: str | None = None, error_type: str = "invalid_request_error", ) -> dict[str, Any]: return OpenAIErrorResponse( error=OpenAIErrorDetail( type=error_type, code=code, message=message, param=param, ) ).model_dump() for _status, _response in PUBLIC_ERROR_RESPONSES.items(): _code, _message = _ERROR_EXAMPLES[int(_status)] _response["content"] = { "application/json": { "example": error_payload( _message, code=_code, ) } } _response["headers"] = REQUEST_ID_RESPONSE_HEADERS def openai_error_response( message: str, *, code: str, status_code: int, param: str | None = None, error_type: str = "invalid_request_error", headers: dict[str, str] | None = None, ) -> JSONResponse: return JSONResponse( status_code=status_code, content=error_payload( message, code=code, param=param, error_type=error_type, ), headers=headers, ) def normalize_request_id(value: str | None) -> str: """Reuse a safe caller ID or create one suitable for logs and support tickets.""" candidate = (value or "").strip() if _VALID_REQUEST_ID.fullmatch(candidate): return candidate return f"req_{uuid4().hex}" class RequestIDMiddleware: """Add a stable request ID without buffering SSE response bodies.""" def __init__(self, app: ASGIApp) -> None: self.app = app async def __call__( self, scope: Scope, receive: Receive, send: Send, ) -> None: if scope["type"] != "http": await self.app(scope, receive, send) return request_id = normalize_request_id( Headers(scope=scope).get(REQUEST_ID_HEADER) ) scope.setdefault("state", {})["request_id"] = request_id async def send_with_request_id(message: Message) -> None: if message["type"] == "http.response.start": MutableHeaders(scope=message)[REQUEST_ID_HEADER] = request_id await send(message) await self.app(scope, receive, send_with_request_id)