SRHarness / src /sr_harness /tools /code_executor.py
yuzihan@SIM1
Deploy SRHarness Docker Space
d06fa57
Raw History Blame Contribute Delete
6.28 kB
# Copyright (c) 2026-present, Yumeow. Licensed under the MIT License.
"""Execute data-only Python programs in an operating-system sandbox."""
from __future__ import annotations
import io
import json
from typing import Any, Dict
from ..runtime import get_sandbox_runner
from ..utils import bounded_value
from .base_tool import BaseTool, ToolMetadata
class LimitedWriter(io.StringIO):
"""String buffer retained for bounded-output shell utilities."""
SUFFIX = "...[truncated]"
def __init__(self, limit: int):
super().__init__()
self.limit = limit
def write(self, text: str) -> int:
remaining = self.limit - self.tell() - len(self.SUFFIX)
if remaining <= 0:
return len(text)
if len(text) > remaining:
super().write(text[:remaining])
super().write(self.SUFFIX)
return len(text)
return super().write(text)
@BaseTool.register("code_executor")
class CodeExecutorTool(BaseTool):
"""Implementation of the code executor tool."""
metadata = ToolMetadata(name="code_executor")
DEFAULT_TIMEOUT_SECONDS = 30
DEFAULT_MEMORY_LIMIT_MB = 1024
DEFAULT_OUTPUT_LIMIT_BYTES = 64 * 1024
MAX_TIMEOUT_SECONDS = 120
MAX_MEMORY_LIMIT_MB = 4096
MAX_OUTPUT_LIMIT_BYTES = 1024 * 1024
def execute(
self,
program: str,
timeout_seconds: int = DEFAULT_TIMEOUT_SECONDS,
memory_limit_mb: int = DEFAULT_MEMORY_LIMIT_MB,
output_limit_bytes: int = DEFAULT_OUTPUT_LIMIT_BYTES,
) -> Dict[str, Any]:
"""Execute Python code and return printed output.
1) Use `import sys, json; data_dict = json.loads(sys.stdin.read())` to
access data mapping variable names to lists.
2) Use `print()` to produce output.
3) The code is executed in an operating-system sandbox with resource limits.
4) Installed scientific Python libraries are available. Files outside the
ephemeral sandbox, networking, and child-process creation are unavailable.
Args:
program: Python code string to execute, starting with `import sys, json;
data_dict = json.loads(sys.stdin.read())` to access input data.
timeout_seconds: Wall-clock timeout in seconds. The effective value is capped.
memory_limit_mb: Address-space memory limit in MB. The effective value is capped.
output_limit_bytes: Limit on the amount of output (in bytes) that can be produced.
"""
timeout_seconds = self.bounded_int(
timeout_seconds, self.DEFAULT_TIMEOUT_SECONDS, 1, self.MAX_TIMEOUT_SECONDS
)
memory_limit_mb = self.bounded_int(
memory_limit_mb, self.DEFAULT_MEMORY_LIMIT_MB, 64, self.MAX_MEMORY_LIMIT_MB
)
output_limit_bytes = self.bounded_int(
output_limit_bytes, self.DEFAULT_OUTPUT_LIMIT_BYTES, 1024, self.MAX_OUTPUT_LIMIT_BYTES
)
stdin_text = json.dumps(self.serialization(self.context.data), ensure_ascii=False)
result = get_sandbox_runner().run(
self.extract_code(program),
stdin=stdin_text,
timeout_seconds=timeout_seconds,
memory_limit_mb=memory_limit_mb,
output_limit_bytes=output_limit_bytes,
interruption_event=getattr(self, "cancel_event", None),
)
if result.exit_code != 0:
raise RuntimeError(result.stderr or f"Sandboxed program exited with {result.exit_code}")
return {
"stdout": result.stdout,
"stderr": result.stderr,
"duration": result.duration_seconds,
"cpu_seconds": result.cpu_seconds,
"peak_memory_bytes": result.peak_memory_bytes,
"timeout_seconds": timeout_seconds,
"memory_limit_mb": memory_limit_mb,
"output_limit_bytes": output_limit_bytes,
}
@classmethod
def format_result_dict(cls, result: Dict[str, Any]) -> str:
"""Format a tool result for the language model.
Args:
result: Result mapping to format or update.
Returns:
str: The operation result.
"""
parts = []
if result["stdout"]:
parts.append(result["stdout"].rstrip())
if result["stderr"]:
parts.append(f"Program stderr:\n{result['stderr'].rstrip()}")
if not parts:
parts.append("Program completed successfully and printed no output.")
parts.append(f"(Execution duration: {result['duration']:.3f} seconds.)")
return "\n".join(parts)
@classmethod
def extract_code(cls, code: str) -> str:
"""Extract Python source from an optional Markdown fence."""
raw_code = str(code).strip()
if "```python" in raw_code:
return raw_code.split("```python")[-1].split("```")[0].strip()
if "```" not in raw_code:
return raw_code
parts = raw_code.split("```")
if len(parts) <= 1:
return raw_code
potential_code = parts[1]
if "\n" not in potential_code:
return potential_code.strip()
first_line, rest = potential_code.split("\n", 1)
return rest.strip() if first_line.strip().isalpha() else potential_code.strip()
@classmethod
def serialization(cls, value: Any) -> Any:
"""Convert context data to JSON-compatible values."""
if value is None or isinstance(value, (str, int, float, bool)):
return value
if isinstance(value, dict):
return {str(key): cls.serialization(item) for key, item in value.items()}
if isinstance(value, (list, tuple, set)):
return [cls.serialization(item) for item in value]
if hasattr(value, "tolist"):
return cls.serialization(value.tolist())
if hasattr(value, "item"):
return cls.serialization(value.item())
raise TypeError(f"unsupported data type {type(value).__name__}")
@classmethod
def bounded_int(cls, value: Any, default: int, minimum: int, maximum: int) -> int:
"""Return a bounded integer tool argument."""
return bounded_value(value, min=minimum, max=maximum, default=default, converter=int)