Files
codex-app-server/cloud/sandbox.py
T

123 lines
4.4 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""沙箱执行器:可插拔的任务执行接口。
零第三方依赖,只用 Python 标准库;不重写 agent 逻辑。
- Executor:基类,子类必须实现 execute(task)。
- LocalExecutor:先行实现,复用 host.py 现有 worktree + codex 沙箱,
通过 host.py CLI 子进程做只读查询封装;不直接跑模型。
- DockerExecutor:占位,未接通容器执行。
- CloudExecutor:占位,未接通云端执行。
"""
import shutil
import subprocess
import sys
MAX_STDOUT_CHARS = 2000
class Executor:
"""执行器基类。
task 是 dict,含 id、project_id、repo、prompt、branch(可选)。
execute(task) -> dict,至少含 executor、task_id、status、stdout。
"""
def execute(self, task) -> dict:
raise NotImplementedError("子类必须实现 execute(task)")
def _truncate_stdout(text):
if text is None:
return ""
return str(text)[:MAX_STDOUT_CHARS]
def _default_runner(cmd):
"""subprocess.run 的薄封装:捕获输出、文本模式。"""
return subprocess.run(cmd, capture_output=True, text=True)
class LocalExecutor(Executor):
"""本地执行器:调 host.py CLI 子进程做执行封装。
真正跑模型回合的 dispatch/run 由控制面在确认模型 key 供给后才
调用,本模块只做执行器骨架和 CLI 调用封装。因此 execute() 在此
只发 host.py 的只读命令(例如 status),绝不在 execute 里直接
跑模型回合;需要跑模型时,控制面另行调用 host.py dispatch/run。
构造函数:LocalExecutor(host_py_path, config_path=None, runner=None)。
runner 默认是 subprocess.run 的薄封装;单测可注入假 runner,假
runner 返回 CompletedProcess 风格的 result 对象(含 returncode、
stdout),本类只读这两个字段,不依赖其它字段。
"""
def __init__(self, host_py_path, config_path=None, runner=None):
self.host_py_path = host_py_path
self.config_path = config_path
self.runner = runner if runner is not None else _default_runner
def _command(self, task_id):
cmd = [sys.executable, self.host_py_path]
if self.config_path is not None:
cmd += ["--config", self.config_path]
cmd += ["status", "--task", str(task_id)]
return cmd
def execute(self, task) -> dict:
task_id = task.get("id") if isinstance(task, dict) else None
if task_id is None:
raise ValueError("任务缺少 id")
result = self.runner(self._command(task_id))
returncode = getattr(result, "returncode", 1)
stdout = _truncate_stdout(getattr(result, "stdout", ""))
return {
"executor": "local",
"task_id": task_id,
"status": "completed" if returncode == 0 else "failed",
"stdout": stdout,
"returncode": returncode,
}
class DockerExecutor(Executor):
"""容器执行器(占位,未接通)。
本机无 docker(shutil.which("docker") 为 None)时 raise
RuntimeError("docker not available"),绝不静默降级为本地执行。
有 docker 时也只返回占位失败,不真跑容器。
后续接线:容器挂载(把任务 worktree 以只读/受限写挂载进容器)、
网络隔离(默认无外网,仅放行模型 API 所需出口)、资源限额与
容器回收,届时再替换本占位实现。
"""
def execute(self, task) -> dict:
task_id = task.get("id") if isinstance(task, dict) else None
if task_id is None:
raise ValueError("任务缺少 id")
if shutil.which("docker") is None:
raise RuntimeError("docker not available")
return {
"executor": "docker",
"task_id": task_id,
"status": "failed",
"stdout": "docker executor not implemented: "
"container mount and network isolation pending",
"returncode": None,
}
class CloudExecutor(Executor):
"""云端执行器(占位,未接通)。
execute(task) 一律 raise RuntimeError,绝不假装已接通云端。
接通前提:真实云账号 + 预算上限配置,届时由控制面注入凭证与
限额后再替换本占位实现。
"""
def execute(self, task) -> dict:
raise RuntimeError(
"cloud executor not connected: missing cloud account "
"and budget limit")