Merge pull request #2041 from pikasTech/feat/sub2api-profit-report
Pipelines as Code CI / hwlab-web-probe-sentinel-nc01- Success
Pipelines as Code CI / platform-infra-gitea-nc01- Success
Pipelines as Code CI / unidesk-host- Success

feat: 增加 Sub2API 24 小时利润统计 CLI
This commit is contained in:
Lyon
2026-07-14 21:55:22 +08:00
committed by GitHub
14 changed files with 481 additions and 5 deletions
@@ -86,6 +86,8 @@ bun scripts/cli.ts platform-infra sub2api codex-pool runtime apply --target PK01
bun scripts/cli.ts platform-infra sub2api codex-pool runtime delete --target PK01 --account <name-or-id> --kind temp-unschedulable
bun scripts/cli.ts platform-infra sub2api codex-pool runtime apply --target PK01 --account <name-or-id> --kind model-mapping --model <client-model> --upstream-model <upstream-model>
bun scripts/cli.ts platform-infra sub2api codex-pool runtime delete --target PK01 --account <name-or-id> --kind model-mapping --model <client-model>
bun scripts/cli.ts platform-infra sub2api codex-pool profit --target PK01 --since 24h
bun scripts/cli.ts platform-infra sub2api codex-pool profit --target PK01 --since 24h --json
bun scripts/cli.ts platform-infra sub2api codex-pool trace --request-id <requestId>
bun scripts/cli.ts platform-infra sub2api codex-pool sentinel-image status --target D601
bun scripts/cli.ts platform-infra sub2api codex-pool sentinel-image build --target D601 --confirm
@@ -94,6 +96,13 @@ bun scripts/cli.ts platform-infra sub2api codex-pool sentinel-report --target D6
bun scripts/cli.ts platform-infra sub2api codex-pool cleanup-probes --target D601 --confirm
```
- `profit` 只读聚合 Sub2API 原生 `/api/v1/admin/usage`
- 默认窗口、盈利分组、自用分组、排除分组、净收入系数和账号采购成本来源由 `config/platform-infra/sub2api-codex-pool.yaml#profit` 声明;
- 默认 Kubernetes 列表按完整账号名称显示请求数、Token、原生 `actual_cost`、采购成本系数、收入、上游成本和利润贡献;
- 汇总同时给出“不扣自用成本”和“扣除自用成本”两种口径,自用分组不计收入;
- 账号名称末尾缺少采购成本系数,或存在未分类流量时,相关口径必须显示 `partial`,禁止把缺失值当作零;
- `--json` 保留精确十进制字符串、原生来源、时间窗、分页数和完整性明细;命令不修改 runtime。
`config/platform-infra/sub2api-codex-pool.yaml` 控制:
- `pool.groupName`: Sub2API group 名称。
@@ -45,6 +45,16 @@ runtime:
durationMinutes: 1
description: 明确命中 524 上游不可用、过载或并发限制时短时冷却当前账号。
profit:
defaultWindow: 24h
usageCostField: actual_cost
accountCostSource: account-name-trailing-coefficient
netRevenueCoefficient: 0.10
groups:
profitable: [unidesk-codex-pool]
selfUse: [自用]
excluded: [grok]
pool:
groupName: unidesk-codex-pool
groupDescription: UniDesk-managed Codex API-key pool for YAML-selected Sub2API clients.
@@ -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 netRevenueCoefficient = numberValue(value.netRevenueCoefficient);
if (netRevenueCoefficient === null || netRevenueCoefficient < 0) {
throw new Error(`${codexPoolConfigPath}.profit.netRevenueCoefficient 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-coefficient") {
throw new Error(`${codexPoolConfigPath}.profit.accountCostSource must be account-name-trailing-coefficient`);
}
return {
defaultWindow,
usageCostField,
accountCostSource,
netRevenueCoefficient,
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,174 @@
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 net-revenue=${text(policy.netRevenueCoefficient)} account-cost=${text(policy.accountCostSource)}`,
"",
"ACCOUNTS",
];
const accounts = arrayOfRecords(report.accounts);
lines.push(table(
["GROUP", "CLASS", "ACCOUNT", "ID", "REQ", "TOKENS", "API_AMOUNT", "COST_RATE", "REVENUE", "UPSTREAM_COST", "CONTRIBUTION", "STATUS"],
accounts.map((row) => [
text(row.groupName), text(row.class), text(row.accountName), text(row.accountId), text(row.requestCount), compactTokens(row.tokenCount),
usd(row.apiAmountUsd), text(row.costCoefficient), usd(row.revenueUsd), usd(row.upstreamCostUsd), usd(row.profitContributionUsd), text(row.completeness),
]),
));
lines.push("", "PROFIT");
lines.push(table(
["SCOPE", "STATUS", "REVENUE", "PROFIT_POOL_COST", "SELF_USE_COST", "PROFIT"],
[
["不扣自用", text(record(summary.withoutSelfUse).status), usd(summary.revenueUsd), usd(summary.profitPoolCostUsd), "-", usd(record(summary.withoutSelfUse).profitUsd)],
["扣除自用", text(record(summary.withSelfUse).status), usd(summary.revenueUsd), usd(summary.profitPoolCostUsd), usd(summary.selfUseCostUsd), usd(record(summary.withSelfUse).profitUsd)],
],
));
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 usd(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")
net_rate = profit_decimal(config.get("netRevenueCoefficient"))
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 * net_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["costCoefficient"] = profit_decimal_text(rate) if rate is not None else None
aggregate["revenueUsd"] = profit_decimal_text(revenue)
aggregate["upstreamCostUsd"] = profit_decimal_text(upstream_cost) if rate is not None or group_class not in ("profit", "self-use") else None
aggregate["profitContributionUsd"] = 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["revenueUsd"]) for row in profit_accounts), profit_decimal(0))
profit_cost = sum((profit_decimal(row["upstreamCostUsd"]) for row in profit_accounts if row["upstreamCostUsd"] is not None), profit_decimal(0))
self_cost = sum((profit_decimal(row["upstreamCostUsd"]) for row in self_accounts if row["upstreamCostUsd"] 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": {
"revenueUsd": profit_decimal_text(revenue),
"profitPoolCostUsd": profit_decimal_text(profit_cost),
"selfUseCostUsd": profit_decimal_text(self_cost),
"withoutSelfUse": {"status": "complete" if without_complete else "partial", "profitUsd": profit_decimal_text(without_self) if without_complete else None},
"withSelfUse": {"status": "complete" if with_complete else "partial", "profitUsd": 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("");
}
@@ -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";
@@ -181,6 +181,18 @@ export interface CodexRuntimeConfig {
templatesById: Record<string, CodexRuntimeTemplate>;
}
export interface CodexPoolProfitConfig {
defaultWindow: string;
usageCostField: "actual_cost";
accountCostSource: "account-name-trailing-coefficient";
netRevenueCoefficient: 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,7 +363,7 @@ 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",
@@ -361,6 +374,7 @@ export function codexPoolHelp(): unknown {
"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",