refactor: require yaml codex pool config
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import { createHash } from "node:crypto";
|
||||
import { chmodSync, copyFileSync, existsSync, mkdirSync, readFileSync, readdirSync, statSync, writeFileSync } from "node:fs";
|
||||
import { chmodSync, copyFileSync, existsSync, mkdirSync, readFileSync, statSync, writeFileSync } from "node:fs";
|
||||
import { homedir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import type { UniDeskConfig } from "./config";
|
||||
@@ -9,7 +9,6 @@ import { applyLocalCaddyManagedSite } from "./pk01-caddy";
|
||||
import {
|
||||
codexPoolSentinelSummary,
|
||||
codexPoolSentinelRuntimeImage,
|
||||
defaultCodexPoolSentinelConfig,
|
||||
readCodexPoolSentinelConfig,
|
||||
renderCodexPoolSentinelManifest,
|
||||
type CodexPoolSentinelConfig,
|
||||
@@ -23,12 +22,6 @@ const fieldManager = "unidesk-platform-infra";
|
||||
const sub2apiConfigPath = rootPath("config", "platform-infra", "sub2api.yaml");
|
||||
const codexPoolConfigPath = rootPath("config", "platform-infra", "sub2api-codex-pool.yaml");
|
||||
const sentinelImageDockerfilePath = rootPath("src", "components", "platform-infra", "sub2api", "sentinel.Dockerfile");
|
||||
const defaultPoolGroupName = "unidesk-codex-pool";
|
||||
const defaultPoolApiKeyName = "unidesk-codex-pool-api-key";
|
||||
const defaultPoolApiKeySecretName = "sub2api-codex-pool-api-key";
|
||||
const defaultPoolApiKeySecretKey = "API_KEY";
|
||||
const defaultMinOwnerBalanceUsd = 1000;
|
||||
const defaultAccountPriority = 10;
|
||||
const remoteJobDir = "/tmp/unidesk-platform-infra-sub2api-codex-pool";
|
||||
const remoteJobTimeoutMs = 15 * 60_000;
|
||||
const remoteJobPollMs = 5_000;
|
||||
@@ -136,9 +129,11 @@ interface CodexPoolConfig {
|
||||
relatedIssues: number[];
|
||||
};
|
||||
groupName: string;
|
||||
groupDescription: string;
|
||||
apiKeyName: string;
|
||||
apiKeySecretName: string;
|
||||
apiKeySecretKey: string;
|
||||
adminEmailDefault: string;
|
||||
minOwnerBalanceUsd: number;
|
||||
minOwnerConcurrency: number;
|
||||
minOwnerConcurrencySource: "auto" | "yaml";
|
||||
@@ -760,6 +755,7 @@ async function codexPoolSync(config: UniDeskConfig, options: SyncOptions): Promi
|
||||
serviceName: runtimeTarget.serviceName,
|
||||
serviceDns: runtimeTarget.serviceDns,
|
||||
appSecretName: runtimeTarget.appSecretName,
|
||||
adminEmailDefault: pool.adminEmailDefault,
|
||||
proxy: runtimeTarget.egressProxy?.applyToSentinel ? {
|
||||
httpProxy: runtimeTarget.egressProxy.httpProxy,
|
||||
noProxy: runtimeTarget.egressProxy.noProxy,
|
||||
@@ -769,9 +765,11 @@ async function codexPoolSync(config: UniDeskConfig, options: SyncOptions): Promi
|
||||
},
|
||||
pool: {
|
||||
groupName: pool.groupName,
|
||||
groupDescription: pool.groupDescription,
|
||||
apiKeyName: pool.apiKeyName,
|
||||
apiKeySecretName: pool.apiKeySecretName,
|
||||
apiKeySecretKey: pool.apiKeySecretKey,
|
||||
adminEmailDefault: pool.adminEmailDefault,
|
||||
minOwnerBalanceUsd: pool.minOwnerBalanceUsd,
|
||||
minOwnerConcurrency: pool.minOwnerConcurrency,
|
||||
defaultAccountPriority: pool.defaultAccountPriority,
|
||||
@@ -1171,10 +1169,7 @@ function collectCodexProfiles(): CodexProfile[] {
|
||||
const pool = readCodexPoolConfig();
|
||||
if (!existsSync(codexDir)) return [];
|
||||
const seenAccountNames = new Set<string>();
|
||||
const configs = pool.profiles.length > 0
|
||||
? pool.profiles
|
||||
: discoverCodexProfileConfigs(codexDir, pool.defaultTempUnschedulable, pool.defaultSentinelProtect, pool.defaultAccountPriority);
|
||||
return configs.map((entry) => {
|
||||
return pool.profiles.map((entry) => {
|
||||
const resolved = resolveProfileFiles(codexDir, entry);
|
||||
const profile = entry.profile;
|
||||
const accountName = entry.accountName ?? uniqueAccountName(profile, seenAccountNames);
|
||||
@@ -1247,41 +1242,6 @@ function collectCodexProfiles(): CodexProfile[] {
|
||||
});
|
||||
}
|
||||
|
||||
function discoverCodexProfileConfigs(
|
||||
codexDir: string,
|
||||
defaultTempUnschedulable = defaultCodexTempUnschedulablePolicy(),
|
||||
defaultSentinelProtect = defaultCodexSentinelProtectPolicy(),
|
||||
defaultPriority = defaultAccountPriority,
|
||||
): CodexPoolProfileConfig[] {
|
||||
return readdirSync(codexDir)
|
||||
.filter((file) => file === "config.toml" || file.startsWith("config.toml."))
|
||||
.sort((a, b) => {
|
||||
if (a === "config.toml") return -1;
|
||||
if (b === "config.toml") return 1;
|
||||
return a.localeCompare(b);
|
||||
})
|
||||
.map((configFile) => {
|
||||
const suffix = configFile === "config.toml" ? "" : configFile.slice("config.toml.".length);
|
||||
const profile = suffix === "" ? "default" : suffix;
|
||||
return {
|
||||
profile,
|
||||
accountName: null,
|
||||
configFile,
|
||||
authFile: suffix === "" ? "auth.json" : `auth.json.${suffix}`,
|
||||
fallbackConfigFile: null,
|
||||
fallbackAuthFile: null,
|
||||
openaiResponsesWebSocketsV2Mode: null,
|
||||
upstreamUserAgent: null,
|
||||
trustUpstream: false,
|
||||
sentinelProtect: defaultSentinelProtect,
|
||||
priority: defaultPriority,
|
||||
capacity: null,
|
||||
loadFactor: null,
|
||||
tempUnschedulable: defaultTempUnschedulable,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
function resolveProfileFiles(codexDir: string, profile: CodexPoolProfileConfig): { configFile: string; authFile: string } {
|
||||
const configFile = existsSync(join(codexDir, profile.configFile)) || profile.fallbackConfigFile === null
|
||||
? profile.configFile
|
||||
@@ -1293,7 +1253,6 @@ function resolveProfileFiles(codexDir: string, profile: CodexPoolProfileConfig):
|
||||
}
|
||||
|
||||
function readCodexPoolConfig(): CodexPoolConfig {
|
||||
const defaults = defaultCodexPoolConfig();
|
||||
if (!existsSync(codexPoolConfigPath)) throw new Error(`${codexPoolConfigPath} is required`);
|
||||
const parsed = Bun.YAML.parse(readFileSync(codexPoolConfigPath, "utf8")) as unknown;
|
||||
if (!isRecord(parsed)) throw new Error(`${codexPoolConfigPath} must contain a YAML object`);
|
||||
@@ -1306,19 +1265,18 @@ 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", defaults.defaultTempUnschedulable);
|
||||
const defaultTempUnschedulable = readTempUnschedulablePolicy(pool.defaultTempUnschedulable, "pool.defaultTempUnschedulable");
|
||||
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", defaults.defaultSentinelProtect);
|
||||
const defaultSentinelProtect = readSentinelProtectPolicy(pool.defaultSentinelProtect, "pool.defaultSentinelProtect");
|
||||
const profiles = readProfileConfig(
|
||||
parsed.profiles,
|
||||
defaults.profiles,
|
||||
defaultTempUnschedulable,
|
||||
defaultSentinelProtect,
|
||||
defaultAccountPriorityValue,
|
||||
);
|
||||
const manualAccounts = readManualAccountsConfig(parsed.manualAccounts, defaults.manualAccounts);
|
||||
const manualAccounts = readManualAccountsConfig(parsed.manualAccounts);
|
||||
assertProtectedManualAccountsNotManaged(profiles, manualAccounts);
|
||||
const declaredAccountCapacity = desiredProfileCapacityTotal(profiles, defaultAccountCapacityValue);
|
||||
const minOwnerConcurrencySource = pool.minOwnerConcurrency === undefined || pool.minOwnerConcurrency === null ? "auto" : "yaml";
|
||||
@@ -1333,11 +1291,13 @@ function readCodexPoolConfig(): CodexPoolConfig {
|
||||
owner: requiredStringConfigField(metadata, "owner", "metadata"),
|
||||
relatedIssues: integerArrayConfigField(metadata, "relatedIssues", "metadata"),
|
||||
},
|
||||
groupName: stringValue(pool.groupName) ?? defaults.groupName,
|
||||
apiKeyName: stringValue(pool.apiKeyName) ?? defaults.apiKeyName,
|
||||
apiKeySecretName: stringValue(pool.apiKeySecretName) ?? defaults.apiKeySecretName,
|
||||
apiKeySecretKey: stringValue(pool.apiKeySecretKey) ?? defaults.apiKeySecretKey,
|
||||
minOwnerBalanceUsd: numberValue(pool.minOwnerBalanceUsd) ?? defaults.minOwnerBalanceUsd,
|
||||
groupName: requiredStringConfigField(pool, "groupName", "pool"),
|
||||
groupDescription: readRequiredDescription(pool.groupDescription, "pool.groupDescription", 300),
|
||||
apiKeyName: requiredStringConfigField(pool, "apiKeyName", "pool"),
|
||||
apiKeySecretName: requiredStringConfigField(pool, "apiKeySecretName", "pool"),
|
||||
apiKeySecretKey: requiredStringConfigField(pool, "apiKeySecretKey", "pool"),
|
||||
adminEmailDefault: readRequiredEmail(pool.adminEmailDefault, "pool.adminEmailDefault"),
|
||||
minOwnerBalanceUsd: readRequiredPositiveNumber(pool.minOwnerBalanceUsd, "pool.minOwnerBalanceUsd"),
|
||||
minOwnerConcurrency,
|
||||
minOwnerConcurrencySource,
|
||||
defaultAccountPriority: defaultAccountPriorityValue,
|
||||
@@ -1347,10 +1307,10 @@ function readCodexPoolConfig(): CodexPoolConfig {
|
||||
defaultSentinelProtect,
|
||||
profiles,
|
||||
manualAccounts,
|
||||
publicExposure: readPublicExposureConfig(parsed.publicExposure, defaults.publicExposure),
|
||||
localCodex: readLocalCodexConfig(parsed.localCodex, defaults.localCodex),
|
||||
publicExposure: readPublicExposureConfig(parsed.publicExposure),
|
||||
localCodex: readLocalCodexConfig(parsed.localCodex),
|
||||
sentinel: {
|
||||
...readCodexPoolSentinelConfig(parsed.sentinel, defaults.sentinel, codexPoolConfigPath),
|
||||
...readCodexPoolSentinelConfig(parsed.sentinel, codexPoolConfigPath),
|
||||
protectedManualAccounts: manualAccounts.protected.map((account) => account.accountName),
|
||||
},
|
||||
};
|
||||
@@ -1366,96 +1326,13 @@ function readCodexPoolConfig(): CodexPoolConfig {
|
||||
return config;
|
||||
}
|
||||
|
||||
function defaultCodexPoolConfig(): CodexPoolConfig {
|
||||
const runtimeTarget = codexPoolRuntimeTarget();
|
||||
return {
|
||||
version: 1,
|
||||
kind: "platform-infra-sub2api-codex-pool",
|
||||
metadata: {
|
||||
id: "sub2api-codex-pool",
|
||||
owner: "unidesk",
|
||||
relatedIssues: [],
|
||||
},
|
||||
groupName: defaultPoolGroupName,
|
||||
apiKeyName: defaultPoolApiKeyName,
|
||||
apiKeySecretName: defaultPoolApiKeySecretName,
|
||||
apiKeySecretKey: defaultPoolApiKeySecretKey,
|
||||
minOwnerBalanceUsd: defaultMinOwnerBalanceUsd,
|
||||
minOwnerConcurrency: 1,
|
||||
minOwnerConcurrencySource: "auto",
|
||||
defaultAccountPriority,
|
||||
defaultAccountCapacity: 5,
|
||||
defaultAccountLoadFactor: 1,
|
||||
defaultTempUnschedulable: defaultCodexTempUnschedulablePolicy(),
|
||||
defaultSentinelProtect: defaultCodexSentinelProtectPolicy(),
|
||||
profiles: [],
|
||||
manualAccounts: {
|
||||
protected: [],
|
||||
},
|
||||
publicExposure: {
|
||||
enabled: false,
|
||||
proxyName: "platform-infra-sub2api",
|
||||
configMapName: "sub2api-frpc-config",
|
||||
deploymentName: "sub2api-frpc",
|
||||
frpcImage: "fatedier/frpc:v0.68.1",
|
||||
serverAddr: "74.48.78.17",
|
||||
serverPort: 7000,
|
||||
remotePort: 21880,
|
||||
localIP: runtimeTarget.serviceDns.split(":")[0],
|
||||
localPort: 8080,
|
||||
publicBaseUrl: "http://74.48.78.17:21880",
|
||||
masterBaseUrl: "http://127.0.0.1:21880",
|
||||
masterFrps: {
|
||||
configPath: "/opt/hwlab-frp/frps.dev.toml",
|
||||
containerName: "hwlab-frps-dev",
|
||||
},
|
||||
masterCaddy: {
|
||||
enabled: true,
|
||||
domain: "sub2api.74-48-78-17.nip.io",
|
||||
configPath: "/etc/caddy/Caddyfile",
|
||||
serviceName: "caddy",
|
||||
upstreamBaseUrl: "http://127.0.0.1:21880",
|
||||
responseHeaderTimeoutSeconds: 180,
|
||||
edgeRetry: {
|
||||
enabled: false,
|
||||
tryDurationSeconds: 0,
|
||||
tryIntervalMilliseconds: 250,
|
||||
retryMatch: {
|
||||
methods: [],
|
||||
paths: [],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
localCodex: {
|
||||
backupSuffix: "pre-sub2api",
|
||||
providerName: "OpenAI",
|
||||
wireApi: "responses",
|
||||
modelContextWindow: 272000,
|
||||
modelAutoCompactTokenLimit: 240000,
|
||||
supportsWebSockets: true,
|
||||
responsesWebSocketsV2: true,
|
||||
responsesSmokeModel: "gpt-5.5",
|
||||
},
|
||||
sentinel: defaultCodexPoolSentinelConfig(),
|
||||
};
|
||||
}
|
||||
|
||||
export function defaultCodexTempUnschedulablePolicy(): CodexTempUnschedulablePolicy {
|
||||
return {
|
||||
enabled: false,
|
||||
rules: [],
|
||||
};
|
||||
}
|
||||
|
||||
function readProfileConfig(
|
||||
value: unknown,
|
||||
defaults: CodexPoolProfileConfig[],
|
||||
defaultTempUnschedulable: CodexTempUnschedulablePolicy,
|
||||
defaultSentinelProtect: CodexSentinelProtectPolicy,
|
||||
defaultPriority: number,
|
||||
): CodexPoolProfileConfig[] {
|
||||
if (!isRecord(value)) return defaults;
|
||||
if (!isRecord(value)) throw new Error(`${codexPoolConfigPath}.profiles must be a YAML object`);
|
||||
const entries = value.entries;
|
||||
if (!Array.isArray(entries)) throw new Error(`${codexPoolConfigPath}.profiles.entries must be a YAML array`);
|
||||
return entries.map((entry, index) => {
|
||||
@@ -1506,11 +1383,9 @@ function desiredProfileCapacityTotal(profiles: CodexPoolProfileConfig[], default
|
||||
return profiles.reduce((total, profile) => total + (profile.capacity ?? defaultCapacity), 0);
|
||||
}
|
||||
|
||||
function readManualAccountsConfig(value: unknown, defaults: CodexPoolManualAccountsConfig): CodexPoolManualAccountsConfig {
|
||||
if (value === undefined || value === null) return defaults;
|
||||
function readManualAccountsConfig(value: unknown): CodexPoolManualAccountsConfig {
|
||||
if (!isRecord(value)) throw new Error(`${codexPoolConfigPath}.manualAccounts must be a YAML object`);
|
||||
const protectedRaw = value.protected;
|
||||
if (protectedRaw === undefined || protectedRaw === null) return { protected: [] };
|
||||
if (!Array.isArray(protectedRaw)) throw new Error(`${codexPoolConfigPath}.manualAccounts.protected must be a YAML array`);
|
||||
const seen = new Set<string>();
|
||||
const protectedAccounts = protectedRaw.map((entry, index): CodexPoolManualAccountProtection => {
|
||||
@@ -1536,7 +1411,8 @@ function readManualAccountProxyBinding(value: unknown, key: string): CodexPoolMa
|
||||
if (value === undefined || value === null) return null;
|
||||
if (!isRecord(value)) throw new Error(`${codexPoolConfigPath}.${key} must be a YAML object`);
|
||||
const enabled = value.enabled === undefined ? true : value.enabled === true;
|
||||
const source = stringValue(value.source) ?? "target-egress-proxy";
|
||||
const source = stringValue(value.source);
|
||||
if (source === null) throw new Error(`${codexPoolConfigPath}.${key}.source is required`);
|
||||
if (source !== "target-egress-proxy") throw new Error(`${codexPoolConfigPath}.${key}.source must be target-egress-proxy`);
|
||||
const proxyName = stringValue(value.proxyName);
|
||||
if (proxyName === null || proxyName.trim().length === 0) throw new Error(`${codexPoolConfigPath}.${key}.proxyName is required`);
|
||||
@@ -1552,7 +1428,8 @@ function readManualAccountGroupBinding(value: unknown, key: string): CodexPoolMa
|
||||
if (value === undefined || value === null) return null;
|
||||
if (!isRecord(value)) throw new Error(`${codexPoolConfigPath}.${key} must be a YAML object`);
|
||||
const enabled = value.enabled === undefined ? true : value.enabled === true;
|
||||
const source = stringValue(value.source) ?? "pool-group";
|
||||
const source = stringValue(value.source);
|
||||
if (source === null) throw new Error(`${codexPoolConfigPath}.${key}.source is required`);
|
||||
if (source !== "pool-group") throw new Error(`${codexPoolConfigPath}.${key}.source must be pool-group`);
|
||||
return {
|
||||
enabled,
|
||||
@@ -1618,36 +1495,42 @@ function readTrustUpstream(value: unknown, key: string): boolean {
|
||||
return parsed;
|
||||
}
|
||||
|
||||
export function defaultCodexSentinelProtectPolicy(): CodexSentinelProtectPolicy {
|
||||
return {
|
||||
enabled: false,
|
||||
consecutiveFailures: 1,
|
||||
initialRetryDelaySeconds: 2,
|
||||
maxRetryDelaySeconds: 60,
|
||||
backoffMultiplier: 2,
|
||||
};
|
||||
}
|
||||
|
||||
function readSentinelProtectPolicy(value: unknown, key: string, fallback = defaultCodexSentinelProtectPolicy()): CodexSentinelProtectPolicy {
|
||||
if (value === undefined || value === null) return fallback;
|
||||
function readSentinelProtectPolicy(value: unknown, key: string, fallback?: CodexSentinelProtectPolicy): CodexSentinelProtectPolicy {
|
||||
if (value === undefined || value === null) {
|
||||
if (fallback !== undefined) return fallback;
|
||||
throw new Error(`${codexPoolConfigPath}.${key} is required`);
|
||||
}
|
||||
if (!isRecord(value)) throw new Error(`${codexPoolConfigPath}.${key} must be a YAML object`);
|
||||
const enabled = readBooleanConfig(value.enabled, `${key}.enabled`, fallback.enabled);
|
||||
if (!enabled) return { ...fallback, enabled: false };
|
||||
const enabled = readBooleanConfig(value.enabled, `${key}.enabled`, fallback?.enabled);
|
||||
const required = ["consecutiveFailures", "initialRetryDelaySeconds", "maxRetryDelaySeconds", "backoffMultiplier"];
|
||||
for (const field of required) {
|
||||
if (value[field] === undefined || value[field] === null) {
|
||||
if (fallback === undefined && (value[field] === undefined || value[field] === null)) {
|
||||
throw new Error(`${codexPoolConfigPath}.${key}.${field} is required`);
|
||||
}
|
||||
if (enabled && value[field] === undefined && fallback === undefined) {
|
||||
throw new Error(`${codexPoolConfigPath}.${key}.${field} is required when enabled=true`);
|
||||
}
|
||||
}
|
||||
const consecutiveFailures = readBoundedInteger(value.consecutiveFailures, `${key}.consecutiveFailures`, 1, 20);
|
||||
const initialRetryDelaySeconds = readBoundedInteger(value.initialRetryDelaySeconds, `${key}.initialRetryDelaySeconds`, 1, 3600);
|
||||
const maxRetryDelaySeconds = readBoundedInteger(value.maxRetryDelaySeconds, `${key}.maxRetryDelaySeconds`, 1, 3600);
|
||||
const backoffMultiplier = readBoundedInteger(value.backoffMultiplier, `${key}.backoffMultiplier`, 1, 10);
|
||||
const consecutiveFailures = value.consecutiveFailures === undefined || value.consecutiveFailures === null
|
||||
? fallback?.consecutiveFailures
|
||||
: readBoundedInteger(value.consecutiveFailures, `${key}.consecutiveFailures`, 1, 20);
|
||||
const initialRetryDelaySeconds = value.initialRetryDelaySeconds === undefined || value.initialRetryDelaySeconds === null
|
||||
? fallback?.initialRetryDelaySeconds
|
||||
: readBoundedInteger(value.initialRetryDelaySeconds, `${key}.initialRetryDelaySeconds`, 1, 3600);
|
||||
const maxRetryDelaySeconds = value.maxRetryDelaySeconds === undefined || value.maxRetryDelaySeconds === null
|
||||
? fallback?.maxRetryDelaySeconds
|
||||
: readBoundedInteger(value.maxRetryDelaySeconds, `${key}.maxRetryDelaySeconds`, 1, 3600);
|
||||
const backoffMultiplier = value.backoffMultiplier === undefined || value.backoffMultiplier === null
|
||||
? fallback?.backoffMultiplier
|
||||
: readBoundedInteger(value.backoffMultiplier, `${key}.backoffMultiplier`, 1, 10);
|
||||
if (consecutiveFailures === undefined || initialRetryDelaySeconds === undefined || maxRetryDelaySeconds === undefined || backoffMultiplier === undefined) {
|
||||
throw new Error(`${codexPoolConfigPath}.${key} is missing required sentinel protection fields`);
|
||||
}
|
||||
if (maxRetryDelaySeconds < initialRetryDelaySeconds) {
|
||||
throw new Error(`${codexPoolConfigPath}.${key}.maxRetryDelaySeconds must be >= initialRetryDelaySeconds`);
|
||||
}
|
||||
return {
|
||||
enabled: true,
|
||||
enabled,
|
||||
consecutiveFailures,
|
||||
initialRetryDelaySeconds,
|
||||
maxRetryDelaySeconds,
|
||||
@@ -1663,8 +1546,11 @@ function readBoundedInteger(value: unknown, key: string, min: number, max: numbe
|
||||
return parsed;
|
||||
}
|
||||
|
||||
function readAccountPriority(value: unknown, key: string, fallback = defaultAccountPriority): number {
|
||||
if (value === undefined || value === null) return fallback;
|
||||
function readAccountPriority(value: unknown, key: string, fallback?: number): number {
|
||||
if (value === undefined || value === null) {
|
||||
if (fallback !== undefined) return fallback;
|
||||
throw new Error(`${codexPoolConfigPath}.${key} is required`);
|
||||
}
|
||||
const priority = numberValue(value);
|
||||
if (priority === null || !Number.isInteger(priority) || priority < 0 || priority > 1000) {
|
||||
throw new Error(`${codexPoolConfigPath}.${key} must be an integer from 0 to 1000`);
|
||||
@@ -1696,8 +1582,8 @@ function readAccountLoadFactor(value: unknown, key: string): number {
|
||||
return loadFactor;
|
||||
}
|
||||
|
||||
function readCaddyTimeoutSeconds(value: unknown, key: string, fallback: number): number {
|
||||
if (value === undefined || value === null) return fallback;
|
||||
function readCaddyTimeoutSeconds(value: unknown, key: string): number {
|
||||
if (value === undefined || value === null) throw new Error(`${codexPoolConfigPath}.${key} is required`);
|
||||
const seconds = numberValue(value);
|
||||
if (seconds === null || !Number.isInteger(seconds) || seconds < 30 || seconds > 900) {
|
||||
throw new Error(`${codexPoolConfigPath}.${key} must be an integer from 30 to 900`);
|
||||
@@ -1705,16 +1591,14 @@ function readCaddyTimeoutSeconds(value: unknown, key: string, fallback: number):
|
||||
return seconds;
|
||||
}
|
||||
|
||||
function readCaddyEdgeRetryConfig(value: unknown, fallback: CodexPoolCaddyEdgeRetryConfig, key: string): CodexPoolCaddyEdgeRetryConfig {
|
||||
if (value === undefined || value === null) return cloneCaddyEdgeRetryConfig(fallback);
|
||||
function readCaddyEdgeRetryConfig(value: unknown, key: string): CodexPoolCaddyEdgeRetryConfig {
|
||||
if (!isRecord(value)) throw new Error(`${codexPoolConfigPath}.${key} must be a YAML object`);
|
||||
const enabled = readBooleanConfig(value.enabled, `${key}.enabled`, fallback.enabled);
|
||||
if (!enabled) return { ...cloneCaddyEdgeRetryConfig(fallback), enabled: false };
|
||||
const enabled = readBooleanConfig(value.enabled, `${key}.enabled`);
|
||||
|
||||
const tryDurationSeconds = readPositiveInteger(value.tryDurationSeconds, `${key}.tryDurationSeconds`);
|
||||
const tryIntervalMilliseconds = readPositiveInteger(value.tryIntervalMilliseconds, `${key}.tryIntervalMilliseconds`);
|
||||
const retryMatchValue = isRecord(value.retryMatch) ? value.retryMatch : null;
|
||||
if (retryMatchValue === null) throw new Error(`${codexPoolConfigPath}.${key}.retryMatch must be a YAML object when enabled=true`);
|
||||
if (retryMatchValue === null) throw new Error(`${codexPoolConfigPath}.${key}.retryMatch must be a YAML object`);
|
||||
const retryMatch = {
|
||||
methods: readCaddyRetryMethods(retryMatchValue.methods, `${key}.retryMatch.methods`),
|
||||
paths: readCaddyRetryPaths(retryMatchValue.paths, `${key}.retryMatch.paths`),
|
||||
@@ -1733,8 +1617,8 @@ function readPositiveInteger(value: unknown, key: string): number {
|
||||
return parsed;
|
||||
}
|
||||
|
||||
function readPositiveIntegerConfig(value: unknown, key: string, fallback: number): number {
|
||||
if (value === undefined || value === null) return fallback;
|
||||
function readPositiveIntegerConfig(value: unknown, key: string): number {
|
||||
if (value === undefined || value === null) throw new Error(`${codexPoolConfigPath}.${key} is required`);
|
||||
return readPositiveInteger(value, key);
|
||||
}
|
||||
|
||||
@@ -1770,13 +1654,20 @@ function readCaddyRetryPaths(value: unknown, key: string): string[] {
|
||||
return paths;
|
||||
}
|
||||
|
||||
function readTempUnschedulablePolicy(value: unknown, key: string, fallback: CodexTempUnschedulablePolicy): CodexTempUnschedulablePolicy {
|
||||
if (value === undefined || value === null) return cloneTempUnschedulablePolicy(fallback);
|
||||
function readTempUnschedulablePolicy(value: unknown, key: string, fallback?: CodexTempUnschedulablePolicy): CodexTempUnschedulablePolicy {
|
||||
if (value === undefined || value === null) {
|
||||
if (fallback !== undefined) return cloneTempUnschedulablePolicy(fallback);
|
||||
throw new Error(`${codexPoolConfigPath}.${key} is required`);
|
||||
}
|
||||
if (!isRecord(value)) throw new Error(`${codexPoolConfigPath}.${key} must be a YAML object`);
|
||||
const enabled = readBooleanConfig(value.enabled, `${key}.enabled`, fallback.enabled);
|
||||
const rules = value.rules === undefined || value.rules === null
|
||||
? cloneTempUnschedulablePolicy(fallback).rules
|
||||
: readTempUnschedulableRules(value.rules, `${key}.rules`);
|
||||
const enabled = readBooleanConfig(value.enabled, `${key}.enabled`, fallback?.enabled);
|
||||
let rules: CodexTempUnschedulableRule[];
|
||||
if (value.rules === undefined || value.rules === null) {
|
||||
if (fallback === undefined) throw new Error(`${codexPoolConfigPath}.${key}.rules is required`);
|
||||
rules = cloneTempUnschedulablePolicy(fallback).rules;
|
||||
} else {
|
||||
rules = readTempUnschedulableRules(value.rules, `${key}.rules`);
|
||||
}
|
||||
if (enabled && rules.length === 0) throw new Error(`${codexPoolConfigPath}.${key}.rules must not be empty when enabled=true`);
|
||||
return { enabled, rules };
|
||||
}
|
||||
@@ -1830,60 +1721,49 @@ function cloneTempUnschedulablePolicy(policy: CodexTempUnschedulablePolicy): Cod
|
||||
};
|
||||
}
|
||||
|
||||
function cloneCaddyEdgeRetryConfig(config: CodexPoolCaddyEdgeRetryConfig): CodexPoolCaddyEdgeRetryConfig {
|
||||
return {
|
||||
enabled: config.enabled,
|
||||
tryDurationSeconds: config.tryDurationSeconds,
|
||||
tryIntervalMilliseconds: config.tryIntervalMilliseconds,
|
||||
retryMatch: {
|
||||
methods: [...config.retryMatch.methods],
|
||||
paths: [...config.retryMatch.paths],
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function readBooleanConfig(value: unknown, key: string, fallback: boolean): boolean {
|
||||
if (value === undefined || value === null) return fallback;
|
||||
function readBooleanConfig(value: unknown, key: string, fallback?: boolean): boolean {
|
||||
if (value === undefined || value === null) {
|
||||
if (fallback !== undefined) return fallback;
|
||||
throw new Error(`${codexPoolConfigPath}.${key} is required`);
|
||||
}
|
||||
const parsed = booleanValue(value);
|
||||
if (parsed === null) throw new Error(`${codexPoolConfigPath}.${key} must be a boolean`);
|
||||
return parsed;
|
||||
}
|
||||
|
||||
function readPublicExposureConfig(value: unknown, defaults: CodexPoolPublicExposureConfig): CodexPoolPublicExposureConfig {
|
||||
if (!isRecord(value)) return defaults;
|
||||
const masterFrpsValue = isRecord(value.masterFrps) ? value.masterFrps : {};
|
||||
const masterCaddyValue = isRecord(value.masterCaddy) ? value.masterCaddy : {};
|
||||
function readPublicExposureConfig(value: unknown): CodexPoolPublicExposureConfig {
|
||||
if (!isRecord(value)) throw new Error(`${codexPoolConfigPath}.publicExposure must be a YAML object`);
|
||||
const masterFrpsValue = requiredRecordConfigField(value, "masterFrps", "publicExposure");
|
||||
const masterCaddyValue = requiredRecordConfigField(value, "masterCaddy", "publicExposure");
|
||||
const config: CodexPoolPublicExposureConfig = {
|
||||
enabled: booleanValue(value.enabled) ?? defaults.enabled,
|
||||
proxyName: stringValue(value.proxyName) ?? defaults.proxyName,
|
||||
configMapName: stringValue(value.configMapName) ?? defaults.configMapName,
|
||||
deploymentName: stringValue(value.deploymentName) ?? defaults.deploymentName,
|
||||
frpcImage: stringValue(value.frpcImage) ?? defaults.frpcImage,
|
||||
serverAddr: stringValue(value.serverAddr) ?? defaults.serverAddr,
|
||||
serverPort: numberValue(value.serverPort) ?? defaults.serverPort,
|
||||
remotePort: numberValue(value.remotePort) ?? defaults.remotePort,
|
||||
localIP: stringValue(value.localIP) ?? defaults.localIP,
|
||||
localPort: numberValue(value.localPort) ?? defaults.localPort,
|
||||
publicBaseUrl: normalizeBaseUrl(stringValue(value.publicBaseUrl)) ?? defaults.publicBaseUrl,
|
||||
masterBaseUrl: normalizeBaseUrl(stringValue(value.masterBaseUrl)) ?? defaults.masterBaseUrl,
|
||||
enabled: readBooleanConfig(value.enabled, "publicExposure.enabled"),
|
||||
proxyName: requiredStringConfigField(value, "proxyName", "publicExposure"),
|
||||
configMapName: requiredStringConfigField(value, "configMapName", "publicExposure"),
|
||||
deploymentName: requiredStringConfigField(value, "deploymentName", "publicExposure"),
|
||||
frpcImage: requiredStringConfigField(value, "frpcImage", "publicExposure"),
|
||||
serverAddr: requiredStringConfigField(value, "serverAddr", "publicExposure"),
|
||||
serverPort: readRequiredPort(value.serverPort, "publicExposure.serverPort"),
|
||||
remotePort: readRequiredPort(value.remotePort, "publicExposure.remotePort"),
|
||||
localIP: requiredStringConfigField(value, "localIP", "publicExposure"),
|
||||
localPort: readRequiredPort(value.localPort, "publicExposure.localPort"),
|
||||
publicBaseUrl: readRequiredBaseUrl(value.publicBaseUrl, "publicExposure.publicBaseUrl"),
|
||||
masterBaseUrl: readRequiredBaseUrl(value.masterBaseUrl, "publicExposure.masterBaseUrl"),
|
||||
masterFrps: {
|
||||
configPath: stringValue(masterFrpsValue.configPath) ?? defaults.masterFrps.configPath,
|
||||
containerName: stringValue(masterFrpsValue.containerName) ?? defaults.masterFrps.containerName,
|
||||
configPath: requiredStringConfigField(masterFrpsValue, "configPath", "publicExposure.masterFrps"),
|
||||
containerName: requiredStringConfigField(masterFrpsValue, "containerName", "publicExposure.masterFrps"),
|
||||
},
|
||||
masterCaddy: {
|
||||
enabled: booleanValue(masterCaddyValue.enabled) ?? defaults.masterCaddy.enabled,
|
||||
domain: stringValue(masterCaddyValue.domain) ?? defaults.masterCaddy.domain,
|
||||
configPath: stringValue(masterCaddyValue.configPath) ?? defaults.masterCaddy.configPath,
|
||||
serviceName: stringValue(masterCaddyValue.serviceName) ?? defaults.masterCaddy.serviceName,
|
||||
upstreamBaseUrl: normalizeBaseUrl(stringValue(masterCaddyValue.upstreamBaseUrl)) ?? defaults.masterCaddy.upstreamBaseUrl,
|
||||
enabled: readBooleanConfig(masterCaddyValue.enabled, "publicExposure.masterCaddy.enabled"),
|
||||
domain: requiredStringConfigField(masterCaddyValue, "domain", "publicExposure.masterCaddy"),
|
||||
configPath: requiredStringConfigField(masterCaddyValue, "configPath", "publicExposure.masterCaddy"),
|
||||
serviceName: requiredStringConfigField(masterCaddyValue, "serviceName", "publicExposure.masterCaddy"),
|
||||
upstreamBaseUrl: readRequiredBaseUrl(masterCaddyValue.upstreamBaseUrl, "publicExposure.masterCaddy.upstreamBaseUrl"),
|
||||
responseHeaderTimeoutSeconds: readCaddyTimeoutSeconds(
|
||||
masterCaddyValue.responseHeaderTimeoutSeconds,
|
||||
"publicExposure.masterCaddy.responseHeaderTimeoutSeconds",
|
||||
defaults.masterCaddy.responseHeaderTimeoutSeconds,
|
||||
),
|
||||
edgeRetry: readCaddyEdgeRetryConfig(
|
||||
masterCaddyValue.edgeRetry,
|
||||
defaults.masterCaddy.edgeRetry,
|
||||
"publicExposure.masterCaddy.edgeRetry",
|
||||
),
|
||||
},
|
||||
@@ -1907,17 +1787,17 @@ function readPublicExposureConfig(value: unknown, defaults: CodexPoolPublicExpos
|
||||
return config;
|
||||
}
|
||||
|
||||
function readLocalCodexConfig(value: unknown, defaults: CodexPoolLocalCodexConfig): CodexPoolLocalCodexConfig {
|
||||
if (!isRecord(value)) return defaults;
|
||||
function readLocalCodexConfig(value: unknown): CodexPoolLocalCodexConfig {
|
||||
if (!isRecord(value)) throw new Error(`${codexPoolConfigPath}.localCodex must be a YAML object`);
|
||||
const config = {
|
||||
backupSuffix: stringValue(value.backupSuffix) ?? defaults.backupSuffix,
|
||||
providerName: stringValue(value.providerName) ?? defaults.providerName,
|
||||
wireApi: stringValue(value.wireApi) ?? defaults.wireApi,
|
||||
modelContextWindow: readPositiveIntegerConfig(value.modelContextWindow, "localCodex.modelContextWindow", defaults.modelContextWindow),
|
||||
modelAutoCompactTokenLimit: readPositiveIntegerConfig(value.modelAutoCompactTokenLimit, "localCodex.modelAutoCompactTokenLimit", defaults.modelAutoCompactTokenLimit),
|
||||
supportsWebSockets: readBooleanConfig(value.supportsWebSockets, "localCodex.supportsWebSockets", defaults.supportsWebSockets),
|
||||
responsesWebSocketsV2: readBooleanConfig(value.responsesWebSocketsV2, "localCodex.responsesWebSocketsV2", defaults.responsesWebSocketsV2),
|
||||
responsesSmokeModel: stringValue(value.responsesSmokeModel) ?? defaults.responsesSmokeModel,
|
||||
backupSuffix: requiredStringConfigField(value, "backupSuffix", "localCodex"),
|
||||
providerName: requiredStringConfigField(value, "providerName", "localCodex"),
|
||||
wireApi: requiredStringConfigField(value, "wireApi", "localCodex"),
|
||||
modelContextWindow: readPositiveIntegerConfig(value.modelContextWindow, "localCodex.modelContextWindow"),
|
||||
modelAutoCompactTokenLimit: readPositiveIntegerConfig(value.modelAutoCompactTokenLimit, "localCodex.modelAutoCompactTokenLimit"),
|
||||
supportsWebSockets: readBooleanConfig(value.supportsWebSockets, "localCodex.supportsWebSockets"),
|
||||
responsesWebSocketsV2: readBooleanConfig(value.responsesWebSocketsV2, "localCodex.responsesWebSocketsV2"),
|
||||
responsesSmokeModel: requiredStringConfigField(value, "responsesSmokeModel", "localCodex"),
|
||||
};
|
||||
if (!/^[A-Za-z0-9._-]+$/u.test(config.backupSuffix)) throw new Error(`${codexPoolConfigPath}.localCodex.backupSuffix has an unsupported format`);
|
||||
validateProxyName(config.providerName, "localCodex.providerName");
|
||||
@@ -2054,9 +1934,11 @@ function codexPoolConfigSummary(pool: CodexPoolConfig): Record<string, unknown>
|
||||
kind: pool.kind,
|
||||
metadata: pool.metadata,
|
||||
groupName: pool.groupName,
|
||||
groupDescription: pool.groupDescription,
|
||||
apiKeyName: pool.apiKeyName,
|
||||
apiKeySecretName: pool.apiKeySecretName,
|
||||
apiKeySecretKey: pool.apiKeySecretKey,
|
||||
adminEmailDefault: pool.adminEmailDefault,
|
||||
minOwnerBalanceUsd: pool.minOwnerBalanceUsd,
|
||||
minOwnerConcurrency: pool.minOwnerConcurrency,
|
||||
minOwnerConcurrencySource: pool.minOwnerConcurrencySource,
|
||||
@@ -3676,6 +3558,41 @@ function requiredStringConfigField(obj: Record<string, unknown>, key: string, pa
|
||||
return value;
|
||||
}
|
||||
|
||||
function readRequiredDescription(value: unknown, key: string, maxChars: number): string {
|
||||
const text = stringValue(value);
|
||||
if (text === null) throw new Error(`${codexPoolConfigPath}.${key} must be a non-empty string`);
|
||||
if (text.length > maxChars) throw new Error(`${codexPoolConfigPath}.${key} must be at most ${maxChars} characters`);
|
||||
if (/[\r\n]/u.test(text)) throw new Error(`${codexPoolConfigPath}.${key} must not contain newlines`);
|
||||
return text;
|
||||
}
|
||||
|
||||
function readRequiredEmail(value: unknown, key: string): string {
|
||||
const text = stringValue(value);
|
||||
if (text === null) throw new Error(`${codexPoolConfigPath}.${key} must be a non-empty string`);
|
||||
if (!/^[^@\s]+@[^@\s]+[.][^@\s]+$/u.test(text)) throw new Error(`${codexPoolConfigPath}.${key} must be an email address`);
|
||||
return text;
|
||||
}
|
||||
|
||||
function readRequiredBaseUrl(value: unknown, key: string): string {
|
||||
const baseUrl = normalizeBaseUrl(stringValue(value));
|
||||
if (baseUrl === null) throw new Error(`${codexPoolConfigPath}.${key} must be a valid http(s) URL`);
|
||||
return baseUrl;
|
||||
}
|
||||
|
||||
function readRequiredPort(value: unknown, key: string): number {
|
||||
const port = numberValue(value);
|
||||
if (port === null || !Number.isInteger(port) || port < 1 || port > 65535) {
|
||||
throw new Error(`${codexPoolConfigPath}.${key} must be an integer port`);
|
||||
}
|
||||
return port;
|
||||
}
|
||||
|
||||
function readRequiredPositiveNumber(value: unknown, key: string): number {
|
||||
const number = numberValue(value);
|
||||
if (number === null || number <= 0) throw new Error(`${codexPoolConfigPath}.${key} must be > 0`);
|
||||
return number;
|
||||
}
|
||||
|
||||
function requiredRecordConfigField(obj: Record<string, unknown>, key: string, path: string): Record<string, unknown> {
|
||||
const value = obj[key];
|
||||
const prefix = path.length > 0 ? `${path}.` : "";
|
||||
@@ -4151,11 +4068,8 @@ function desiredAccountCapacityTotal(pool: CodexPoolConfig): number {
|
||||
function desiredAccountCapacityMap(pool: CodexPoolConfig): Record<string, number> {
|
||||
const codexDir = join(homedir(), ".codex");
|
||||
const seenAccountNames = new Set<string>();
|
||||
const configs = pool.profiles.length > 0
|
||||
? pool.profiles
|
||||
: discoverCodexProfileConfigs(codexDir, pool.defaultTempUnschedulable, pool.defaultSentinelProtect, pool.defaultAccountPriority);
|
||||
const capacities: Record<string, number> = {};
|
||||
for (const entry of configs) {
|
||||
for (const entry of pool.profiles) {
|
||||
const accountName = entry.accountName ?? uniqueAccountName(entry.profile, seenAccountNames);
|
||||
seenAccountNames.add(accountName);
|
||||
capacities[accountName] = entry.capacity ?? pool.defaultAccountCapacity;
|
||||
@@ -4166,11 +4080,8 @@ function desiredAccountCapacityMap(pool: CodexPoolConfig): Record<string, number
|
||||
function desiredAccountLoadFactorMap(pool: CodexPoolConfig): Record<string, number> {
|
||||
const codexDir = join(homedir(), ".codex");
|
||||
const seenAccountNames = new Set<string>();
|
||||
const configs = pool.profiles.length > 0
|
||||
? pool.profiles
|
||||
: discoverCodexProfileConfigs(codexDir, pool.defaultTempUnschedulable, pool.defaultSentinelProtect, pool.defaultAccountPriority);
|
||||
const loadFactors: Record<string, number> = {};
|
||||
for (const entry of configs) {
|
||||
for (const entry of pool.profiles) {
|
||||
const accountName = entry.accountName ?? uniqueAccountName(entry.profile, seenAccountNames);
|
||||
seenAccountNames.add(accountName);
|
||||
loadFactors[accountName] = entry.loadFactor ?? pool.defaultAccountLoadFactor;
|
||||
@@ -4181,11 +4092,8 @@ function desiredAccountLoadFactorMap(pool: CodexPoolConfig): Record<string, numb
|
||||
function desiredAccountWebSocketsV2ModeMap(pool: CodexPoolConfig): Record<string, OpenAIResponsesWebSocketsV2Mode | null> {
|
||||
const codexDir = join(homedir(), ".codex");
|
||||
const seenAccountNames = new Set<string>();
|
||||
const configs = pool.profiles.length > 0
|
||||
? pool.profiles
|
||||
: discoverCodexProfileConfigs(codexDir, pool.defaultTempUnschedulable, pool.defaultSentinelProtect, pool.defaultAccountPriority);
|
||||
const modes: Record<string, OpenAIResponsesWebSocketsV2Mode | null> = {};
|
||||
for (const entry of configs) {
|
||||
for (const entry of pool.profiles) {
|
||||
const accountName = entry.accountName ?? uniqueAccountName(entry.profile, seenAccountNames);
|
||||
seenAccountNames.add(accountName);
|
||||
modes[accountName] = entry.openaiResponsesWebSocketsV2Mode;
|
||||
@@ -4196,11 +4104,8 @@ function desiredAccountWebSocketsV2ModeMap(pool: CodexPoolConfig): Record<string
|
||||
function desiredAccountTempUnschedulableMap(pool: CodexPoolConfig): Record<string, unknown> {
|
||||
const codexDir = join(homedir(), ".codex");
|
||||
const seenAccountNames = new Set<string>();
|
||||
const configs = pool.profiles.length > 0
|
||||
? pool.profiles
|
||||
: discoverCodexProfileConfigs(codexDir, pool.defaultTempUnschedulable, pool.defaultSentinelProtect, pool.defaultAccountPriority);
|
||||
const policies: Record<string, unknown> = {};
|
||||
for (const entry of configs) {
|
||||
for (const entry of pool.profiles) {
|
||||
const accountName = entry.accountName ?? uniqueAccountName(entry.profile, seenAccountNames);
|
||||
seenAccountNames.add(accountName);
|
||||
policies[accountName] = renderSub2ApiTempUnschedulableCredentials(entry.tempUnschedulable);
|
||||
@@ -4230,9 +4135,11 @@ SERVICE_DNS = ${JSON.stringify(target.serviceDns)}
|
||||
FIELD_MANAGER = "${fieldManager}"
|
||||
APP_SECRET_NAME = ${JSON.stringify(target.appSecretName)}
|
||||
POOL_GROUP_NAME = "${pool.groupName}"
|
||||
POOL_GROUP_DESCRIPTION = ${JSON.stringify(pool.groupDescription)}
|
||||
POOL_API_KEY_NAME = "${pool.apiKeyName}"
|
||||
POOL_API_KEY_SECRET_NAME = "${pool.apiKeySecretName}"
|
||||
POOL_API_KEY_SECRET_KEY = "${pool.apiKeySecretKey}"
|
||||
POOL_ADMIN_EMAIL_DEFAULT = ${JSON.stringify(pool.adminEmailDefault)}
|
||||
MIN_OWNER_BALANCE_USD = ${JSON.stringify(pool.minOwnerBalanceUsd)}
|
||||
MIN_OWNER_CONCURRENCY = ${JSON.stringify(pool.minOwnerConcurrency)}
|
||||
MIN_OWNER_CONCURRENCY_SOURCE = ${JSON.stringify(pool.minOwnerConcurrencySource)}
|
||||
@@ -4507,7 +4414,7 @@ def find_access_token(data):
|
||||
return None
|
||||
|
||||
def login():
|
||||
admin_email = get_config_value("sub2api-config", "ADMIN_EMAIL") or "admin@sub2api.platform-infra.local"
|
||||
admin_email = get_config_value("sub2api-config", "ADMIN_EMAIL") or POOL_ADMIN_EMAIL_DEFAULT
|
||||
admin_password = decode_secret_value(APP_SECRET_NAME, "ADMIN_PASSWORD")
|
||||
if not admin_password:
|
||||
raise RuntimeError("ADMIN_PASSWORD missing from sub2api-secrets")
|
||||
@@ -4523,7 +4430,7 @@ def login():
|
||||
def group_payload():
|
||||
return {
|
||||
"name": POOL_GROUP_NAME,
|
||||
"description": "UniDesk-managed Codex API-key pool for G14 k3s internal Sub2API clients.",
|
||||
"description": POOL_GROUP_DESCRIPTION,
|
||||
"platform": "openai",
|
||||
"rate_multiplier": 1,
|
||||
"is_exclusive": False,
|
||||
|
||||
Reference in New Issue
Block a user