import type { BudgetLimit } from "./workflow-types.js"; export const UNLIMITED: BudgetLimit = Object.freeze({ kind: "unlimited" }); export function limited(value: number): BudgetLimit { if (!Number.isFinite(value) || value <= 0) { throw new TypeError("Budget limit must be positive and finite"); } return { kind: "limited", value }; } export function limitedInteger(value: number): BudgetLimit { if (!Number.isSafeInteger(value)) throw new TypeError("Budget limit must be a safe integer"); return limited(value); } export function parseBudgetLimit( value: unknown, name: string, options: { readonly integer: boolean; readonly allowLegacyNumber?: boolean } = { integer: false }, ): BudgetLimit { if (options.allowLegacyNumber && typeof value === "number") { return options.integer ? limitedInteger(value) : limited(value); } if (typeof value !== "object" || value === null || Array.isArray(value)) { throw new TypeError(`${name} must be limited(value) or unlimited`); } const object = value as Record; const keys = Object.keys(object); if (object["kind"] === "unlimited") { if (keys.length !== 1) throw new TypeError(`${name} unlimited limit has unknown fields`); return UNLIMITED; } if (object["kind"] !== "limited" || keys.some((key) => key !== "kind" && key !== "value")) { throw new TypeError(`${name} must be limited(value) or unlimited`); } if (keys.length !== 2) throw new TypeError(`${name} limited limit requires value`); const numeric = object["value"]; if (typeof numeric !== "number") throw new TypeError(`${name} value must be a number`); return options.integer ? limitedInteger(numeric) : limited(numeric); } export function isUnlimited(limit: BudgetLimit): boolean { return limit.kind === "unlimited"; } export function budgetValue(limit: BudgetLimit): number | undefined { return limit.kind === "limited" ? limit.value : undefined; } export function budgetExceeded(observed: number, limit: BudgetLimit): boolean { return limit.kind === "limited" && observed > limit.value; } export function remainingBudget(limit: BudgetLimit, used: number | "unlimited"): BudgetLimit { if (limit.kind === "unlimited") return UNLIMITED; if (used === "unlimited") return { kind: "limited", value: 0 }; return { kind: "limited", value: Math.max(0, limit.value - used) }; } export function reserveBudget( current: number | "unlimited", requested: BudgetLimit, ): number | "unlimited" { if (current === "unlimited" || requested.kind === "unlimited") return "unlimited"; return current + requested.value; } export function assertBudgetRequestFits( available: BudgetLimit, requested: BudgetLimit, message: string, ): void { if (available.kind === "unlimited") return; if (requested.kind === "unlimited" || requested.value > available.value) { throw new Error(message); } } export function budgetExhausted(observed: number, limit: BudgetLimit): boolean { return limit.kind === "limited" && observed >= limit.value; } export function budgetAtLeast(limit: BudgetLimit, observed: number): boolean { return limit.kind === "unlimited" || limit.value >= observed; } export function budgetLimitLabel(limit: BudgetLimit): string { return limit.kind === "unlimited" ? "unlimited" : String(limit.value); }