diff --git a/tools/code_execution_tool.py b/tools/code_execution_tool.py index c5ab9b71c..7de62c7fd 100644 --- a/tools/code_execution_tool.py +++ b/tools/code_execution_tool.py @@ -17,6 +17,7 @@ Architecture: Platform: Linux / macOS only (Unix domain sockets). Disabled on Windows. """ +import ast import json import logging import os @@ -369,6 +370,35 @@ def execute_code( if not code or not code.strip(): return json.dumps({"error": "No code provided."}) + # --- Syntax validation: catch Python syntax errors before spawning sandbox --- + try: + ast.parse(code, filename="") + except SyntaxError as exc: + # Format a clear, actionable error for the LLM + lineno = exc.lineno or "?" + offset = exc.offset + # Show context: the offending source line if available + lines = code.splitlines() + context_line = "" + if isinstance(lineno, int) and 0 < lineno <= len(lines): + context_line = lines[lineno - 1].rstrip() + error_parts = [ + f"SyntaxError on line {lineno}", + f"{exc.msg}", + ] + if offset and context_line: + # Show the line with a caret pointing to the offset + error_parts.append(f"> {context_line}") + error_parts.append(f"> {' ' * (offset - 1)}^") + elif context_line: + error_parts.append(f"> {context_line}") + return json.dumps({ + "status": "error", + "error": "\n".join(error_parts), + "tool_calls_made": 0, + "duration_seconds": 0, + }, ensure_ascii=False) + # Import interrupt event from terminal_tool (cooperative cancellation) from tools.terminal_tool import _interrupt_event