Source code for atloop.runtime.tool_runtime
"""Tool runtime for executing tools in sandbox (legacy compatibility layer)."""
from typing import List, Optional
from atloop.runtime.sandbox_adapter import SandboxAdapter
from atloop.tools.base import ToolResult
from atloop.tools.registry import ToolRegistry
[docs]
class ToolRuntime:
"""
Tool runtime for executing tools in sandbox.
This is a legacy compatibility layer. New code should use ToolRegistry directly.
"""
[docs]
def __init__(self, sandbox: SandboxAdapter, skill_loader=None):
"""
Initialize tool runtime.
Args:
sandbox: Sandbox adapter instance
skill_loader: Optional skill loader instance for read_skill_file tool
"""
self.sandbox = sandbox
self.registry = ToolRegistry(sandbox, skill_loader=skill_loader)
[docs]
def run(self, cmd: str, timeout_sec: int = 600) -> ToolResult:
"""
Execute shell command.
Args:
cmd: Shell command to execute
timeout_sec: Command timeout in seconds
Returns:
ToolResult instance
"""
return self.registry.execute("run", {"cmd": cmd, "timeout_sec": timeout_sec})
[docs]
def list_tree(
self, max_depth: int = 4, ignore_patterns: Optional[List[str]] = None
) -> ToolResult:
"""
List file tree.
Args:
max_depth: Maximum depth to traverse
ignore_patterns: Patterns to ignore (e.g., ["node_modules", ".git"])
Returns:
ToolResult with file tree
"""
if ignore_patterns is None:
ignore_patterns = ["node_modules", "dist", "build", ".git", "__pycache__"]
# Build find command with ignore patterns
import shlex
ignore_parts = []
for pattern in ignore_patterns:
ignore_parts.append(f"-name {shlex.quote(pattern)} -prune")
if ignore_parts:
ignore_expr = " -o ".join(ignore_parts)
cmd = (
f"find . -maxdepth {max_depth} \\( {ignore_expr} -o -type f -print \\) | head -100"
)
else:
cmd = f"find . -maxdepth {max_depth} -type f | head -100"
return self.run(cmd, timeout_sec=60)
[docs]
def search(self, query: str, glob: Optional[str] = None, max_results: int = 50) -> ToolResult:
"""
Search using grep.
Args:
query: Search query (treated as regex pattern)
glob: Glob pattern to filter files (e.g., "*.py", "**/*.js")
max_results: Maximum number of results
Returns:
ToolResult with search results
"""
return self.registry.execute(
"search",
{
"query": query,
"glob": glob,
"max_results": max_results,
},
)
[docs]
def read_file(
self,
path: str,
start_line: int = 1,
end_line: Optional[int] = None,
offset: Optional[int] = None,
limit: Optional[int] = None,
) -> ToolResult:
"""
Read file lines (legacy method - use ToolRegistry.execute("read_file", ...) instead).
Args:
path: File path
start_line: Start line number (1-indexed) - legacy parameter
end_line: End line number (1-indexed) - legacy parameter
offset: Line number to start reading from (1-indexed) - new parameter
limit: Number of lines to read - new parameter
Returns:
ToolResult with file content
"""
args = {"path": path}
if offset is not None:
args["offset"] = offset
if limit is not None:
args["limit"] = limit
elif end_line is not None:
# Legacy support: convert end_line to limit
args["offset"] = start_line
args["limit"] = end_line - start_line + 1
return self.registry.execute("read_file", args)
[docs]
def write_file(self, path: str, content: str) -> ToolResult:
"""
Write file (legacy method - use ToolRegistry.execute("write_file", ...) instead).
Args:
path: File path
content: File content
Returns:
ToolResult
"""
return self.registry.execute("write_file", {"path": path, "content": content})
[docs]
def git_diff(self) -> ToolResult:
"""
Get git diff (legacy method - use ToolRegistry.execute("run", {"cmd": "git diff"}) instead).
Returns:
ToolResult with git diff output
"""
return self.run("git diff", timeout_sec=30)
[docs]
def apply_patch(self, patch: str) -> ToolResult:
"""
Apply patch (legacy method - use ToolRegistry.execute("run", {"cmd": "git apply"}) instead).
Args:
patch: Patch content
Returns:
ToolResult
"""
# Write patch to temporary file and apply
import tempfile
with tempfile.NamedTemporaryFile(mode="w", suffix=".patch", delete=False) as f:
f.write(patch)
patch_file = f.name
try:
# Use git apply or patch command
result = self.run(
f"git apply {patch_file} 2>&1 || patch -p1 < {patch_file} 2>&1", timeout_sec=30
)
finally:
import os
try:
os.unlink(patch_file)
except Exception:
pass
return result