Coverage for src / osiris_cli / errors.py: 0%
115 statements
« prev ^ index » next coverage.py v7.13.0, created at 2025-12-31 05:01 +0200
« prev ^ index » next coverage.py v7.13.0, created at 2025-12-31 05:01 +0200
1"""
2Custom exception hierarchy for Osiris CLI
4Provides context-rich error messages with recovery suggestions
5and proper error categorization for better debugging.
6"""
8from typing import Optional, Dict, Any
11class OsirisError(Exception):
12 """
13 Base exception for all Osiris CLI errors.
15 All Osiris exceptions should inherit from this class.
16 Provides standard error formatting and context.
17 """
19 def __init__(
20 self,
21 message: str,
22 details: Optional[Dict[str, Any]] = None,
23 recovery_suggestion: Optional[str] = None
24 ):
25 self.message = message
26 self.details = details or {}
27 self.recovery_suggestion = recovery_suggestion
28 super().__init__(self.message)
30 def __str__(self):
31 error_str = f"{self.__class__.__name__}: {self.message}"
32 if self.details:
33 error_str += f"\nDetails: {self.details}"
34 if self.recovery_suggestion:
35 error_str += f"\nSuggestion: {self.recovery_suggestion}"
36 return error_str
39# Configuration Errors
40class ConfigError(OsirisError):
41 """Configuration-related errors (missing keys, invalid settings, etc.)"""
42 pass
45class ConfigFileError(ConfigError):
46 """Error reading or writing configuration files"""
47 pass
50class APIKeyError(ConfigError):
51 """Missing or invalid API key"""
53 def __init__(self, provider: str, **kwargs):
54 super().__init__(
55 message=f"API key missing or invalid for provider: {provider}",
56 recovery_suggestion=f"Set {provider.upper()}_API_KEY environment variable or configure via 'osiris enhanced quick-start'",
57 **kwargs
58 )
61# API Errors
62class APIError(OsirisError):
63 """Base class for API-related errors"""
64 pass
67class APITimeoutError(APIError):
68 """API request timed out"""
70 def __init__(self, provider: str, timeout: int, **kwargs):
71 super().__init__(
72 message=f"Request to {provider} timed out after {timeout}s",
73 recovery_suggestion="Try again or increase timeout in settings",
74 **kwargs
75 )
78class APIRateLimitError(APIError):
79 """API rate limit exceeded"""
81 def __init__(self, provider: str, retry_after: Optional[int] = None, **kwargs):
82 message = f"Rate limit exceeded for {provider}"
83 if retry_after:
84 message += f", retry after {retry_after}s"
86 super().__init__(
87 message=message,
88 recovery_suggestion="Wait a moment and try again, or use a different provider",
89 **kwargs
90 )
93class APIAuthenticationError(APIError):
94 """API authentication failed (401, 403)"""
96 def __init__(self, provider: str, status_code: int, **kwargs):
97 super().__init__(
98 message=f"Unauthorized ({status_code}) for {provider}",
99 recovery_suggestion=f"Check your {provider.upper()}_API_KEY is valid",
100 **kwargs
101 )
104class APIInsufficientCreditsError(APIError):
105 """API insufficient credits (402)"""
107 def __init__(self, provider: str, **kwargs):
108 super().__init__(
109 message=f"Insufficient credits (402) for {provider}",
110 recovery_suggestion=f"Add credits to your {provider} account or switch providers",
111 **kwargs
112 )
115class APIModelNotFoundError(APIError):
116 """Requested model not found (404)"""
118 def __init__(self, provider: str, model: str, **kwargs):
119 super().__init__(
120 message=f"Model not found (404) for {provider}: {model}",
121 recovery_suggestion="Check model name or try a different model",
122 **kwargs
123 )
126class APIServerError(APIError):
127 """API server error (5xx)"""
129 def __init__(self, provider: str, status_code: int, **kwargs):
130 super().__init__(
131 message=f"Server error from {provider} (HTTP {status_code})",
132 recovery_suggestion="Provider is having issues, try again later or use a different provider",
133 **kwargs
134 )
137# Tool Errors
138class ToolError(OsirisError):
139 """Base class for tool execution errors"""
140 pass
143class ToolNotFoundError(ToolError):
144 """Requested tool not found in registry"""
146 def __init__(self, tool_name: str, **kwargs):
147 super().__init__(
148 message=f"Tool '{tool_name}' not found",
149 recovery_suggestion="Use /tools to see available tools",
150 **kwargs
151 )
154class ToolExecutionError(ToolError):
155 """Tool execution failed"""
157 def __init__(self, tool_name: str, error: str, **kwargs):
158 super().__init__(
159 message=f"Tool '{tool_name}' failed: {error}",
160 **kwargs
161 )
164class ToolPermissionError(ToolError):
165 """Tool execution blocked by permissions"""
167 def __init__(self, tool_name: str, reason: str, **kwargs):
168 super().__init__(
169 message=f"Tool '{tool_name}' blocked: {reason}",
170 recovery_suggestion="Enable YOLO mode or add to auto-allow list",
171 **kwargs
172 )
175# Session Errors
176class SessionError(OsirisError):
177 """Base class for session-related errors"""
178 pass
181class SessionNotFoundError(SessionError):
182 """Session file not found"""
184 def __init__(self, session_id: str, **kwargs):
185 super().__init__(
186 message=f"Session '{session_id}' not found",
187 recovery_suggestion="Use /session list to see available sessions",
188 **kwargs
189 )
192class SessionCorruptedError(SessionError):
193 """Session file corrupted or invalid"""
195 def __init__(self, session_id: str, **kwargs):
196 super().__init__(
197 message=f"Session '{session_id}' is corrupted",
198 recovery_suggestion="Start a new session",
199 **kwargs
200 )
203# Context Errors
204class ContextError(OsirisError):
205 """Base class for context-related errors"""
206 pass
209class ContextFileTooLargeError(ContextError):
210 """Context file exceeds size limit"""
212 def __init__(self, file_path: str, size_mb: float, limit_mb: float, **kwargs):
213 super().__init__(
214 message=f"File '{file_path}' is too large ({size_mb:.1f}MB > {limit_mb}MB)",
215 recovery_suggestion="Use /context to add specific sections or increase limit in settings",
216 **kwargs
217 )
220class ContextFileNotFoundError(ContextError):
221 """Context file not found"""
223 def __init__(self, file_path: str, **kwargs):
224 super().__init__(
225 message=f"File '{file_path}' not found",
226 **kwargs
227 )
230# Database Errors
231class DatabaseError(OsirisError):
232 """Base class for database-related errors"""
233 pass
236class DatabaseConnectionError(DatabaseError):
237 """Failed to connect to database"""
239 def __init__(self, db_type: str, error: str, **kwargs):
240 super().__init__(
241 message=f"Failed to connect to {db_type} database: {error}",
242 recovery_suggestion="Check connection string and credentials",
243 **kwargs
244 )
247class DatabaseQueryError(DatabaseError):
248 """Database query failed"""
250 def __init__(self, query: str, error: str, **kwargs):
251 super().__init__(
252 message=f"Query failed: {error}",
253 details={"query": query},
254 **kwargs
255 )
258# Cloud Errors
259class CloudError(OsirisError):
260 """Base class for cloud provider errors"""
261 pass
264class CloudAuthenticationError(CloudError):
265 """Cloud provider authentication failed"""
267 def __init__(self, provider: str, **kwargs):
268 super().__init__(
269 message=f"Failed to authenticate with {provider}",
270 recovery_suggestion=f"Configure {provider} CLI credentials",
271 **kwargs
272 )
275class CloudResourceNotFoundError(CloudError):
276 """Cloud resource not found"""
278 def __init__(self, provider: str, resource_type: str, resource_id: str, **kwargs):
279 super().__init__(
280 message=f"{provider} {resource_type} '{resource_id}' not found",
281 **kwargs
282 )
285# Memory/Agent Errors
286class MemoryError(OsirisError):
287 """Base class for agent memory errors"""
288 pass
291class MemoryCorruptedError(MemoryError):
292 """Agent memory file corrupted"""
294 def __init__(self, agent_name: str, **kwargs):
295 super().__init__(
296 message=f"Memory for agent '{agent_name}' is corrupted",
297 recovery_suggestion="Use /init to reset agent memory",
298 **kwargs
299 )
302# Validation Errors
303class ValidationError(OsirisError):
304 """Input validation failed"""
305 pass
308class InvalidModelError(ValidationError):
309 """Invalid model name or not supported"""
311 def __init__(self, model: str, provider: str, **kwargs):
312 super().__init__(
313 message=f"Model '{model}' is not valid for provider {provider}",
314 recovery_suggestion="Use /model to see available models",
315 **kwargs
316 )
319class InvalidProviderError(ValidationError):
320 """Invalid provider name"""
322 def __init__(self, provider: str, **kwargs):
323 super().__init__(
324 message=f"Provider '{provider}' is not supported",
325 recovery_suggestion="Use /provider to see available providers",
326 **kwargs
327 )
330# Helper function to convert standard exceptions to Osiris exceptions
331def wrap_exception(exc: Exception, context: str = "") -> OsirisError:
332 """
333 Wrap standard Python exceptions in OsirisError with context.
335 Args:
336 exc: Original exception
337 context: Additional context about where error occurred
339 Returns:
340 OsirisError with wrapped exception
341 """
342 if isinstance(exc, OsirisError):
343 return exc
345 message = str(exc)
346 if context:
347 message = f"{context}: {message}"
349 # Map common exceptions to specific Osiris errors
350 if isinstance(exc, FileNotFoundError):
351 return ContextFileNotFoundError(str(exc))
352 elif isinstance(exc, PermissionError):
353 return ToolPermissionError("unknown", str(exc))
354 elif isinstance(exc, TimeoutError):
355 return APITimeoutError("unknown", 30)
356 elif isinstance(exc, ConnectionError):
357 return APIError(message)
358 else:
359 # Generic wrapper
360 return OsirisError(
361 message=message,
362 details={"original_exception": type(exc).__name__}
363 )