In-memory AdmissionController (idle/reserved/running, FIFO tickets keyed by
the session cookie, reservation and heartbeat expiry, promotion after
cleanup, EMA wait estimate) behind POST /api/v1/queue/join,
GET /api/v1/queue/status and DELETE /api/v1/queue/ticket. POST /api/v1/query
claims the session's reservation first and joins implicitly when idle.
Settings: CONFLUENCE_WEB_QUEUE_{RESERVATION_SECONDS,HEARTBEAT_SECONDS,MAX_LENGTH}.
138 lines
4.6 KiB
Python
138 lines
4.6 KiB
Python
"""Sanitized application errors and HTTP/protocol mappings."""
|
|
|
|
from __future__ import annotations
|
|
|
|
|
|
import re
|
|
|
|
_BEARER_PATTERN = re.compile(r"Bearer\s+[A-Za-z0-9_\-\.]+", re.IGNORECASE)
|
|
_TOKEN_PATTERN = re.compile(r"\b[A-Za-z0-9_\-]{32,}\b")
|
|
|
|
|
|
def sanitize_message(msg: str, max_bytes: int = 1024) -> str:
|
|
"""Sanitize and bound an error message to max_bytes UTF-8, stripping secret lookalikes."""
|
|
if not msg:
|
|
return ""
|
|
# Strip potential token/secret lookalikes
|
|
cleaned = _BEARER_PATTERN.sub("Bearer [REDACTED]", msg)
|
|
cleaned = _TOKEN_PATTERN.sub("[REDACTED]", cleaned)
|
|
# Strip newlines and carriage returns
|
|
cleaned = cleaned.replace("\r", " ").replace("\n", " ")
|
|
encoded = cleaned.encode("utf-8")
|
|
if len(encoded) <= max_bytes:
|
|
return cleaned
|
|
truncated = encoded[:max_bytes]
|
|
# Ensure valid UTF-8 boundary
|
|
return truncated.decode("utf-8", errors="ignore")
|
|
|
|
|
|
class AppError(Exception):
|
|
"""Base application error with sanitized error code and message."""
|
|
|
|
def __init__(self, code: str, message: str, status_code: int = 500):
|
|
super().__init__(message)
|
|
self.code = code
|
|
self.message = sanitize_message(message)
|
|
self.status_code = status_code
|
|
|
|
def to_error_dict(self) -> dict:
|
|
return {
|
|
"code": self.code,
|
|
"message": self.message,
|
|
}
|
|
|
|
def to_envelope(self) -> dict:
|
|
return {
|
|
"error": self.to_error_dict(),
|
|
}
|
|
|
|
|
|
class InvalidInputError(AppError):
|
|
def __init__(self, message: str = "Invalid input"):
|
|
super().__init__("invalid_input", message, status_code=400)
|
|
|
|
|
|
class ModelContextExceededError(AppError):
|
|
def __init__(self, message: str = "Model context window exceeded"):
|
|
super().__init__("model_context_exceeded", message, status_code=400)
|
|
|
|
|
|
class OriginDeniedError(AppError):
|
|
def __init__(self, message: str = "Origin denied by policy"):
|
|
super().__init__("origin_denied", message, status_code=403)
|
|
|
|
|
|
class DestinationDeniedError(AppError):
|
|
def __init__(self, message: str = "Destination denied by policy"):
|
|
super().__init__("destination_denied", message, status_code=403)
|
|
|
|
|
|
class ConfluenceAuthFailedError(AppError):
|
|
def __init__(self, message: str = "Confluence authentication failed"):
|
|
super().__init__("confluence_auth_failed", message, status_code=403)
|
|
|
|
|
|
class ArtifactNotFoundError(AppError):
|
|
def __init__(self, message: str = "Artifact not found"):
|
|
super().__init__("artifact_not_found", message, status_code=404)
|
|
|
|
|
|
class BusyError(AppError):
|
|
def __init__(self, message: str = "Active query already in progress"):
|
|
super().__init__("busy", message, status_code=409)
|
|
|
|
|
|
class RequestTooLargeError(AppError):
|
|
def __init__(self, message: str = "Request body exceeds maximum allowed size"):
|
|
super().__init__("request_too_large", message, status_code=413)
|
|
|
|
|
|
class UpstreamFailedError(AppError):
|
|
def __init__(self, message: str = "Upstream request failed"):
|
|
super().__init__("upstream_failed", message, status_code=502)
|
|
|
|
|
|
class UpstreamResponseTooLargeError(AppError):
|
|
def __init__(self, message: str = "Upstream response exceeds maximum allowed size"):
|
|
super().__init__("upstream_response_too_large", message, status_code=502)
|
|
|
|
|
|
class TlsFailedError(AppError):
|
|
def __init__(self, message: str = "TLS certificate verification failed"):
|
|
super().__init__("tls_failed", message, status_code=502)
|
|
|
|
|
|
class ConnectivityFailedError(AppError):
|
|
def __init__(self, message: str = "Failed to connect to upstream server"):
|
|
super().__init__("connectivity_failed", message, status_code=502)
|
|
|
|
|
|
class ModelOutputLimitError(AppError):
|
|
def __init__(self, message: str = "Model output exceeded limit"):
|
|
super().__init__("model_output_limit", message, status_code=502)
|
|
|
|
|
|
class QueryTimeoutError(AppError):
|
|
def __init__(self, message: str = "Query execution timed out"):
|
|
super().__init__("query_timeout", message, status_code=504)
|
|
|
|
|
|
class ExecutionFailedError(AppError):
|
|
def __init__(self, message: str = "Query execution failed"):
|
|
super().__init__("execution_failed", message, status_code=500)
|
|
|
|
|
|
class CleanupFailedError(AppError):
|
|
def __init__(self, message: str = "Container cleanup could not be confirmed"):
|
|
super().__init__("cleanup_failed", message, status_code=500)
|
|
|
|
|
|
class QueueFullError(AppError):
|
|
def __init__(self, message: str = "The queue is full"):
|
|
super().__init__("queue_full", message, status_code=503)
|
|
|
|
|
|
class TicketNotFoundError(AppError):
|
|
def __init__(self, message: str = "Ticket not found"):
|
|
super().__init__("ticket_not_found", message, status_code=404)
|