Spaces:
Sleeping
Sleeping
File size: 3,476 Bytes
79c4c46 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 | """Error taxonomy shared by the agent service, the internal API, and the UI.
Every error carries a stable ``code``, an HTTP status for the internal API, and
a message that is safe to show to users (no keys, URLs, or prompt content).
"""
from __future__ import annotations
import asyncio
import httpx
class ClearCastError(Exception):
"""Base error with a stable code and a client-safe message."""
code = "internal_error"
http_status = 500
retryable = False
def __init__(
self,
message: str,
*,
code: str | None = None,
http_status: int | None = None,
retryable: bool | None = None,
) -> None:
super().__init__(message)
self.message = message
if code is not None:
self.code = code
if http_status is not None:
self.http_status = http_status
if retryable is not None:
self.retryable = retryable
class ServiceUnavailableError(ClearCastError):
code = "service_unavailable"
http_status = 503
retryable = True
class ConfigurationError(ServiceUnavailableError):
code = "configuration_missing"
retryable = False
class ToolServiceUnavailableError(ServiceUnavailableError):
"""The MCP weather tool subprocess could not be started or reached."""
code = "tool_service_unavailable"
class ModelProviderError(ClearCastError):
code = "model_provider_error"
http_status = 502
class SessionBusyError(ClearCastError):
code = "session_busy"
http_status = 429
retryable = True
class PlanNotFoundError(ClearCastError):
code = "plan_not_found"
http_status = 404
class ReviewConflictError(ClearCastError):
code = "review_conflict"
http_status = 409
def classify_model_exception(exc: BaseException) -> ModelProviderError | None:
"""Map OpenAI/httpx/timeout failures to a categorised, client-safe error."""
import openai
if isinstance(exc, openai.APITimeoutError | httpx.TimeoutException | asyncio.TimeoutError):
return ModelProviderError(
"The language model did not respond in time.",
code="model_timeout",
http_status=504,
retryable=True,
)
if isinstance(exc, openai.RateLimitError):
return ModelProviderError(
"The language model provider is rate limiting requests.",
code="model_rate_limited",
http_status=503,
retryable=True,
)
if isinstance(exc, openai.AuthenticationError | openai.PermissionDeniedError):
return ModelProviderError(
"The language model provider rejected the configured credentials.",
code="model_auth_error",
)
if isinstance(exc, openai.APIConnectionError):
return ModelProviderError(
"The language model provider could not be reached.",
code="model_unavailable",
retryable=True,
)
if isinstance(exc, openai.BadRequestError):
return ModelProviderError("The language model provider rejected the request.", code="model_bad_request")
if isinstance(exc, openai.APIStatusError):
return ModelProviderError(
f"The language model provider returned an error (HTTP {exc.status_code}).",
retryable=exc.status_code >= 500,
)
return None
|