81 lines
2.6 KiB
Python
81 lines
2.6 KiB
Python
"""容器内 fs 工具 helper(DockerExecutor 通过 `docker exec python tool_runner.py` 调用)。
|
|
|
|
调用约定:
|
|
- argv[1] = tool name(read / write / edit / glob / grep)
|
|
- stdin = JSON 序列化的 args(用 stdin 而非 argv 是为了不被 shell metachar 切路径,
|
|
CJK / 引号 / 路径分隔符全透明传)
|
|
- stdout = tool execute 返回的文本(LLM 拿到的)
|
|
- exit code = 0 ok / 1 工具内部抛异常 / 2 参数 / unknown tool
|
|
|
|
base_dir = `os.getcwd()` ── docker exec --workdir /workspace/<wd> 已切到 task 工作目录
|
|
user_root = `/workspace` ── bind mount 边界,Tool._display 据此渲相对路径
|
|
|
|
不依赖任何 zcbot 自家包外的东西,纯用 `tools.fs` 五个 Tool 子类。容器镜像里
|
|
`/sandbox/tools/` 是 host repo `tools/` 目录的拷贝(Dockerfile `COPY tools/`)。
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
import os
|
|
import sys
|
|
import traceback
|
|
from pathlib import Path
|
|
|
|
|
|
# 镜像里 /sandbox/ 下放了 tools/ 的拷贝,让 import 走 /sandbox/
|
|
sys.path.insert(0, "/sandbox")
|
|
|
|
from tools.fs import EditTool, GlobTool, GrepTool, ReadTool, WriteTool # noqa: E402
|
|
|
|
|
|
TOOLS = {
|
|
"read": ReadTool,
|
|
"write": WriteTool,
|
|
"edit": EditTool,
|
|
"glob": GlobTool,
|
|
"grep": GrepTool,
|
|
}
|
|
|
|
|
|
def main() -> int:
|
|
if len(sys.argv) < 2:
|
|
print("[Error] tool_runner: missing tool name argv[1]", file=sys.stderr)
|
|
return 2
|
|
name = sys.argv[1]
|
|
if name not in TOOLS:
|
|
print(f"[Error] tool_runner: unknown tool: {name}", file=sys.stderr)
|
|
return 2
|
|
|
|
try:
|
|
args_raw = sys.stdin.read()
|
|
args = json.loads(args_raw) if args_raw.strip() else {}
|
|
except json.JSONDecodeError as e:
|
|
print(f"[Error] tool_runner: invalid JSON args: {e}", file=sys.stderr)
|
|
return 2
|
|
|
|
cls = TOOLS[name]
|
|
tool = cls(base_dir=Path(os.getcwd()), user_root=Path("/workspace"))
|
|
try:
|
|
result = tool.execute(**args)
|
|
except TypeError as e:
|
|
print(f"[Error] bad arguments to {name}: {e}", file=sys.stderr)
|
|
return 2
|
|
except Exception as e:
|
|
# 容器内 Tool 抛异常(IO / 权限等)── 落 stderr + 退非 0,DockerExecutor
|
|
# 兜底成 ToolResult content;traceback 限 80 行防爆 LLM context
|
|
print(f"[Error executing {name}] {type(e).__name__}: {e}", file=sys.stderr)
|
|
tb = traceback.format_exc().splitlines()
|
|
if len(tb) > 80:
|
|
tb = tb[:40] + [f"... ({len(tb) - 40} lines truncated) ..."]
|
|
print("\n".join(tb), file=sys.stderr)
|
|
return 1
|
|
|
|
if not isinstance(result, str):
|
|
result = str(result)
|
|
sys.stdout.write(result)
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|