feat: 排除内部员工补偿账号

This commit is contained in:
Codex
2026-07-15 06:22:56 +02:00
parent 7b43da1ab8
commit fc4befc583
6 changed files with 60 additions and 9 deletions
@@ -204,12 +204,19 @@ export function readCodexPoolConfig(): CodexPoolConfig {
function readCreditsConfig(value: unknown): CodexPoolCreditsConfig {
if (!isRecord(value)) throw new Error(`${codexPoolConfigPath}.credits must be a YAML object`);
const timezone = requiredStringConfigField(value, "timezone", "credits");
const excludedUsers = readUniqueStringArray(value.excludedUsers, "credits.excludedUsers", true);
if (excludedUsers.some((selector) => selector.length > 256 || /[\r\n\0]/u.test(selector))) {
throw new Error(`${codexPoolConfigPath}.credits.excludedUsers contains an unsupported selector`);
}
if (new Set(excludedUsers.map((selector) => selector.toLowerCase())).size !== excludedUsers.length) {
throw new Error(`${codexPoolConfigPath}.credits.excludedUsers must not contain case-insensitive duplicates`);
}
try {
new Intl.DateTimeFormat("en-US", { timeZone: timezone }).format(new Date(0));
} catch {
throw new Error(`${codexPoolConfigPath}.credits.timezone must be a valid IANA timezone`);
}
return { timezone };
return { timezone, excludedUsers };
}
function readProfitConfig(value: unknown): CodexPoolProfitConfig {
@@ -13,6 +13,7 @@ interface CreditsOptions {
activeStartUtc: string | null;
activeEndUtc: string | null;
timezone: string;
excludedUsers: string[];
users: string[];
amountUsd: number;
reason: string;
@@ -24,7 +25,7 @@ interface CreditsOptions {
export async function codexPoolCredits(config: UniDeskConfig, args: string[]): Promise<Record<string, unknown> | RenderedCliResult> {
if (args.includes("--help")) return renderCreditsHelp();
const pool = readCodexPoolConfig();
const options = parseCreditsOptions(args, pool.credits.timezone);
const options = parseCreditsOptions(args, pool.credits);
const target = codexPoolRuntimeTarget(options.targetId);
const remote = await runRemoteCodexPoolScript(config, "credits", creditsScript(options, pool, target), target);
const report = parseJsonOutput(remote.stdout);
@@ -41,6 +42,7 @@ export async function codexPoolCredits(config: UniDeskConfig, args: string[]): P
activeStartUtc: options.activeStartUtc,
activeEndUtc: options.activeEndUtc,
timezone: options.timezone,
excludedUsers: options.excludedUsers,
users: options.users,
amountUsd: options.amountUsd,
reason: options.reason,
@@ -53,7 +55,8 @@ export async function codexPoolCredits(config: UniDeskConfig, args: string[]): P
return options.json ? renderCreditsJson(response) : renderCredits(response);
}
function parseCreditsOptions(args: string[], timezone: string): CreditsOptions {
function parseCreditsOptions(args: string[], creditsConfig: { timezone: string; excludedUsers: string[] }): CreditsOptions {
const { timezone, excludedUsers } = creditsConfig;
const [action = "grant", ...rest] = args;
if (action !== "grant") throw new Error("credits usage: grant (--active-since 24h|--active-start <ISO> --active-end <ISO>|--users <id-or-email,...>) --amount-usd <amount> --reason <text> [--confirm] [--target <id>] [--json]");
let activeSince: string | null = null;
@@ -110,7 +113,7 @@ function parseCreditsOptions(args: string[], timezone: string): CreditsOptions {
if (confirm && users.length === 0) throw new Error("confirmed credits require the exact --users selectors returned by an active-user dry-run");
if (amountUsd === null) throw new Error("credits grant requires --amount-usd");
if (reason === null || reason.length === 0 || reason.length > 500 || /[\r\n\0]/u.test(reason)) throw new Error("credits grant requires a single-line --reason up to 500 characters");
return { activeSince, activeStart, activeEnd, activeStartUtc, activeEndUtc, timezone, users, amountUsd, reason, confirm, targetId, json };
return { activeSince, activeStart, activeEnd, activeStartUtc, activeEndUtc, timezone, excludedUsers, users, amountUsd, reason, confirm, targetId, json };
}
function isIsoDateTime(value: string): boolean {
@@ -183,6 +186,7 @@ function renderCreditsHelp(): RenderedCliResult {
"Notes:",
" Default mode is dry-run. --active-since selects distinct active non-admin users from native usage.",
" Offset-free fixed-window values use config/platform-infra/sub2api-codex-pool.yaml#credits.timezone and the interval is [start,end).",
" YAML credits.excludedUsers selectors are excluded from both active-window and exact-user grants.",
" --confirm performs additive balance writes and then reads every selected user back.",
].join("\n"),
contentType: "text/plain",
@@ -193,8 +197,9 @@ function renderCredits(response: Record<string, unknown>): RenderedCliResult {
const report = record(response.report);
const summary = record(report.summary);
const rows = arrayOfRecords(report.items);
const excludedRows = arrayOfRecords(report.excluded);
const lines = [
`SUB2API USER CREDITS ok=${response.ok === true} mode=${text(report.mode)} selection=${text(record(report.selection).kind)} selected=${text(summary.selected)} amount-usd=${text(summary.amountUsd)} total-usd=${text(summary.totalAmountUsd)}`,
`SUB2API USER CREDITS ok=${response.ok === true} mode=${text(report.mode)} selection=${text(record(report.selection).kind)} selected=${text(summary.selected)} excluded=${text(summary.excluded)} amount-usd=${text(summary.amountUsd)} total-usd=${text(summary.totalAmountUsd)}`,
`WRITE attempted=${text(summary.writeAttempted)} succeeded=${text(summary.writeSucceeded)} failed=${text(summary.writeFailed)} reconciled=${text(summary.reconciled)} mutation=${text(report.mutation)}`,
];
const selection = record(report.selection);
@@ -211,10 +216,21 @@ function renderCredits(response: Record<string, unknown>): RenderedCliResult {
]),
]));
}
if (excludedRows.length > 0) {
lines.push("", "EXCLUDED", renderTable([
["EMAIL", "USER_ID", "REQUESTS", "REASON", "MATCHED_SELECTOR"],
...excludedRows.map((row) => [
text(row.email), text(row.userId), text(row.requestCount), text(row.reason), text(row.matchedSelector),
]),
]));
}
if (report.mode === "dry-run") {
const selectors = Array.isArray(report.confirmationUserIds) ? report.confirmationUserIds.join(",") : "<exact-user-ids>";
const confirmationUserIds = Array.isArray(report.confirmationUserIds) ? report.confirmationUserIds : [];
const selectors = confirmationUserIds.length > 0 ? confirmationUserIds.join(",") : "<none>";
lines.push("", `CONFIRM SELECTORS --users ${selectors}`);
lines.push("NEXT: use these exact selectors with --confirm only after explicit user authorization.");
lines.push(confirmationUserIds.length > 0
? "NEXT: use these exact selectors with --confirm only after explicit user authorization."
: "NEXT: no users remain eligible for confirmation.");
}
return { ok: response.ok === true, command: "platform-infra sub2api codex-pool credits", renderedText: lines.join("\n"), contentType: "text/plain", projection: response };
}
@@ -69,6 +69,15 @@ def credits_active_usage(token, since, active_start, active_end, active_start_ut
}
page += 1
def credits_excluded_selector(user, payload):
user_id = str(user.get("id")) if user.get("id") is not None else ""
email = str(user.get("email") or "").strip().lower()
for selector in payload.get("excludedUsers") or []:
normalized = str(selector).strip().lower()
if normalized == user_id or normalized == email:
return str(selector)
return None
def credits_selected_users(token, payload):
users = credits_all_users(token)
users_by_id = {str(item.get("id")): item for item in users if item.get("id") is not None}
@@ -85,8 +94,14 @@ def credits_selected_users(token, payload):
excluded = []
for user_id, request_count in requests.items():
user = users_by_id.get(user_id)
excluded_selector = credits_excluded_selector(user, payload) if isinstance(user, dict) else None
if not isinstance(user, dict):
excluded.append({"userId": user_id, "reason": "user-not-found"})
elif excluded_selector is not None:
excluded.append({
"userId": user.get("id"), "email": user.get("email"), "requestCount": request_count,
"reason": "yaml-excluded-user", "matchedSelector": excluded_selector,
})
elif user.get("role") == "admin":
excluded.append({"userId": user.get("id"), "email": user.get("email"), "reason": "admin-excluded"})
elif user.get("status") != "active" or user.get("deleted_at") is not None:
@@ -95,6 +110,7 @@ def credits_selected_users(token, payload):
selected.append((user, request_count))
return selected, excluded, selection
selected = []
excluded = []
seen = set()
for selector in payload.get("users") or []:
user = users_by_id.get(str(selector)) or users_by_email.get(str(selector).strip().lower())
@@ -104,8 +120,15 @@ def credits_selected_users(token, payload):
if user_id in seen:
raise RuntimeError("duplicate user selector: " + str(selector))
seen.add(user_id)
selected.append((user, None))
return selected, [], {"kind": "exact-users", "selectors": payload.get("users") or []}
excluded_selector = credits_excluded_selector(user, payload)
if excluded_selector is not None:
excluded.append({
"userId": user.get("id"), "email": user.get("email"), "requestCount": None,
"reason": "yaml-excluded-user", "matchedSelector": excluded_selector,
})
else:
selected.append((user, None))
return selected, excluded, {"kind": "exact-users", "selectors": payload.get("users") or []}
def run_credits_grant():
payload = json.loads(base64.b64decode(PAYLOAD_B64).decode("utf-8")) if PAYLOAD_B64 else {}
@@ -221,6 +221,7 @@ export interface CodexPoolProfitConfig {
export interface CodexPoolCreditsConfig {
timezone: string;
excludedUsers: string[];
}
export interface CodexPoolConfig {