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:
@@ -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]",
|
||||
|
||||
@@ -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",
|
||||
|
||||
Reference in New Issue
Block a user