hermes-agent/terminal_tool.py
2025-07-25 15:15:36 +00:00

244 lines
No EOL
8.1 KiB
Python

#!/usr/bin/env python3
"""
Terminal Tools Module
This module provides terminal/command execution tools using Hecate's VM infrastructure.
It wraps Hecate's functionality to provide a simple interface for executing commands
on Morph VMs with automatic lifecycle management.
Available tools:
- terminal_execute_tool: Execute a single command and get output
- terminal_session_tool: Execute a command in a persistent session
Usage:
from terminal_tool import terminal_execute_tool, terminal_session_tool
# Execute a single command
result = terminal_execute_tool("ls -la")
# Execute in a session (for interactive commands)
result = terminal_session_tool("python", input_keys="print('hello')\\nexit()\\n")
"""
import json
import os
from typing import Optional
from hecate import run_tool_with_lifecycle_management
from morphcloud._llm import ToolCall
def terminal_execute_tool(
command: str,
background: bool = False,
timeout: Optional[int] = None
) -> str:
"""
Execute a command on a Morph VM and return the output.
This tool uses Hecate's VM lifecycle management to automatically create
and manage VMs. VMs are reused within the configured lifetime window
and automatically cleaned up after inactivity.
Args:
command: The command to execute
background: Whether to run the command in the background (default: False)
timeout: Command timeout in seconds (optional)
Returns:
str: JSON string containing the command output, exit code, and any errors
Example:
>>> result = terminal_execute_tool("ls -la /tmp")
>>> print(json.loads(result))
{
"output": "total 8\\ndrwxrwxrwt 2 root root 4096 ...",
"exit_code": 0,
"error": null
}
"""
try:
# Create tool call for Hecate
tool_input = {
"command": command,
"background": background
}
if timeout is not None:
tool_input["timeout"] = timeout
tool_call = ToolCall(
name="run_command",
input=tool_input
)
# Execute with lifecycle management
result = run_tool_with_lifecycle_management(tool_call)
# Format the result
formatted_result = {
"output": result.get("output", ""),
"exit_code": result.get("returncode", result.get("exit_code", -1)),
"error": result.get("error")
}
# Add session info if present (for interactive sessions)
if "session_id" in result:
formatted_result["session_id"] = result["session_id"]
if "screen" in result:
formatted_result["screen"] = result["screen"]
return json.dumps(formatted_result)
except Exception as e:
return json.dumps({
"output": "",
"exit_code": -1,
"error": f"Failed to execute command: {str(e)}"
})
def terminal_session_tool(
command: Optional[str] = None,
input_keys: Optional[str] = None,
session_id: Optional[str] = None,
idle_threshold: float = 5.0
) -> str:
"""
Execute a command in an interactive terminal session.
This tool is useful for:
- Running interactive programs (vim, python REPL, etc.)
- Maintaining state between commands
- Sending keystrokes to running programs
Args:
command: Command to start a new session (optional if continuing existing session)
input_keys: Keystrokes to send to the session (e.g., "hello\\n" for typing hello + Enter)
session_id: ID of existing session to continue (optional)
idle_threshold: Seconds to wait for output before considering session idle (default: 5.0)
Returns:
str: JSON string containing session info, screen content, and any errors
Example:
# Start a Python REPL session
>>> result = terminal_session_tool("python")
>>> session_data = json.loads(result)
>>> session_id = session_data["session_id"]
# Send commands to the session
>>> result = terminal_session_tool(
... input_keys="print('Hello, World!')\\n",
... session_id=session_id
... )
"""
try:
tool_input = {}
if command:
tool_input["command"] = command
if input_keys:
tool_input["input_keys"] = input_keys
if session_id:
tool_input["session_id"] = session_id
if idle_threshold != 5.0:
tool_input["idle_threshold"] = idle_threshold
tool_call = ToolCall(
name="run_command",
input=tool_input
)
# Execute with lifecycle management
result = run_tool_with_lifecycle_management(tool_call)
# Format the result for session tools
formatted_result = {
"session_id": result.get("session_id"),
"screen": result.get("screen", ""),
"exit_code": result.get("returncode", result.get("exit_code", 0)),
"error": result.get("error"),
"status": "active" if result.get("session_id") else "ended"
}
# Include output if present (for non-interactive commands)
if "output" in result:
formatted_result["output"] = result["output"]
return json.dumps(formatted_result)
except Exception as e:
return json.dumps({
"session_id": None,
"screen": "",
"exit_code": -1,
"error": f"Failed to manage session: {str(e)}",
"status": "error"
})
def check_hecate_requirements() -> bool:
"""
Check if all requirements for terminal tools are met.
Returns:
bool: True if all requirements are met, False otherwise
"""
# Check for required environment variables
required_vars = ["MORPH_API_KEY"]
optional_vars = ["OPENAI_API_KEY"] # Needed for Hecate's LLM features
missing_required = [var for var in required_vars if not os.getenv(var)]
missing_optional = [var for var in optional_vars if not os.getenv(var)]
if missing_required:
print(f"Missing required environment variables: {', '.join(missing_required)}")
return False
if missing_optional:
print(f"Warning: Missing optional environment variables: {', '.join(missing_optional)}")
print(" (Some Hecate features may be limited)")
# Check if Hecate is importable
try:
import hecate
return True
except ImportError:
print("Hecate is not installed. Please install it with: pip install hecate")
return False
# Module-level initialization check
_requirements_met = check_hecate_requirements()
if __name__ == "__main__":
"""
Simple test/demo when run directly
"""
print("Terminal Tools Module")
print("=" * 40)
if not _requirements_met:
print("Requirements not met. Please check the messages above.")
exit(1)
print("All requirements met!")
print("\nAvailable Tools:")
print(" - terminal_execute_tool: Execute single commands")
print(" - terminal_session_tool: Interactive terminal sessions")
print("\nUsage Examples:")
print(" # Execute a command")
print(" result = terminal_execute_tool('ls -la')")
print(" ")
print(" # Start an interactive session")
print(" result = terminal_session_tool('python')")
print(" session_data = json.loads(result)")
print(" session_id = session_data['session_id']")
print(" ")
print(" # Send input to the session")
print(" result = terminal_session_tool(")
print(" input_keys='print(\"Hello\")\\\\n',")
print(" session_id=session_id")
print(" )")
print("\nEnvironment Variables:")
print(f" MORPH_API_KEY: {'Set' if os.getenv('MORPH_API_KEY') else 'Not set'}")
print(f" OPENAI_API_KEY: {'Set' if os.getenv('OPENAI_API_KEY') else 'Not set (optional)'}")
print(f" HECATE_VM_LIFETIME_SECONDS: {os.getenv('HECATE_VM_LIFETIME_SECONDS', '300')} (default: 300)")