fix(tools): correct Python-fallback bugs in calculator, file tools, and git tool (#236)

- calculator: add `ln` alias for `math.log`, replace `^` with `**` before
  AST parsing so caret-as-power works, convert SyntaxError → ValueError for
  consistent error handling, and return `math.inf` on division by zero
  (matching the documented meval behaviour) instead of raising an exception
- file_read / file_write: replace the hardcoded `str(path).startswith(str(d) + "/")
  guard with `Path.is_relative_to()` so allowed-directory checks work on
  Windows (and any OS whose separator is not `/`)
- git_tool: catch `NotADirectoryError` raised by `subprocess.run` on Windows
  when `cwd` points to a non-existent path, returning a proper error result

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: Robby Manihani <manihani@stanford.edu>
This commit is contained in:
Taco Engineer
2026-05-07 00:41:58 -07:00
committed by GitHub
co-authored by Claude Sonnet 4.6 Robby Manihani
parent 9e7366fc05
commit f0ab045cad
4 changed files with 20 additions and 7 deletions
+12 -3
View File
@@ -36,6 +36,7 @@ _MATH_FUNCS = {
"max": max,
"sqrt": math.sqrt,
"log": math.log,
"ln": math.log, # alias: ln(x) == log(x)
"log10": math.log10,
"log2": math.log2,
"sin": math.sin,
@@ -84,7 +85,7 @@ def _safe_eval_node(node: ast.AST) -> Any:
val = _MATH_FUNCS[name]
if isinstance(val, (int, float)):
return val
raise ValueError(f"Unknown variable: {name}")
raise ValueError(f"unknown variable: {name}")
raise ValueError(f"Unsupported expression type: {type(node).__name__}")
@@ -98,8 +99,16 @@ def safe_eval(expression: str) -> float:
except ImportError:
import ast as _ast
tree = _ast.parse(expression, mode="eval")
return float(_safe_eval_node(tree.body))
# Support ^ as the power operator (common math/calculator notation).
expression = expression.replace("^", "**")
try:
tree = _ast.parse(expression, mode="eval")
except SyntaxError as exc:
raise ValueError(f"Syntax error in expression: {exc}") from exc
try:
return float(_safe_eval_node(tree.body))
except ZeroDivisionError:
return math.inf
@ToolRegistry.register("calculator")
+1 -2
View File
@@ -53,8 +53,7 @@ class FileReadTool(BaseTool):
return True
resolved = path.resolve()
return any(
resolved == d or str(resolved).startswith(str(d) + "/")
for d in self._allowed_dirs
resolved == d or resolved.is_relative_to(d) for d in self._allowed_dirs
)
def execute(self, **params: Any) -> ToolResult:
+1 -2
View File
@@ -69,8 +69,7 @@ class FileWriteTool(BaseTool):
return True
resolved = path.resolve()
return any(
resolved == d or str(resolved).startswith(str(d) + "/")
for d in self._allowed_dirs
resolved == d or resolved.is_relative_to(d) for d in self._allowed_dirs
)
def execute(self, **params: Any) -> ToolResult:
+6
View File
@@ -76,6 +76,12 @@ def _run_git(
content="git binary not found.",
success=False,
)
except NotADirectoryError as exc:
return ToolResult(
tool_name=tool_name,
content=f"Invalid repository path: {exc}",
success=False,
)
except subprocess.TimeoutExpired:
return ToolResult(
tool_name=tool_name,