// REQ-LIMIT-1: editable concurrency policy, never a compiled-in capacity ceiling. import { requireObject, requireId, requireString, rejectUnknownKeys } from "./util.mjs"; import { isDailyModelQuota } from "../../plugins/src/model-quota.mjs"; import { BusinessError, refuse } from "./errors.mjs"; const name = (value, at) => requireString(value, at, { max: 200 }); const count = (value, at) => { if (!Number.isSafeInteger(value) || value < 0) refuse(`${at} must be a whole number of 0 or more`); return value; }; const own = (object, key) => Object.hasOwn(object ?? {}, key) ? object[key] : undefined; export function checkModels(value, where = "launch.models") { requireObject(value, where); if (!Object.keys(value).length) refuse(`${where} must name at least one model`); return Object.fromEntries(Object.entries(value).map(([key, entry]) => { requireId(key, where); requireObject(entry, `${where}.${key}`); const moduleBacked = Object.hasOwn(entry, "module") || Object.hasOwn(entry, "upstream"); rejectUnknownKeys(entry, moduleBacked ? ["provider", "model", "module", "upstream"] : entry.provider === "proxy" ? ["provider", "model", "route"] : ["provider", "model"], `${where}.${key}`); if (moduleBacked) { requireId(entry.module, `${where}.${key}.module`); requireId(entry.upstream, `${where}.${key}.upstream`); if (typeof entry.model !== "string" || !/^[A-Za-z0-9][A-Za-z0-9._:/-]{0,199}$/.test(entry.model)) refuse(`${where}.${key}.model is invalid`); } if (entry.provider === "proxy" && !moduleBacked) { if (!Object.hasOwn(entry, "route")) throw Object.assign(new BusinessError(`${where}.${key}.route is required`), { code: "proxy-route-missing" }); if (typeof entry.route !== "string" || !/^[a-z0-9][a-z0-9._-]{0,63}$/.test(entry.route)) throw Object.assign(new BusinessError(`${where}.${key}.route is invalid`), { code: "proxy-route-unknown" }); } return [key, { provider: name(entry.provider, `${where}.${key}.provider`), model: name(entry.model, `${where}.${key}.model`), ...(moduleBacked ? { module: entry.module, upstream: entry.upstream } : {}), ...(entry.provider === "proxy" && !moduleBacked ? { route: entry.route } : {}), }]; })); } // Row 125 (#1619, REQ-LIMIT-3): one token limit, a count of 0 or more and the // canonical time it was set or last reset. The settings writer stamps `since`. const SINCE = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/; const tokenLimit = (value, at) => { requireObject(value, at); rejectUnknownKeys(value, ["limit", "since"], at); const since = value.since; if (typeof since !== "string" || !SINCE.test(since) || Number.isNaN(Date.parse(since)) || new Date(since).toISOString() !== since) refuse(`${at}.since must be a canonical timestamp`); return { limit: count(value.limit, `${at}.limit`), since }; }; // Row 126 (#1620, REQ-LIMIT-2): a daily quota is a token limit with the wall-clock time its day // starts and the IANA zone that time is read in. A zone must be a name (an offset such as +05:00 // is refused) that Intl accepts. const RESET = /^([01]\d|2[0-3]):[0-5]\d$/; const ZONE = /^[A-Za-z][A-Za-z0-9_+-]*(\/[A-Za-z0-9_+-]+)*$/; const dailyQuota = (value, at) => { requireObject(value, at); rejectUnknownKeys(value, ["limit", "since", "reset", "timeZone"], at); const { limit, since } = tokenLimit({ limit: value.limit, since: value.since }, at); if (typeof value.reset !== "string" || !RESET.test(value.reset)) refuse(`${at}.reset must be a time of day, HH:MM`); const timeZone = value.timeZone; let known = typeof timeZone === "string" && ZONE.test(timeZone); if (known) { try { new Intl.DateTimeFormat("en-US", { timeZone }); } catch { known = false; } } if (!known) refuse(`${at}.timeZone must be an IANA time zone name`); return { limit, since, reset: value.reset, timeZone }; }; const tokenMap = (value, at, key, each) => { requireObject(value, at); return Object.fromEntries(Object.entries(value).map(([k, v]) => [key(k, at), each(v, `${at}.${k}`)])); }; // launch.limits.tokens: instances, providers, models (per provider), keys and daily, each optional. export function checkTokenLimits(value, where = "launch.limits.tokens") { requireObject(value, where); rejectUnknownKeys(value, ["instances", "providers", "models", "keys", "daily"], where); const out = {}; if (Object.hasOwn(value, "instances")) out.instances = tokenMap(value.instances, `${where}.instances`, requireId, tokenLimit); if (Object.hasOwn(value, "providers")) out.providers = tokenMap(value.providers, `${where}.providers`, name, tokenLimit); if (Object.hasOwn(value, "models")) { out.models = tokenMap(value.models, `${where}.models`, name, (models, at) => tokenMap(models, at, name, tokenLimit)); } // P5 adds daily model leaves, including digit-leading IDs; plain keys keep // their existing account-name schema and cumulative account semantics. if (Object.hasOwn(value, "keys")) { requireObject(value.keys, `${where}.keys`); out.keys = Object.fromEntries(Object.entries(value.keys).map(([key, entry]) => { const at = `${where}.keys.${key}`; if (isDailyModelQuota(key, entry)) return [key, dailyQuota(entry, at)]; return [requireId(key, `${where}.keys`), tokenLimit(entry, at)]; })); } // One quota per account; the business read checks each key names launch.account. if (Object.hasOwn(value, "daily")) out.daily = tokenMap(value.daily, `${where}.daily`, requireId, dailyQuota); return out; } // Row 126 (#1620): the account a business's claude-code launches use. `credential` names the // environment variables of one credential set, joined with "+", so a secret can't pass the check. // Which sets exist is the launcher's to check (seat imports business, not the other way). const CREDENTIAL = /^[A-Z][A-Z0-9_]*(\+[A-Z][A-Z0-9_]*)*$/; export function checkAccount(value, where = "launch.account") { requireObject(value, where); rejectUnknownKeys(value, ["name", "credential"], where); const accountName = requireId(value.name, `${where}.name`); if (typeof value.credential !== "string" || (value.credential !== "proxy" && !CREDENTIAL.test(value.credential))) { refuse(`${where}.credential must name environment variables, joined with "+"`); } return { name: accountName, credential: value.credential }; } export function checkLimits(value = {}, where = "launch.limits") { requireObject(value, where); rejectUnknownKeys(value, ["total", "providers", "models", "tokens"], where); const out = {}; if (Object.hasOwn(value, "total")) out.total = count(value.total, `${where}.total`); if (Object.hasOwn(value, "providers")) { requireObject(value.providers, `${where}.providers`); out.providers = Object.fromEntries(Object.entries(value.providers).map(([provider, limit]) => { return [name(provider, where), count(limit, `${where}.providers.${provider}`)]; })); } if (Object.hasOwn(value, "models")) { requireObject(value.models, `${where}.models`); out.models = Object.fromEntries(Object.entries(value.models).map(([provider, models]) => { name(provider, where); requireObject(models, where); return [provider, Object.fromEntries(Object.entries(models).map(([model, limit]) => { return [name(model, where), count(limit, `${where}.models.${provider}.${model}`)]; }))]; })); } if (Object.hasOwn(value, "tokens")) out.tokens = checkTokenLimits(value.tokens, `${where}.tokens`); return out; } export function usageCounts(sessions, business) { const running = sessions.filter(session => session.business === business); const providerCounts = new Map(); const modelCounts = new Map(); for (const session of running) { const { provider, model } = session; if (typeof provider !== "string" || typeof model !== "string") { refuse("running session has no exact provider/model; reconcile before launching"); } providerCounts.set(provider, (providerCounts.get(provider) ?? 0) + 1); if (!modelCounts.has(provider)) modelCounts.set(provider, new Map()); const models = modelCounts.get(provider); models.set(model, (models.get(model) ?? 0) + 1); } const providers = Object.fromEntries(providerCounts); const models = Object.fromEntries([...modelCounts].map(([provider, counts]) => { return [provider, Object.fromEntries(counts)]; })); return { total: running.length, providers, models }; } export function limitHit(sessions, business, limits, selected) { const usage = usageCounts(sessions, business); const { provider, model } = selected; const checks = [ ["total", limits.total, usage.total], ["provider", own(limits.providers, provider), own(usage.providers, provider) ?? 0], ["model", own(own(limits.models, provider), model), own(own(usage.models, provider), model) ?? 0], ]; for (const [scope, limit, running] of checks) { if (limit !== undefined && running >= limit) { return { scope, limit, running, ...(scope !== "total" ? { provider } : {}), ...(scope === "model" ? { model } : {}), }; } } return null; }