#!/usr/bin/env python3 """ OpenHands Build Agent Script Executes build and test tasks using OpenHands SDK. Designed for GitHub Actions integration. Usage: python agent_build.py Environment Variables (all required): LLM_API_KEY: API key for the LLM TASK: Build task to execute REPO_NAME: Name of the repository COMMIT_SHA: Commit SHA being built Environment Variables (optional): LLM_MODEL: Language model to use (default: anthropic/claude-sonnet-4-5-20250929) LLM_BASE_URL: Optional base URL for LLM API RETRY_COUNT: Attempt number (for retry logic) PREVIOUS_ERRORS: Error messages from previous attempts Example: export LLM_API_KEY="sk-..." export TASK="Build and test the project" export REPO_NAME="my-project" export COMMIT_SHA="abc123def456" python agent_build.py """ import os import sys import logging import json from pathlib import Path from datetime import datetime from openhands.sdk import LLM, Conversation, get_logger from openhands.tools.preset.default import get_default_agent def setup_logging(): """Configure logging for the build process.""" # Create logs directory logs_dir = Path("build_logs") logs_dir.mkdir(exist_ok=True) # Setup logger logger = get_logger(__name__) # Create file handler log_file = logs_dir / f"build_{datetime.now().strftime('%Y%m%d_%H%M%S')}.log" file_handler = logging.FileHandler(log_file) file_handler.setLevel(logging.INFO) # Create console handler console_handler = logging.StreamHandler() console_handler.setLevel(logging.INFO) # Format formatter = logging.Formatter( '%(asctime)s - %(name)s - %(levelname)s - %(message)s' ) file_handler.setFormatter(formatter) console_handler.setFormatter(formatter) # Get root logger root_logger = logging.getLogger() root_logger.setLevel(logging.INFO) root_logger.addHandler(file_handler) root_logger.addHandler(console_handler) return logger, log_file def load_context(): """Load build context from environment variables.""" required_vars = ['LLM_API_KEY', 'TASK', 'REPO_NAME', 'COMMIT_SHA'] missing = [var for var in required_vars if not os.getenv(var)] if missing: raise ValueError(f"Missing required environment variables: {missing}") return { 'api_key': os.getenv('LLM_API_KEY'), 'task': os.getenv('TASK'), 'repo_name': os.getenv('REPO_NAME'), 'commit_sha': os.getenv('COMMIT_SHA'), 'retry_count': os.getenv('RETRY_COUNT', '0'), 'previous_errors': os.getenv('PREVIOUS_ERRORS', ''), 'model': os.getenv('LLM_MODEL', 'anthropic/claude-sonnet-4-5-20250929'), 'base_url': os.getenv('LLM_BASE_URL', ''), } def create_llm(config): """Create and configure LLM instance.""" llm_config = { 'model': config['model'], 'api_key': config['api_key'], 'usage_id': f"build-{config['repo_name']}-{config['commit_sha'][:8]}", 'drop_params': True, } if config['base_url']: llm_config['base_url'] = config['base_url'] logger.info(f"Initializing LLM: {config['model']}") return LLM(**llm_config) def build_enhanced_task(config): """Build enhanced task prompt with context.""" task_parts = [] # Header task_parts.append("=" * 80) task_parts.append(f"BUILD TASK: {config['repo_name']}") task_parts.append(f"Commit: {config['commit_sha']}") if int(config['retry_count']) > 0: task_parts.append(f"Retry Attempt: {config['retry_count']}") task_parts.append("=" * 80) task_parts.append("") # Previous errors (for retry) if config['previous_errors']: task_parts.append("PREVIOUS BUILD ERRORS:") task_parts.append("-" * 80) task_parts.append(config['previous_errors']) task_parts.append("-" * 80) task_parts.append("") task_parts.append("Please analyze these errors and fix them in this retry attempt.") task_parts.append("") # Main task task_parts.append("TASK:") task_parts.append(config['task']) task_parts.append("") # Instructions task_parts.append("INSTRUCTIONS:") task_parts.append("1. Analyze the project structure and identify build system") task_parts.append("2. Install dependencies (npm install, pip install, etc.)") task_parts.append("3. Run build commands (npm run build, ./build.sh, etc.)") task_parts.append("4. Execute tests (npm test, pytest, etc.)") task_parts.append("5. Report detailed results:") task_parts.append(" - Dependencies installed: YES/NO") task_parts.append(" - Build completed: YES/NO") task_parts.append(" - Tests passed: YES/NO") task_parts.append(" - All errors must be documented") task_parts.append("") task_parts.append("6. If errors occur:") task_parts.append(" - Analyze the error messages") task_parts.append(" - Attempt to fix them") task_parts.append(" - Retry build process") task_parts.append(" - Document what was fixed") task_parts.append("") task_parts.append("SUCCESS CRITERIA:") task_parts.append("- All dependencies installed successfully") task_parts.append("- Build completes without errors") task_parts.append("- All tests pass") task_parts.append("") task_parts.append("=" * 80) return "\n".join(task_parts) def save_build_metadata(config, status, duration): """Save build metadata to JSON file.""" metadata = { 'repo_name': config['repo_name'], 'commit_sha': config['commit_sha'], 'retry_count': int(config['retry_count']), 'status': status, 'duration_seconds': duration, 'timestamp': datetime.now().isoformat(), 'model': config['model'], } metadata_file = Path("build_metadata.json") with open(metadata_file, 'w') as f: json.dump(metadata, f, indent=2) logger.info(f"Build metadata saved to {metadata_file}") def main(): """Execute build task with OpenHands SDK.""" start_time = datetime.now() status = 'unknown' try: # Setup logger, log_file = setup_logging() logger.info("=" * 80) logger.info("Starting OpenHands Build") logger.info("=" * 80) # Load context config = load_context() logger.info(f"Loaded configuration: {config['repo_name']}@{config['commit_sha'][:8]}") # Create LLM llm = create_llm(config) # Create agent with default tools logger.info("Initializing OpenHands agent with default tools") agent = get_default_agent(llm=llm, cli_mode=True) # Create conversation with workspace cwd = Path.cwd() logger.info(f"Workspace: {cwd}") conversation = Conversation(agent=agent, workspace=cwd) # Build enhanced task task = build_enhanced_task(config) logger.info(f"Task prepared ({len(task)} characters)") logger.debug(f"Task:\n{task}") # Execute task logger.info("Sending task to OpenHands agent...") conversation.send_message(task) logger.info("Running agent conversation...") result = conversation.run() # Calculate duration end_time = datetime.now() duration = (end_time - start_time).total_seconds() # Save metadata status = 'success' save_build_metadata(config, status, duration) logger.info("=" * 80) logger.info(f"Build completed successfully in {duration:.2f} seconds") logger.info(f"Logs saved to: {log_file}") logger.info("=" * 80) return 0 except Exception as e: # Calculate duration end_time = datetime.now() duration = (end_time - start_time).total_seconds() # Log error logger.error("=" * 80) logger.error(f"Build failed after {duration:.2f} seconds") logger.error(f"Error: {str(e)}") logger.error("=" * 80) # Try to save metadata even on failure try: config = load_context() status = 'failure' save_build_metadata(config, status, duration) except Exception: pass return 1 if __name__ == "__main__": sys.exit(main())