Merge remote-tracking branch 'origin/master' into fix/selfmedia-production-shared-pac-repository

# Conflicts:
#	docs/MDTODO/selfmedia-production-delivery.md
This commit is contained in:
Codex
2026-07-14 16:21:06 +02:00
70 changed files with 5314 additions and 162 deletions
+2 -2
View File
@@ -307,7 +307,7 @@ function renderTextOutput(command: string, text: string): string {
trigger,
...(dump === null ? {} : { dump }),
next: [
"Use rg --max-count, head, tail, --limit, --tail-bytes, or an id-specific query.",
"Use the command's semantic summary, page cursor, stable id, or id-specific detail query before requesting complete output.",
"Use --full or --raw only when the complete one-off payload is intentionally required.",
],
},
@@ -341,7 +341,7 @@ function oversizedOutputNext(envelope: JsonEnvelope<unknown>): string[] {
];
}
return [
"Use --limit, --tail-bytes, an id-specific view, or another semantic narrow query.",
"Use the command's semantic summary, page cursor, stable id, or id-specific detail query; improve the command when none is available.",
"Use --full or --raw only when the complete one-off payload is intentionally required.",
];
}
@@ -21,7 +21,7 @@ import {
import { parseEnvFile, readTextFile, redactRepoPath, requiredEnvValue } from "../secrets";
import { runSshCommandCapture, type SshCaptureResult } from "../ssh";
import type { CodexPoolCaddyEdgeRetryConfig, CodexPoolConfig, CodexPoolLocalCodexConfig, CodexPoolManualAccountGroupBinding, CodexPoolManualAccountProtection, CodexPoolManualAccountProxyBinding, CodexPoolManualAccountsConfig, CodexPoolManualBindingKind, CodexPoolManualBindingProvider, CodexPoolManualBindingSource, CodexPoolManualBindingSourcesConfig, CodexPoolProfileConfig, CodexPoolPublicExposureConfig, CodexProfile, CodexRuntimeConfig, CodexRuntimeTemplate, CodexSentinelProtectPolicy, CodexTempUnschedulablePolicy, CodexTempUnschedulableRule, OpenAIResponsesWebSocketsV2Mode } from "./types";
import type { CodexPoolCaddyEdgeRetryConfig, CodexPoolConfig, CodexPoolLocalCodexConfig, CodexPoolManualAccountGroupBinding, CodexPoolManualAccountProtection, CodexPoolManualAccountProxyBinding, CodexPoolManualAccountsConfig, CodexPoolManualBindingKind, CodexPoolManualBindingProvider, CodexPoolManualBindingSource, CodexPoolManualBindingSourcesConfig, CodexPoolProfileConfig, CodexPoolProfitConfig, CodexPoolPublicExposureConfig, CodexProfile, CodexRuntimeConfig, CodexRuntimeTemplate, CodexSentinelProtectPolicy, CodexTempUnschedulablePolicy, CodexTempUnschedulableRule, OpenAIResponsesWebSocketsV2Mode } from "./types";
import { booleanValue, integerArrayConfigField, integerConfigField, isRecord, normalizeBaseUrl, numberValue, readRequiredBaseUrl, readRequiredDescription, readRequiredEmail, readRequiredPort, readRequiredPositiveNumber, requiredRecordConfigField, requiredStringConfigField, stringValue, validateKubernetesName } from "./config-utils";
import { readAuthAPIKey, uniqueAccountName } from "./redaction";
import { codexPoolConfigPath } from "./types";
@@ -179,6 +179,7 @@ export function readCodexPoolConfig(): CodexPoolConfig {
defaultSentinelProtect,
profiles,
runtime,
profit: readProfitConfig(parsed.profit),
manualAccounts,
publicExposure: readPublicExposureConfig(parsed.publicExposure),
localCodex: readLocalCodexConfig(parsed.localCodex),
@@ -199,6 +200,51 @@ export function readCodexPoolConfig(): CodexPoolConfig {
return config;
}
function readProfitConfig(value: unknown): CodexPoolProfitConfig {
if (!isRecord(value)) throw new Error(`${codexPoolConfigPath}.profit must be a YAML object`);
const groups = requiredRecordConfigField(value, "groups", "profit");
const profitable = readUniqueStringArray(groups.profitable, "profit.groups.profitable", false);
const selfUse = readUniqueStringArray(groups.selfUse, "profit.groups.selfUse", false);
const excluded = readUniqueStringArray(groups.excluded, "profit.groups.excluded", true);
const allGroups = [...profitable, ...selfUse, ...excluded];
if (new Set(allGroups).size !== allGroups.length) {
throw new Error(`${codexPoolConfigPath}.profit.groups entries must be disjoint`);
}
const saleRateCnyPerApiUsd = numberValue(value.saleRateCnyPerApiUsd);
if (saleRateCnyPerApiUsd === null || saleRateCnyPerApiUsd < 0) {
throw new Error(`${codexPoolConfigPath}.profit.saleRateCnyPerApiUsd must be >= 0`);
}
const defaultWindow = requiredStringConfigField(value, "defaultWindow", "profit");
if (!/^[1-9][0-9]*(?:s|m|h|d)$/u.test(defaultWindow)) {
throw new Error(`${codexPoolConfigPath}.profit.defaultWindow must be a duration such as 24h`);
}
const usageCostField = requiredStringConfigField(value, "usageCostField", "profit");
if (usageCostField !== "actual_cost") {
throw new Error(`${codexPoolConfigPath}.profit.usageCostField must be actual_cost`);
}
const accountCostSource = requiredStringConfigField(value, "accountCostSource", "profit");
if (accountCostSource !== "account-name-trailing-cny-per-api-usd") {
throw new Error(`${codexPoolConfigPath}.profit.accountCostSource must be account-name-trailing-cny-per-api-usd`);
}
return {
defaultWindow,
usageCostField,
accountCostSource,
saleRateCnyPerApiUsd,
groups: { profitable, selfUse, excluded },
};
}
function readUniqueStringArray(value: unknown, path: string, allowEmpty: boolean): string[] {
if (!Array.isArray(value) || !value.every((item) => typeof item === "string" && item.trim().length > 0)) {
throw new Error(`${codexPoolConfigPath}.${path} must be an array of non-empty strings`);
}
const normalized = value.map((item) => item.trim());
if (!allowEmpty && normalized.length === 0) throw new Error(`${codexPoolConfigPath}.${path} must not be empty`);
if (new Set(normalized).size !== normalized.length) throw new Error(`${codexPoolConfigPath}.${path} must not contain duplicates`);
return normalized;
}
export function readRuntimeConfig(value: unknown): CodexRuntimeConfig {
if (!isRecord(value)) throw new Error(`${codexPoolConfigPath}.runtime must be a YAML object`);
if (!Array.isArray(value.templates)) throw new Error(`${codexPoolConfigPath}.runtime.templates must be a YAML array`);
@@ -14,3 +14,4 @@ export * from "./accounts";
export * from "./remote-python-sync";
export * from "./remote";
export * from "./feedback";
export * from "./profit";
@@ -27,6 +27,7 @@ import { codexPoolFeedback } from "./feedback";
import { renderCodexPoolPlan } from "./render";
import { codexPoolRuntime } from "./runtime";
import { codexPoolFaults } from "./faults";
import { codexPoolProfit } from "./profit";
import { defaultCodexPoolRuntimeTargetId } from "./runtime-target";
import { codexPoolHelp } from "./types";
@@ -41,6 +42,7 @@ export async function runCodexPoolCommand(config: UniDeskConfig, args: string[])
if (action === "validate") return await codexPoolValidate(config, parseDisclosureOptions(args.slice(1)));
if (action === "runtime") return await codexPoolRuntime(config, args.slice(1));
if (action === "faults") return await codexPoolFaults(config, args.slice(1));
if (action === "profit") return await codexPoolProfit(config, args.slice(1));
if (action === "trace") return await codexPoolTrace(config, parseTraceOptions(args.slice(1)));
if (action === "feedback") return await codexPoolFeedback(config, parseFeedbackOptions(args.slice(1)));
if (action === "sentinel-image") return await codexPoolSentinelImage(config, parseSentinelImageOptions(args.slice(1)));
@@ -0,0 +1,180 @@
import type { UniDeskConfig } from "../config";
import type { RenderedCliResult } from "../output";
import { readCodexPoolConfig } from "./config";
import { boolField, compactCapture, parseJsonOutput, runRemoteCodexPoolScript } from "./remote";
import { profitScript } from "./remote-scripts";
import { codexPoolRuntimeTarget, defaultCodexPoolRuntimeTargetId } from "./runtime-target";
interface ProfitOptions {
since: string;
targetId: string;
json: boolean;
}
export async function codexPoolProfit(config: UniDeskConfig, args: string[]): Promise<Record<string, unknown> | RenderedCliResult> {
const pool = readCodexPoolConfig();
if (args.includes("--help")) return renderProfitHelp(pool.profit.defaultWindow);
const options = parseProfitOptions(args, pool.profit.defaultWindow);
const target = codexPoolRuntimeTarget(options.targetId);
const payload = { since: options.since, config: pool.profit };
const remote = await runRemoteCodexPoolScript(config, "profit", profitScript(payload, pool, target), target);
const report = parseJsonOutput(remote.stdout);
const ok = remote.exitCode === 0 && boolField(report, "ok", false);
const response = {
ok,
action: "platform-infra-sub2api-codex-pool-profit",
target: { id: target.id, route: target.route, runtimeMode: target.runtimeMode, endpoint: target.serviceDns },
source: {
kind: "sub2api-native-admin-usage",
mutation: false,
configPath: "config/platform-infra/sub2api-codex-pool.yaml#profit",
},
report,
remote: compactCapture(remote, { full: !ok || report === null }),
valuesPrinted: false,
};
return options.json ? renderProfitJson(response) : renderProfit(response);
}
function parseProfitOptions(args: string[], defaultWindow: string): ProfitOptions {
let since = defaultWindow;
let targetId = defaultCodexPoolRuntimeTargetId();
let json = false;
const readValue = (index: number, option: string): [string, number] => {
const value = args[index + 1];
if (value === undefined || value.startsWith("--")) throw new Error(`${option} requires a value`);
return [value.trim(), index + 1];
};
for (let index = 0; index < args.length; index += 1) {
const arg = args[index]!;
if (arg === "--json") json = true;
else if (arg === "--since") {
[since, index] = readValue(index, "--since");
} else if (arg.startsWith("--since=")) since = arg.slice("--since=".length).trim();
else if (arg === "--target") {
[targetId, index] = readValue(index, "--target");
} else if (arg.startsWith("--target=")) targetId = arg.slice("--target=".length).trim();
else throw new Error(`unsupported profit option: ${arg}`);
}
if (!/^[1-9][0-9]*(?:s|m|h|d)$/u.test(since)) throw new Error("--since must be a duration such as 24h, 90m, or 7d");
if (!targetId || /[\r\n\0]/u.test(targetId)) throw new Error("--target has an unsupported format");
return { since, targetId, json };
}
function renderProfitHelp(defaultWindow: string): RenderedCliResult {
return {
ok: true,
command: "platform-infra sub2api codex-pool profit --help",
renderedText: [
"SUB2API CODEX-POOL PROFIT",
"Usage:",
" bun scripts/cli.ts platform-infra sub2api codex-pool profit [--since 24h] [--target PK01] [--json]",
"Options:",
` --since <duration> Native usage window; YAML default is ${defaultWindow}.`,
" --target <id>",
" --json Emit exact structured values.",
"Notes:",
" Default output is a Kubernetes-style account list plus both profit scopes.",
" This command is read-only and does not change Sub2API runtime state.",
].join("\n"),
contentType: "text/plain",
};
}
function renderProfit(response: Record<string, unknown>): RenderedCliResult {
const report = record(response.report);
const window = record(report.window);
const policy = record(report.policy);
const summary = record(report.summary);
const completeness = record(report.completeness);
const lines = [
`SUB2API PROFIT status=${text(report.status)} target=${text(record(response.target).id)} window=${text(window.since)}`,
`WINDOW ${text(window.startAt)} -> ${text(window.endAt)} SOURCE native-admin-usage/${text(record(report.source).usageCostField)}`,
`POLICY sale-rate=${text(policy.saleRateCnyPerApiUsd)}CNY/API_USD account-cost=${text(policy.accountCostSource)}`,
"",
"ACCOUNTS",
];
const accounts = arrayOfRecords(report.accounts);
lines.push(table(
["GROUP", "CLASS", "ACCOUNT", "ID", "REQ", "TOKENS", "API_USD", "COST_CNY/USD", "REVENUE_CNY", "UPSTREAM_CNY", "PROFIT_CNY", "STATUS"],
accounts.map((row) => [
text(row.groupName), text(row.class), text(row.accountName), text(row.accountId), text(row.requestCount), compactTokens(row.tokenCount),
apiUsd(row.apiAmountUsd), text(row.costRateCnyPerApiUsd), cny(row.revenueCny), cny(row.upstreamCostCny), cny(row.profitContributionCny), text(row.completeness),
]),
));
lines.push("", "PROFIT");
lines.push(table(
["SCOPE", "STATUS", "REVENUE_CNY", "PROFIT_POOL_CNY", "SELF_USE_CNY", "PROFIT_CNY"],
[
["不扣自用", text(record(summary.withoutSelfUse).status), cny(summary.revenueCny), cny(summary.profitPoolCostCny), "-", cny(record(summary.withoutSelfUse).profitCny)],
["扣除自用", text(record(summary.withSelfUse).status), cny(summary.revenueCny), cny(summary.profitPoolCostCny), cny(summary.selfUseCostCny), cny(record(summary.withSelfUse).profitCny)],
],
));
const missingCosts = arrayOfRecords(completeness.missingCostAccounts);
const unclassified = arrayOfRecords(completeness.unclassifiedTraffic);
const missingGroups = Array.isArray(completeness.configuredGroupsMissingAtRuntime) ? completeness.configuredGroupsMissingAtRuntime : [];
if (missingCosts.length || unclassified.length || missingGroups.length) {
lines.push("", "COMPLETENESS");
for (const row of missingCosts) lines.push(`- missing-cost group=${text(row.groupName)} account=${text(row.accountName)} id=${text(row.accountId)}`);
for (const row of unclassified) lines.push(`- unclassified-traffic group=${text(row.groupName)} account=${text(row.accountName)} requests=${text(row.requestCount)}`);
for (const group of missingGroups) lines.push(`- configured-group-missing ${text(group)}`);
}
return {
ok: response.ok === true,
command: "platform-infra sub2api codex-pool profit",
renderedText: lines.join("\n"),
contentType: "text/plain",
projection: response,
};
}
function renderProfitJson(response: Record<string, unknown>): RenderedCliResult {
return {
ok: response.ok === true,
command: "platform-infra sub2api codex-pool profit --json",
renderedText: JSON.stringify(response),
contentType: "application/json",
projection: response,
};
}
function record(value: unknown): Record<string, unknown> {
return typeof value === "object" && value !== null && !Array.isArray(value) ? value as Record<string, unknown> : {};
}
function arrayOfRecords(value: unknown): Record<string, unknown>[] {
return Array.isArray(value) ? value.filter((item): item is Record<string, unknown> => typeof item === "object" && item !== null && !Array.isArray(item)) : [];
}
function text(value: unknown): string {
if (value === null || value === undefined || value === "") return "-";
return String(value).replace(/[\r\n\t]+/gu, " ");
}
function compactTokens(value: unknown): string {
const numeric = Number(value);
if (!Number.isFinite(numeric)) return text(value);
if (Math.abs(numeric) >= 1_000_000_000) return `${(numeric / 1_000_000_000).toFixed(3)}B`;
if (Math.abs(numeric) >= 1_000_000) return `${(numeric / 1_000_000).toFixed(3)}M`;
if (Math.abs(numeric) >= 1_000) return `${(numeric / 1_000).toFixed(3)}K`;
return String(numeric);
}
function apiUsd(value: unknown): string {
if (value === null || value === undefined || value === "") return "-";
const numeric = Number(value);
return Number.isFinite(numeric) ? `$${numeric.toFixed(6)}` : text(value);
}
function cny(value: unknown): string {
if (value === null || value === undefined || value === "") return "-";
const numeric = Number(value);
return Number.isFinite(numeric) ? `¥${numeric.toFixed(6)}` : text(value);
}
function table(headers: string[], rows: string[][]): string {
if (rows.length === 0) return `${headers.join(" ")}\n-`;
const widths = headers.map((header, index) => Math.max(header.length, ...rows.map((row) => (row[index] ?? "").length)));
return [headers, ...rows].map((row) => row.map((cell, index) => cell.padEnd(widths[index]!)).join(" ").trimEnd()).join("\n");
}
@@ -30,7 +30,7 @@ function pyJson(value: unknown): string {
return `json.loads(${JSON.stringify(JSON.stringify(value))})`;
}
export function remotePythonCoreScript(mode: "sync" | "validate" | "trace" | "cleanup-probes" | "sentinel-probe", encodedPayload: string, pool: CodexPoolConfig, target: CodexPoolRuntimeTarget): string {
export function remotePythonCoreScript(mode: "sync" | "validate" | "trace" | "cleanup-probes" | "sentinel-probe" | "profit", encodedPayload: string, pool: CodexPoolConfig, target: CodexPoolRuntimeTarget): string {
const hostDockerEnvPath = target.runtimeMode === "host-docker" ? target.hostDockerEnvPath : null;
return `
set -u
@@ -0,0 +1,208 @@
export const remotePythonProfitScript = String.raw`
def profit_decimal(value):
from decimal import Decimal, InvalidOperation
if value is None or isinstance(value, bool):
return Decimal("0")
try:
return Decimal(str(value))
except (InvalidOperation, ValueError):
return Decimal("0")
def profit_decimal_text(value):
text_value = format(value, "f")
if "." in text_value:
text_value = text_value.rstrip("0").rstrip(".")
return text_value or "0"
def profit_parse_time(value):
if not isinstance(value, str) or not value:
return None
try:
parsed = datetime.fromisoformat(value.replace("Z", "+00:00"))
return parsed.replace(tzinfo=timezone.utc) if parsed.tzinfo is None else parsed.astimezone(timezone.utc)
except ValueError:
return None
def profit_window_start(window, end_at):
matched = re.fullmatch(r"([1-9][0-9]*)([smhd])", str(window or ""))
if matched is None:
raise RuntimeError("profit window must be a duration such as 24h")
amount = int(matched.group(1))
unit = matched.group(2)
seconds = amount * {"s": 1, "m": 60, "h": 3600, "d": 86400}[unit]
return end_at - timedelta(seconds=seconds)
def profit_group_class(name, config):
groups = config.get("groups") if isinstance(config.get("groups"), dict) else {}
if name in (groups.get("profitable") or []):
return "profit"
if name in (groups.get("selfUse") or []):
return "self-use"
if name in (groups.get("excluded") or []):
return "excluded"
return "unclassified"
def profit_account_rate(name):
matched = re.search(r"(?:^|\s)([0-9]+(?:\.[0-9]+)?)$", str(name or "").strip())
return profit_decimal(matched.group(1)) if matched is not None else None
def profit_usage_rows(token, group_id, start_at, end_at):
rows = []
page = 1
page_size = 100
while True:
query = (
f"/api/v1/admin/usage?page={page}&page_size={page_size}&group_id={group_id}"
f"&start_date={start_at.date().isoformat()}&end_date={end_at.date().isoformat()}"
"&timezone=UTC&sort_by=created_at&sort_order=desc"
)
data = ensure_success(curl_api("GET", query, bearer=token), f"list usage for group {group_id}")
batch = extract_items(data)
reached_start = False
for row in batch:
if not isinstance(row, dict):
continue
created_at = profit_parse_time(row.get("created_at"))
if created_at is not None and created_at < start_at:
reached_start = True
continue
if created_at is not None and created_at <= end_at:
rows.append(row)
total = int(data.get("total", len(rows))) if isinstance(data, dict) else len(rows)
if not batch or len(batch) < page_size or reached_start or page * page_size >= total:
return rows, page
page += 1
def profit_row_account_id(row):
for key in ("account_id", "accountId"):
if row.get(key) is not None:
return row.get(key)
account = row.get("account")
return account.get("id") if isinstance(account, dict) else None
def profit_row_account_name(row):
for key in ("account_name", "accountName"):
if isinstance(row.get(key), str) and row.get(key):
return row.get(key)
account = row.get("account")
return account.get("name") if isinstance(account, dict) else None
def profit_add_usage(target, row, cost_field):
target["requestCount"] += 1
target["inputTokens"] += int(row.get("input_tokens") or 0)
target["outputTokens"] += int(row.get("output_tokens") or 0)
target["apiAmount"] += profit_decimal(row.get(cost_field))
def profit_empty_account(group, group_class, account_id, account_name):
return {
"groupId": group.get("id"),
"groupName": group.get("name"),
"class": group_class,
"accountId": account_id,
"accountName": account_name,
"requestCount": 0,
"inputTokens": 0,
"outputTokens": 0,
"apiAmount": profit_decimal(0),
}
def run_profit_report():
payload = json.loads(base64.b64decode(PAYLOAD_B64).decode("utf-8")) if PAYLOAD_B64 else {}
config = payload.get("config") if isinstance(payload.get("config"), dict) else {}
window = payload.get("since")
cost_field = config.get("usageCostField")
sale_rate = profit_decimal(config.get("saleRateCnyPerApiUsd"))
end_at = datetime.now(timezone.utc)
start_at = profit_window_start(window, end_at)
_, token, _ = login()
groups = [item for item in list_groups(token) if isinstance(item, dict)]
account_rows = []
group_rows = []
total_pages = 0
configured_names = set()
configured_groups = config.get("groups") if isinstance(config.get("groups"), dict) else {}
for names in configured_groups.values():
if isinstance(names, list):
configured_names.update(str(name) for name in names)
runtime_names = {str(group.get("name")) for group in groups}
for group in sorted(groups, key=lambda item: int(item.get("id") or 0)):
group_id = group.get("id")
group_name = str(group.get("name") or "")
group_class = profit_group_class(group_name, config)
members = list_accounts_for_group(token, group_id)
accounts_by_id = {str(item.get("id")): item for item in members if isinstance(item, dict) and item.get("id") is not None}
usage, pages = profit_usage_rows(token, group_id, start_at, end_at)
total_pages += pages
aggregates = {}
for row in usage:
account_id = profit_row_account_id(row)
account = accounts_by_id.get(str(account_id))
account_name = profit_row_account_name(row) or (account.get("name") if isinstance(account, dict) else None) or f"account-{account_id}"
key = str(account_id) if account_id is not None else "name:" + str(account_name)
if key not in aggregates:
aggregates[key] = profit_empty_account(group, group_class, account_id, account_name)
profit_add_usage(aggregates[key], row, cost_field)
group_amount = profit_decimal(0)
group_tokens = 0
group_requests = 0
for aggregate in aggregates.values():
rate = profit_account_rate(aggregate["accountName"])
amount = aggregate.pop("apiAmount")
revenue = amount * sale_rate if group_class == "profit" else profit_decimal(0)
upstream_cost = amount * rate if rate is not None and group_class in ("profit", "self-use") else profit_decimal(0)
aggregate["tokenCount"] = aggregate.pop("inputTokens") + aggregate.pop("outputTokens")
aggregate["apiAmountUsd"] = profit_decimal_text(amount)
aggregate["costRateCnyPerApiUsd"] = profit_decimal_text(rate) if rate is not None else None
aggregate["revenueCny"] = profit_decimal_text(revenue)
aggregate["upstreamCostCny"] = profit_decimal_text(upstream_cost) if rate is not None or group_class not in ("profit", "self-use") else None
aggregate["profitContributionCny"] = profit_decimal_text(revenue - upstream_cost) if rate is not None or group_class not in ("profit", "self-use") else None
aggregate["completeness"] = "complete" if rate is not None or group_class not in ("profit", "self-use") else "partial"
account_rows.append(aggregate)
group_amount += amount
group_tokens += aggregate["tokenCount"]
group_requests += aggregate["requestCount"]
group_rows.append({
"groupId": group_id,
"groupName": group_name,
"class": group_class,
"requestCount": group_requests,
"tokenCount": group_tokens,
"apiAmountUsd": profit_decimal_text(group_amount),
})
profit_accounts = [row for row in account_rows if row["class"] == "profit"]
self_accounts = [row for row in account_rows if row["class"] == "self-use"]
missing_profit = [row for row in profit_accounts if row["completeness"] != "complete"]
missing_self = [row for row in self_accounts if row["completeness"] != "complete"]
unclassified = [row for row in account_rows if row["class"] == "unclassified" and row["requestCount"] > 0]
revenue = sum((profit_decimal(row["revenueCny"]) for row in profit_accounts), profit_decimal(0))
profit_cost = sum((profit_decimal(row["upstreamCostCny"]) for row in profit_accounts if row["upstreamCostCny"] is not None), profit_decimal(0))
self_cost = sum((profit_decimal(row["upstreamCostCny"]) for row in self_accounts if row["upstreamCostCny"] is not None), profit_decimal(0))
without_self = revenue - profit_cost
with_self = without_self - self_cost
without_complete = not missing_profit and not unclassified
with_complete = without_complete and not missing_self
account_rows.sort(key=lambda row: (row["class"], row["groupName"], -profit_decimal(row["apiAmountUsd"]), str(row["accountName"])))
return {
"ok": True,
"mode": "profit",
"status": "complete" if with_complete else "partial",
"window": {"since": window, "startAt": start_at.isoformat(), "endAt": end_at.isoformat(), "timezone": "UTC"},
"source": {"kind": "sub2api-native-admin-usage", "endpoint": "/api/v1/admin/usage", "usageCostField": cost_field, "pagesRead": total_pages},
"policy": config,
"groups": group_rows,
"accounts": account_rows,
"summary": {
"revenueCny": profit_decimal_text(revenue),
"profitPoolCostCny": profit_decimal_text(profit_cost),
"selfUseCostCny": profit_decimal_text(self_cost),
"withoutSelfUse": {"status": "complete" if without_complete else "partial", "profitCny": profit_decimal_text(without_self) if without_complete else None},
"withSelfUse": {"status": "complete" if with_complete else "partial", "profitCny": profit_decimal_text(with_self) if with_complete else None},
},
"completeness": {
"missingCostAccounts": [{"groupName": row["groupName"], "accountId": row["accountId"], "accountName": row["accountName"]} for row in missing_profit + missing_self],
"unclassifiedTraffic": [{"groupName": row["groupName"], "accountId": row["accountId"], "accountName": row["accountName"], "requestCount": row["requestCount"]} for row in unclassified],
"configuredGroupsMissingAtRuntime": sorted(configured_names - runtime_names),
},
}
`;
@@ -170,6 +170,8 @@ try:
result = run_cleanup_probes()
elif MODE == "sentinel-probe":
result = run_sentinel_probe()
elif MODE == "profit":
result = run_profit_report()
else:
result = run_validate()
except Exception as exc:
@@ -7,14 +7,16 @@ import { remotePythonValidationScript } from "./remote-python-validation";
import { remotePythonSyncValidateScript } from "./remote-python-sync-validate";
import { remotePythonTraceScript } from "./remote-python-trace";
import { remotePythonSentinelProbeScript } from "./remote-python-sentinel-probe";
import { remotePythonProfitScript } from "./remote-python-profit";
export function remotePythonScript(mode: "sync" | "validate" | "trace" | "cleanup-probes" | "sentinel-probe", encodedPayload: string, pool: CodexPoolConfig, target: CodexPoolRuntimeTarget): string {
export function remotePythonScript(mode: "sync" | "validate" | "trace" | "cleanup-probes" | "sentinel-probe" | "profit", encodedPayload: string, pool: CodexPoolConfig, target: CodexPoolRuntimeTarget): string {
return [
remotePythonCoreScript(mode, encodedPayload, pool, target),
remotePythonSentinelScript,
remotePythonValidationScript,
remotePythonSyncValidateScript,
remotePythonTraceScript,
remotePythonProfitScript,
remotePythonSentinelProbeScript,
].join("");
}
@@ -88,6 +88,11 @@ def classify_trace_event(item, account_names_by_id):
"error": item.get("error"),
"excludedAccountCount": item.get("excluded_account_count"),
})
elif "openai.forward_failed" in message:
event.update({
"type": "forward-failed",
"error": item.get("error"),
})
elif "account_upstream_error" in message:
event.update({
"type": "upstream-error",
@@ -160,10 +165,15 @@ def trace_reason(events, final_event):
failovers = [item for item in events if item.get("type") == "failover"]
select_failures = [item for item in events if item.get("type") == "select-failed"]
upstream_errors = [item for item in events if item.get("type") == "upstream-error"]
forward_failures = [item for item in events if item.get("type") == "forward-failed"]
if failover_budget_exhausted(failovers, final_event):
return "failover-budget-exhausted"
if failovers and select_failures:
return "failover-attempted-no-candidate"
if forward_failures and isinstance(final_event, dict) and isinstance(final_event.get("statusCode"), int) and final_event.get("statusCode") < 400:
return "forward-failed-http-success"
if forward_failures:
return "forward-failed"
if failovers:
return "failover-attempted"
if select_failures:
@@ -217,6 +227,16 @@ def trace_untried_schedulable_accounts(failovers, final_event, account_snapshot)
})
return result
def ops_start_time_for_since(since):
if not isinstance(since, str):
return None
match = re.fullmatch(r"\s*(\d+)\s*([smhd])\s*", since.lower())
if match is None:
return None
value = int(match.group(1))
multiplier = {"s": 1, "m": 60, "h": 3600, "d": 86400}[match.group(2)]
return (datetime.now(timezone.utc) - timedelta(seconds=value * multiplier)).strftime("%Y-%m-%dT%H:%M:%SZ")
def run_trace():
payload = json.loads(base64.b64decode(PAYLOAD_B64).decode("utf-8")) if PAYLOAD_B64 else {}
request_id = payload.get("requestId")
@@ -227,6 +247,89 @@ def run_trace():
if not isinstance(request_id, str) or not request_id:
raise RuntimeError("trace payload missing requestId")
admin_email, token, admin_compliance = login()
request_query = quote(request_id, safe="")
native_request_status = {"status": "unavailable"}
native_request = None
try:
request_data = ensure_success(
curl_api("GET", f"/api/v1/admin/ops/requests?request_id={request_query}&kind=all&page=1&page_size=100", bearer=token),
"get ops request details",
)
request_items = extract_items(request_data)
native_request = next((item for item in request_items if isinstance(item, dict) and item.get("request_id") == request_id), None)
native_request_status = {
"status": "available",
"matched": native_request is not None,
"returned": len(request_items),
"total": request_data.get("total", len(request_items)) if isinstance(request_data, dict) else len(request_items),
}
if native_request is None:
ops_start_time = ops_start_time_for_since(since)
error_query = f"/api/v1/admin/ops/request-errors?q={request_query}&view=all&page=1&page_size=20"
if isinstance(ops_start_time, str):
error_query += "&start_time=" + quote(ops_start_time, safe="")
error_data = ensure_success(
curl_api("GET", error_query, bearer=token),
"get ops request error fallback",
)
error_items = extract_items(error_data)
native_request = next((item for item in error_items if isinstance(item, dict) and item.get("request_id") == request_id), None)
native_request_status.update({
"fallback": "sub2api-native-admin-ops-request-errors",
"fallbackStartTime": ops_start_time,
"fallbackReturned": len(error_items),
"fallbackTotal": error_data.get("total", len(error_items)) if isinstance(error_data, dict) else len(error_items),
})
if isinstance(native_request, dict):
native_request = dict(native_request)
native_request["kind"] = "error"
error_id = native_request.get("id")
if isinstance(error_id, int):
try:
detail = ensure_success(curl_api("GET", f"/api/v1/admin/ops/request-errors/{error_id}", bearer=token), "get ops request error detail")
if isinstance(detail, dict):
native_request.update(detail)
except Exception:
pass
native_request_status.update({"matched": True, "source": "sub2api-native-admin-ops-request-errors"})
except Exception as exc:
native_request_status = {"status": "unavailable", "reason": text(str(exc), 500)}
native_system_log_status = {"status": "unavailable"}
native_matched = []
try:
system_log_data = ensure_success(
curl_api("GET", f"/api/v1/admin/ops/system-logs?request_id={request_query}&page=1&page_size=200", bearer=token),
"get ops request system logs",
)
system_log_items = extract_items(system_log_data)
for row in system_log_items:
if not isinstance(row, dict):
continue
parsed = dict(row.get("extra") or {}) if isinstance(row.get("extra"), dict) else {}
for source_key in ("request_id", "client_request_id", "account_id", "platform", "model"):
if parsed.get(source_key) is None and row.get(source_key) is not None:
parsed[source_key] = row.get(source_key)
parsed["_at"] = row.get("created_at")
parsed["_message"] = row.get("message")
parsed["_line"] = json.dumps({
"created_at": row.get("created_at"),
"message": row.get("message"),
"request_id": row.get("request_id"),
"account_id": row.get("account_id"),
"platform": row.get("platform"),
"model": row.get("model"),
"extra": parsed,
}, ensure_ascii=False, default=str)
native_matched.append(parsed)
native_system_log_status = {
"status": "available",
"returned": len(system_log_items),
"total": system_log_data.get("total", len(system_log_items)) if isinstance(system_log_data, dict) else len(system_log_items),
}
except Exception as exc:
native_system_log_status = {"status": "unavailable", "reason": text(str(exc), 500)}
account_snapshot, account_snapshot_error = account_snapshot_from_runtime(token)
account_names_by_id = {}
for row in account_snapshot:
@@ -235,17 +338,21 @@ def run_trace():
account_id = int(account_id)
if isinstance(account_id, int) and isinstance(row.get("accountName"), str):
account_names_by_id[account_id] = row.get("accountName")
proc = kubectl(["-n", NAMESPACE, "logs", "deployment/sub2api", f"--since={since}", f"--tail={tail}"])
stdout = proc.stdout.decode("utf-8", errors="replace")
proc = runtime_logs(since, tail)
log_bytes = proc.stdout + b"\\n" + proc.stderr
stdout = log_bytes.decode("utf-8", errors="replace")
parsed_lines = []
matched = []
runtime_matched = []
for line in stdout.splitlines():
parsed = parse_log_line(line)
if parsed is None:
continue
parsed_lines.append(parsed)
if request_id in line:
matched.append(parsed)
runtime_matched.append(parsed)
matched = native_matched if native_matched else runtime_matched
matched.sort(key=lambda item: (log_time_epoch(item) is None, log_time_epoch(item) or 0))
trace_event_source = "sub2api-native-admin-ops-system-logs" if native_matched else "runtime-logs-fallback"
first_epoch = None
last_epoch = None
for item in matched:
@@ -266,34 +373,94 @@ def run_trace():
window_lines = matched
events = [classify_trace_event(item, account_names_by_id) for item in matched]
request_start = next((item for item in events if item.get("type") == "request-start"), None)
if isinstance(native_request, dict):
if not isinstance(request_start, dict):
request_start = {
"type": "request-start",
"at": native_request.get("created_at"),
"requestId": native_request.get("request_id"),
"path": native_request.get("request_path") or native_request.get("inbound_endpoint"),
"model": native_request.get("model"),
"accountId": native_request.get("account_id"),
"accountName": account_names_by_id.get(native_request.get("account_id")),
"stream": native_request.get("stream"),
"source": "sub2api-native-admin-ops-requests",
}
else:
if request_start.get("stream") is None:
request_start["stream"] = native_request.get("stream")
if not request_start.get("model"):
request_start["model"] = native_request.get("model")
final_event = next((item for item in reversed(events) if item.get("type") == "final"), None)
if not isinstance(final_event, dict) and isinstance(native_request, dict) and isinstance(native_request.get("status_code"), int):
final_event = {
"type": "final",
"at": native_request.get("created_at"),
"requestId": native_request.get("request_id"),
"path": native_request.get("request_path") or native_request.get("inbound_endpoint"),
"model": native_request.get("model"),
"accountId": native_request.get("account_id"),
"accountName": account_names_by_id.get(native_request.get("account_id")),
"statusCode": native_request.get("status_code"),
"latencyMs": native_request.get("duration_ms"),
"source": "sub2api-native-admin-ops-requests",
}
reference_at = request_start.get("at") if isinstance(request_start, dict) else None
reference_epoch = log_time_epoch({"_at": reference_at}) if isinstance(reference_at, str) else first_epoch
reference_source = "request-start" if isinstance(reference_at, str) and reference_epoch is not None else "first-matched-event"
native_duration_ms = native_request.get("duration_ms") if isinstance(native_request, dict) else None
if not isinstance(native_duration_ms, int) and isinstance(final_event, dict):
native_duration_ms = final_event.get("latencyMs")
if (
isinstance(request_start, dict)
and request_start.get("source") == "sub2api-native-admin-ops-requests"
and isinstance(native_duration_ms, int)
and native_duration_ms >= 0
and reference_epoch is not None
):
reference_epoch -= native_duration_ms / 1000
reference_at = datetime.fromtimestamp(reference_epoch, timezone.utc).isoformat().replace("+00:00", "Z")
reference_source = "native-completion-minus-duration"
for event in events:
epoch = log_time_epoch({"_at": event.get("at")}) if isinstance(event.get("at"), str) else None
event["elapsedMs"] = round((epoch - reference_epoch) * 1000) if epoch is not None and reference_epoch is not None else None
failovers = [item for item in events if item.get("type") == "failover"]
select_failures = [item for item in events if item.get("type") == "select-failed"]
upstream_errors = [item for item in events if item.get("type") == "upstream-error"]
forward_failures = [item for item in events if item.get("type") == "forward-failed"]
temp_unsched = [with_trace_phase(classify_trace_event(item, account_names_by_id), first_epoch, last_epoch) for item in window_lines if "account_temp_unschedulable" in str(item.get("_message") or "")]
admin_sched = [with_trace_phase(classify_trace_event(item, account_names_by_id), first_epoch, last_epoch) for item in window_lines if ("schedulable" in str(item.get("_message") or "") or "/schedulable" in str(item.get("path") or ""))]
window_events = [classify_trace_event(item, account_names_by_id) for item in window_lines]
final_errors = [item for item in window_events if item.get("type") == "final" and isinstance(item.get("statusCode"), int) and item.get("statusCode") >= 400]
window_failovers = [item for item in window_events if item.get("type") == "failover"]
window_select_failures = [item for item in window_events if item.get("type") == "select-failed"]
window_forward_failures = [item for item in window_events if item.get("type") == "forward-failed"]
untried_schedulable_accounts = trace_untried_schedulable_accounts(failovers, final_event or {}, account_snapshot)
first_failover_elapsed = failovers[0].get("elapsedMs") if failovers else None
first_select_failed_elapsed = select_failures[0].get("elapsedMs") if select_failures else None
final_epoch = log_time_epoch({"_at": final_event.get("at")}) if isinstance(final_event, dict) and isinstance(final_event.get("at"), str) else None
final_elapsed = round((final_epoch - reference_epoch) * 1000) if final_epoch is not None and reference_epoch is not None else None
failover_to_final = final_elapsed - first_failover_elapsed if isinstance(final_elapsed, int) and isinstance(first_failover_elapsed, int) else None
reason = trace_reason(events, final_event)
if not matched:
if not matched and not isinstance(native_request, dict):
outcome = "not-found"
elif isinstance(final_event, dict) and isinstance(final_event.get("statusCode"), int) and final_event.get("statusCode") < 400:
outcome = "succeeded"
outcome = "degraded" if forward_failures else ("succeeded-with-failover" if failovers else "succeeded")
elif isinstance(final_event, dict):
outcome = "failed"
else:
outcome = "incomplete"
return {
"ok": proc.returncode == 0 and len(matched) > 0,
"ok": proc.returncode == 0 and (len(matched) > 0 or isinstance(native_request, dict)),
"mode": "trace",
"targetId": TARGET_ID,
"runtimeMode": RUNTIME_MODE,
"namespace": NAMESPACE,
"serviceDns": SERVICE_DNS,
"appPod": APP_POD,
"admin": {"email": admin_email, "tokenPrinted": False, "compliance": admin_compliance},
"requestId": request_id,
"eventSource": trace_event_source,
"summary": {
"outcome": outcome,
"reason": reason,
@@ -311,10 +478,16 @@ def run_trace():
},
"request": request_start or {},
"final": final_event or {},
"nativeOps": {
"requestDetails": native_request_status,
"request": native_request,
"systemLogs": native_system_log_status,
},
"events": events,
"failovers": failovers,
"selectFailures": select_failures,
"upstreamErrors": upstream_errors,
"forwardFailures": forward_failures,
"tempUnschedulable": temp_unsched,
"adminSchedulable": admin_sched[-20:],
"windowStats": {
@@ -323,20 +496,35 @@ def run_trace():
"finalErrorCount": len(final_errors),
"failoverCount": len(window_failovers),
"selectFailedCount": len(window_select_failures),
"forwardFailedCount": len(window_forward_failures),
"matchedForwardFailedCount": len(forward_failures),
"tempUnschedulableCount": len(temp_unsched),
"adminSchedulableCount": len(admin_sched),
},
"diagnostics": {
"failoverBudgetExhausted": failover_budget_exhausted(failovers, final_event or {}),
"untriedSchedulableAccounts": untried_schedulable_accounts,
"timing": {
"referenceAt": reference_at if isinstance(reference_at, str) else (events[0].get("at") if events else None),
"referenceSource": reference_source,
"firstFailoverElapsedMs": first_failover_elapsed,
"firstSelectFailedElapsedMs": first_select_failed_elapsed,
"finalElapsedMs": final_elapsed,
"failoverToFinalMs": failover_to_final,
"clientDeadlineAvailable": False,
"clientDeadlineRemainingMs": None,
"attribution": "Elapsed values come from indexed event timestamps. The client deadline is not present in Sub2API Ops records, so remaining request budget cannot be calculated.",
},
},
"accountSnapshot": account_snapshot,
"accountSnapshotError": account_snapshot_error,
"rawLines": [{"line": item.get("_line")} for item in matched[-30:]] if show_lines else [],
"showLines": show_lines,
"logs": {
"source": trace_event_source,
"contextSource": f"host-docker:{HOST_DOCKER_APP_CONTAINER}" if RUNTIME_MODE == "host-docker" else f"k3s:{NAMESPACE}/deployment/sub2api",
"exitCode": proc.returncode,
"stderrTail": text(proc.stderr, 1000),
"stderrTail": text(proc.stderr, 1000) if proc.returncode != 0 else "",
"stdoutLineCount": len(stdout.splitlines()),
},
"valuesPrinted": False,
@@ -51,6 +51,11 @@ export function sentinelProbeScript(payload: unknown, pool: CodexPoolConfig, tar
return remotePythonScript("sentinel-probe", encoded, pool, target);
}
export function profitScript(payload: unknown, pool: CodexPoolConfig, target: CodexPoolRuntimeTarget): string {
const encoded = Buffer.from(JSON.stringify(payload), "utf8").toString("base64");
return remotePythonScript("profit", encoded, pool, target);
}
export function sentinelReportScript(pool: CodexPoolConfig, events: number, target: CodexPoolRuntimeTarget): string {
const stateName = pool.sentinel.stateConfigMapName;
const cronJobName = pool.sentinel.cronJobName;
@@ -28,7 +28,7 @@ export async function capture(config: UniDeskConfig, target: string, args: strin
return await runSshCommandCapture(config, target, args, input);
}
export type RemoteCodexPoolMode = "sync" | "validate" | "runtime" | "sentinel-probe" | "sentinel-image-status" | "sentinel-image-build";
export type RemoteCodexPoolMode = "sync" | "validate" | "runtime" | "faults" | "profit" | "sentinel-probe" | "sentinel-image-status" | "sentinel-image-build";
export async function runRemoteCodexPoolScript(config: UniDeskConfig, mode: RemoteCodexPoolMode, script: string, target = codexPoolRuntimeTarget()): Promise<SshCaptureResult> {
const jobName = `codex-pool-${mode}-${Date.now().toString(36)}`.slice(0, 63);
@@ -84,6 +84,7 @@ export async function runRemoteCodexPoolScript(config: UniDeskConfig, mode: Remo
export function codexPoolModeCommand(mode: RemoteCodexPoolMode): string {
if (mode === "sync") return "bun scripts/cli.ts platform-infra sub2api codex-pool sync --confirm";
if (mode === "runtime") return "bun scripts/cli.ts platform-infra sub2api codex-pool runtime list";
if (mode === "profit") return "bun scripts/cli.ts platform-infra sub2api codex-pool profit --since 24h";
if (mode === "sentinel-probe") return "bun scripts/cli.ts platform-infra sub2api codex-pool sentinel-probe --account <accountName> --confirm";
if (mode === "sentinel-image-status") return "bun scripts/cli.ts platform-infra sub2api codex-pool sentinel-image status";
if (mode === "sentinel-image-build") return "bun scripts/cli.ts platform-infra sub2api codex-pool sentinel-image build --confirm";
@@ -398,8 +398,12 @@ export function renderTraceReport(
const failovers = recordArray(parsed.failovers);
const selectFailures = recordArray(parsed.selectFailures);
const upstreamErrors = recordArray(parsed.upstreamErrors);
const forwardFailures = recordArray(parsed.forwardFailures);
const tempUnschedulable = recordArray(parsed.tempUnschedulable);
const windowStats = isRecord(parsed.windowStats) ? parsed.windowStats : {};
const diagnostics = isRecord(parsed.diagnostics) ? parsed.diagnostics : {};
const timing = isRecord(diagnostics.timing) ? diagnostics.timing : {};
const logs = isRecord(parsed.logs) ? parsed.logs : {};
const accountSnapshot = recordArray(parsed.accountSnapshot);
const lines: string[] = [];
lines.push([
@@ -409,6 +413,17 @@ export function renderTraceReport(
`outcome=${stringValue(summary.outcome) ?? "-"}`,
`reason=${stringValue(summary.reason) ?? "-"}`,
].join(" "));
lines.push([
"SOURCE",
`target=${textValue(parsed.targetId)}`,
`runtime=${textValue(parsed.runtimeMode)}`,
`logs=${textValue(logs.source)}`,
`context=${textValue(logs.contextSource)}`,
`exit=${textValue(logs.exitCode)}`,
].join(" "));
if (parsed.ok !== true && stringValue(logs.stderrTail) !== null) {
lines.push(`LOG ERROR ${shorten(stringValue(logs.stderrTail) ?? "", 240)}`);
}
lines.push([
"REQUEST",
`path=${request.path ?? final.path ?? "-"}`,
@@ -426,13 +441,23 @@ export function renderTraceReport(
`events=${textValue(summary.eventCount)}`,
`window=${window.beforeSeconds ?? "?"}s/${window.afterSeconds ?? "?"}s`,
].join(" "));
lines.push(renderTable([
["TIME_REF", "FIRST_FAILOVER_MS", "SELECT_FAILED_MS", "FINAL_MS", "FAILOVER_TO_FINAL_MS", "CLIENT_DEADLINE", "REMAINING_MS"],
[
textValue(timing.referenceSource), textValue(timing.firstFailoverElapsedMs), textValue(timing.firstSelectFailedElapsedMs),
textValue(timing.finalElapsedMs), textValue(timing.failoverToFinalMs), timing.clientDeadlineAvailable === true ? "available" : "unavailable",
textValue(timing.clientDeadlineRemainingMs),
],
]));
if (typeof timing.attribution === "string") lines.push(`NOTE: ${timing.attribution}`);
if (failovers.length > 0) {
lines.push("");
lines.push("FAILOVER");
lines.push(renderTable([
["AT", "ACCOUNT", "UPSTREAM", "SWITCH", "MAX"],
["AT", "ELAPSED_MS", "ACCOUNT", "UPSTREAM", "SWITCH", "MAX"],
...failovers.map((item) => [
shortIso(item.at),
textValue(item.elapsedMs),
formatAccountRef(item),
textValue(item.upstreamStatus),
textValue(item.switchCount),
@@ -444,14 +469,29 @@ export function renderTraceReport(
lines.push("");
lines.push("SELECT-FAILED");
lines.push(renderTable([
["AT", "ERROR", "EXCLUDED"],
["AT", "ELAPSED_MS", "ERROR", "EXCLUDED"],
...selectFailures.map((item) => [
shortIso(item.at),
textValue(item.elapsedMs),
shorten(stringValue(item.error) ?? "-", 56),
textValue(item.excludedAccountCount),
]),
]));
}
if (forwardFailures.length > 0) {
lines.push("");
lines.push("FORWARD-FAILED");
lines.push(renderTable([
["AT", "ELAPSED_MS", "ACCOUNT", "STATUS", "ERROR"],
...forwardFailures.map((item) => [
shortIso(item.at),
textValue(item.elapsedMs),
formatAccountRef(item),
textValue(item.statusCode),
shorten(stringValue(item.error) ?? "-", 72),
]),
]));
}
if (upstreamErrors.length > 0 || tempUnschedulable.length > 0) {
lines.push("");
lines.push("ACCOUNT SIGNALS");
@@ -482,13 +522,14 @@ export function renderTraceReport(
lines.push("");
lines.push("WINDOW STATS");
lines.push(renderTable([
["MATCH", "EVENTS", "FINAL_4XX_5XX", "FAILOVER", "SELECT_FAIL", "TEMP_UNSCHED", "ADMIN_SCHED"],
["MATCH", "EVENTS", "FINAL_4XX_5XX", "FAILOVER", "SELECT_FAIL", "FORWARD_FAIL", "TEMP_UNSCHED", "ADMIN_SCHED"],
[
textValue(windowStats.matchedLines),
textValue(windowStats.eventCount),
textValue(windowStats.finalErrorCount),
textValue(windowStats.failoverCount),
textValue(windowStats.selectFailedCount),
textValue(windowStats.forwardFailedCount),
textValue(windowStats.tempUnschedulableCount),
textValue(windowStats.adminSchedulableCount),
],
@@ -0,0 +1,120 @@
import { defaultCodexPoolRuntimeTargetId } from "./runtime-target";
export type RuntimeAction = "list" | "get" | "errors" | "infrastructure" | "apply" | "delete";
export interface RuntimeOptions {
action: RuntimeAction;
account: string | null;
accounts: string[];
group: string | null;
allGroups: boolean;
platform: string | null;
pageToken: string | null;
template: string | null;
kind: "temp-unschedulable";
confirm: boolean;
full: boolean;
raw: boolean;
json: boolean;
since: string;
tail: number;
targetId: string;
}
export function parseRuntimeOptions(args: string[]): RuntimeOptions {
const [actionRaw = "list", ...rest] = args;
if (!(["list", "get", "errors", "infrastructure", "apply", "delete"] as string[]).includes(actionRaw)) {
throw new Error("runtime usage: list|get|errors|infrastructure|apply|delete [--account <name-or-id>|--accounts <selectors>] [--group <name-or-id>|--all-groups] [--platform <name>] [--page-token <token>] [--since 24h] [--tail 50000] [--template <id>] [--kind temp-unschedulable] [--confirm] [--target <id>] [--json|--full|--raw]");
}
let account: string | null = null;
let accounts: string[] = [];
let group: string | null = null;
let allGroups = false;
let platform: string | null = null;
let pageToken: string | null = null;
let template: string | null = null;
let kind = "temp-unschedulable" as const;
let confirm = false;
let full = false;
let raw = false;
let json = false;
let since = "24h";
let tail = 50_000;
let targetId = defaultCodexPoolRuntimeTargetId();
for (let index = 0; index < rest.length; index += 1) {
const arg = rest[index]!;
const readValue = (name: string): string => {
const value = rest[index + 1];
if (value === undefined || value.startsWith("--")) throw new Error(`${name} requires a value`);
index += 1;
return value.trim();
};
if (arg === "--confirm") confirm = true;
else if (arg === "--full") full = true;
else if (arg === "--raw") raw = true;
else if (arg === "--json") json = true;
else if (arg === "-o" && readValue("-o") === "json") json = true;
else if (arg === "--account") account = readValue("--account");
else if (arg.startsWith("--account=")) account = arg.slice("--account=".length).trim();
else if (arg === "--accounts") accounts = parseAccountSelectors(readValue("--accounts"));
else if (arg.startsWith("--accounts=")) accounts = parseAccountSelectors(arg.slice("--accounts=".length));
else if (arg === "--group") group = readValue("--group");
else if (arg.startsWith("--group=")) group = arg.slice("--group=".length).trim();
else if (arg === "--all-groups") allGroups = true;
else if (arg === "--platform") platform = readValue("--platform");
else if (arg.startsWith("--platform=")) platform = arg.slice("--platform=".length).trim();
else if (arg === "--page-token") pageToken = readValue("--page-token");
else if (arg.startsWith("--page-token=")) pageToken = arg.slice("--page-token=".length).trim();
else if (arg === "--template") template = readValue("--template");
else if (arg.startsWith("--template=")) template = arg.slice("--template=".length).trim();
else if (arg === "--kind") kind = parseRuntimeKind(readValue("--kind"));
else if (arg.startsWith("--kind=")) kind = parseRuntimeKind(arg.slice("--kind=".length));
else if (arg === "--target") targetId = readValue("--target");
else if (arg.startsWith("--target=")) targetId = arg.slice("--target=".length).trim();
else if (arg === "--since") since = readValue("--since");
else if (arg.startsWith("--since=")) since = arg.slice("--since=".length).trim();
else if (arg === "--tail") tail = parseRuntimeTail(readValue("--tail"));
else if (arg.startsWith("--tail=")) tail = parseRuntimeTail(arg.slice("--tail=".length));
else throw new Error(`unsupported runtime option: ${arg}`);
}
const action = actionRaw as RuntimeAction;
if (account !== null && accounts.length > 0) throw new Error("use only one of --account or --accounts");
if ((action === "get" || action === "infrastructure") && !account) throw new Error(`runtime ${action} requires --account <name-or-id>`);
if ((action === "apply" || action === "delete") && account === null && accounts.length === 0) throw new Error(`runtime ${action} requires --account or --accounts`);
if (accounts.length > 0 && action !== "apply" && action !== "delete") throw new Error(`runtime ${action} does not accept --accounts`);
if (account !== null && (account.length > 256 || /[\r\n]/u.test(account))) throw new Error("--account has an unsupported format");
if (group !== null && (group.length > 256 || /[\r\n]/u.test(group))) throw new Error("--group has an unsupported format");
if (platform !== null && (!/^[A-Za-z0-9._-]+$/u.test(platform) || platform.length > 64)) throw new Error("--platform has an unsupported format");
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") throw new Error(`runtime ${action} does not accept group scope options`);
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");
if (action === "apply" && !template) throw new Error("runtime apply requires --template <id>");
if (action !== "apply" && template !== null) throw new Error(`runtime ${action} does not accept --template`);
if (action !== "errors" && action !== "infrastructure" && (since !== "24h" || tail !== 50_000)) throw new Error(`runtime ${action} does not accept --since or --tail`);
if (!/^\d+[mhd]$/u.test(since)) throw new Error("--since must use <number>m, <number>h, or <number>d");
if (["list", "get", "errors", "infrastructure"].includes(action) && confirm) throw new Error(`runtime ${action} does not accept --confirm`);
if ([full, raw, json].filter(Boolean).length > 1) throw new Error("use only one of --json, --full, or --raw");
return { action, account, accounts, group, allGroups, platform, pageToken, template, kind, confirm, full, raw, json, since, tail, targetId };
}
function parseAccountSelectors(value: string): string[] {
const selectors = value.split(",").map((item) => item.trim()).filter(Boolean);
if (selectors.length === 0 || selectors.length > 100) throw new Error("--accounts requires 1 to 100 comma-separated selectors");
if (selectors.some((item) => item.length > 256 || /[\r\n]/u.test(item))) throw new Error("--accounts has an unsupported selector");
if (new Set(selectors).size !== selectors.length) throw new Error("--accounts contains duplicate selectors");
return selectors;
}
function parseRuntimeKind(value: string): "temp-unschedulable" {
if (value !== "temp-unschedulable") throw new Error("--kind must be temp-unschedulable");
return value;
}
function parseRuntimeTail(value: string): number {
const parsed = Number(value);
if (!Number.isInteger(parsed) || parsed < 100 || parsed > 200_000) throw new Error("--tail must be an integer from 100 to 200000");
return parsed;
}
File diff suppressed because it is too large Load Diff
@@ -181,6 +181,18 @@ export interface CodexRuntimeConfig {
templatesById: Record<string, CodexRuntimeTemplate>;
}
export interface CodexPoolProfitConfig {
defaultWindow: string;
usageCostField: "actual_cost";
accountCostSource: "account-name-trailing-cny-per-api-usd";
saleRateCnyPerApiUsd: number;
groups: {
profitable: string[];
selfUse: string[];
excluded: string[];
};
}
export interface CodexPoolConfig {
version: number;
kind: string;
@@ -206,6 +218,7 @@ export interface CodexPoolConfig {
defaultSentinelProtect: CodexSentinelProtectPolicy;
profiles: CodexPoolProfileConfig[];
runtime: CodexRuntimeConfig;
profit: CodexPoolProfitConfig;
manualAccounts: CodexPoolManualAccountsConfig;
publicExposure: CodexPoolPublicExposureConfig;
localCodex: CodexPoolLocalCodexConfig;
@@ -350,17 +363,18 @@ export function codexPoolHelp(): unknown {
const pool = readCodexPoolConfig();
const runtimeTarget = codexPoolRuntimeTarget();
return {
command: "platform-infra sub2api codex-pool plan|sync|validate|runtime|faults|trace|feedback|sentinel-image|sentinel-probe|sentinel-report|cleanup-probes|expose|configure-local",
command: "platform-infra sub2api codex-pool plan|sync|validate|runtime|faults|profit|trace|feedback|sentinel-image|sentinel-probe|sentinel-report|cleanup-probes|expose|configure-local",
output: "json, except trace, feedback, and sentinel-report default to low-noise text tables",
usage: [
"bun scripts/cli.ts platform-infra sub2api codex-pool plan",
"bun scripts/cli.ts platform-infra sub2api codex-pool plan --target D601",
"bun scripts/cli.ts platform-infra sub2api codex-pool sync [--target D601] --confirm [--prune-removed]",
"bun scripts/cli.ts platform-infra sub2api codex-pool validate [--target D601] [--full|--raw]",
"bun scripts/cli.ts platform-infra sub2api codex-pool runtime list [--target PK01] [--full|--raw]",
"bun scripts/cli.ts platform-infra sub2api codex-pool runtime get --account <name-or-id> [--target PK01] [--full|--raw]",
"bun scripts/cli.ts platform-infra sub2api codex-pool runtime errors [--account <name-or-id>] [--since 24h] [--tail 50000] [--target PK01] [--full|--raw]",
"bun scripts/cli.ts platform-infra sub2api codex-pool runtime list [--target PK01] [--json|--full|--raw]",
"bun scripts/cli.ts platform-infra sub2api codex-pool runtime get --account <name-or-id> [--target PK01] [--json|--full|--raw]",
"bun scripts/cli.ts platform-infra sub2api codex-pool runtime errors [--group <name-or-id>|--all-groups] [--platform <name>] [--page-token <group-id>] [--account <name-or-id>] [--since 24h] [--tail 50000] [--target PK01] [--json|--full|--raw]",
"bun scripts/cli.ts platform-infra sub2api codex-pool faults [--level P0|P1|P2] [--group <name-or-id>] [--account <name-or-id>] [--model <model>] [--stream sync|stream] [--endpoint <path>] [--request-id <id>] [--page-token <token>] [--target PK01] [--json]",
"bun scripts/cli.ts platform-infra sub2api codex-pool profit [--since 24h] [--target PK01] [--json]",
"bun scripts/cli.ts platform-infra sub2api codex-pool runtime apply --account <name-or-id> --template <id> [--target PK01] [--confirm]",
"bun scripts/cli.ts platform-infra sub2api codex-pool runtime apply --accounts <name-or-id,...> --template <id> [--target PK01] [--confirm] [--full|--raw]",
"bun scripts/cli.ts platform-infra sub2api codex-pool runtime delete --account <name-or-id> --kind temp-unschedulable [--target PK01] [--confirm]",
+2
View File
@@ -424,6 +424,7 @@ export function platformInfraHelp(): unknown {
"bun scripts/cli.ts platform-infra sub2api codex-pool validate",
"bun scripts/cli.ts platform-infra sub2api codex-pool trace --request-id <requestId>",
"bun scripts/cli.ts platform-infra sub2api codex-pool feedback --user <email-or-id> [--window 30m] [--page-token <token>|--request-id <client-or-internal-id>] [--json]",
"bun scripts/cli.ts platform-infra sub2api codex-pool profit [--since 24h] [--target PK01] [--json]",
"bun scripts/cli.ts platform-infra sub2api codex-pool sentinel-image status",
"bun scripts/cli.ts platform-infra sub2api codex-pool sentinel-probe --account unidesk-codex-hy --confirm",
"bun scripts/cli.ts platform-infra sub2rank plan [--target NC01]",
@@ -507,6 +508,7 @@ export function platformInfraHelp(): unknown {
"bun scripts/cli.ts platform-infra sub2api codex-pool validate",
"bun scripts/cli.ts platform-infra sub2api codex-pool trace --request-id <requestId>",
"bun scripts/cli.ts platform-infra sub2api codex-pool feedback --user <email-or-id> [--window 30m] [--page-token <token>|--request-id <client-or-internal-id>] [--json]",
"bun scripts/cli.ts platform-infra sub2api codex-pool profit [--since 24h] [--target PK01] [--json]",
"bun scripts/cli.ts platform-infra sub2api codex-pool sentinel-image status",
],
module: "scripts/src/platform-infra-sub2api-codex.ts",