fix: 支持 Sub2API 非默认分组账号回读
This commit is contained in:
@@ -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: [
|
||||
|
||||
Reference in New Issue
Block a user