feat: per-task model/effort/approval + relay models endpoint (#59)

Co-authored-by: host <host@test.local>
This commit is contained in:
openqjandhost authored and GitHub committed 2026-10-27 01:44:53 +08:00
1 parent cff152c5a2
commit 8b2f3968dc
9 files changed
+521 -16

No files matched your search

+51 -11
View File
@@ -133,6 +133,27 @@ def _default_runner(cmd):
return subprocess.run(cmd, capture_output=True, text=True)
def _approval_cli_args(approval):
"""console 权限三档 → host.py 顶层全局参数。
请求批准(ask):approvalPolicy=untrusted,敏感操作先问;
帮我批准(auto):approvalPolicy=never,自动批准,仍受
工作区沙箱限制;完全访问(full):approvalPolicy=never
+ sandbox=danger-full-access,不再受工作区限制(危险)。
None 或未知值:不传参数,host.py 走配置文件默认值。
docker 任务的配置沙箱本身就是 danger-full-access,所以
full 档对 docker 任务无变化,变化只在 approvalPolicy。
"""
if approval == "ask":
return ["--approval-policy", "untrusted"]
if approval == "auto":
return ["--approval-policy", "never"]
if approval == "full":
return ["--approval-policy", "never",
"--sandbox", "danger-full-access"]
return []
class LocalExecutor(Executor):
"""本地执行器:调 host.py CLI 子进程做执行封装。
@@ -166,13 +187,16 @@ class LocalExecutor(Executor):
# 不改 host.py 的 dispatch/run 签名。
self.model = model
def _base_command(self, model=None):
def _base_command(self, model=None, effort=None, approval=None):
cmd = [sys.executable, self.host_py_path]
if self.config_path is not None:
cmd += ["--config", self.config_path]
effective = model or self.model
if effective:
cmd += ["--model", effective]
if effort:
cmd += ["--effort", effort]
cmd += _approval_cli_args(approval)
return cmd
def _command(self, task_id):
@@ -256,7 +280,9 @@ class LocalExecutor(Executor):
raise ValueError("任务缺少 id")
if not task.get("prompt"):
raise ValueError("任务缺少 prompt")
cmd = self._base_command(model=task.get("model"))
cmd = self._base_command(model=task.get("model"),
effort=task.get("effort"),
approval=task.get("approval"))
cmd += ["dispatch", "--name", self._host_name(task),
"--task", task["prompt"]]
repo = task.get("repo") or self.repo
@@ -277,7 +303,9 @@ class LocalExecutor(Executor):
raise ValueError("任务缺少 id")
if not task.get("prompt"):
raise ValueError("任务缺少 prompt")
cmd = self._base_command(model=task.get("model"))
cmd = self._base_command(model=task.get("model"),
effort=task.get("effort"),
approval=task.get("approval"))
cmd += ["run", "--task", self._host_name(task),
"--prompt", task["prompt"]]
return self._result_from_runner(
@@ -303,7 +331,9 @@ class LocalExecutor(Executor):
if not goal_text:
raise ValueError("任务缺少 prompt")
rounds = task.get("goal_max_rounds") or 3
cmd = self._base_command(model=task.get("model"))
cmd = self._base_command(model=task.get("model"),
effort=task.get("effort"),
approval=task.get("approval"))
cmd += ["goal", "--name", self._host_name(task),
"--task", goal_text, "--verify", verify,
"--max-rounds", str(rounds)]
@@ -371,7 +401,9 @@ class LocalExecutor(Executor):
raise ValueError("任务缺少 id")
if not task.get("prompt"):
raise ValueError("任务缺少 prompt")
cmd = self._base_command(model=task.get("model"))
cmd = self._base_command(model=task.get("model"),
effort=task.get("effort"),
approval=task.get("approval"))
if kind == "run":
cmd += ["run", "--task", self._host_name(task),
"--prompt", task["prompt"]]
@@ -636,7 +668,8 @@ class DockerExecutor(Executor):
cmd += list(inner_cmd)
return cmd
def _inner_host_command(self, *args, model=None):
def _inner_host_command(self, *args, model=None, effort=None,
approval=None):
"""容器内 host.py 命令:python3 /app/host.py [args...]。
config_path 若已给,容器内用 /app/<basename> 指代它——
@@ -644,8 +677,9 @@ class DockerExecutor(Executor):
本就放在项目目录);不在代码目录时不静默猜路径,
由 _inner_host_command 的调用方在传 config 前自行确认。
这里只做最简单的同名映射,不展开宿主绝对路径进容器。
--model 是 host.py 的顶层全局参数,必须放在子命令之前,
所以 model 在这里插到 --config 之后、子命令之前。
--model/--effort/--approval-policy/--sandbox 是 host.py
的顶层全局参数,必须放在子命令之前,所以在这里插到
--config 之后、子命令之前。
"""
cmd = ["python3", "/app/host.py"]
if self.config_path is not None:
@@ -654,6 +688,9 @@ class DockerExecutor(Executor):
effective = model or self.model
if effective:
cmd += ["--model", effective]
if effort:
cmd += ["--effort", effort]
cmd += _approval_cli_args(approval)
cmd += list(args)
return cmd
@@ -790,7 +827,8 @@ class DockerExecutor(Executor):
raise ValueError("任务缺少 prompt")
inner = self._inner_host_command(
"dispatch", "--name", self._host_name(task),
"--task", task["prompt"], model=task.get("model"))
"--task", task["prompt"], model=task.get("model"),
effort=task.get("effort"), approval=task.get("approval"))
repo = task.get("repo")
if repo:
inner += ["--repo", repo]
@@ -808,7 +846,8 @@ class DockerExecutor(Executor):
raise ValueError("任务缺少 prompt")
inner = self._inner_host_command(
"run", "--task", self._host_name(task),
"--prompt", task["prompt"], model=task.get("model"))
"--prompt", task["prompt"], model=task.get("model"),
effort=task.get("effort"), approval=task.get("approval"))
return self._run_in_container(task, inner, timeout=timeout,
extra_mounts=extra_mounts,
network=network,
@@ -836,7 +875,8 @@ class DockerExecutor(Executor):
inner = self._inner_host_command(
"goal", "--name", self._host_name(task),
"--task", goal_text, "--verify", verify,
"--max-rounds", str(rounds), model=task.get("model"))
"--max-rounds", str(rounds), model=task.get("model"),
effort=task.get("effort"), approval=task.get("approval"))
repo = task.get("repo")
if repo:
inner += ["--repo", repo]