"""Centralized configuration management.""" from dataclasses import dataclass, field from typing import Optional import os from dotenv import load_dotenv @dataclass class TimingConfig: """Timing configuration for various operations.""" screenshot_delay: float = 0.5 action_delay: float = 0.1 navigation_delay: float = 1.0 click_delay: float = 0.05 typing_delay_ms: int = 12 @dataclass class ScreenConfig: """Screen and display configuration.""" width: int = 1024 height: int = 768 display_number: int = 1 @dataclass class NetworkConfig: """Network access configuration.""" block_all: bool = False allow_list: str = "0.0.0.0/0" @dataclass class SandboxConfig: """Sandbox lifecycle configuration.""" auto_stop_interval: int = 30 # minutes auto_archive_interval: Optional[int] = None auto_delete_interval: Optional[int] = None @dataclass class ClaudeConfig: """Claude API configuration.""" model: str = "claude-sonnet-4-5" max_tokens: int = 4096 max_screenshots_in_history: int = 0 max_messages_in_history: int = 20 # Keep only last N messages (user + assistant pairs) api_timeout: float = 300.0 # 5 minutes max_retries: int = 3 auto_screenshot_after_action: bool = False # Capture screenshot after each action (needed for Gemini, not Claude) @dataclass class GUIConfig: """GUI and browser configuration for desktop automation.""" initial_url: str = "https://www.google.com" search_engine_url: str = "https://www.google.com/" user_agent: Optional[str] = None @dataclass class ExecutionConfig: """Code execution configuration.""" default_timeout: int = 60 max_timeout: int = 300 allow_package_install: bool = True @dataclass class DaytonaConfig: """Complete Daytona configuration.""" # Core configs timing: TimingConfig = field(default_factory=TimingConfig) screen: ScreenConfig = field(default_factory=ScreenConfig) network: NetworkConfig = field(default_factory=NetworkConfig) sandbox: SandboxConfig = field(default_factory=SandboxConfig) claude: ClaudeConfig = field(default_factory=ClaudeConfig) gui: GUIConfig = field(default_factory=GUIConfig) execution: ExecutionConfig = field(default_factory=ExecutionConfig) # API Keys daytona_api_key: Optional[str] = None anthropic_api_key: Optional[str] = None @classmethod def from_env(cls) -> "DaytonaConfig": """Create configuration from environment variables. Returns: DaytonaConfig instance with values from environment """ # Load .env file if it exists load_dotenv() config = cls() # Load API keys config.daytona_api_key = os.environ.get("DAYTONA_API_KEY") config.anthropic_api_key = os.environ.get("ANTHROPIC_API_KEY") # Override with environment variables if present if screen_width := os.environ.get("DAYTONA_SCREEN_WIDTH"): config.screen.width = int(screen_width) if screen_height := os.environ.get("DAYTONA_SCREEN_HEIGHT"): config.screen.height = int(screen_height) if model := os.environ.get("CLAUDE_MODEL"): config.claude.model = model if initial_url := os.environ.get("DAYTONA_INITIAL_URL"): config.gui.initial_url = initial_url if auto_stop := os.environ.get("DAYTONA_AUTO_STOP"): config.sandbox.auto_stop_interval = int(auto_stop) return config def validate(self) -> list[str]: """Validate configuration. Returns: List of validation errors (empty if valid) """ errors = [] # Validate API keys if not self.daytona_api_key: errors.append("DAYTONA_API_KEY is required") if not self.anthropic_api_key: errors.append("ANTHROPIC_API_KEY is required") # Validate screen size if self.screen.width <= 0 or self.screen.height <= 0: errors.append(f"Invalid screen size: {self.screen.width}x{self.screen.height}") # Validate timing if self.timing.screenshot_delay < 0: errors.append("screenshot_delay must be non-negative") # Validate timeouts if self.claude.api_timeout <= 0: errors.append("API timeout must be positive") if self.execution.default_timeout <= 0: errors.append("Execution timeout must be positive") if self.execution.max_timeout < self.execution.default_timeout: errors.append("max_timeout must be greater than or equal to default_timeout") return errors # Global default configuration instance DEFAULT_CONFIG = DaytonaConfig()