terminal tool

This commit is contained in:
hjc-puro 2025-07-26 04:31:17 +00:00
parent 122d8788ae
commit a49596cbb2
3 changed files with 121 additions and 185 deletions

View file

@ -1,67 +1,85 @@
#!/usr/bin/env python3
"""
Terminal Tools Module
Terminal Tool Module
This module provides terminal/command execution tools using Hecate's VM infrastructure.
This module provides a single terminal tool 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
Available tool:
- terminal_tool: Execute commands with optional interactive session support
Usage:
from terminal_tool import terminal_execute_tool, terminal_session_tool
from terminal_tool import terminal_tool
# Execute a single command
result = terminal_execute_tool("ls -la")
result = terminal_tool("ls -la")
# Execute in a session (for interactive commands)
result = terminal_session_tool("python", input_keys="print('hello')\\nexit()\\n")
# Execute in an interactive session
result = terminal_tool("python", input_keys="print('hello')\\nexit()\\n")
"""
import json
import os
from typing import Optional
from typing import Optional, Dict, Any
from hecate import run_tool_with_lifecycle_management
from morphcloud._llm import ToolCall
def terminal_execute_tool(
command: str,
def terminal_tool(
command: Optional[str] = None,
input_keys: Optional[str] = None,
session_id: Optional[str] = None,
background: bool = False,
idle_threshold: float = 5.0,
timeout: Optional[int] = None
) -> str:
"""
Execute a command on a Morph VM and return the output.
Execute a command on a Morph VM with optional interactive session support.
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)
command: The command to execute (optional if continuing existing session)
input_keys: Keystrokes to send to interactive session (e.g., "hello\\n")
session_id: ID of existing session to continue (optional)
background: Whether to run the command in the background (default: False)
idle_threshold: Seconds to wait for output before considering session idle (default: 5.0)
timeout: Command timeout in seconds (optional)
Returns:
str: JSON string containing the command output, exit code, and any errors
str: JSON string containing command output, session info, 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
}
Examples:
# Execute a simple command
>>> result = terminal_tool(command="ls -la /tmp")
# Start an interactive Python session
>>> result = terminal_tool(command="python3")
>>> session_data = json.loads(result)
>>> session_id = session_data["session_id"]
# Send input to the session
>>> result = terminal_tool(input_keys="print('Hello')\\n", session_id=session_id)
# Run a background task
>>> result = terminal_tool(command="sleep 60", background=True)
"""
try:
# Create tool call for Hecate
tool_input = {
"command": command,
"background": background
}
# Build tool input based on provided parameters
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 background:
tool_input["background"] = background
if idle_threshold != 5.0:
tool_input["idle_threshold"] = idle_threshold
if timeout is not None:
tool_input["timeout"] = timeout
@ -73,104 +91,25 @@ def terminal_execute_tool(
# Execute with lifecycle management
result = run_tool_with_lifecycle_management(tool_call)
# Format the result
# Format the result with all possible fields
formatted_result = {
"output": result.get("output", ""),
"screen": result.get("screen", ""),
"session_id": result.get("session_id"),
"exit_code": result.get("returncode", result.get("exit_code", -1)),
"error": result.get("error")
"error": result.get("error"),
"status": "active" if result.get("session_id") else "ended"
}
# 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": "",
"session_id": None,
"exit_code": -1,
"error": f"Failed to manage session: {str(e)}",
"error": f"Failed to execute terminal command: {str(e)}",
"status": "error"
})
@ -211,7 +150,7 @@ if __name__ == "__main__":
"""
Simple test/demo when run directly
"""
print("Terminal Tools Module")
print("Terminal Tool Module")
print("=" * 40)
if not _requirements_met:
@ -219,26 +158,29 @@ if __name__ == "__main__":
exit(1)
print("All requirements met!")
print("\nAvailable Tools:")
print(" - terminal_execute_tool: Execute single commands")
print(" - terminal_session_tool: Interactive terminal sessions")
print("\nAvailable Tool:")
print(" - terminal_tool: Execute commands with optional interactive session support")
print("\nUsage Examples:")
print(" # Execute a command")
print(" result = terminal_execute_tool('ls -la')")
print(" result = terminal_tool(command='ls -la')")
print(" ")
print(" # Start an interactive session")
print(" result = terminal_session_tool('python')")
print(" result = terminal_tool(command='python3')")
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(" result = terminal_tool(")
print(" input_keys='print(\"Hello\")\\\\n',")
print(" session_id=session_id")
print(" )")
print(" ")
print(" # Run a background task")
print(" result = terminal_tool(command='sleep 60', background=True)")
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)")
print(f" HECATE_VM_LIFETIME_SECONDS: {os.getenv('HECATE_VM_LIFETIME_SECONDS', '300')} (default: 300)")
print(f" HECATE_DEFAULT_SNAPSHOT_ID: {os.getenv('HECATE_DEFAULT_SNAPSHOT_ID', 'snapshot_p5294qxt')} (default: snapshot_p5294qxt)")