| """ | |
| DungeonMaster AI - Voice Integration Exceptions | |
| Custom exception hierarchy for clear error handling in voice synthesis operations. | |
| """ | |
| from __future__ import annotations | |
| class VoiceIntegrationError(Exception): | |
| """Base exception for all voice integration errors.""" | |
| def __init__(self, message: str = "A voice integration error occurred") -> None: | |
| self.message = message | |
| super().__init__(self.message) | |
| class VoiceAPIError(VoiceIntegrationError): | |
| """Raised when ElevenLabs API returns an error.""" | |
| def __init__( | |
| self, | |
| message: str = "ElevenLabs API error", | |
| status_code: int | None = None, | |
| ) -> None: | |
| self.status_code = status_code | |
| if status_code: | |
| message = f"{message} (HTTP {status_code})" | |
| super().__init__(message) | |
| class VoiceRateLimitError(VoiceAPIError): | |
| """Raised when rate limit is exceeded (HTTP 429).""" | |
| def __init__( | |
| self, | |
| message: str = "Rate limit exceeded", | |
| retry_after_seconds: float | None = None, | |
| ) -> None: | |
| self.retry_after_seconds = retry_after_seconds | |
| if retry_after_seconds: | |
| message = f"{message}. Retry after {retry_after_seconds:.1f}s" | |
| super().__init__(message, status_code=429) | |
| class VoiceQuotaExhaustedError(VoiceAPIError): | |
| """Raised when monthly quota is exhausted.""" | |
| def __init__( | |
| self, | |
| message: str = "Monthly voice quota exhausted", | |
| quota_reset_date: str | None = None, | |
| ) -> None: | |
| self.quota_reset_date = quota_reset_date | |
| if quota_reset_date: | |
| message = f"{message}. Resets on {quota_reset_date}" | |
| super().__init__(message, status_code=402) | |
| class VoiceAuthenticationError(VoiceAPIError): | |
| """Raised when API key is invalid or expired (HTTP 401).""" | |
| def __init__(self, message: str = "Invalid or expired ElevenLabs API key") -> None: | |
| super().__init__(message, status_code=401) | |
| class VoiceUnavailableError(VoiceIntegrationError): | |
| """Raised when voice service is unavailable after retries.""" | |
| def __init__( | |
| self, | |
| message: str = "Voice service is currently unavailable", | |
| reason: str | None = None, | |
| ) -> None: | |
| self.reason = reason | |
| if reason: | |
| message = f"{message}: {reason}" | |
| super().__init__(message) | |
| class VoiceCircuitBreakerOpenError(VoiceIntegrationError): | |
| """Raised when circuit breaker is open and rejecting requests.""" | |
| def __init__( | |
| self, | |
| message: str = "Circuit breaker is open - too many recent failures", | |
| retry_after_seconds: float | None = None, | |
| ) -> None: | |
| self.retry_after_seconds = retry_after_seconds | |
| if retry_after_seconds: | |
| message = f"{message}. Retry after {retry_after_seconds:.1f}s" | |
| super().__init__(message) | |
| class VoiceNotFoundError(VoiceIntegrationError): | |
| """Raised when requested voice ID doesn't exist.""" | |
| def __init__(self, voice_id: str) -> None: | |
| self.voice_id = voice_id | |
| super().__init__(f"Voice not found: {voice_id}") | |
| class VoiceSynthesisError(VoiceIntegrationError): | |
| """Raised when synthesis fails for the given text.""" | |
| def __init__( | |
| self, | |
| message: str = "Voice synthesis failed", | |
| text_preview: str | None = None, | |
| ) -> None: | |
| self.text_preview = text_preview | |
| if text_preview: | |
| preview = text_preview[:50] + "..." if len(text_preview) > 50 else text_preview | |
| message = f"{message} for text: '{preview}'" | |
| super().__init__(message) | |
| class VoiceConfigurationError(VoiceIntegrationError): | |
| """Raised when voice settings are misconfigured.""" | |
| def __init__(self, message: str = "Voice configuration error") -> None: | |
| super().__init__(message) | |