feat: per-task model/effort/approval + relay models endpoint (#59)
Co-authored-by: host <host@test.local>
This commit is contained in:
1 parent
cff152c5a2
commit
8b2f3968dc
9 files changed
+521
-16
No files matched your search
+54
-3
@@ -83,6 +83,8 @@ from cloud.oauth import (
|
||||
start_oauth_login,
|
||||
unbind_external_identity,
|
||||
)
|
||||
from cloud.relay import RelayError as _RelayError
|
||||
from cloud.relay import get_relay_models as _get_relay_models
|
||||
from cloud.repos import validate_repo_url
|
||||
from cloud.push import (
|
||||
create_subscription as _push_create,
|
||||
@@ -389,6 +391,12 @@ def migrate_tasks_table(conn):
|
||||
# HA 阶段 1:认领任务的 worker 所在主机名;/pr 据此判断
|
||||
# worktree 在本机(同步跑)还是别机(排 pr 任务给该主机)。
|
||||
"worktree_host": "TEXT",
|
||||
# 任务级选项(console 选模型/强度/权限用):NULL = 跟随
|
||||
# worker 配置默认值;model 不做白名单(中转目录会变,
|
||||
# 下拉只是建议值),effort/approval 由 _create_task 校验。
|
||||
"model": "TEXT",
|
||||
"effort": "TEXT",
|
||||
"approval": "TEXT",
|
||||
}
|
||||
for name, coltype in new_cols.items():
|
||||
if name not in cols:
|
||||
@@ -524,6 +532,9 @@ def _task_to_dict(row):
|
||||
"verify_command": _row_get(row, "verify_command"),
|
||||
"goal_max_rounds": _row_get(row, "goal_max_rounds"),
|
||||
"worktree_host": _row_get(row, "worktree_host"),
|
||||
"model": _row_get(row, "model"),
|
||||
"effort": _row_get(row, "effort"),
|
||||
"approval": _row_get(row, "approval"),
|
||||
}
|
||||
|
||||
|
||||
@@ -817,6 +828,8 @@ class CloudControlPlaneHandler(BaseHTTPRequestHandler):
|
||||
self._metrics(user_id)
|
||||
elif path == "/models" and method == "GET":
|
||||
self._models_overview(user_id)
|
||||
elif path == "/relay/models" and method == "GET":
|
||||
self._relay_models(user_id)
|
||||
elif path.startswith("/projects/"):
|
||||
self._dispatch_project(user_id, method, path, query)
|
||||
elif path == "/tasks" and method == "POST":
|
||||
@@ -1295,6 +1308,20 @@ class CloudControlPlaneHandler(BaseHTTPRequestHandler):
|
||||
self._send_json(200, {"task_id": task_id,
|
||||
"project_id": task["project_id"], **report})
|
||||
|
||||
def _relay_models(self, user_id):
|
||||
# 中转可用模型列表:服务端凭环境变量里的 relay key 现取
|
||||
# 现回,只把模型 id 交给调用者。key 绝不进响应、日志、
|
||||
# 缓存(见 cloud/relay.py)。单测可经 server.relay_fetcher
|
||||
# 注入假取数函数;中转挂了或未配 key 时 502,不编造列表。
|
||||
fetcher = getattr(self.server, "relay_fetcher", None)
|
||||
try:
|
||||
models, cached, updated_at = _get_relay_models(fetcher=fetcher)
|
||||
except _RelayError as exc:
|
||||
self._send_json(502, {"error": str(exc)})
|
||||
return
|
||||
self._send_json(200, {
|
||||
"models": models, "cached": cached, "updated_at": updated_at})
|
||||
|
||||
def _models_overview(self, user_id):
|
||||
# 优先级列表来自环境变量 MODELS_PRIORITY(逗号分隔),未设时
|
||||
# 用默认列表;这只是控制面的默认配置,不是 provider 的承诺。
|
||||
@@ -1382,6 +1409,28 @@ class CloudControlPlaneHandler(BaseHTTPRequestHandler):
|
||||
self._send_json(400, {
|
||||
"error": "executor must be local, docker or cloud"})
|
||||
return
|
||||
# 任务级选项:model 不做白名单(中转目录会变,下拉只是
|
||||
# 建议值);effort 必须是 codex 的 effort 档位;approval
|
||||
# 是 console 三档(ask/auto/full),非法值 400 且不建任务。
|
||||
# 三者都缺省(NULL)时 worker 走配置默认值,行为与过去一致。
|
||||
model = body.get("model")
|
||||
if model is not None:
|
||||
if not isinstance(model, str) or not model.strip():
|
||||
self._send_json(400, {
|
||||
"error": "model must be a non-empty string"})
|
||||
return
|
||||
model = model.strip()
|
||||
effort = body.get("effort")
|
||||
if effort is not None and effort not in (
|
||||
"low", "medium", "high", "xhigh", "max"):
|
||||
self._send_json(400, {
|
||||
"error": "effort must be low, medium, high, xhigh or max"})
|
||||
return
|
||||
approval = body.get("approval")
|
||||
if approval is not None and approval not in ("ask", "auto", "full"):
|
||||
self._send_json(400, {
|
||||
"error": "approval must be ask, auto or full"})
|
||||
return
|
||||
# goal 字段:verify_command 非空即 goal 模式(worker 走
|
||||
# host.py goal 循环);goal 文本缺省用 prompt;轮次缺省 3
|
||||
# (与 host.py 默认一致),非法值 400 且不建任务。
|
||||
@@ -1431,12 +1480,14 @@ class CloudControlPlaneHandler(BaseHTTPRequestHandler):
|
||||
"INSERT INTO tasks(id, project_id, prompt, status,"
|
||||
" idempotency_key, depends_on, priority, created_by,"
|
||||
" created_at, updated_at, last_heartbeat, executor,"
|
||||
" goal, verify_command, goal_max_rounds)"
|
||||
" goal, verify_command, goal_max_rounds,"
|
||||
" model, effort, approval)"
|
||||
" VALUES(?, ?, ?, 'queued', ?, ?, ?, ?, ?, ?, NULL, ?,"
|
||||
" ?, ?, ?)",
|
||||
" ?, ?, ?, ?, ?, ?)",
|
||||
(task_id, project_id, prompt, idempotency_key,
|
||||
json.dumps(depends_on), priority, user_id, now, now,
|
||||
executor, goal, verify_command, goal_max_rounds),
|
||||
executor, goal, verify_command, goal_max_rounds,
|
||||
model, effort, approval),
|
||||
)
|
||||
conn.commit()
|
||||
except (sqlite3.IntegrityError, _PgIntegrityError):
|
||||
|
||||
Reference in new issue
Block a user