feat: add Sub2API runtime configuration CRUD

This commit is contained in:
Codex
2026-07-13 20:17:08 +02:00
parent b8d6b7be03
commit 4d63c8e922
12 changed files with 696 additions and 284 deletions
@@ -27,9 +27,9 @@ import { collectCodexProfiles, readCodexPoolConfig } from "./config";
import { codexPoolSentinelProbeConfigFingerprint, fingerprint } from "./config-utils";
import { apiKeyPreview, codexConsumerBaseUrl, fetchPoolApiKey, probePublicModels, validatePublicGatewayWithKey, writeLocalCodexConfig } from "./local-codex";
import { manualBindingSourcePlan, poolTarget, prepareTargetPublicExposureSecret, protectedManualAccountNamesForTarget, resolvedManualAccountProtections, secretMaterialSummary, sentinelProfileSecrets, targetFrpPublicExposure, targetPublicExposureApplyScript, targetPublicExposureSummary } from "./public-exposure";
import { codexPoolConfigSummary, compactProfile, compactRuntimeAccountState, compactSentinelProbeResult, redactProfile, renderSub2ApiTempUnschedulableCredentials } from "./redaction";
import { codexPoolConfigSummary, compactProfile, compactSentinelProbeResult, redactProfile, renderSub2ApiTempUnschedulableCredentials } from "./redaction";
import { boolField, capture, compactCapture, parseJsonOutput, runRemoteCodexPoolScript } from "./remote";
import { cleanupProbesScript, runtimeStateScript, sentinelImageBuildScript, sentinelImageStatusScript, sentinelProbeScript, sentinelReportScript, syncScript, traceScript, validateScript } from "./remote-scripts";
import { cleanupProbesScript, sentinelImageBuildScript, sentinelImageStatusScript, sentinelProbeScript, sentinelReportScript, syncScript, traceScript, validateScript } from "./remote-scripts";
import { codexPoolSyncSummary, codexPoolValidationSummary, renderCodexPoolPlan, renderCodexPoolSentinelProbeResult, renderCodexPoolSyncResult, renderCodexPoolValidateResult, renderSentinelReport, renderTraceReport, renderedCliResult } from "./render";
import { codexPoolRuntimeTarget, defaultCodexPoolRuntimeTargetId, targetFlag } from "./runtime-target";
import { codexPoolConfigPath, serviceName, sub2apiConfigPath } from "./types";
@@ -317,35 +317,6 @@ export async function codexPoolValidate(config: UniDeskConfig, options: Disclosu
return options.full ? response : renderCodexPoolValidateResult(response);
}
export async function codexPoolRuntimeState(config: UniDeskConfig, options: DisclosureOptions): Promise<Record<string, unknown>> {
const pool = readCodexPoolConfig();
const runtimeTarget = codexPoolRuntimeTarget(options.targetId);
const result = await runRemoteCodexPoolScript(config, "runtime-state", runtimeStateScript(pool, runtimeTarget), runtimeTarget);
const parsed = parseJsonOutput(result.stdout);
if (options.raw) {
return {
ok: result.exitCode === 0 && boolField(parsed, "ok", false),
action: "platform-infra-sub2api-codex-pool-runtime-state",
remote: compactCapture(result, { full: true }),
parsed,
valuesPrinted: false,
};
}
return {
ok: result.exitCode === 0 && boolField(parsed, "ok", false),
action: "platform-infra-sub2api-codex-pool-runtime-state",
target: {
id: runtimeTarget.id,
route: runtimeTarget.route,
namespace: runtimeTarget.namespace,
runtimeMode: runtimeTarget.runtimeMode,
},
state: options.full ? parsed : compactRuntimeAccountState(parsed),
remote: compactCapture(result, { full: result.exitCode !== 0 || parsed === null }),
valuesPrinted: false,
};
}
export async function codexPoolTrace(config: UniDeskConfig, options: TraceOptions): Promise<Record<string, unknown> | RenderedCliResult> {
const pool = readCodexPoolConfig();
const runtimeTarget = codexPoolRuntimeTarget(options.targetId);
@@ -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, CodexSentinelProtectPolicy, CodexTempUnschedulablePolicy, CodexTempUnschedulableRule, OpenAIResponsesWebSocketsV2Mode } from "./types";
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 { 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";
@@ -59,6 +59,7 @@ export function collectCodexProfiles(): CodexProfile[] {
priority: entry.priority,
capacity: entry.capacity ?? pool.defaultAccountCapacity,
loadFactor: entry.loadFactor ?? pool.defaultAccountLoadFactor,
tempUnschedulableTemplate: entry.tempUnschedulableTemplate,
tempUnschedulable: entry.tempUnschedulable,
authOpenAIKeyShape: existsSync(authPath) ? "unknown" : "missing",
ok: false,
@@ -127,13 +128,21 @@ export function readCodexPoolConfig(): CodexPoolConfig {
if (!isRecord(pool)) throw new Error(`${codexPoolConfigPath}.pool must be a YAML object`);
if (!isRecord(parsed.profiles)) throw new Error(`${codexPoolConfigPath}.profiles must be a YAML object`);
rejectSchedulableYamlField(pool, "pool");
const defaultTempUnschedulable = readTempUnschedulablePolicy(pool.defaultTempUnschedulable, "pool.defaultTempUnschedulable");
const runtime = readRuntimeConfig(parsed.runtime);
const defaultTempUnschedulableTemplate = readRuntimeTemplateRef(
pool.defaultTempUnschedulableTemplate,
"pool.defaultTempUnschedulableTemplate",
runtime,
);
const defaultTempUnschedulable = runtime.templatesById[defaultTempUnschedulableTemplate]!.tempUnschedulable;
const defaultAccountPriorityValue = readAccountPriority(pool.defaultAccountPriority, "pool.defaultAccountPriority");
const defaultAccountCapacityValue = readAccountCapacity(pool.defaultAccountCapacity, "pool.defaultAccountCapacity");
const defaultAccountLoadFactorValue = readAccountLoadFactor(pool.defaultAccountLoadFactor, "pool.defaultAccountLoadFactor");
const defaultSentinelProtect = readSentinelProtectPolicy(pool.defaultSentinelProtect, "pool.defaultSentinelProtect");
const profiles = readProfileConfig(
parsed.profiles,
runtime,
defaultTempUnschedulableTemplate,
defaultTempUnschedulable,
defaultSentinelProtect,
defaultAccountPriorityValue,
@@ -165,9 +174,11 @@ export function readCodexPoolConfig(): CodexPoolConfig {
defaultAccountPriority: defaultAccountPriorityValue,
defaultAccountCapacity: defaultAccountCapacityValue,
defaultAccountLoadFactor: defaultAccountLoadFactorValue,
defaultTempUnschedulableTemplate,
defaultTempUnschedulable,
defaultSentinelProtect,
profiles,
runtime,
manualAccounts,
publicExposure: readPublicExposureConfig(parsed.publicExposure),
localCodex: readLocalCodexConfig(parsed.localCodex),
@@ -188,8 +199,46 @@ export function readCodexPoolConfig(): CodexPoolConfig {
return config;
}
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`);
const templates: CodexRuntimeTemplate[] = value.templates.map((item, index) => {
if (!isRecord(item)) throw new Error(`${codexPoolConfigPath}.runtime.templates[${index}] must be a YAML object`);
const id = requiredStringConfigField(item, "id", `runtime.templates[${index}]`);
if (!/^[a-z0-9](?:[a-z0-9._-]*[a-z0-9])?$/u.test(id)) {
throw new Error(`${codexPoolConfigPath}.runtime.templates[${index}].id has an unsupported format`);
}
const kind = requiredStringConfigField(item, "kind", `runtime.templates[${index}]`);
if (kind !== "temp-unschedulable") {
throw new Error(`${codexPoolConfigPath}.runtime.templates[${index}].kind must be temp-unschedulable`);
}
return {
id,
kind,
description: readRequiredDescription(item.description, `runtime.templates[${index}].description`, 300),
tempUnschedulable: readTempUnschedulablePolicy(item.spec, `runtime.templates[${index}].spec`),
};
});
if (templates.length === 0) throw new Error(`${codexPoolConfigPath}.runtime.templates must not be empty`);
const templatesById: Record<string, CodexRuntimeTemplate> = {};
for (const template of templates) {
if (templatesById[template.id] !== undefined) throw new Error(`${codexPoolConfigPath}.runtime.templates duplicates id ${template.id}`);
templatesById[template.id] = template;
}
return { templates, templatesById };
}
export function readRuntimeTemplateRef(value: unknown, key: string, runtime: CodexRuntimeConfig): string {
const id = stringValue(value);
if (id === null) throw new Error(`${codexPoolConfigPath}.${key} must be a non-empty template id`);
if (runtime.templatesById[id] === undefined) throw new Error(`${codexPoolConfigPath}.${key} references missing runtime template ${id}`);
return id;
}
export function readProfileConfig(
value: unknown,
runtime: CodexRuntimeConfig,
defaultTempUnschedulableTemplate: string,
defaultTempUnschedulable: CodexTempUnschedulablePolicy,
defaultSentinelProtect: CodexSentinelProtectPolicy,
defaultPriority: number,
@@ -221,7 +270,13 @@ export function readProfileConfig(
const priority = readAccountPriority(entry.priority, `profiles.entries[${index}].priority`, defaultPriority);
const capacity = entry.capacity === undefined || entry.capacity === null ? null : readAccountCapacity(entry.capacity, `profiles.entries[${index}].capacity`);
const loadFactor = entry.loadFactor === undefined || entry.loadFactor === null ? null : readAccountLoadFactor(entry.loadFactor, `profiles.entries[${index}].loadFactor`);
const tempUnschedulable = readTempUnschedulablePolicy(entry.tempUnschedulable, `profiles.entries[${index}].tempUnschedulable`, defaultTempUnschedulable);
if (entry.tempUnschedulable !== undefined) {
throw new Error(`${codexPoolConfigPath}.profiles.entries[${index}].tempUnschedulable was replaced by tempUnschedulableTemplate`);
}
const tempUnschedulableTemplate = entry.tempUnschedulableTemplate === undefined || entry.tempUnschedulableTemplate === null
? defaultTempUnschedulableTemplate
: readRuntimeTemplateRef(entry.tempUnschedulableTemplate, `profiles.entries[${index}].tempUnschedulableTemplate`, runtime);
const tempUnschedulable = runtime.templatesById[tempUnschedulableTemplate]?.tempUnschedulable ?? defaultTempUnschedulable;
return {
profile,
accountName,
@@ -236,6 +291,7 @@ export function readProfileConfig(
priority,
capacity,
loadFactor,
tempUnschedulableTemplate,
tempUnschedulable,
};
});
@@ -22,8 +22,9 @@ import { parseEnvFile, readTextFile, redactRepoPath, requiredEnvValue } from "..
import { runSshCommandCapture, type SshCaptureResult } from "../ssh";
import type { ConfirmOptions, DisclosureOptions, SentinelImageOptions, SentinelProbeOptions, SentinelReportOptions, SyncOptions, TraceOptions } from "./types";
import { codexPoolCleanupProbes, codexPoolConfigureLocal, codexPoolExpose, codexPoolPlan, codexPoolRuntimeState, codexPoolSentinelImage, codexPoolSentinelProbe, codexPoolSentinelReport, codexPoolSync, codexPoolTrace, codexPoolValidate } from "./actions";
import { codexPoolCleanupProbes, codexPoolConfigureLocal, codexPoolExpose, codexPoolPlan, codexPoolSentinelImage, codexPoolSentinelProbe, codexPoolSentinelReport, codexPoolSync, codexPoolTrace, codexPoolValidate } from "./actions";
import { renderCodexPoolPlan } from "./render";
import { codexPoolRuntime } from "./runtime";
import { defaultCodexPoolRuntimeTargetId } from "./runtime-target";
import { codexPoolHelp } from "./types";
@@ -36,7 +37,7 @@ export async function runCodexPoolCommand(config: UniDeskConfig, args: string[])
}
if (action === "sync") return await codexPoolSync(config, parseSyncOptions(args.slice(1)));
if (action === "validate") return await codexPoolValidate(config, parseDisclosureOptions(args.slice(1)));
if (action === "runtime-state") return await codexPoolRuntimeState(config, parseDisclosureOptions(args.slice(1)));
if (action === "runtime") return await codexPoolRuntime(config, args.slice(1));
if (action === "trace") return await codexPoolTrace(config, parseTraceOptions(args.slice(1)));
if (action === "sentinel-image") return await codexPoolSentinelImage(config, parseSentinelImageOptions(args.slice(1)));
if (action === "sentinel-probe") return await codexPoolSentinelProbe(config, parseSentinelProbeOptions(args.slice(1)));
@@ -70,6 +70,7 @@ export function redactProfile(profile: CodexProfile): Record<string, unknown> {
priority: profile.priority,
capacity: profile.capacity,
loadFactor: profile.loadFactor,
tempUnschedulableTemplate: profile.tempUnschedulableTemplate,
tempUnschedulable: tempUnschedulableSummary(profile.tempUnschedulable),
apiKeyPresent: profile.apiKey !== null && profile.apiKey.length > 0,
apiKeyBytes: profile.apiKey === null ? 0 : Buffer.byteLength(profile.apiKey, "utf8"),
@@ -93,6 +94,7 @@ export function compactProfile(profile: CodexProfile): Record<string, unknown> {
sentinelProtectConsecutiveFailures: profile.sentinelProtect.enabled ? profile.sentinelProtect.consecutiveFailures : undefined,
capacity: profile.capacity,
loadFactor: profile.loadFactor,
tempUnschedulableTemplate: profile.tempUnschedulableTemplate,
tempUnschedulableEnabled: profile.tempUnschedulable.enabled,
tempUnschedulableRuleCount: profile.tempUnschedulable.rules.length,
apiKeyPresent: profile.apiKey !== null && profile.apiKey.length > 0,
@@ -142,7 +144,14 @@ export function codexPoolConfigSummary(pool: CodexPoolConfig, target: CodexPoolR
defaultAccountPriority: pool.defaultAccountPriority,
defaultAccountCapacity: pool.defaultAccountCapacity,
defaultAccountLoadFactor: pool.defaultAccountLoadFactor,
defaultTempUnschedulableTemplate: pool.defaultTempUnschedulableTemplate,
defaultTempUnschedulable: tempUnschedulableSummary(pool.defaultTempUnschedulable),
runtimeTemplates: pool.runtime.templates.map((template) => ({
id: template.id,
kind: template.kind,
description: template.description,
tempUnschedulable: tempUnschedulableSummary(template.tempUnschedulable),
})),
defaultSentinelProtect: pool.defaultSentinelProtect,
profileCount: pool.profiles.length,
manualAccounts: {
@@ -188,43 +197,6 @@ export function pickSummaryFields(item: Record<string, unknown>, keys: string[])
return result;
}
export function compactRuntimeAccountState(parsed: Record<string, unknown> | null): Record<string, unknown> | null {
if (parsed === null) return null;
const state = isRecord(parsed.state) ? parsed.state : {};
const accounts = recordArray(state.accounts).map((item) => {
const temp = isRecord(item.tempUnschedulable) ? item.tempUnschedulable : {};
const poolMode = isRecord(item.poolMode) ? item.poolMode : {};
const optimization = isRecord(item.optimization) ? item.optimization : {};
return {
...pickSummaryFields(item, ["accountName", "accountId", "type", "status", "schedulable", "proxyId"]),
tempEnabled: temp.enabled,
tempStatusCodes: temp.statusCodes,
tempActive: temp.active,
poolModeEnabled: poolMode.enabled,
tempUnschedulableNeedsAlignment: optimization.tempUnschedulableNeedsAlignment,
credentialsPrinted: false,
};
});
const runtimeImage = isRecord(parsed.runtimeImage) ? parsed.runtimeImage : {};
return {
ok: parsed.ok,
mode: parsed.mode,
namespace: parsed.namespace,
serviceDns: parsed.serviceDns,
appPod: parsed.appPod,
runtimeImage: pickSummaryFields(runtimeImage, ["container", "image", "ready", "restartCount", "startedAt", "health"]),
group: state.group,
yamlBaseline: state.yamlBaseline,
summary: state.summary,
accounts,
disclosure: {
full: "bun scripts/cli.ts platform-infra sub2api codex-pool runtime-state --full",
raw: "bun scripts/cli.ts platform-infra sub2api codex-pool runtime-state --raw",
},
valuesPrinted: false,
};
}
export function compactStatusBlock(block: unknown, keys: string[]): Record<string, unknown> | null {
if (!isRecord(block)) return null;
const items = recordArray(block.items);
@@ -30,7 +30,7 @@ function pyJson(value: unknown): string {
return `json.loads(${JSON.stringify(JSON.stringify(value))})`;
}
export function remotePythonScript(mode: "sync" | "validate" | "runtime-state" | "trace" | "cleanup-probes" | "sentinel-probe", encodedPayload: string, pool: CodexPoolConfig, target: CodexPoolRuntimeTarget): string {
export function remotePythonScript(mode: "sync" | "validate" | "trace" | "cleanup-probes" | "sentinel-probe", encodedPayload: string, pool: CodexPoolConfig, target: CodexPoolRuntimeTarget): string {
const hostDockerEnvPath = target.runtimeMode === "host-docker" ? target.hostDockerEnvPath : null;
return `
set -u
@@ -2437,134 +2437,6 @@ def account_temp_unschedulable_status(token):
"valuesPrinted": False,
}
def normalized_int(value):
if isinstance(value, bool):
return None
if isinstance(value, int):
return value
if isinstance(value, float):
return int(value)
if isinstance(value, str):
try:
return int(value.strip())
except ValueError:
return None
return None
def normalized_status_codes(value):
if not isinstance(value, list):
return None
codes = []
for item in value:
code = normalized_int(item)
if code is not None and 100 <= code <= 599 and code not in codes:
codes.append(code)
return sorted(codes)
def temp_unschedulable_active(until):
if not isinstance(until, str) or not until.strip():
return False
try:
parsed = datetime.fromisoformat(until.replace("Z", "+00:00"))
if parsed.tzinfo is None:
parsed = parsed.replace(tzinfo=timezone.utc)
return parsed > datetime.now(timezone.utc)
except ValueError:
return False
def account_runtime_state(token):
group = next((item for item in list_groups(token) if item.get("name") == POOL_GROUP_NAME), None)
if not isinstance(group, dict) or group.get("id") is None:
return {
"ok": False,
"group": {"name": POOL_GROUP_NAME, "id": None, "found": False},
"accounts": [],
"error": "pool-group-not-found",
"valuesPrinted": False,
}
baseline = normalize_temp_unschedulable_credentials(EXPECTED_DEFAULT_TEMP_UNSCHEDULABLE)
baseline_codes = sorted(set(rule.get("error_code") for rule in baseline["rules"] if isinstance(rule.get("error_code"), int)))
protected_names = set(item.get("accountName") for item in MANUAL_ACCOUNT_PROTECTIONS if isinstance(item, dict) and isinstance(item.get("accountName"), str))
accounts = list_accounts_for_group(token, group["id"])
items = []
for account in sorted(accounts, key=lambda item: str(item.get("name") or "")):
detail = get_account_detail(token, account)
credentials = runtime_account_credentials(detail)
runtime_policy = normalize_temp_unschedulable_credentials(credentials)
runtime_codes = sorted(set(rule.get("error_code") for rule in runtime_policy["rules"] if isinstance(rule.get("error_code"), int)))
name = detail.get("name") or account.get("name")
pool_mode = bool_value(credentials.get("pool_mode"))
temp_until = detail.get("temp_unschedulable_until") or detail.get("tempUnschedulableUntil")
temp_reason = detail.get("temp_unschedulable_reason") or detail.get("tempUnschedulableReason") or ""
origin = "yaml-managed" if name in EXPECTED_ACCOUNT_TEMP_UNSCHEDULABLE else ("yaml-protected-manual" if name in protected_names else "runtime-manual")
supports_policy = (detail.get("type") or account.get("type")) == "apikey"
policy_matches = runtime_policy == baseline if supports_policy else None
missing_codes = [code for code in baseline_codes if code not in runtime_codes] if supports_policy else []
items.append({
"accountName": name,
"accountId": detail.get("id") or account.get("id"),
"origin": origin,
"platform": detail.get("platform") or account.get("platform"),
"type": detail.get("type") or account.get("type"),
"status": detail.get("status") or account.get("status"),
"schedulable": detail.get("schedulable") if detail.get("schedulable") is not None else account.get("schedulable"),
"proxyId": detail.get("proxy_id") if detail.get("proxy_id") is not None else account.get("proxy_id"),
"priority": detail.get("priority") if detail.get("priority") is not None else account.get("priority"),
"concurrency": detail.get("concurrency") if detail.get("concurrency") is not None else account.get("concurrency"),
"currentConcurrency": account.get("current_concurrency"),
"baseUrl": normalize_runtime_base_url(credentials.get("base_url")),
"tempUnschedulable": {
"enabled": runtime_policy["enabled"],
"ruleCount": len(runtime_policy["rules"]),
"statusCodes": runtime_codes,
"rules": summarize_temp_unschedulable_rules(runtime_policy["rules"]),
"until": temp_until,
"reasonPreview": text(str(temp_reason), 500) if temp_reason else "",
"stateRecorded": temp_until is not None or bool(temp_reason),
"active": temp_unschedulable_active(temp_until),
"matchesYamlBaseline": policy_matches,
"missingYamlBaselineStatusCodes": missing_codes,
},
"poolMode": {
"enabled": pool_mode,
"retryCountConfigured": normalized_int(credentials.get("pool_mode_retry_count")) if "pool_mode_retry_count" in credentials else None,
"retryStatusCodesConfigured": normalized_status_codes(credentials.get("pool_mode_retry_status_codes")) if "pool_mode_retry_status_codes" in credentials else None,
},
"optimization": {
"tempUnschedulableNeedsAlignment": supports_policy and runtime_policy != baseline,
"poolModeShouldBeDisabled": pool_mode,
},
"credentialsPrinted": False,
})
temp_alignment = [item["accountName"] for item in items if item["optimization"]["tempUnschedulableNeedsAlignment"]]
pool_mode_enabled = [item["accountName"] for item in items if item["poolMode"]["enabled"]]
return {
"ok": True,
"group": {
"name": POOL_GROUP_NAME,
"id": group.get("id"),
"found": True,
"platform": group.get("platform"),
"status": group.get("status"),
},
"yamlBaseline": {
"enabled": baseline["enabled"],
"ruleCount": len(baseline["rules"]),
"statusCodes": baseline_codes,
},
"summary": {
"accountCount": len(items),
"tempUnschedulableEnabledCount": sum(1 for item in items if item["tempUnschedulable"]["enabled"]),
"tempUnschedulableNeedsAlignmentCount": len(temp_alignment),
"tempUnschedulableNeedsAlignment": temp_alignment,
"poolModeEnabledCount": len(pool_mode_enabled),
"poolModeEnabled": pool_mode_enabled,
"optimizationAvailable": bool(temp_alignment or pool_mode_enabled),
},
"accounts": items,
"valuesPrinted": False,
}
def account_capacity_status(token):
accounts = list_accounts(token)
by_name = {item.get("name"): item for item in accounts if isinstance(item.get("name"), str)}
@@ -2905,21 +2777,6 @@ def run_validate():
"validation": {"gatewayModels": gateway, "gatewayResponses": responses_smoke, "gatewayResponsesRecent": responses_evidence, "gatewayCompactRecent": compact_evidence},
}
def run_runtime_state():
admin_email, token, admin_compliance = login()
state = account_runtime_state(token)
return {
"ok": state.get("ok") is True,
"mode": "runtime-state",
"namespace": NAMESPACE,
"serviceDns": SERVICE_DNS,
"appPod": APP_POD,
"admin": {"email": admin_email, "tokenPrinted": False, "compliance": admin_compliance},
"runtimeImage": app_pod_runtime_image(),
"state": state,
"valuesPrinted": False,
}
def parse_log_line(line):
json_start = line.find("{")
if json_start < 0:
@@ -3430,8 +3287,6 @@ def run_cleanup_probes():
try:
if MODE == "sync":
result = run_sync()
elif MODE == "runtime-state":
result = run_runtime_state()
elif MODE == "trace":
result = run_trace()
elif MODE == "cleanup-probes":
@@ -35,10 +35,6 @@ export function validateScript(pool: CodexPoolConfig, target: CodexPoolRuntimeTa
return remotePythonScript("validate", "", pool, target);
}
export function runtimeStateScript(pool: CodexPoolConfig, target: CodexPoolRuntimeTarget): string {
return remotePythonScript("runtime-state", "", pool, target);
}
export function traceScript(pool: CodexPoolConfig, options: TraceOptions, target: CodexPoolRuntimeTarget): string {
const encoded = Buffer.from(JSON.stringify({
requestId: options.requestId,
@@ -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-state" | "sentinel-probe" | "sentinel-image-status" | "sentinel-image-build";
export type RemoteCodexPoolMode = "sync" | "validate" | "runtime" | "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);
@@ -83,7 +83,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-state") return "bun scripts/cli.ts platform-infra sub2api codex-pool runtime-state";
if (mode === "runtime") return "bun scripts/cli.ts platform-infra sub2api codex-pool runtime list";
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";
@@ -0,0 +1,552 @@
import type { UniDeskConfig } from "../config";
import { desiredAccountNames } from "./accounts";
import { readCodexPoolConfig } from "./config";
import { protectedManualAccountsForTarget } from "./public-exposure";
import { renderSub2ApiTempUnschedulableCredentials } from "./redaction";
import { boolField, compactCapture, parseJsonOutput, runRemoteCodexPoolScript } from "./remote";
import { codexPoolRuntimeTarget, defaultCodexPoolRuntimeTargetId } from "./runtime-target";
type RuntimeAction = "list" | "get" | "errors" | "apply" | "delete";
interface RuntimeOptions {
action: RuntimeAction;
account: string | null;
template: string | null;
kind: "temp-unschedulable";
confirm: boolean;
full: boolean;
raw: boolean;
since: string;
tail: number;
targetId: string;
}
export async function codexPoolRuntime(config: UniDeskConfig, args: string[]): Promise<Record<string, unknown>> {
const options = parseRuntimeOptions(args);
const pool = readCodexPoolConfig();
const target = codexPoolRuntimeTarget(options.targetId);
const selectedTemplate = options.template === null ? null : pool.runtime.templatesById[options.template];
if (options.template !== null && selectedTemplate === undefined) {
throw new Error(`unknown runtime template ${options.template}; available: ${pool.runtime.templates.map((item) => item.id).join(", ")}`);
}
const payload = {
action: options.action,
account: options.account,
kind: options.kind,
confirm: options.confirm,
full: options.full,
since: options.since,
tail: options.tail,
groupName: pool.groupName,
adminEmailDefault: pool.adminEmailDefault,
managedAccountNames: desiredAccountNames(pool),
protectedManualAccountNames: protectedManualAccountsForTarget(pool, target).map((item) => item.accountName),
templates: pool.runtime.templates.map((template) => ({
id: template.id,
kind: template.kind,
description: template.description,
credentials: renderSub2ApiTempUnschedulableCredentials(template.tempUnschedulable),
})),
selectedTemplate: selectedTemplate === null || selectedTemplate === undefined ? null : {
id: selectedTemplate.id,
kind: selectedTemplate.kind,
credentials: renderSub2ApiTempUnschedulableCredentials(selectedTemplate.tempUnschedulable),
},
};
const result = await runRemoteCodexPoolScript(config, "runtime", runtimeScript(payload, target), target);
const parsed = parseJsonOutput(result.stdout);
const ok = result.exitCode === 0 && boolField(parsed, "ok", false);
if (options.raw) {
return {
ok,
action: "platform-infra-sub2api-codex-pool-runtime",
remote: compactCapture(result, { full: true }),
parsed,
valuesPrinted: false,
};
}
return {
ok,
action: "platform-infra-sub2api-codex-pool-runtime",
target: {
id: target.id,
route: target.route,
namespace: target.namespace,
runtimeMode: target.runtimeMode,
endpoint: target.serviceDns,
},
request: {
operation: options.action,
account: options.account,
template: options.template,
kind: options.kind,
confirm: options.confirm,
since: options.action === "errors" ? options.since : undefined,
tail: options.action === "errors" ? options.tail : undefined,
},
runtime: parsed,
remote: compactCapture(result, { full: options.full || result.exitCode !== 0 || parsed === null }),
disclosure: options.action === "list"
? {
get: "bun scripts/cli.ts platform-infra sub2api codex-pool runtime get --account <name-or-id>",
errors: "bun scripts/cli.ts platform-infra sub2api codex-pool runtime errors [--account <name-or-id>] [--since 24h]",
}
: undefined,
valuesPrinted: false,
};
}
export function parseRuntimeOptions(args: string[]): RuntimeOptions {
const [actionRaw = "list", ...rest] = args;
if (actionRaw !== "list" && actionRaw !== "get" && actionRaw !== "errors" && actionRaw !== "apply" && actionRaw !== "delete") {
throw new Error("runtime usage: list|get|errors|apply|delete [--account <name-or-id>] [--since 24h] [--tail 50000] [--template <id>] [--kind temp-unschedulable] [--confirm] [--target <id>] [--full|--raw]");
}
let account: string | null = null;
let template: string | null = null;
let kind = "temp-unschedulable" as const;
let confirm = false;
let full = false;
let raw = 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 === "--account") account = readValue("--account");
else if (arg.startsWith("--account=")) account = arg.slice("--account=".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}`);
}
if ((actionRaw === "get" || actionRaw === "apply" || actionRaw === "delete") && !account) {
throw new Error(`runtime ${actionRaw} requires --account <name-or-id>`);
}
if (account !== null && (account.length > 256 || /[\r\n]/u.test(account))) throw new Error("--account has an unsupported format");
if (actionRaw === "apply" && !template) throw new Error("runtime apply requires --template <id>");
if (actionRaw !== "apply" && template !== null) throw new Error(`runtime ${actionRaw} does not accept --template`);
if (actionRaw !== "errors" && (since !== "24h" || tail !== 50_000)) throw new Error(`runtime ${actionRaw} 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 ((actionRaw === "list" || actionRaw === "get" || actionRaw === "errors") && confirm) throw new Error(`runtime ${actionRaw} does not accept --confirm`);
if (full && raw) throw new Error("use only one of --full or --raw");
return { action: actionRaw, account, template, kind, confirm, full, raw, since, tail, targetId };
}
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;
}
function pyJson(value: unknown): string {
return `json.loads(${JSON.stringify(JSON.stringify(value))})`;
}
function runtimeScript(payload: unknown, target: ReturnType<typeof codexPoolRuntimeTarget>): string {
return `
set -u
python3 - <<'PY'
import json
import re
import subprocess
import sys
from urllib.parse import quote
RUNTIME_MODE = ${pyJson(target.runtimeMode)}
NAMESPACE = ${pyJson(target.namespace)}
HOST_DOCKER_APP_PORT = ${pyJson(target.hostDockerAppPort)}
HOST_DOCKER_ENV_PATH = ${pyJson(target.hostDockerEnvPath)}
HOST_DOCKER_APP_CONTAINER = "sub2api-app"
APP_SECRET_NAME = ${pyJson(target.appSecretName)}
PAYLOAD = ${pyJson(payload)}
def run(cmd, input_bytes=None):
return subprocess.run(cmd, input=input_bytes, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
def text(value, limit=1000):
if isinstance(value, bytes):
value = value.decode("utf-8", errors="replace")
return str(value)[-limit:]
def docker(args):
proc = run(["docker", *args])
if proc.returncode == 0:
return proc
sudo_proc = run(["sudo", "-n", "docker", *args])
return sudo_proc if sudo_proc.returncode == 0 else proc
def kubectl(args, input_bytes=None):
return run(["kubectl", *args], input_bytes)
def kube_json(args, label):
proc = kubectl([*args, "-o", "json"])
if proc.returncode != 0:
raise RuntimeError(f"{label} failed: {text(proc.stderr)}")
return json.loads(proc.stdout.decode("utf-8"))
def read_host_env():
try:
with open(HOST_DOCKER_ENV_PATH, "r", encoding="utf-8") as handle:
lines = handle.read().splitlines()
except PermissionError:
proc = run(["sudo", "-n", "cat", HOST_DOCKER_ENV_PATH])
if proc.returncode != 0:
raise RuntimeError("read host env failed: " + text(proc.stderr))
lines = proc.stdout.decode("utf-8", errors="replace").splitlines()
values = {}
for line in lines:
stripped = line.strip()
if not stripped or stripped.startswith("#") or "=" not in stripped:
continue
key, value = stripped.split("=", 1)
value = value.strip()
if len(value) >= 2 and value[0] == value[-1] and value[0] in ("'", '"'):
value = value[1:-1]
values[key.strip()] = value
return values
def select_app_pod():
if RUNTIME_MODE == "host-docker":
return HOST_DOCKER_APP_CONTAINER
pods = kube_json(["-n", NAMESPACE, "get", "pods", "-l", "app.kubernetes.io/name=sub2api"], "sub2api pods").get("items") or []
running = [item for item in pods if (item.get("status") or {}).get("phase") == "Running"]
if not running:
raise RuntimeError("sub2api app pod not found")
return running[0]["metadata"]["name"]
APP_POD = select_app_pod()
def config_value(name, key, fallback=None):
if RUNTIME_MODE == "host-docker":
return read_host_env().get(key) or fallback
data = kube_json(["-n", NAMESPACE, "get", "configmap", name], f"configmap/{name}").get("data") or {}
return data.get(key) or fallback
def secret_value(name, key):
if RUNTIME_MODE == "host-docker":
return read_host_env().get(key)
import base64
data = kube_json(["-n", NAMESPACE, "get", "secret", name], f"secret/{name}").get("data") or {}
return base64.b64decode(data[key]).decode("utf-8") if key in data else None
def parse_curl(proc):
output = proc.stdout.decode("utf-8", errors="replace")
marker = "\\n__HTTP_CODE__:"
pos = output.rfind(marker)
if pos < 0:
return {"ok": False, "httpStatus": 0, "json": None, "body": output, "stderr": text(proc.stderr)}
body = output[:pos]
try:
status = int(output[pos + len(marker):].strip()[-3:])
except ValueError:
status = 0
try:
parsed = json.loads(body) if body.strip() else None
except json.JSONDecodeError:
parsed = None
return {"ok": proc.returncode == 0 and 200 <= status < 300, "httpStatus": status, "json": parsed, "body": body, "stderr": text(proc.stderr)}
def curl_api(method, path, bearer=None, payload=None):
body = b"" if payload is None else json.dumps(payload, separators=(",", ":")).encode("utf-8")
script = r'''
set -eu
method="$1"
url="$2"
token="\${3:-}"
tmp="$(mktemp)"
trap 'rm -f "$tmp"' EXIT
cat > "$tmp"
if [ -n "$token" ] && [ "$method" = GET ] && [ ! -s "$tmp" ]; then
curl -sS -w '\n__HTTP_CODE__:%{http_code}' -X "$method" -H "Authorization: Bearer $token" "$url"
elif [ -n "$token" ]; then
curl -sS -w '\n__HTTP_CODE__:%{http_code}' -X "$method" -H 'Content-Type: application/json' -H "Authorization: Bearer $token" --data-binary @"$tmp" "$url"
elif [ "$method" = GET ] && [ ! -s "$tmp" ]; then
curl -sS -w '\n__HTTP_CODE__:%{http_code}' -X "$method" "$url"
else
curl -sS -w '\n__HTTP_CODE__:%{http_code}' -X "$method" -H 'Content-Type: application/json' --data-binary @"$tmp" "$url"
fi
'''
if RUNTIME_MODE == "host-docker":
proc = run(["sh", "-c", script, "sh", method, f"http://127.0.0.1:{HOST_DOCKER_APP_PORT}{path}", bearer or ""], body)
else:
proc = run(["kubectl", "-n", NAMESPACE, "exec", "-i", APP_POD, "--", "sh", "-c", script, "sh", method, f"http://127.0.0.1:8080{path}", bearer or ""], body)
return parse_curl(proc)
def data_of(response, label):
parsed = response.get("json")
code = parsed.get("code") if isinstance(parsed, dict) else None
if response.get("ok") is not True or (code is not None and code != 0):
message = parsed.get("message") if isinstance(parsed, dict) else text(response.get("body"), 500)
raise RuntimeError(f"{label} failed: http={response.get('httpStatus')} message={message}")
return parsed.get("data") if isinstance(parsed, dict) and "data" in parsed else parsed
def items_of(data):
if isinstance(data, list):
return data
if isinstance(data, dict):
for key in ("items", "groups", "accounts"):
if isinstance(data.get(key), list):
return data[key]
return []
def login():
email = config_value("sub2api-config", "ADMIN_EMAIL", PAYLOAD["adminEmailDefault"])
password = secret_value(APP_SECRET_NAME, "ADMIN_PASSWORD")
if not password:
raise RuntimeError("ADMIN_PASSWORD missing")
data = data_of(curl_api("POST", "/api/v1/auth/login", payload={"email": email, "password": password}), "admin login")
token = None
if isinstance(data, dict):
token = data.get("access_token") or data.get("token")
if not token:
raise RuntimeError("admin login response has no token")
return token
def list_groups(token):
return items_of(data_of(curl_api("GET", "/api/v1/admin/groups/all?platform=openai", bearer=token), "list groups"))
def list_group_accounts(token, group_id):
path = f"/api/v1/admin/accounts?group_id={group_id}&page=1&page_size=500&platform=openai"
return items_of(data_of(curl_api("GET", path, bearer=token), "list group accounts"))
def account_detail(token, account_id):
return data_of(curl_api("GET", f"/api/v1/admin/accounts/{account_id}", bearer=token), "get account")
def normalize_policy(credentials):
if not isinstance(credentials, dict):
credentials = {}
enabled = credentials.get("temp_unschedulable_enabled") is True
raw = credentials.get("temp_unschedulable_rules")
if isinstance(raw, str):
try:
raw = json.loads(raw)
except json.JSONDecodeError:
raw = []
rules = raw if isinstance(raw, list) else []
codes = sorted(set(item.get("error_code") for item in rules if isinstance(item, dict) and isinstance(item.get("error_code"), int)))
return {"enabled": enabled, "ruleCount": len(rules), "statusCodes": codes, "rules": rules}
def template_summary(template):
policy = normalize_policy(template.get("credentials"))
return {"id": template.get("id"), "kind": template.get("kind"), "description": template.get("description"), **{key: policy[key] for key in ("enabled", "ruleCount", "statusCodes")}}
def matching_template(credentials):
current = normalize_policy(credentials)
for template in PAYLOAD["templates"]:
if normalize_policy(template.get("credentials")) == current:
return template.get("id")
return None
def account_summary(detail, include_rules=False):
credentials = detail.get("credentials") if isinstance(detail.get("credentials"), dict) else {}
policy = normalize_policy(credentials)
name = detail.get("name")
management = "yaml-managed" if name in PAYLOAD["managedAccountNames"] else ("yaml-protected-manual" if name in PAYLOAD["protectedManualAccountNames"] else "runtime-manual")
result = {
"accountName": name,
"accountId": detail.get("id"),
"management": management,
"type": detail.get("type"),
"status": detail.get("status"),
"schedulable": detail.get("schedulable"),
"proxyId": detail.get("proxy_id"),
"tempUnschedulable": {key: policy[key] for key in ("enabled", "ruleCount", "statusCodes")},
"matchingTemplate": matching_template(credentials),
"poolModeEnabled": credentials.get("pool_mode") is True,
"credentialsPrinted": False,
}
if include_rules:
result["tempUnschedulable"]["rules"] = policy["rules"]
return result
def policy_summary(policy, include_rules=False):
keys = ("enabled", "ruleCount", "statusCodes", "rules") if include_rules else ("enabled", "ruleCount", "statusCodes")
return {key: policy[key] for key in keys}
def find_account(token, accounts, selector):
exact = [item for item in accounts if str(item.get("id")) == selector or item.get("name") == selector]
if len(exact) != 1:
raise RuntimeError("account-not-found-or-ambiguous: " + selector)
return account_detail(token, exact[0]["id"])
def runtime_log_lines():
since = PAYLOAD.get("since", "24h")
tail = str(PAYLOAD.get("tail", 50000))
if RUNTIME_MODE == "host-docker":
proc = docker(["logs", "--since", since, "--tail", tail, APP_POD])
else:
proc = kubectl(["-n", NAMESPACE, "logs", APP_POD, "--since=" + since, "--tail=" + tail])
if proc.returncode != 0:
raise RuntimeError("read Sub2API runtime logs failed: " + text(proc.stderr))
output = proc.stdout + b"\\n" + proc.stderr
return output.decode("utf-8", errors="replace").splitlines()
def compact_error(value):
if not isinstance(value, str):
return ""
return " ".join(value.replace("\x00", "").split())[:240]
def observed_runtime_errors(accounts):
selector = PAYLOAD.get("account")
selected = [item for item in accounts if selector is None or str(item.get("id")) == str(selector) or item.get("name") == selector]
if selector is not None and len(selected) != 1:
raise RuntimeError("account-not-found-or-ambiguous: " + str(selector))
selected_ids = set(item.get("id") for item in selected)
stats = {}
for item in selected:
stats[item.get("id")] = {
"accountName": item.get("name"),
"accountId": item.get("id"),
"upstreamEventCount": 0,
"observedStatusCodes": {},
"forwardFailureCount": 0,
"forwardFailureStatusCodes": {},
"lastSeenAt": None,
"errorSamples": [],
}
scanned = 0
for line in runtime_log_lines():
if "account_upstream_error" not in line and "openai.forward_failed" not in line:
continue
json_start = line.find("{")
if json_start < 0:
continue
try:
item = json.loads(line[json_start:])
except json.JSONDecodeError:
continue
account_id = item.get("account_id") if isinstance(item, dict) else None
if account_id not in selected_ids:
continue
scanned += 1
at = line.split("\t", 1)[0].strip() or None
current = stats[account_id]
if at and (current["lastSeenAt"] is None or at > current["lastSeenAt"]):
current["lastSeenAt"] = at
if "account_upstream_error" in line:
code = item.get("status_code")
if isinstance(code, int):
key = str(code)
current["observedStatusCodes"][key] = current["observedStatusCodes"].get(key, 0) + 1
current["upstreamEventCount"] += 1
continue
current["forwardFailureCount"] += 1
error = compact_error(item.get("error"))
match = re.search(r"(?:upstream error:\\s*|status(?:_code)?[=:]\\s*)(\\d{3})", error, re.IGNORECASE)
if match:
key = match.group(1)
current["forwardFailureStatusCodes"][key] = current["forwardFailureStatusCodes"].get(key, 0) + 1
if error and error not in current["errorSamples"] and len(current["errorSamples"]) < 3:
current["errorSamples"].append(error)
rows = []
aggregate = {}
for item in selected:
current = stats[item.get("id")]
observed = [{"statusCode": int(code), "count": count} for code, count in sorted(current["observedStatusCodes"].items(), key=lambda pair: int(pair[0]))]
forwarded = [{"statusCode": int(code), "count": count} for code, count in sorted(current["forwardFailureStatusCodes"].items(), key=lambda pair: int(pair[0]))]
for entry in observed:
key = str(entry["statusCode"])
aggregate[key] = aggregate.get(key, 0) + entry["count"]
current["observedStatusCodes"] = observed
current["forwardFailureStatusCodes"] = forwarded
if PAYLOAD.get("full") is not True:
current["errorSamples"] = current["errorSamples"][:1]
if current["upstreamEventCount"] > 0 or current["forwardFailureCount"] > 0 or selector is not None:
rows.append(current)
return {
"window": {"since": PAYLOAD.get("since"), "tail": PAYLOAD.get("tail"), "matchedLogEvents": scanned},
"aggregateObservedStatusCodes": [{"statusCode": int(code), "count": count} for code, count in sorted(aggregate.items(), key=lambda pair: int(pair[0]))],
"accountCountWithErrors": sum(1 for item in rows if item["upstreamEventCount"] > 0 or item["forwardFailureCount"] > 0),
"accounts": rows,
"message": "observedStatusCodes comes only from account_upstream_error; forward failures are supplemental message evidence",
}
def runtime_result():
token = login()
group = next((item for item in list_groups(token) if item.get("name") == PAYLOAD["groupName"]), None)
if not isinstance(group, dict) or group.get("id") is None:
raise RuntimeError("pool-group-not-found")
accounts = list_group_accounts(token, group["id"])
base = {
"group": {"id": group.get("id"), "name": group.get("name"), "status": group.get("status")},
"templates": [template_summary(item) for item in PAYLOAD["templates"]],
"endpoint": "host-docker" if RUNTIME_MODE == "host-docker" else "k3s-service",
"valuesPrinted": False,
}
action = PAYLOAD["action"]
if action == "list":
details = [account_detail(token, item["id"]) for item in accounts]
return {**base, "ok": True, "operation": "list", "mutation": False, "accountCount": len(details), "accounts": [account_summary(item) for item in sorted(details, key=lambda value: value.get("name") or "")]}
if action == "errors":
errors = observed_runtime_errors(accounts)
return {**base, "ok": True, "operation": "errors", "mutation": False, "errors": errors}
detail = find_account(token, accounts, str(PAYLOAD["account"]))
if action == "get":
return {**base, "ok": True, "operation": "get", "mutation": False, "account": account_summary(detail, True)}
if detail.get("type") != "apikey":
raise RuntimeError("runtime temp-unschedulable policy requires an apikey account")
credentials = dict(detail.get("credentials") or {})
before = normalize_policy(credentials)
if action == "apply":
template = PAYLOAD.get("selectedTemplate")
if not isinstance(template, dict):
raise RuntimeError("selected runtime template missing")
desired_fields = template.get("credentials") or {}
desired = dict(credentials)
desired.update(desired_fields)
after_plan = normalize_policy(desired)
exists = "temp_unschedulable_enabled" in credentials or "temp_unschedulable_rules" in credentials
change = "noop" if before == after_plan else ("update" if exists else "create")
else:
desired = dict(credentials)
desired.pop("temp_unschedulable_enabled", None)
desired.pop("temp_unschedulable_rules", None)
after_plan = normalize_policy(desired)
change = "delete" if before != after_plan or "temp_unschedulable_enabled" in credentials or "temp_unschedulable_rules" in credentials else "noop"
include_plan_rules = PAYLOAD.get("full") is True
plan = {
"change": change,
"account": account_summary(detail, include_plan_rules),
"before": policy_summary(before, include_plan_rules),
"after": policy_summary(after_plan, include_plan_rules),
"template": PAYLOAD.get("selectedTemplate", {}).get("id") if isinstance(PAYLOAD.get("selectedTemplate"), dict) else None,
"credentialsPrinted": False,
}
if PAYLOAD.get("confirm") is not True or change == "noop":
return {**base, "ok": True, "operation": action, "mode": "dry-run" if PAYLOAD.get("confirm") is not True else "confirmed-noop", "mutation": False, "plan": plan}
updated = data_of(curl_api("PUT", f"/api/v1/admin/accounts/{detail['id']}", bearer=token, payload={"credentials": desired}), "update account runtime config")
after_detail = updated if isinstance(updated, dict) else account_detail(token, detail["id"])
return {**base, "ok": True, "operation": action, "mode": "confirmed", "mutation": True, "plan": plan, "account": account_summary(after_detail, include_plan_rules)}
try:
result = runtime_result()
except Exception as exc:
result = {"ok": False, "operation": PAYLOAD.get("action"), "mutation": False, "error": str(exc), "valuesPrinted": False}
print(json.dumps(result, ensure_ascii=False, indent=2))
sys.exit(0 if result.get("ok") else 1)
PY
`;
}
@@ -139,6 +139,7 @@ export interface CodexProfile {
priority: number;
capacity: number;
loadFactor: number;
tempUnschedulableTemplate: string;
tempUnschedulable: CodexTempUnschedulablePolicy;
authOpenAIKeyShape: string;
ok: boolean;
@@ -159,6 +160,18 @@ export interface CodexTempUnschedulablePolicy {
rules: CodexTempUnschedulableRule[];
}
export interface CodexRuntimeTemplate {
id: string;
kind: "temp-unschedulable";
description: string;
tempUnschedulable: CodexTempUnschedulablePolicy;
}
export interface CodexRuntimeConfig {
templates: CodexRuntimeTemplate[];
templatesById: Record<string, CodexRuntimeTemplate>;
}
export interface CodexPoolConfig {
version: number;
kind: string;
@@ -179,9 +192,11 @@ export interface CodexPoolConfig {
defaultAccountPriority: number;
defaultAccountCapacity: number;
defaultAccountLoadFactor: number;
defaultTempUnschedulableTemplate: string;
defaultTempUnschedulable: CodexTempUnschedulablePolicy;
defaultSentinelProtect: CodexSentinelProtectPolicy;
profiles: CodexPoolProfileConfig[];
runtime: CodexRuntimeConfig;
manualAccounts: CodexPoolManualAccountsConfig;
publicExposure: CodexPoolPublicExposureConfig;
localCodex: CodexPoolLocalCodexConfig;
@@ -251,6 +266,7 @@ export interface CodexPoolProfileConfig {
priority: number;
capacity: number | null;
loadFactor: number | null;
tempUnschedulableTemplate: string;
tempUnschedulable: CodexTempUnschedulablePolicy;
}
@@ -325,14 +341,18 @@ export function codexPoolHelp(): unknown {
const pool = readCodexPoolConfig();
const runtimeTarget = codexPoolRuntimeTarget();
return {
command: "platform-infra sub2api codex-pool plan|sync|validate|runtime-state|trace|sentinel-image|sentinel-probe|sentinel-report|cleanup-probes|expose|configure-local",
command: "platform-infra sub2api codex-pool plan|sync|validate|runtime|trace|sentinel-image|sentinel-probe|sentinel-report|cleanup-probes|expose|configure-local",
output: "json, except trace 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-state [--target PK01] [--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 apply --account <name-or-id> --template <id> [--target PK01] [--confirm]",
"bun scripts/cli.ts platform-infra sub2api codex-pool runtime delete --account <name-or-id> --kind temp-unschedulable [--target PK01] [--confirm]",
"bun scripts/cli.ts platform-infra sub2api codex-pool trace [--target D601] --request-id <requestId> [--since 24h|--tail 20000|--context-seconds 300|--show-lines|--raw]",
"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",