""" LLM-specific exceptions. """ from typing import Optional, Dict, Any class LLMError(Exception): """Base exception for all LLM operations.""" def __init__( self, message: str, cause: Optional[Exception] = None, context: Optional[Dict[str, Any]] = None, ): super().__init__(message) self.cause = cause self.context = context or {} if cause: self.__cause__ = cause def __str__(self) -> str: base = super().__str__() if self.context: ctx = ", ".join(f"{k}={v}" for k, v in self.context.items()) base = f"{base} [Context: {ctx}]" return base class LLMConfigurationError(LLMError): """Missing API key, invalid model name, or bad provider config.""" pass class LLMAPIError(LLMError): """HTTP-level failure from an LLM provider API. Attributes: status_code: HTTP status code (e.g. 500, 502). response_body: Raw response body text, if available. """ def __init__( self, message: str, status_code: int = 0, response_body: str = "", cause: Optional[Exception] = None, context: Optional[Dict[str, Any]] = None, ): super().__init__(message, cause=cause, context=context) self.status_code = status_code self.response_body = response_body class LLMRateLimitError(LLMAPIError): """429 Too Many Requests from the provider.""" pass class LLMTimeoutError(LLMError): """Request or subprocess exceeded the configured timeout.""" pass class LLMBudgetExceededError(LLMError): """Token budget cap exceeded during a call or delegation chain. Attributes: total: The configured token cap. spent: Tokens already consumed before this call. requested: Tokens this call would have consumed. """ def __init__( self, message: str, total: int = 0, spent: int = 0, requested: int = 0, cause: Optional[Exception] = None, context: Optional[Dict[str, Any]] = None, ): if context is None: context = {"total": total, "spent": spent, "requested": requested} super().__init__(message, cause=cause, context=context) self.total = total self.spent = spent self.requested = requested class LLMSubprocessError(LLMError): """Claude Code CLI subprocess failed. Attributes: return_code: Process exit code. stderr: Captured stderr text. """ def __init__( self, message: str, return_code: int = 1, stderr: str = "", cause: Optional[Exception] = None, context: Optional[Dict[str, Any]] = None, ): super().__init__(message, cause=cause, context=context) self.return_code = return_code self.stderr = stderr