sub2api 网关接入:每用户独立账号 + 任务注入 + 网关可配置

- cloud/sub2api.py:管理 API 客户端(管理员登录/token 刷新/
  建用户/以用户发 key/删用户/查组/网关模型目录)+ 幂等开通
  ensure_user_gateway_key(入库失败尽力清理孤儿 key 与账号)
- worker:网关门(sub2api 时按 created_by 幂等开通,失败任务
  直接 failed;协调员子任务跟父任务属主)+ 默认模型走网关
  目录首个;三条执行路径(Local popen/同步、Docker 同步)注
  入任务级 env 与 --model-provider;NO_PROXY 追加网关 host
- host.py:--model-provider,sub2api 时以 -c 注入 provider 表
  (不改租户 config.toml);未配 SUB2API_BASE_URL 时 loud fail
- sandbox:Local/Docker 执行器传 --model-provider 与任务 env;
  docker 密钥只传名(-e SUB2API_API_KEY),值由 CLI 子进程
  环境提供,绝不进 argv;runner 不支持 env 时 loud fail
- server:OAuth 建号 best-effort 开通;/relay/models 走调用者
  自己的网关模型目录;init_db 补 user_gateway_keys 表
- 测试:新单测 11 个;全量 cloud 679(4 跳过)、host 65、
  deploy 19 全过
- 诚实边界:真模型调用实弹需上游账号挂组后才做;切流前
  xcmapi 保持现网
This commit is contained in:
host committed 2026-10-27 17:22:54 +08:00
1 parent 437684902a
commit ec47328d3c
7 files changed
+996 -54

No files matched your search

+32 -6
View File
@@ -319,28 +319,49 @@ def _append_trace_marker(state_path, task_name, entry):
pass
def _server_command(codex_bin):
def _server_command(codex_bin, provider=None):
"""app-server 启动命令:-c 必须放在子命令之前。
shell_environment_policy.exclude 只过滤 agent shell 看到的
变量,不影响 server 进程自己的中转调用(env_key 直读进程 env)。
provider="sub2api" 时再叠加 -c 覆盖:model_provider 与
provider 表都按任务进程生效,不改任何租户的 config.toml
(多网关任务同项目并发时互不干扰)。网关地址取环境变量
SUB2API_BASE_URL(origin,不带 /v1);未配则 loud fail,
绝不静默跑回默认网关。
"""
return [
cmd = [
codex_bin,
"-c",
"shell_environment_policy.exclude=" + json.dumps(SHELL_ENV_EXCLUDE),
"app-server",
]
if provider is not None:
if provider != "sub2api":
raise ValueError("未知模型 provider: %r" % provider)
origin = os.environ.get("SUB2API_BASE_URL", "").strip().rstrip("/")
if not origin:
raise RuntimeError(
"provider=sub2api 但 SUB2API_BASE_URL 未配置")
cmd += [
"-c", 'model_provider="sub2api"',
"-c", 'model_providers.sub2api.name="Sub2API"',
"-c", 'model_providers.sub2api.base_url="%s/v1"' % origin,
"-c", 'model_providers.sub2api.env_key="SUB2API_API_KEY"',
"-c", 'model_providers.sub2api.wire_api="responses"',
]
cmd.append("app-server")
return cmd
class Server:
"""单个 app-server 进程,多 thread 并发。"""
def __init__(self, codex_bin=None, codex_home=None, approval="deny",
progress_interval=30):
progress_interval=30, model_provider=None):
self.codex_bin = codex_bin or default_codex_bin()
self.codex_home = codex_home
self.approval = approval
self.model_provider = model_provider
# 流式进度间隔(秒):run_turn 等待时每满一个间隔打一次
# 进度;0 表示关闭进度输出,但中断轮询仍每秒进行。
self.progress_interval = progress_interval
@@ -396,7 +417,8 @@ class Server:
os.makedirs(self.codex_home, exist_ok=True)
env["CODEX_HOME"] = self.codex_home
self.proc = subprocess.Popen(
_server_command(self.codex_bin),
_server_command(self.codex_bin,
provider=self.model_provider),
stdin=subprocess.PIPE, stdout=subprocess.PIPE,
stderr=subprocess.DEVNULL, env=env,
)
@@ -1995,6 +2017,9 @@ def main():
help="项目配置文件,默认 host.py 同目录的 host-config.json(若存在)")
ap.add_argument("--state", default=None)
ap.add_argument("--model", default=None)
ap.add_argument("--model-provider", default=None,
help="模型 provider 覆盖:sub2api 时以 -c 注入网关"
" provider(不改 config.toml);不传则走配置")
ap.add_argument("--effort", default=None)
ap.add_argument("--sandbox", default=None)
ap.add_argument("--approval-policy", default=None)
@@ -2381,7 +2406,8 @@ def main():
return
srv = Server(codex_bin=args.codex_bin, codex_home=args.codex_home,
approval=args.approval,
progress_interval=args.progress_interval)
progress_interval=args.progress_interval,
model_provider=args.model_provider)
# 实时链路:只给跑 turn 的命令设链路文件(dispatch/run/goal/
# delegate);只读命令不设。名字映射与 worker 派发一致:
# dispatch/goal/delegate 用 --name,run 用 --task。