261 lines
8.9 KiB
Python
Executable File
261 lines
8.9 KiB
Python
Executable File
#!/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()
|