#!/usr/bin/env python3 """ OpenHands SDK Wrapper for n8n Integration (FIXED VERSION) Usage: python3 /home/bam/openhands-sdk-wrapper-fixed.py "task description" This script runs OpenHands in SDK mode without Docker containers, perfect for n8n workflow integration. FIXED: Ensures files persist to host filesystem """ import sys import os import json import argparse import shutil from datetime import datetime from pathlib import Path # Add SDK to path SDK_PATH = '/tmp/software-agent-sdk' sys.path.insert(0, SDK_PATH) try: from openhands.sdk import LLM, Agent, Conversation, Tool from openhands.tools.file_editor import FileEditorTool from openhands.tools.task_tracker import TaskTrackerTool except ImportError as e: print(f"โŒ Failed to import OpenHands SDK: {e}") print("Make sure SDK is built: cd /tmp/software-agent-sdk && make build") sys.exit(1) def run_openhands_task(task, workspace="/home/bam", verbose=True): """ Execute an OpenHands task using the SDK Args: task: Task description string workspace: Working directory path verbose: Print detailed logs Returns: dict: Results including success status and output """ results = { 'success': False, 'task': task, 'workspace': workspace, 'timestamp': datetime.now().isoformat(), 'error': None, 'files_created': [], 'files_copied': [], 'log_output': [] } try: if verbose: print(f"๐Ÿš€ Starting OpenHands SDK execution...") print(f"๐Ÿ“‹ Task: {task}") print(f"๐Ÿ“ Workspace: {workspace}") print("-" * 50) # Create workspace directory if it doesn't exist workspace_path = Path(workspace) workspace_path.mkdir(parents=True, exist_ok=True) # Load environment env_file = '/home/bam/openhands/.env' if os.path.exists(env_file): with open(env_file, 'r') as f: for line in f: if '=' in line and not line.startswith('#'): key, value = line.strip().split('=', 1) os.environ[key] = value # Configure LLM api_key = os.getenv('MINIMAX_API_KEY') if not api_key: raise ValueError("MINIMAX_API_KEY not found in environment") llm = LLM( model="openai/MiniMax-M2", api_key=api_key, base_url="https://api.minimax.io/v1" ) if verbose: print("โœ… LLM configured successfully") # Create agent with tools agent = Agent( llm=llm, tools=[ Tool(name=FileEditorTool.name), Tool(name=TaskTrackerTool.name), ], ) if verbose: print("โœ… Agent created with tools") # Start conversation with workspace conversation = Conversation(agent=agent, workspace=str(workspace_path)) if verbose: print("๐Ÿ’ฌ Conversation started") # Send task and run conversation.send_message(task) if verbose: print("๐Ÿ“ค Task sent to agent") print("โณ Running agent execution...") conversation.run() if verbose: print("โœ… Agent execution completed") # NOW COPY FILES TO HOST FILESYSTEM # This is the critical fix - OpenHands workspace may be isolated # We need to copy any files created to the host filesystem workspace_path_str = str(workspace_path) if verbose: print("๐Ÿ”„ Checking for files to copy to host filesystem...") # List all files in workspace files_created = [] if os.path.exists(workspace_path_str): for item in os.listdir(workspace_path_str): item_path = os.path.join(workspace_path_str, item) if os.path.isfile(item_path): files_created.append(item) # Copy files from OpenHands workspace to host workspace # The OpenHands SDK creates files in its own workspace # We need to ensure they're visible on the host for filename in files_created: source_path = os.path.join(workspace_path_str, filename) # Skip if it's the SDK wrapper itself or other system files if filename in ['openhands-sdk-wrapper.py', 'openhands-sdk-wrapper-fixed.py']: continue # Copy to host workspace host_dest = os.path.join('/home/bam', filename) try: if verbose: print(f" ๐Ÿ“‹ Copying {filename} to host filesystem") shutil.copy2(source_path, host_dest) results['files_copied'].append(filename) results['files_created'].append(filename) except Exception as e: if verbose: print(f" โš ๏ธ Failed to copy {filename}: {e}") # Also check for common directories that might have been created common_dirs = ['src', 'test', 'build', 'dist', '.git'] for dirname in common_dirs: dir_path = os.path.join(workspace_path_str, dirname) if os.path.isdir(dir_path): host_dir = os.path.join('/home/bam', dirname) try: if verbose: print(f" ๐Ÿ“‹ Copying directory {dirname} to host filesystem") if os.path.exists(host_dir): shutil.rmtree(host_dir) shutil.copytree(dir_path, host_dir) results['files_copied'].append(dirname + '/') except Exception as e: if verbose: print(f" โš ๏ธ Failed to copy directory {dirname}: {e}") # Also list files directly in /home/bam that might have been created # This is a fallback in case OpenHands writes directly to host for filename in os.listdir('/home/bam'): if filename not in results['files_created']: filepath = os.path.join('/home/bam', filename) if os.path.isfile(filepath): results['files_created'].append(filename) results['success'] = True if verbose: print("-" * 50) print(f"โœ… Task completed successfully!") print(f"๐Ÿ“„ Files created in workspace: {results['files_created']}") if results['files_copied']: print(f"๐Ÿ“ฆ Files copied to host: {results['files_copied']}") except Exception as e: results['error'] = str(e) if verbose: print(f"โŒ Task failed: {e}") import traceback traceback.print_exc() return results def main(): """Main entry point for the wrapper script""" parser = argparse.ArgumentParser(description='OpenHands SDK Wrapper for n8n (Fixed)') parser.add_argument('task', help='Task description to execute') parser.add_argument('--workspace', default='/home/bam', help='Working directory') parser.add_argument('--quiet', action='store_true', help='Suppress verbose output') parser.add_argument('--json', action='store_true', help='Output results as JSON') args = parser.parse_args() # Run the task results = run_openhands_task( task=args.task, workspace=args.workspace, verbose=not args.quiet ) # Output results if args.json: print(json.dumps(results, indent=2)) else: if results['success']: print("SUCCESS") if results['files_created']: print(f"Files created: {', '.join(results['files_created'])}") else: print("FAILED") if results['error']: print(f"Error: {results['error']}") # Exit with appropriate code sys.exit(0 if results['success'] else 1) if __name__ == "__main__": # If called without arguments, assume direct execution (for testing) if len(sys.argv) == 1: # Default test task task = "Create a file named sdk-wrapper-test-fixed.txt with content: SDK wrapper FIXED test successful!" print(f"๐Ÿงช Running default test task: {task}") results = run_openhands_task(task) print("\n" + "="*50) print("RESULTS:") print(f"Success: {results['success']}") print(f"Files created: {results['files_created']}") if results['files_copied']: print(f"Files copied to host: {results['files_copied']}") if results['error']: print(f"Error: {results['error']}") # Check if file was created if results['success']: test_file = '/home/bam/sdk-wrapper-test-fixed.txt' if os.path.exists(test_file): print("\nโœ… File content:") with open(test_file, 'r') as f: print(f.read()) else: print("\nโš ๏ธ File not found on host filesystem") else: main()