fix: 支持 Sub2API 非默认分组账号回读

This commit is contained in:
pikastech
2026-07-24 05:08:24 +02:00
parent 852b958101
commit 730a062b13
5 changed files with 44 additions and 3 deletions
@@ -284,6 +284,16 @@ bun scripts/cli.ts platform-infra sub2api codex-pool cleanup-probes --target D60
- 手工账号需要采用模板时:
- 写入使用显式 `runtime apply --account ...` 或 `--accounts ... --template ... --confirm`。
- 删除使用相同显式 selector 的 `runtime delete --kind temp-unschedulable --confirm`。
- 向非默认分组创建 runtime-manual 账号时:
- 先用 `upstream-groups runtime get --group <id-or-exact-name>` 解析分组;
- `runtime create --group` 优先传回读得到的正整数 group ID;
- `--platform` 使用该分组回读的 platform,不根据上游协议名称猜测;
- API Key 只经标准输入传入,先 dry-run,再执行一次 confirm
- 创建后使用
`codex-pool runtime get --group <id-or-exact-name> --account <id-or-exact-name>`
精确回读非默认分组账号;
- confirm 和 get 都必须披露分组、代理、base URL、模型映射、运行策略与对账
结果,但不得输出 API Key。
- 账号全局 `priority` 使用 `runtime apply --kind priority --priority <1-1000>`
- 数值越小优先级越高,不存在分组级 priority;
- 单账号和精准批量都必须先 dry-run,确认写入后逐账号回读并显示 before、desired、actual 与 reconciled
@@ -144,7 +144,7 @@ export function parseRuntimeOptions(args: string[]): RuntimeOptions {
if (pageToken !== null && (!/^\d+$/u.test(pageToken) || pageToken.length > 20)) throw new Error("--page-token must be a numeric group cursor");
if (group !== null && allGroups) throw new Error("use only one of --group or --all-groups");
if ((allGroups || pageToken !== null) && action !== "errors") throw new Error(`runtime ${action} does not accept all-groups pagination options`);
if ((group !== null || platform !== null) && action !== "errors" && action !== "infrastructure" && action !== "create" && !(action === "apply" && kind === "model-mapping")) throw new Error(`runtime ${action} does not accept group scope options`);
if ((group !== null || platform !== null) && action !== "get" && action !== "errors" && action !== "infrastructure" && action !== "create" && !(action === "apply" && kind === "model-mapping")) throw new Error(`runtime ${action} does not accept group scope options`);
if (recent !== null && action !== "list") throw new Error(`runtime ${action} does not accept --recent`);
if (pageToken !== null && !allGroups) throw new Error("--page-token requires --all-groups");
if (account !== null && allGroups) throw new Error("--account cannot be combined with --all-groups; use --group first");
@@ -316,6 +316,13 @@ def openai_quota_summary(token, detail):
def account_summary(detail, include_rules=False, token=None):
credentials = detail.get("credentials") if isinstance(detail.get("credentials"), dict) else {}
raw_groups = detail.get("group_ids") if isinstance(detail.get("group_ids"), list) else []
group_ids = sorted(set(
int(item.get("id") if isinstance(item, dict) else item)
for item in raw_groups
if str(item.get("id") if isinstance(item, dict) else item).isdigit()
))
model_mapping = credentials.get("model_mapping") if isinstance(credentials.get("model_mapping"), dict) else {}
policy = normalize_policy(credentials)
name = detail.get("name")
management = "yaml-managed" if name in PAYLOAD["managedAccountNames"] else ("yaml-protected-manual" if name in PAYLOAD["protectedManualAccountNames"] else "runtime-manual")
@@ -323,10 +330,14 @@ def account_summary(detail, include_rules=False, token=None):
"accountName": name,
"accountId": detail.get("id"),
"management": management,
"platform": detail.get("platform"),
"type": detail.get("type"),
"groupIds": group_ids,
"status": detail.get("status"),
"schedulable": detail.get("schedulable"),
"proxyId": detail.get("proxy_id"),
"baseUrl": credentials.get("base_url"),
"modelMapping": model_mapping,
"concurrencyLimit": detail.get("concurrency"),
"currentConcurrency": detail.get("current_concurrency"),
"loadFactor": detail.get("load_factor"),
@@ -1318,7 +1318,9 @@ def runtime_result():
"valuesPrinted": False,
}
group_selector = PAYLOAD.get("group")
group_platform = PAYLOAD.get("platform") if action in ("errors", "infrastructure", "create") or (action == "apply" and PAYLOAD.get("kind") == "model-mapping") else "openai"
group_platform = PAYLOAD.get("platform") if action in ("get", "errors", "infrastructure", "create") or (action == "apply" and PAYLOAD.get("kind") == "model-mapping") else "openai"
if action == "get" and group_selector is None:
group_platform = "openai"
if action in ("errors", "infrastructure") and group_selector is None:
group_platform = "openai"
groups = list_groups(token, group_platform)
@@ -1407,12 +1409,24 @@ def runtime_result():
account_id = saved.get("id")
data_of(curl_api("POST", f"/api/v1/admin/accounts/{account_id}/schedulable", bearer=token, payload={"schedulable": True}), "enable runtime account scheduling")
actual_detail = account_detail(token, account_id)
actual_credentials = actual_detail.get("credentials") if isinstance(actual_detail.get("credentials"), dict) else {}
actual_group_ids = sorted(set(
int(item.get("id") if isinstance(item, dict) else item)
for item in (actual_detail.get("group_ids") or [])
if str(item.get("id") if isinstance(item, dict) else item).isdigit()
))
actual_model_mapping = actual_credentials.get("model_mapping") if isinstance(actual_credentials.get("model_mapping"), dict) else {}
actual_policy = normalize_policy(actual_detail.get("credentials"))
desired_policy = normalize_policy(credentials)
reconciled = (
actual_detail.get("name") == name
and actual_detail.get("platform") == account_platform
and int(actual_detail.get("priority") or 0) == int(PAYLOAD.get("priority"))
and int(actual_detail.get("concurrency") or 0) == int(PAYLOAD.get("capacity"))
and int(actual_detail.get("proxy_id") or 0) == int(PAYLOAD.get("proxyId"))
and actual_group_ids == [int(group.get("id"))]
and str(actual_credentials.get("base_url") or "").rstrip("/") == str(credentials["base_url"]).rstrip("/")
and actual_model_mapping == (PAYLOAD.get("modelMappings") or {})
and actual_policy == desired_policy
)
item = {
@@ -1420,9 +1434,13 @@ def runtime_result():
"accountId": account_id,
"actual": {
"name": actual_detail.get("name"),
"platform": actual_detail.get("platform"),
"groupIds": actual_group_ids,
"priority": actual_detail.get("priority"),
"capacity": actual_detail.get("concurrency"),
"proxyId": actual_detail.get("proxy_id"),
"baseUrl": actual_credentials.get("base_url"),
"modelMapping": actual_model_mapping,
"template": matching_template(actual_detail.get("credentials")),
},
"write": {"attempted": True, "status": "succeeded", "error": None},
@@ -146,10 +146,12 @@ function renderRuntimeHelp(args: string[]): RenderedCliResult {
...common,
],
get: [
"Usage: bun scripts/cli.ts platform-infra sub2api codex-pool runtime get --account <name-or-id> [--target <id>] [--json|--full|--raw]",
"Usage: bun scripts/cli.ts platform-infra sub2api codex-pool runtime get --account <name-or-id> [--group <name-or-id>] [--platform <name>] [--target <id>] [--json|--full|--raw]",
"",
"Options:",
" --account <name-or-id> Exact account selector",
" --group <name-or-id> Optional exact group scope for non-default groups",
" --platform <name> Optional platform scope used with --group",
...common,
],
errors: [