1119 lines
38 KiB
Python
1119 lines
38 KiB
Python
"""
|
|||
|
|
Centralized error handling framework for the journal organizer application.
|
||
|
|
Provides consistent error types and handling patterns across all components.
|
||
|
|
"""
|
||
|
|
|
||
|
|
import logging
|
||
|
|
import traceback
|
||
|
|
from dataclasses import dataclass, field
|
||
|
|
from datetime import datetime
|
||
|
|
from typing import Dict, Any, Optional, Union, List
|
||
|
|
|
||
|
|
|
||
|
|
class JournalOrganizerError(Exception):
|
||
|
|
"""Base exception for journal organizer errors"""
|
||
|
|
|
||
|
|
def __init__(
|
||
|
|
self,
|
||
|
|
message: str,
|
||
|
|
context: Optional[Dict[str, Any]] = None,
|
||
|
|
cause: Optional[Exception] = None,
|
||
|
|
) -> None:
|
||
|
|
"""
|
||
|
|
Initialize base error
|
||
|
|
|
||
|
|
Args:
|
||
|
|
message: Error message
|
||
|
|
context: Additional context information
|
||
|
|
cause: Original exception that caused this error
|
||
|
|
"""
|
||
|
|
super().__init__(message)
|
||
|
|
self.message = message
|
||
|
|
self.context = context or {}
|
||
|
|
self.cause = cause
|
||
|
|
self.timestamp = datetime.now().isoformat()
|
||
|
|
|
||
|
|
def to_dict(self) -> Dict[str, Any]:
|
||
|
|
"""Convert error to dictionary for logging/serialization"""
|
||
|
|
return {
|
||
|
|
"error_type": self.__class__.__name__,
|
||
|
|
"message": self.message,
|
||
|
|
"context": self.context,
|
||
|
|
"timestamp": self.timestamp,
|
||
|
|
"cause": str(self.cause) if self.cause else None,
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
class ConfigurationError(JournalOrganizerError):
|
||
|
|
"""Raised when configuration is invalid or missing"""
|
||
|
|
|
||
|
|
def __init__(
|
||
|
|
self,
|
||
|
|
message: str,
|
||
|
|
config_key: Optional[str] = None,
|
||
|
|
config_value: Optional[Any] = None,
|
||
|
|
cause: Optional[Exception] = None,
|
||
|
|
) -> None:
|
||
|
|
context = {}
|
||
|
|
if config_key:
|
||
|
|
context["config_key"] = config_key
|
||
|
|
if config_value is not None:
|
||
|
|
# Sanitize sensitive values
|
||
|
|
if (
|
||
|
|
"key" in str(config_key).lower()
|
||
|
|
or "password" in str(config_key).lower()
|
||
|
|
):
|
||
|
|
context["config_value"] = "[REDACTED]"
|
||
|
|
else:
|
||
|
|
context["config_value"] = str(config_value)
|
||
|
|
|
||
|
|
super().__init__(message, context, cause)
|
||
|
|
|
||
|
|
|
||
|
|
class ClaudeConfigurationError(ConfigurationError):
|
||
|
|
"""Specific error for Claude API configuration issues"""
|
||
|
|
|
||
|
|
def __init__(
|
||
|
|
self,
|
||
|
|
message: str,
|
||
|
|
config_key: Optional[str] = None,
|
||
|
|
config_value: Optional[Any] = None,
|
||
|
|
suggestions: Optional[list] = None,
|
||
|
|
corrective_action: Optional[str] = None,
|
||
|
|
cause: Optional[Exception] = None,
|
||
|
|
) -> None:
|
||
|
|
# Add Claude-specific context
|
||
|
|
context = {}
|
||
|
|
if config_key:
|
||
|
|
context["config_key"] = config_key
|
||
|
|
if config_value is not None:
|
||
|
|
# Sanitize sensitive values for Claude config
|
||
|
|
if any(sensitive in str(config_key).lower() for sensitive in ["key", "token", "secret"]):
|
||
|
|
context["config_value"] = "[REDACTED]"
|
||
|
|
else:
|
||
|
|
context["config_value"] = str(config_value)
|
||
|
|
if suggestions:
|
||
|
|
context["suggestions"] = suggestions
|
||
|
|
if corrective_action:
|
||
|
|
context["corrective_action"] = corrective_action
|
||
|
|
|
||
|
|
super().__init__(message, config_key, config_value, cause)
|
||
|
|
self.suggestions = suggestions or []
|
||
|
|
self.corrective_action = corrective_action
|
||
|
|
|
||
|
|
def get_user_friendly_message(self) -> str:
|
||
|
|
"""Get a user-friendly error message with suggestions"""
|
||
|
|
message_parts = [self.message]
|
||
|
|
|
||
|
|
if self.suggestions:
|
||
|
|
message_parts.append("\nSuggestions:")
|
||
|
|
for suggestion in self.suggestions:
|
||
|
|
message_parts.append(f" • {suggestion}")
|
||
|
|
|
||
|
|
if self.corrective_action:
|
||
|
|
message_parts.append(f"\nTo fix this: {self.corrective_action}")
|
||
|
|
|
||
|
|
return "\n".join(message_parts)
|
||
|
|
|
||
|
|
|
||
|
|
class ClaudeAPIURLError(ClaudeConfigurationError):
|
||
|
|
"""Error for invalid Claude API URL configuration"""
|
||
|
|
|
||
|
|
def __init__(
|
||
|
|
self,
|
||
|
|
message: str,
|
||
|
|
invalid_url: Optional[str] = None,
|
||
|
|
cause: Optional[Exception] = None,
|
||
|
|
) -> None:
|
||
|
|
suggestions = [
|
||
|
|
"Use a valid HTTP or HTTPS URL (e.g., https://api.anthropic.com)",
|
||
|
|
"Ensure the URL doesn't have trailing slashes",
|
||
|
|
"For custom endpoints, verify the server is running and accessible",
|
||
|
|
"For localhost endpoints, use http://localhost:PORT or https://localhost:PORT"
|
||
|
|
]
|
||
|
|
|
||
|
|
corrective_action = (
|
||
|
|
"Update your configuration with a valid API URL. "
|
||
|
|
"The default Anthropic API URL is 'https://api.anthropic.com'"
|
||
|
|
)
|
||
|
|
|
||
|
|
super().__init__(
|
||
|
|
message=message,
|
||
|
|
config_key="api_url",
|
||
|
|
config_value=invalid_url,
|
||
|
|
suggestions=suggestions,
|
||
|
|
corrective_action=corrective_action,
|
||
|
|
cause=cause
|
||
|
|
)
|
||
|
|
|
||
|
|
|
||
|
|
class ClaudeModelValidationError(ClaudeConfigurationError):
|
||
|
|
"""Error for invalid Claude model names with suggestions"""
|
||
|
|
|
||
|
|
def __init__(
|
||
|
|
self,
|
||
|
|
message: str,
|
||
|
|
invalid_model: Optional[str] = None,
|
||
|
|
valid_models: Optional[list] = None,
|
||
|
|
cause: Optional[Exception] = None,
|
||
|
|
) -> None:
|
||
|
|
# Default valid models if not provided
|
||
|
|
if not valid_models:
|
||
|
|
valid_models = [
|
||
|
|
"claude-3-5-sonnet-20241022",
|
||
|
|
"claude-3-5-haiku-20241022",
|
||
|
|
"claude-3-opus-20240229",
|
||
|
|
"claude-3-sonnet-20240229",
|
||
|
|
"claude-3-haiku-20240307"
|
||
|
|
]
|
||
|
|
|
||
|
|
suggestions = [
|
||
|
|
f"Use one of these supported models: {', '.join(valid_models[:3])}",
|
||
|
|
"Model names should follow pattern: claude-X-Y-YYYYMMDD or claude-X-Y-latest",
|
||
|
|
"Check the Anthropic documentation for the latest available models",
|
||
|
|
"Ensure the model name is spelled correctly and includes the date suffix"
|
||
|
|
]
|
||
|
|
|
||
|
|
corrective_action = (
|
||
|
|
f"Update your model configuration to use a valid model name. "
|
||
|
|
f"Recommended: '{valid_models[0]}'"
|
||
|
|
)
|
||
|
|
|
||
|
|
super().__init__(
|
||
|
|
message=message,
|
||
|
|
config_key="model",
|
||
|
|
config_value=invalid_model,
|
||
|
|
suggestions=suggestions,
|
||
|
|
corrective_action=corrective_action,
|
||
|
|
cause=cause
|
||
|
|
)
|
||
|
|
|
||
|
|
|
||
|
|
class ClaudeAPIKeyError(ClaudeConfigurationError):
|
||
|
|
"""Error for invalid Claude API key configuration"""
|
||
|
|
|
||
|
|
def __init__(
|
||
|
|
self,
|
||
|
|
message: str,
|
||
|
|
cause: Optional[Exception] = None,
|
||
|
|
) -> None:
|
||
|
|
suggestions = [
|
||
|
|
"Obtain a valid API key from https://console.anthropic.com/",
|
||
|
|
"Ensure the API key starts with 'sk-ant-'",
|
||
|
|
"Set the API key as an environment variable: export ANTHROPIC_API_KEY='sk-ant-...'",
|
||
|
|
"Verify the API key is not truncated or contains extra whitespace",
|
||
|
|
"Check that your API key has the necessary permissions"
|
||
|
|
]
|
||
|
|
|
||
|
|
corrective_action = (
|
||
|
|
"Get a valid Claude API key from the Anthropic console and set it in your configuration "
|
||
|
|
"or as the ANTHROPIC_API_KEY environment variable"
|
||
|
|
)
|
||
|
|
|
||
|
|
super().__init__(
|
||
|
|
message=message,
|
||
|
|
config_key="api_key",
|
||
|
|
config_value="[REDACTED]",
|
||
|
|
suggestions=suggestions,
|
||
|
|
corrective_action=corrective_action,
|
||
|
|
cause=cause
|
||
|
|
)
|
||
|
|
|
||
|
|
|
||
|
|
class ClaudeConnectionError(ClaudeConfigurationError):
|
||
|
|
"""Error for Claude API connection issues"""
|
||
|
|
|
||
|
|
def __init__(
|
||
|
|
self,
|
||
|
|
message: str,
|
||
|
|
api_url: Optional[str] = None,
|
||
|
|
status_code: Optional[int] = None,
|
||
|
|
cause: Optional[Exception] = None,
|
||
|
|
) -> None:
|
||
|
|
suggestions = []
|
||
|
|
corrective_action = ""
|
||
|
|
|
||
|
|
# Provide specific suggestions based on the error context
|
||
|
|
if status_code == 401:
|
||
|
|
suggestions = [
|
||
|
|
"Verify your API key is correct and active",
|
||
|
|
"Check that the API key has not expired",
|
||
|
|
"Ensure you're using the correct API key for the endpoint"
|
||
|
|
]
|
||
|
|
corrective_action = "Update your API key with a valid, active key from the Anthropic console"
|
||
|
|
elif status_code == 404:
|
||
|
|
suggestions = [
|
||
|
|
"Verify the API URL is correct",
|
||
|
|
"Check if the API endpoint is available",
|
||
|
|
"For custom endpoints, ensure the service is running"
|
||
|
|
]
|
||
|
|
corrective_action = "Verify and correct your API URL configuration"
|
||
|
|
elif status_code == 429:
|
||
|
|
suggestions = [
|
||
|
|
"You've exceeded the API rate limit",
|
||
|
|
"Wait before making more requests",
|
||
|
|
"Consider implementing request throttling"
|
||
|
|
]
|
||
|
|
corrective_action = "Wait for the rate limit to reset before retrying"
|
||
|
|
elif "connection" in message.lower() or "timeout" in message.lower():
|
||
|
|
suggestions = [
|
||
|
|
"Check your internet connection",
|
||
|
|
"Verify the API URL is accessible",
|
||
|
|
"For custom endpoints, ensure the server is running",
|
||
|
|
"Check firewall settings that might block the connection"
|
||
|
|
]
|
||
|
|
corrective_action = "Verify network connectivity and API endpoint availability"
|
||
|
|
elif "ssl" in message.lower() or "certificate" in message.lower():
|
||
|
|
suggestions = [
|
||
|
|
"For localhost endpoints, SSL errors are expected",
|
||
|
|
"For custom HTTPS endpoints, ensure valid SSL certificates",
|
||
|
|
"Consider using HTTP for local development endpoints"
|
||
|
|
]
|
||
|
|
corrective_action = "Check SSL certificate configuration or use HTTP for local endpoints"
|
||
|
|
else:
|
||
|
|
suggestions = [
|
||
|
|
"Check the API endpoint status",
|
||
|
|
"Verify your configuration is correct",
|
||
|
|
"Try again after a short delay"
|
||
|
|
]
|
||
|
|
corrective_action = "Review your Claude API configuration and try again"
|
||
|
|
|
||
|
|
context = {}
|
||
|
|
if api_url:
|
||
|
|
context["api_url"] = api_url
|
||
|
|
if status_code:
|
||
|
|
context["status_code"] = status_code
|
||
|
|
|
||
|
|
super().__init__(
|
||
|
|
message=message,
|
||
|
|
config_key="connection",
|
||
|
|
suggestions=suggestions,
|
||
|
|
corrective_action=corrective_action,
|
||
|
|
cause=cause
|
||
|
|
)
|
||
|
|
|
||
|
|
# Add additional context
|
||
|
|
self.context.update(context)
|
||
|
|
|
||
|
|
|
||
|
|
class APIError(JournalOrganizerError):
|
||
|
|
"""Raised when external API calls fail"""
|
||
|
|
|
||
|
|
def __init__(
|
||
|
|
self,
|
||
|
|
message: str,
|
||
|
|
api_name: Optional[str] = None,
|
||
|
|
status_code: Optional[int] = None,
|
||
|
|
response_data: Optional[str] = None,
|
||
|
|
cause: Optional[Exception] = None,
|
||
|
|
) -> None:
|
||
|
|
context = {}
|
||
|
|
if api_name:
|
||
|
|
context["api_name"] = api_name
|
||
|
|
if status_code:
|
||
|
|
context["status_code"] = status_code
|
||
|
|
if response_data:
|
||
|
|
# Limit response data length to prevent log spam
|
||
|
|
context["response_data"] = (
|
||
|
|
f"{response_data[:500]}..."
|
||
|
|
if len(response_data) > 500
|
||
|
|
else response_data
|
||
|
|
)
|
||
|
|
|
||
|
|
super().__init__(message, context, cause)
|
||
|
|
|
||
|
|
|
||
|
|
class ValidationError(JournalOrganizerError):
|
||
|
|
"""Raised when input validation fails"""
|
||
|
|
|
||
|
|
def __init__(
|
||
|
|
self,
|
||
|
|
message: str,
|
||
|
|
field_name: Optional[str] = None,
|
||
|
|
field_value: Optional[Any] = None,
|
||
|
|
validation_rule: Optional[str] = None,
|
||
|
|
cause: Optional[Exception] = None,
|
||
|
|
) -> None:
|
||
|
|
context = {}
|
||
|
|
if field_name:
|
||
|
|
context["field_name"] = field_name
|
||
|
|
if field_value is not None:
|
||
|
|
# Sanitize potentially sensitive field values
|
||
|
|
if any(
|
||
|
|
sensitive in str(field_name).lower()
|
||
|
|
for sensitive in ["password", "key", "token", "secret"]
|
||
|
|
):
|
||
|
|
context["field_value"] = "[REDACTED]"
|
||
|
|
else:
|
||
|
|
context["field_value"] = str(field_value)[:100] # Limit length
|
||
|
|
if validation_rule:
|
||
|
|
context["validation_rule"] = validation_rule
|
||
|
|
|
||
|
|
super().__init__(message, context, cause)
|
||
|
|
|
||
|
|
|
||
|
|
class FileSystemError(JournalOrganizerError):
|
||
|
|
"""Raised when file system operations fail"""
|
||
|
|
|
||
|
|
def __init__(
|
||
|
|
self,
|
||
|
|
message: str,
|
||
|
|
file_path: Optional[str] = None,
|
||
|
|
operation: Optional[str] = None,
|
||
|
|
cause: Optional[Exception] = None,
|
||
|
|
) -> None:
|
||
|
|
context = {}
|
||
|
|
if file_path:
|
||
|
|
context["file_path"] = file_path
|
||
|
|
if operation:
|
||
|
|
context["operation"] = operation
|
||
|
|
|
||
|
|
super().__init__(message, context, cause)
|
||
|
|
|
||
|
|
|
||
|
|
class SecurityError(JournalOrganizerError):
|
||
|
|
"""Raised when security violations are detected"""
|
||
|
|
|
||
|
|
def __init__(
|
||
|
|
self,
|
||
|
|
message: str,
|
||
|
|
security_issue: Optional[str] = None,
|
||
|
|
attempted_path: Optional[str] = None,
|
||
|
|
risk_level: str = "high",
|
||
|
|
cause: Optional[Exception] = None,
|
||
|
|
) -> None:
|
||
|
|
context = {}
|
||
|
|
if security_issue:
|
||
|
|
context["security_issue"] = security_issue
|
||
|
|
if attempted_path:
|
||
|
|
# Sanitize the attempted path to prevent log injection
|
||
|
|
context["attempted_path"] = str(attempted_path)[:500] # Limit length
|
||
|
|
context["risk_level"] = risk_level
|
||
|
|
|
||
|
|
super().__init__(message, context, cause)
|
||
|
|
|
||
|
|
|
||
|
|
@dataclass
|
||
|
|
class ErrorContext:
|
||
|
|
"""Context information for error handling"""
|
||
|
|
|
||
|
|
component: str
|
||
|
|
operation: str
|
||
|
|
user_message: str = ""
|
||
|
|
technical_details: Dict[str, Any] = field(default_factory=dict)
|
||
|
|
severity: str = "error" # error, warning, info
|
||
|
|
|
||
|
|
def __post_init__(self) -> None:
|
||
|
|
"""Validate error context after initialization"""
|
||
|
|
if not self.component or not self.component.strip():
|
||
|
|
raise ValueError("component cannot be empty")
|
||
|
|
if not self.operation or not self.operation.strip():
|
||
|
|
raise ValueError("operation cannot be empty")
|
||
|
|
valid_severities = ["error", "warning", "info"]
|
||
|
|
if self.severity not in valid_severities:
|
||
|
|
raise ValueError(f"severity must be one of {valid_severities}")
|
||
|
|
if not isinstance(self.technical_details, dict):
|
||
|
|
raise ValueError("technical_details must be a dictionary")
|
||
|
|
|
||
|
|
def to_dict(self) -> Dict[str, Any]:
|
||
|
|
"""Convert to dictionary"""
|
||
|
|
return {
|
||
|
|
"component": self.component,
|
||
|
|
"operation": self.operation,
|
||
|
|
"user_message": self.user_message,
|
||
|
|
"technical_details": self.technical_details,
|
||
|
|
"severity": self.severity,
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
class ErrorHandler:
|
||
|
|
"""Centralized error handling and logging"""
|
||
|
|
|
||
|
|
def __init__(self, logger: Optional[logging.Logger] = None) -> None:
|
||
|
|
"""
|
||
|
|
Initialize error handler
|
||
|
|
|
||
|
|
Args:
|
||
|
|
logger: Logger instance to use (creates default if None)
|
||
|
|
"""
|
||
|
|
self.logger = logger or logging.getLogger(__name__)
|
||
|
|
|
||
|
|
def handle_error(self, error: Exception, context: ErrorContext) -> Dict[str, Any]:
|
||
|
|
"""
|
||
|
|
Handle any error with consistent logging and formatting
|
||
|
|
|
||
|
|
Args:
|
||
|
|
error: The exception that occurred
|
||
|
|
context: Error context information
|
||
|
|
|
||
|
|
Returns:
|
||
|
|
Standardized error response dictionary
|
||
|
|
"""
|
||
|
|
# Log the error with appropriate level
|
||
|
|
log_level = getattr(logging, context.severity.upper(), logging.ERROR)
|
||
|
|
|
||
|
|
if isinstance(error, JournalOrganizerError):
|
||
|
|
# Use our custom error information
|
||
|
|
error_dict = error.to_dict()
|
||
|
|
# Sanitize the error dictionary
|
||
|
|
sanitized_error_dict = self._sanitize_context_data(error_dict)
|
||
|
|
self.logger.log(
|
||
|
|
log_level,
|
||
|
|
f"{context.component}.{context.operation}: {error.message}",
|
||
|
|
extra={"error_context": sanitized_error_dict},
|
||
|
|
)
|
||
|
|
else:
|
||
|
|
# Handle unexpected errors
|
||
|
|
sanitized_context = self._sanitize_context_data(context.to_dict())
|
||
|
|
self.logger.log(
|
||
|
|
log_level,
|
||
|
|
f"{context.component}.{context.operation}: {str(error)}",
|
||
|
|
extra={
|
||
|
|
"error_context": sanitized_context,
|
||
|
|
"traceback": traceback.format_exc(),
|
||
|
|
},
|
||
|
|
)
|
||
|
|
|
||
|
|
# Return standardized error response with sanitized messages
|
||
|
|
return {
|
||
|
|
"success": False,
|
||
|
|
"error": self._sanitize_error_message(str(error)),
|
||
|
|
"message": context.user_message or f"Error in {context.operation}",
|
||
|
|
"component": context.component,
|
||
|
|
"operation": context.operation,
|
||
|
|
"timestamp": datetime.now().isoformat(),
|
||
|
|
"error_type": error.__class__.__name__,
|
||
|
|
}
|
||
|
|
|
||
|
|
def handle_api_error(
|
||
|
|
self, error: Exception, api_name: str, operation: str
|
||
|
|
) -> Dict[str, Any]:
|
||
|
|
"""
|
||
|
|
Handle API-related errors consistently
|
||
|
|
|
||
|
|
Args:
|
||
|
|
error: The exception that occurred
|
||
|
|
api_name: Name of the API (e.g., 'obsidian', 'claude')
|
||
|
|
operation: Operation being performed
|
||
|
|
|
||
|
|
Returns:
|
||
|
|
Standardized error response
|
||
|
|
"""
|
||
|
|
if isinstance(error, APIError):
|
||
|
|
api_error = error
|
||
|
|
else:
|
||
|
|
# Wrap generic exceptions in APIError
|
||
|
|
api_error = APIError(
|
||
|
|
message=f"{api_name} API error: {str(error)}",
|
||
|
|
api_name=api_name,
|
||
|
|
cause=error,
|
||
|
|
)
|
||
|
|
|
||
|
|
context = ErrorContext(
|
||
|
|
component=f"{api_name}_api",
|
||
|
|
operation=operation,
|
||
|
|
user_message=f"Failed to communicate with {api_name}. Please check your configuration and try again.",
|
||
|
|
severity="error",
|
||
|
|
)
|
||
|
|
|
||
|
|
return self.handle_error(api_error, context)
|
||
|
|
|
||
|
|
def handle_validation_error(
|
||
|
|
self, error: Exception, field_name: str, operation: str
|
||
|
|
) -> Dict[str, Any]:
|
||
|
|
"""
|
||
|
|
Handle validation errors with user-friendly messages
|
||
|
|
|
||
|
|
Args:
|
||
|
|
error: The validation exception
|
||
|
|
field_name: Name of the field that failed validation
|
||
|
|
operation: Operation being performed
|
||
|
|
|
||
|
|
Returns:
|
||
|
|
Standardized error response
|
||
|
|
"""
|
||
|
|
if isinstance(error, ValidationError):
|
||
|
|
validation_error = error
|
||
|
|
else:
|
||
|
|
validation_error = ValidationError(
|
||
|
|
message=f"Invalid {field_name}: {str(error)}",
|
||
|
|
field_name=field_name,
|
||
|
|
cause=error,
|
||
|
|
)
|
||
|
|
|
||
|
|
context = ErrorContext(
|
||
|
|
component="validation",
|
||
|
|
operation=operation,
|
||
|
|
user_message=f"Please check your {field_name} and try again.",
|
||
|
|
severity="warning",
|
||
|
|
)
|
||
|
|
|
||
|
|
return self.handle_error(validation_error, context)
|
||
|
|
|
||
|
|
def handle_configuration_error(
|
||
|
|
self, error: Exception, config_key: str
|
||
|
|
) -> Dict[str, Any]:
|
||
|
|
"""
|
||
|
|
Handle configuration errors with helpful guidance
|
||
|
|
|
||
|
|
Args:
|
||
|
|
error: The configuration exception
|
||
|
|
config_key: Configuration key that caused the error
|
||
|
|
|
||
|
|
Returns:
|
||
|
|
Standardized error response
|
||
|
|
"""
|
||
|
|
if isinstance(error, ConfigurationError):
|
||
|
|
config_error = error
|
||
|
|
else:
|
||
|
|
config_error = ConfigurationError(
|
||
|
|
message=f"Configuration error for {config_key}: {str(error)}",
|
||
|
|
config_key=config_key,
|
||
|
|
cause=error,
|
||
|
|
)
|
||
|
|
|
||
|
|
# Provide helpful guidance based on config key
|
||
|
|
guidance_messages = {
|
||
|
|
"api_key": "Please check your API key configuration. Ensure the key is valid and properly set.",
|
||
|
|
"vault_path": "Please verify that the Obsidian vault path exists and is accessible.",
|
||
|
|
"url": "Please check the API URL configuration. Ensure the service is running and accessible.",
|
||
|
|
"model": "Please verify the AI model name is correct and available.",
|
||
|
|
}
|
||
|
|
|
||
|
|
user_message = guidance_messages.get(
|
||
|
|
config_key, f"Please check your {config_key} configuration."
|
||
|
|
)
|
||
|
|
|
||
|
|
context = ErrorContext(
|
||
|
|
component="configuration",
|
||
|
|
operation="load_config",
|
||
|
|
user_message=user_message,
|
||
|
|
severity="error",
|
||
|
|
)
|
||
|
|
|
||
|
|
return self.handle_error(config_error, context)
|
||
|
|
|
||
|
|
def handle_claude_configuration_error(
|
||
|
|
self, error: Exception, config_key: str = None, operation: str = "validate_config"
|
||
|
|
) -> Dict[str, Any]:
|
||
|
|
"""
|
||
|
|
Handle Claude-specific configuration errors with detailed guidance
|
||
|
|
|
||
|
|
Args:
|
||
|
|
error: The configuration exception
|
||
|
|
config_key: Specific Claude configuration key that caused the error
|
||
|
|
operation: Operation being performed when error occurred
|
||
|
|
|
||
|
|
Returns:
|
||
|
|
Standardized error response with Claude-specific guidance
|
||
|
|
"""
|
||
|
|
# Convert to Claude-specific error if not already
|
||
|
|
if isinstance(error, ClaudeConfigurationError):
|
||
|
|
claude_error = error
|
||
|
|
else:
|
||
|
|
# Create appropriate Claude error based on config key
|
||
|
|
if config_key == "api_url":
|
||
|
|
claude_error = ClaudeAPIURLError(
|
||
|
|
message=str(error),
|
||
|
|
cause=error
|
||
|
|
)
|
||
|
|
elif config_key == "model":
|
||
|
|
claude_error = ClaudeModelValidationError(
|
||
|
|
message=str(error),
|
||
|
|
cause=error
|
||
|
|
)
|
||
|
|
elif config_key == "api_key":
|
||
|
|
claude_error = ClaudeAPIKeyError(
|
||
|
|
message=str(error),
|
||
|
|
cause=error
|
||
|
|
)
|
||
|
|
else:
|
||
|
|
claude_error = ClaudeConfigurationError(
|
||
|
|
message=f"Claude configuration error for {config_key}: {str(error)}",
|
||
|
|
config_key=config_key,
|
||
|
|
cause=error
|
||
|
|
)
|
||
|
|
|
||
|
|
# Get user-friendly message with suggestions
|
||
|
|
user_message = claude_error.get_user_friendly_message()
|
||
|
|
|
||
|
|
context = ErrorContext(
|
||
|
|
component="claude_configuration",
|
||
|
|
operation=operation,
|
||
|
|
user_message=user_message,
|
||
|
|
severity="error",
|
||
|
|
)
|
||
|
|
|
||
|
|
return self.handle_error(claude_error, context)
|
||
|
|
|
||
|
|
def handle_claude_connection_error(
|
||
|
|
self, error: Exception, api_url: str = None, operation: str = "test_connection"
|
||
|
|
) -> Dict[str, Any]:
|
||
|
|
"""
|
||
|
|
Handle Claude API connection errors with specific guidance
|
||
|
|
|
||
|
|
Args:
|
||
|
|
error: The connection exception
|
||
|
|
api_url: API URL that failed to connect
|
||
|
|
operation: Operation being performed when error occurred
|
||
|
|
|
||
|
|
Returns:
|
||
|
|
Standardized error response with connection-specific guidance
|
||
|
|
"""
|
||
|
|
# Extract status code if available
|
||
|
|
status_code = None
|
||
|
|
error_str = str(error)
|
||
|
|
|
||
|
|
# Try to extract HTTP status codes from common error messages
|
||
|
|
import re
|
||
|
|
status_match = re.search(r'\b(4\d{2}|5\d{2})\b', error_str)
|
||
|
|
if status_match:
|
||
|
|
status_code = int(status_match.group(1))
|
||
|
|
|
||
|
|
# Create Claude connection error
|
||
|
|
if isinstance(error, ClaudeConnectionError):
|
||
|
|
connection_error = error
|
||
|
|
else:
|
||
|
|
connection_error = ClaudeConnectionError(
|
||
|
|
message=str(error),
|
||
|
|
api_url=api_url,
|
||
|
|
status_code=status_code,
|
||
|
|
cause=error
|
||
|
|
)
|
||
|
|
|
||
|
|
# Get user-friendly message with suggestions
|
||
|
|
user_message = connection_error.get_user_friendly_message()
|
||
|
|
|
||
|
|
context = ErrorContext(
|
||
|
|
component="claude_api",
|
||
|
|
operation=operation,
|
||
|
|
user_message=user_message,
|
||
|
|
severity="error",
|
||
|
|
)
|
||
|
|
|
||
|
|
return self.handle_error(connection_error, context)
|
||
|
|
|
||
|
|
def _sanitize_error_message(self, message: str) -> str:
|
||
|
|
"""
|
||
|
|
Sanitize error messages to prevent sensitive information exposure
|
||
|
|
|
||
|
|
Args:
|
||
|
|
message: Original error message
|
||
|
|
|
||
|
|
Returns:
|
||
|
|
Sanitized error message
|
||
|
|
"""
|
||
|
|
# List of patterns that might contain sensitive information
|
||
|
|
sensitive_patterns = [
|
||
|
|
"api_key",
|
||
|
|
"password",
|
||
|
|
"token",
|
||
|
|
"secret",
|
||
|
|
"auth",
|
||
|
|
"bearer",
|
||
|
|
"key",
|
||
|
|
"pwd",
|
||
|
|
"pass",
|
||
|
|
"credential",
|
||
|
|
"authorization",
|
||
|
|
]
|
||
|
|
|
||
|
|
sanitized = message
|
||
|
|
|
||
|
|
# Replace potential sensitive values with placeholder
|
||
|
|
import re
|
||
|
|
|
||
|
|
# Pattern 1: key=value, key:value patterns
|
||
|
|
for pattern in sensitive_patterns:
|
||
|
|
patterns_to_replace = [
|
||
|
|
# Match key=value patterns (with optional quotes)
|
||
|
|
rf'{pattern}["\']?\s*[:=]\s*["\']?[^\s"\'&,;]+["\']?',
|
||
|
|
# Match Bearer token patterns
|
||
|
|
rf"Bearer\s+[^\s]+",
|
||
|
|
# Match quoted key-value pairs
|
||
|
|
rf'["\']?{pattern}["\']?\s*:\s*["\'][^"\']+["\']',
|
||
|
|
# Match URL parameters
|
||
|
|
rf"{pattern}=[^&\s]+",
|
||
|
|
# Match JSON-like patterns
|
||
|
|
rf'["\']?{pattern}["\']?\s*:\s*["\']?[^"\'&,;\s]+["\']?',
|
||
|
|
]
|
||
|
|
|
||
|
|
for regex_pattern in patterns_to_replace:
|
||
|
|
sanitized = re.sub(
|
||
|
|
regex_pattern,
|
||
|
|
f"{pattern}=[REDACTED]",
|
||
|
|
sanitized,
|
||
|
|
flags=re.IGNORECASE,
|
||
|
|
)
|
||
|
|
|
||
|
|
# Additional patterns for common sensitive data
|
||
|
|
additional_patterns = [
|
||
|
|
# Email addresses
|
||
|
|
(
|
||
|
|
r"\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b",
|
||
|
|
"[EMAIL_REDACTED]",
|
||
|
|
),
|
||
|
|
# IP addresses (be conservative, only redact private ranges)
|
||
|
|
(
|
||
|
|
r"\b(?:10\.|172\.(?:1[6-9]|2[0-9]|3[01])\.|192\.168\.)\d{1,3}\.\d{1,3}\b",
|
||
|
|
"[IP_REDACTED]",
|
||
|
|
),
|
||
|
|
# File paths that might contain usernames
|
||
|
|
(r"/Users/[^/\s]+", "/Users/[USER_REDACTED]"),
|
||
|
|
(r"C:\\\\Users\\\\[^\\\\s]+", "C:\\\\Users\\\\[USER_REDACTED]"),
|
||
|
|
# UUIDs (might be sensitive identifiers)
|
||
|
|
(
|
||
|
|
r"\b[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}\b",
|
||
|
|
"[UUID_REDACTED]",
|
||
|
|
),
|
||
|
|
# Long hex strings that might be tokens/keys
|
||
|
|
(r"\b[0-9a-fA-F]{32,}\b", "[HEX_TOKEN_REDACTED]"),
|
||
|
|
]
|
||
|
|
|
||
|
|
for pattern, replacement in additional_patterns:
|
||
|
|
sanitized = re.sub(pattern, replacement, sanitized)
|
||
|
|
|
||
|
|
return sanitized
|
||
|
|
|
||
|
|
def _sanitize_context_data(self, data: Dict[str, Any]) -> Dict[str, Any]:
|
||
|
|
"""
|
||
|
|
Sanitize context data to prevent sensitive information exposure
|
||
|
|
|
||
|
|
Args:
|
||
|
|
data: Original context data
|
||
|
|
|
||
|
|
Returns:
|
||
|
|
Sanitized context data
|
||
|
|
"""
|
||
|
|
if not isinstance(data, dict):
|
||
|
|
return data
|
||
|
|
|
||
|
|
sanitized = {}
|
||
|
|
sensitive_keys = {
|
||
|
|
"api_key",
|
||
|
|
"password",
|
||
|
|
"token",
|
||
|
|
"secret",
|
||
|
|
"auth",
|
||
|
|
"bearer",
|
||
|
|
"key",
|
||
|
|
"pwd",
|
||
|
|
"pass",
|
||
|
|
"credential",
|
||
|
|
"authorization",
|
||
|
|
"private_key",
|
||
|
|
"client_secret",
|
||
|
|
"access_token",
|
||
|
|
"refresh_token",
|
||
|
|
}
|
||
|
|
|
||
|
|
for key, value in data.items():
|
||
|
|
key_lower = str(key).lower()
|
||
|
|
|
||
|
|
# Check if key contains sensitive information
|
||
|
|
if any(sensitive in key_lower for sensitive in sensitive_keys):
|
||
|
|
sanitized[key] = "[REDACTED]"
|
||
|
|
elif isinstance(value, dict):
|
||
|
|
# Recursively sanitize nested dictionaries
|
||
|
|
sanitized[key] = self._sanitize_context_data(value)
|
||
|
|
elif isinstance(value, str):
|
||
|
|
# Sanitize string values
|
||
|
|
sanitized[key] = self._sanitize_error_message(value)
|
||
|
|
else:
|
||
|
|
sanitized[key] = value
|
||
|
|
|
||
|
|
return sanitized
|
||
|
|
|
||
|
|
|
||
|
|
# Global error handler instance
|
||
|
|
_global_error_handler: Optional[ErrorHandler] = None
|
||
|
|
|
||
|
|
|
||
|
|
def get_error_handler() -> ErrorHandler:
|
||
|
|
"""Get the global error handler instance"""
|
||
|
|
global _global_error_handler
|
||
|
|
if _global_error_handler is None:
|
||
|
|
_global_error_handler = ErrorHandler()
|
||
|
|
return _global_error_handler
|
||
|
|
|
||
|
|
|
||
|
|
def set_error_handler(handler: ErrorHandler) -> None:
|
||
|
|
"""Set the global error handler instance"""
|
||
|
|
global _global_error_handler
|
||
|
|
_global_error_handler = handler
|
||
|
|
|
||
|
|
|
||
|
|
def audit_error_message_security(message: str) -> Dict[str, Any]:
|
||
|
|
"""
|
||
|
|
Audit an error message for potential security issues
|
||
|
|
|
||
|
|
Args:
|
||
|
|
message: Error message to audit
|
||
|
|
|
||
|
|
Returns:
|
||
|
|
Dictionary containing audit results
|
||
|
|
"""
|
||
|
|
issues = []
|
||
|
|
|
||
|
|
# Check for potential sensitive patterns
|
||
|
|
sensitive_patterns = [
|
||
|
|
(r"\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b", "email_address"),
|
||
|
|
(r"\b(?:\d{1,3}\.){3}\d{1,3}\b", "ip_address"),
|
||
|
|
(r"/Users/[^/\s]+", "user_path"),
|
||
|
|
(r"C:\\Users\\[^\\s]+", "windows_user_path"),
|
||
|
|
(r"\b[0-9a-fA-F]{32,}\b", "potential_token"),
|
||
|
|
(
|
||
|
|
r"(?i)(api_key|password|token|secret|auth|bearer)\s*[:=]\s*[^\s]+",
|
||
|
|
"credential_pattern",
|
||
|
|
),
|
||
|
|
(r"Bearer\s+[^\s]+", "bearer_token"),
|
||
|
|
]
|
||
|
|
|
||
|
|
for pattern, issue_type in sensitive_patterns:
|
||
|
|
import re
|
||
|
|
|
||
|
|
if re.search(pattern, message):
|
||
|
|
issues.append(
|
||
|
|
{
|
||
|
|
"type": issue_type,
|
||
|
|
"pattern": pattern,
|
||
|
|
"severity": "high"
|
||
|
|
if "token" in issue_type or "credential" in issue_type
|
||
|
|
else "medium",
|
||
|
|
}
|
||
|
|
)
|
||
|
|
|
||
|
|
return {
|
||
|
|
"message": message,
|
||
|
|
"has_issues": len(issues) > 0,
|
||
|
|
"issues": issues,
|
||
|
|
"risk_level": "high"
|
||
|
|
if any(issue["severity"] == "high" for issue in issues)
|
||
|
|
else "medium"
|
||
|
|
if issues
|
||
|
|
else "low",
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
def transform_pydantic_validation_error(validation_error, config_section: str = "configuration") -> ClaudeConfigurationError:
|
||
|
|
"""
|
||
|
|
Transform Pydantic validation errors into user-friendly Claude configuration errors
|
||
|
|
|
||
|
|
Args:
|
||
|
|
validation_error: Pydantic ValidationError instance
|
||
|
|
config_section: Configuration section being validated
|
||
|
|
|
||
|
|
Returns:
|
||
|
|
ClaudeConfigurationError with user-friendly message and suggestions
|
||
|
|
"""
|
||
|
|
try:
|
||
|
|
# Import pydantic ValidationError if available
|
||
|
|
from pydantic import ValidationError as PydanticValidationError
|
||
|
|
|
||
|
|
if not isinstance(validation_error, PydanticValidationError):
|
||
|
|
# Not a pydantic error, create generic error
|
||
|
|
return ClaudeConfigurationError(
|
||
|
|
message=f"Configuration validation failed: {str(validation_error)}",
|
||
|
|
cause=validation_error
|
||
|
|
)
|
||
|
|
|
||
|
|
errors = validation_error.errors()
|
||
|
|
if not errors:
|
||
|
|
return ClaudeConfigurationError(
|
||
|
|
message="Unknown validation error occurred",
|
||
|
|
cause=validation_error
|
||
|
|
)
|
||
|
|
|
||
|
|
# Process the first error (most relevant)
|
||
|
|
first_error = errors[0]
|
||
|
|
field_path = " -> ".join(str(loc) for loc in first_error.get('loc', []))
|
||
|
|
error_msg = first_error.get('msg', 'Unknown validation error')
|
||
|
|
error_type = first_error.get('type', 'unknown')
|
||
|
|
|
||
|
|
# Determine the specific field that failed
|
||
|
|
field_name = first_error.get('loc', [])[-1] if first_error.get('loc') else 'unknown'
|
||
|
|
|
||
|
|
# Create specific error based on field
|
||
|
|
if field_name == 'api_url':
|
||
|
|
return ClaudeAPIURLError(
|
||
|
|
message=f"Invalid API URL: {error_msg}",
|
||
|
|
invalid_url=first_error.get('input'),
|
||
|
|
cause=validation_error
|
||
|
|
)
|
||
|
|
elif field_name == 'model':
|
||
|
|
return ClaudeModelValidationError(
|
||
|
|
message=f"Invalid model name: {error_msg}",
|
||
|
|
invalid_model=first_error.get('input'),
|
||
|
|
cause=validation_error
|
||
|
|
)
|
||
|
|
elif field_name == 'api_key':
|
||
|
|
return ClaudeAPIKeyError(
|
||
|
|
message=f"Invalid API key: {error_msg}",
|
||
|
|
cause=validation_error
|
||
|
|
)
|
||
|
|
else:
|
||
|
|
# Generic Claude configuration error
|
||
|
|
suggestions = []
|
||
|
|
corrective_action = ""
|
||
|
|
|
||
|
|
# Add specific suggestions based on error type
|
||
|
|
if error_type == 'value_error':
|
||
|
|
suggestions.append("Check that the value meets the required format")
|
||
|
|
corrective_action = f"Update the {field_name} field with a valid value"
|
||
|
|
elif error_type == 'missing':
|
||
|
|
suggestions.append(f"The {field_name} field is required")
|
||
|
|
corrective_action = f"Add the missing {field_name} field to your configuration"
|
||
|
|
elif error_type == 'type_error':
|
||
|
|
suggestions.append(f"The {field_name} field has the wrong data type")
|
||
|
|
corrective_action = f"Ensure {field_name} is the correct data type"
|
||
|
|
|
||
|
|
return ClaudeConfigurationError(
|
||
|
|
message=f"Configuration error in {field_path}: {error_msg}",
|
||
|
|
config_key=field_name,
|
||
|
|
suggestions=suggestions,
|
||
|
|
corrective_action=corrective_action,
|
||
|
|
cause=validation_error
|
||
|
|
)
|
||
|
|
|
||
|
|
except ImportError:
|
||
|
|
# Pydantic not available, create generic error
|
||
|
|
return ClaudeConfigurationError(
|
||
|
|
message=f"Configuration validation failed: {str(validation_error)}",
|
||
|
|
cause=validation_error
|
||
|
|
)
|
||
|
|
|
||
|
|
|
||
|
|
def create_claude_configuration_help_message() -> str:
|
||
|
|
"""
|
||
|
|
Create a comprehensive help message for Claude configuration
|
||
|
|
|
||
|
|
Returns:
|
||
|
|
Formatted help message with configuration guidance
|
||
|
|
"""
|
||
|
|
return """
|
||
|
|
Claude API Configuration Help:
|
||
|
|
|
||
|
|
1. API Key (api_key):
|
||
|
|
- Required: Yes
|
||
|
|
- Format: Must start with 'sk-ant-'
|
||
|
|
- Source: Get from https://console.anthropic.com/
|
||
|
|
- Environment Variable: ANTHROPIC_API_KEY
|
||
|
|
- Example: sk-ant-api03-abc123...
|
||
|
|
|
||
|
|
2. API URL (api_url):
|
||
|
|
- Required: No (defaults to https://api.anthropic.com)
|
||
|
|
- Format: Valid HTTP/HTTPS URL
|
||
|
|
- Custom Endpoints: Supported for proxy servers or regional endpoints
|
||
|
|
- Example: https://api.anthropic.com
|
||
|
|
|
||
|
|
3. Model (model):
|
||
|
|
- Required: No (defaults to claude-3-5-sonnet-20241022)
|
||
|
|
- Format: claude-X-Y-YYYYMMDD or claude-X-Y-latest
|
||
|
|
- Supported Models:
|
||
|
|
• claude-3-5-sonnet-20241022 (recommended)
|
||
|
|
• claude-3-5-haiku-20241022
|
||
|
|
• claude-3-opus-20240229
|
||
|
|
• claude-3-sonnet-20240229
|
||
|
|
• claude-3-haiku-20240307
|
||
|
|
|
||
|
|
4. Additional Parameters:
|
||
|
|
- max_tokens: Maximum response length (1-200000, default: 4096)
|
||
|
|
- temperature: Response randomness (0.0-1.0, default: 0.7)
|
||
|
|
|
||
|
|
5. Environment Variables:
|
||
|
|
- Use ${VARIABLE_NAME} syntax in configuration
|
||
|
|
- Set required variables: export ANTHROPIC_API_KEY="your-key"
|
||
|
|
- Default values: ${VARIABLE_NAME:-default_value}
|
||
|
|
|
||
|
|
6. Common Issues:
|
||
|
|
- API Key: Ensure it's complete and starts with 'sk-ant-'
|
||
|
|
- URL: Use valid HTTP/HTTPS format without trailing slashes
|
||
|
|
- Model: Check spelling and include date suffix
|
||
|
|
- Connection: Verify network access and endpoint availability
|
||
|
|
|
||
|
|
7. Testing Configuration:
|
||
|
|
- The system will validate your configuration on startup
|
||
|
|
- Connection tests are performed automatically
|
||
|
|
- Check logs for detailed error information
|
||
|
|
|
||
|
|
For more help, visit: https://docs.anthropic.com/claude/reference/
|
||
|
|
"""
|
||
|
|
|
||
|
|
|
||
|
|
def get_claude_error_recovery_suggestions(error_type: str, context: Dict[str, Any] = None) -> List[str]:
|
||
|
|
"""
|
||
|
|
Get specific recovery suggestions based on error type and context
|
||
|
|
|
||
|
|
Args:
|
||
|
|
error_type: Type of error (e.g., 'connection', 'authentication', 'model')
|
||
|
|
context: Additional context information
|
||
|
|
|
||
|
|
Returns:
|
||
|
|
List of recovery suggestions
|
||
|
|
"""
|
||
|
|
context = context or {}
|
||
|
|
suggestions = []
|
||
|
|
|
||
|
|
if error_type == 'connection':
|
||
|
|
suggestions = [
|
||
|
|
"Check your internet connection",
|
||
|
|
"Verify the API URL is correct and accessible",
|
||
|
|
"For custom endpoints, ensure the service is running",
|
||
|
|
"Check firewall settings that might block API access",
|
||
|
|
"Try again after a short delay"
|
||
|
|
]
|
||
|
|
|
||
|
|
if context.get('api_url') and 'localhost' in context['api_url']:
|
||
|
|
suggestions.insert(2, "For localhost endpoints, ensure the local service is running")
|
||
|
|
|
||
|
|
elif error_type == 'authentication':
|
||
|
|
suggestions = [
|
||
|
|
"Verify your API key is correct and complete",
|
||
|
|
"Check that the API key starts with 'sk-ant-'",
|
||
|
|
"Ensure the API key hasn't expired",
|
||
|
|
"Verify the API key has necessary permissions",
|
||
|
|
"Get a new API key from https://console.anthropic.com/"
|
||
|
|
]
|
||
|
|
|
||
|
|
elif error_type == 'model':
|
||
|
|
suggestions = [
|
||
|
|
"Check the model name spelling and format",
|
||
|
|
"Ensure the model name includes the date suffix (YYYYMMDD)",
|
||
|
|
"Use a supported model like 'claude-3-5-sonnet-20241022'",
|
||
|
|
"Verify the model is available in your region",
|
||
|
|
"Check the Anthropic documentation for available models"
|
||
|
|
]
|
||
|
|
|
||
|
|
if context.get('invalid_model'):
|
||
|
|
suggestions.insert(0, f"Replace '{context['invalid_model']}' with a valid model name")
|
||
|
|
|
||
|
|
elif error_type == 'rate_limit':
|
||
|
|
suggestions = [
|
||
|
|
"Wait before making more API requests",
|
||
|
|
"Implement request throttling in your application",
|
||
|
|
"Consider upgrading your API plan for higher limits",
|
||
|
|
"Reduce the frequency of API calls",
|
||
|
|
"Use exponential backoff for retries"
|
||
|
|
]
|
||
|
|
|
||
|
|
elif error_type == 'validation':
|
||
|
|
suggestions = [
|
||
|
|
"Check your configuration file syntax",
|
||
|
|
"Ensure all required fields are present",
|
||
|
|
"Verify data types match the expected format",
|
||
|
|
"Remove any extra or invalid configuration fields",
|
||
|
|
"Use the configuration example as a reference"
|
||
|
|
]
|
||
|
|
|
||
|
|
else:
|
||
|
|
# Generic suggestions
|
||
|
|
suggestions = [
|
||
|
|
"Check your Claude API configuration",
|
||
|
|
"Verify all required fields are present and correct",
|
||
|
|
"Review the error message for specific details",
|
||
|
|
"Consult the configuration documentation",
|
||
|
|
"Try restarting the application after fixing the configuration"
|
||
|
|
]
|
||
|
|
|
||
|
|
return suggestions
|