fix: stabilize sub2api codex pool operations
This commit is contained in:
@@ -28,6 +28,271 @@ export function renderedCliResult(ok: boolean, command: string, renderedText: st
|
||||
return { ok, command, renderedText, contentType: "text/plain" };
|
||||
}
|
||||
|
||||
export function renderCodexPoolPlan(plan: Record<string, unknown>): RenderedCliResult {
|
||||
const target = isRecord(plan.target) ? plan.target : {};
|
||||
const config = isRecord(plan.config) ? plan.config : {};
|
||||
const pool = isRecord(config.pool) ? config.pool : {};
|
||||
const manualAccounts = isRecord(pool.manualAccounts) ? pool.manualAccounts : {};
|
||||
const sentinel = isRecord(pool.sentinel) ? pool.sentinel : {};
|
||||
const profiles = recordArray(plan.profiles);
|
||||
const invalidProfiles = profiles.filter((profile) => profile.ok !== true);
|
||||
const lines: string[] = [];
|
||||
lines.push([
|
||||
"SUB2API CODEX POOL PLAN",
|
||||
`ok=${plan.ok === true ? "true" : "false"}`,
|
||||
`target=${target.id ?? "-"}`,
|
||||
`profiles=${profiles.length}`,
|
||||
`invalid=${invalidProfiles.length}`,
|
||||
].join(" "));
|
||||
lines.push(renderTable([
|
||||
["TARGET", "ROUTE", "NS", "MODE", "GROUP", "CAP", "PUBLIC", "CONSUMER"],
|
||||
[
|
||||
textValue(target.id),
|
||||
textValue(target.route),
|
||||
textValue(target.namespace),
|
||||
textValue(target.runtimeMode),
|
||||
textValue(pool.groupName ?? target.groupName),
|
||||
textValue(pool.accountCapacityTotal ?? target.accountCapacityTotal),
|
||||
textValue(target.publicBaseUrl),
|
||||
textValue(target.consumerBaseUrl),
|
||||
],
|
||||
]));
|
||||
lines.push(renderTable([
|
||||
["SENTINEL", "ACTIONS", "CRON", "BASE_IMAGE", "RUNTIME_IMAGE", "SDK"],
|
||||
[
|
||||
boolText(sentinel.monitorEnabled),
|
||||
boolText(sentinel.actionsEnabled),
|
||||
textValue(sentinel.cronJobName),
|
||||
textValue(sentinel.image),
|
||||
textValue(sentinel.runtimeImage),
|
||||
textValue(isRecord(sentinel.sdk) ? sentinel.sdk.openaiPythonVersion : undefined),
|
||||
],
|
||||
]));
|
||||
lines.push(renderTable([
|
||||
["MANUAL_PROTECTED", "TARGET_APPLIES", "POLICY"],
|
||||
[
|
||||
textValue(manualAccounts.protectedCount),
|
||||
textValue(manualAccounts.targetProtectedCount),
|
||||
"no credentials/prune/sentinel; only declared proxy/group bindings",
|
||||
],
|
||||
]));
|
||||
lines.push("");
|
||||
lines.push("PROFILES");
|
||||
lines.push(renderTable([
|
||||
["PROFILE", "ACCOUNT", "OK", "CAP", "LF", "PRI", "TRUST", "PROT"],
|
||||
...profiles.map((profile) => [
|
||||
textValue(profile.profile),
|
||||
textValue(profile.accountName),
|
||||
boolText(profile.ok),
|
||||
textValue(profile.capacity),
|
||||
textValue(profile.loadFactor),
|
||||
textValue(profile.priority),
|
||||
boolText(profile.trustUpstream),
|
||||
profile.sentinelProtectEnabled === true ? textValue(profile.sentinelProtectConsecutiveFailures) : "-",
|
||||
]),
|
||||
]));
|
||||
if (invalidProfiles.length > 0) {
|
||||
lines.push("");
|
||||
lines.push("INVALID");
|
||||
lines.push(renderTable([
|
||||
["PROFILE", "ACCOUNT", "ERROR"],
|
||||
...invalidProfiles.map((profile) => [
|
||||
textValue(profile.profile),
|
||||
textValue(profile.accountName),
|
||||
textValue(profile.error),
|
||||
]),
|
||||
]));
|
||||
}
|
||||
const next = isRecord(plan.next) ? plan.next : {};
|
||||
const nextLines = Object.entries(next)
|
||||
.filter(([, value]) => typeof value === "string")
|
||||
.map(([key, value]) => ` ${key}: ${value}`);
|
||||
if (nextLines.length > 0) {
|
||||
lines.push("");
|
||||
lines.push("NEXT");
|
||||
lines.push(...nextLines);
|
||||
}
|
||||
lines.push("");
|
||||
lines.push("Raw: bun scripts/cli.ts platform-infra sub2api codex-pool plan --raw");
|
||||
lines.push("Full: bun scripts/cli.ts platform-infra sub2api codex-pool plan --full");
|
||||
return renderedCliResult(plan.ok === true, "platform-infra sub2api codex-pool plan", lines.join("\n"));
|
||||
}
|
||||
|
||||
export function renderCodexPoolSyncResult(result: Record<string, unknown>): RenderedCliResult {
|
||||
const target = isRecord(result.target) ? result.target : {};
|
||||
const local = isRecord(result.local) ? result.local : {};
|
||||
const image = isRecord(result.sentinelImage) ? result.sentinelImage : {};
|
||||
const imageSummary = isRecord(image.summary) ? image.summary : {};
|
||||
const remote = isRecord(result.remote) ? result.remote : {};
|
||||
const accounts = isRecord(remote.accounts) ? remote.accounts : {};
|
||||
const lines = renderCodexPoolRemoteSummary("SUB2API CODEX POOL SYNC", result, target, remote);
|
||||
lines.push(renderTable([
|
||||
["LOCAL_PROFILES", "PRUNE", "IMAGE_MODE", "IMAGE", "SDK"],
|
||||
[
|
||||
textValue(local.profileCount),
|
||||
boolText(local.pruneRemoved),
|
||||
textValue(imageSummary.mode ?? image.mode),
|
||||
textValue(imageSummary.image ?? image.image),
|
||||
textValue(imageSummary.openaiPythonVersion),
|
||||
],
|
||||
]));
|
||||
lines.push(renderTable([
|
||||
["ACCOUNTS", "CREATED", "UPDATED", "PRUNED", "PRUNE_MODE", "ATTENTION"],
|
||||
[
|
||||
textValue(accounts.desired ?? accounts.itemCount),
|
||||
textValue(accounts.created),
|
||||
textValue(accounts.updated),
|
||||
textValue(accounts.pruned),
|
||||
textValue(accounts.pruneMode),
|
||||
textValue(recordArray(accounts.attentionItems).length),
|
||||
],
|
||||
]));
|
||||
appendCodexPoolCheckTable(lines, remote);
|
||||
appendNext(lines, result.next);
|
||||
lines.push("");
|
||||
lines.push("Full: bun scripts/cli.ts platform-infra sub2api codex-pool sync --confirm --full");
|
||||
lines.push("Raw: bun scripts/cli.ts platform-infra sub2api codex-pool sync --confirm --raw");
|
||||
return renderedCliResult(result.ok === true, "platform-infra sub2api codex-pool sync", lines.join("\n"));
|
||||
}
|
||||
|
||||
export function renderCodexPoolValidateResult(result: Record<string, unknown>): RenderedCliResult {
|
||||
const target = isRecord(result.target) ? result.target : {};
|
||||
const summary = isRecord(result.summary) ? result.summary : {};
|
||||
const lines = renderCodexPoolRemoteSummary("SUB2API CODEX POOL VALIDATE", result, target, summary);
|
||||
appendCodexPoolCheckTable(lines, summary);
|
||||
const validation = isRecord(summary.validation) ? summary.validation : {};
|
||||
lines.push(renderTable([
|
||||
["GATEWAY_MODELS", "GATEWAY_RESPONSES", "RESPONSES_RECENT", "COMPACT_RECENT"],
|
||||
[
|
||||
blockOk(validation.gatewayModels),
|
||||
blockOk(validation.gatewayResponses),
|
||||
blockOk(validation.gatewayResponsesRecent),
|
||||
blockOk(validation.gatewayCompactRecent),
|
||||
],
|
||||
]));
|
||||
lines.push("");
|
||||
lines.push("Full: bun scripts/cli.ts platform-infra sub2api codex-pool validate --full");
|
||||
lines.push("Raw: bun scripts/cli.ts platform-infra sub2api codex-pool validate --raw");
|
||||
return renderedCliResult(result.ok === true, "platform-infra sub2api codex-pool validate", lines.join("\n"));
|
||||
}
|
||||
|
||||
export function renderCodexPoolSentinelProbeResult(result: Record<string, unknown>): RenderedCliResult {
|
||||
const target = isRecord(result.target) ? result.target : {};
|
||||
const summary = isRecord(result.summary) ? result.summary : {};
|
||||
const job = isRecord(summary.job) ? summary.job : {};
|
||||
const runSummary = isRecord(summary.summary) ? summary.summary : {};
|
||||
const results = recordArray(summary.results);
|
||||
const state = isRecord(summary.sentinelState) ? summary.sentinelState : {};
|
||||
const lastRun = isRecord(state.lastRun) ? state.lastRun : {};
|
||||
const runtimeSchedulable = isRecord(lastRun.runtimeSchedulable) ? lastRun.runtimeSchedulable : {};
|
||||
const lines: string[] = [];
|
||||
lines.push([
|
||||
"SUB2API CODEX POOL SENTINEL PROBE",
|
||||
`ok=${result.ok === true ? "true" : "false"}`,
|
||||
`target=${target.id ?? "-"}`,
|
||||
`accounts=${Array.isArray(summary.requestedAccounts) ? summary.requestedAccounts.length : results.length}`,
|
||||
`job=${job.status ?? "-"}`,
|
||||
].join(" "));
|
||||
lines.push(renderTable([
|
||||
["JOB", "NS", "JOB_OK", "MARKER_OK", "LAST_RUN", "SELECTED", "OK", "TF", "ACTIONS"],
|
||||
[
|
||||
textValue(job.name),
|
||||
textValue(summary.namespace ?? target.namespace),
|
||||
boolText(summary.jobExecutionOk),
|
||||
boolText(summary.markerOk),
|
||||
textValue(runSummary.at ?? lastRun.at),
|
||||
textValue(runSummary.selected ?? lastRun.selected),
|
||||
textValue(runSummary.okCount ?? lastRun.okCount),
|
||||
textValue(runSummary.transportFailureCount ?? lastRun.transportFailureCount),
|
||||
textValue(runSummary.actionsTaken ?? lastRun.actionsTaken),
|
||||
],
|
||||
]));
|
||||
lines.push("");
|
||||
lines.push("RESULTS");
|
||||
lines.push(renderTable([
|
||||
["ACCOUNT", "OK", "HTTP", "MARKER", "DUR_MS", "KIND", "ACTION"],
|
||||
...results.map((item) => {
|
||||
const action = isRecord(item.action) ? item.action : {};
|
||||
return [
|
||||
textValue(item.accountName),
|
||||
boolText(item.ok),
|
||||
textValue(item.httpStatus),
|
||||
boolText(item.markerMatched),
|
||||
textValue(item.durationMs),
|
||||
textValue(item.failureKind),
|
||||
textValue(action.type),
|
||||
];
|
||||
}),
|
||||
]));
|
||||
lines.push(renderTable([
|
||||
["QUARANTINED", "SCHEDULABLE", "UNSCHEDULABLE", "MISSING"],
|
||||
[
|
||||
textValue(state.quarantinedCount),
|
||||
textValue(runtimeSchedulable.schedulableCount),
|
||||
textValue(runtimeSchedulable.unschedulableCount),
|
||||
textValue(runtimeSchedulable.missingCount),
|
||||
],
|
||||
]));
|
||||
lines.push("");
|
||||
lines.push("Full: bun scripts/cli.ts platform-infra sub2api codex-pool sentinel-probe --account <name> --confirm --full");
|
||||
lines.push("Raw: bun scripts/cli.ts platform-infra sub2api codex-pool sentinel-probe --account <name> --confirm --raw");
|
||||
return renderedCliResult(result.ok === true, "platform-infra sub2api codex-pool sentinel-probe", lines.join("\n"));
|
||||
}
|
||||
|
||||
function renderCodexPoolRemoteSummary(title: string, result: Record<string, unknown>, target: Record<string, unknown>, remote: Record<string, unknown>): string[] {
|
||||
return [
|
||||
[
|
||||
title,
|
||||
`ok=${result.ok === true ? "true" : "false"}`,
|
||||
`target=${target.id ?? "-"}`,
|
||||
`mode=${remote.mode ?? "-"}`,
|
||||
`degraded=${remote.degraded === true ? "true" : "false"}`,
|
||||
].join(" "),
|
||||
renderTable([
|
||||
["TARGET", "ROUTE", "NS", "SERVICE", "POD", "ADMIN", "API_KEY"],
|
||||
[
|
||||
textValue(target.id),
|
||||
textValue(target.route),
|
||||
textValue(remote.namespace ?? target.namespace),
|
||||
textValue(remote.serviceDns),
|
||||
textValue(remote.appPod),
|
||||
blockOk(remote.admin),
|
||||
blockOk(remote.apiKey),
|
||||
],
|
||||
]),
|
||||
];
|
||||
}
|
||||
|
||||
function appendCodexPoolCheckTable(lines: string[], remote: Record<string, unknown>): void {
|
||||
const runtime = isRecord(remote.runtimeCapabilities) ? remote.runtimeCapabilities : {};
|
||||
lines.push(renderTable([
|
||||
["BALANCE", "CONCURRENCY", "CAPACITY", "LOAD", "TEMP_UNSCHED", "MANUAL", "SENTINEL", "RUNTIME"],
|
||||
[
|
||||
blockOk(remote.ownerBalance),
|
||||
blockOk(remote.ownerConcurrency),
|
||||
blockOk(remote.capacity),
|
||||
blockOk(remote.loadFactor),
|
||||
blockOk(remote.tempUnschedulable),
|
||||
blockOk(remote.manualAccounts),
|
||||
blockOk(remote.sentinel),
|
||||
blockOk(runtime),
|
||||
],
|
||||
]));
|
||||
}
|
||||
|
||||
function appendNext(lines: string[], value: unknown): void {
|
||||
if (!isRecord(value)) return;
|
||||
const entries = Object.entries(value).filter(([, item]) => typeof item === "string");
|
||||
if (entries.length === 0) return;
|
||||
lines.push("");
|
||||
lines.push("NEXT");
|
||||
for (const [key, item] of entries) lines.push(` ${key}: ${item}`);
|
||||
}
|
||||
|
||||
function blockOk(value: unknown): string {
|
||||
return isRecord(value) ? boolText(value.ok) : "-";
|
||||
}
|
||||
|
||||
export function renderSentinelReport(
|
||||
parsed: Record<string, unknown> | null,
|
||||
context: { events: number; full: boolean; remote: Record<string, unknown> },
|
||||
@@ -306,6 +571,12 @@ export function textValue(value: unknown): string {
|
||||
return String(value);
|
||||
}
|
||||
|
||||
export function boolText(value: unknown): string {
|
||||
if (value === true) return "Y";
|
||||
if (value === false) return "N";
|
||||
return "-";
|
||||
}
|
||||
|
||||
export function shorten(value: string, maxChars: number): string {
|
||||
return value.length <= maxChars ? value : `${value.slice(0, Math.max(0, maxChars - 1))}…`;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user