/** * Usage policy: how much room an account has left, and when to move off it * BEFORE it hits the wall. * * A subscription has several windows running at once (a 5-hour session window, a * weekly all-models window, and a weekly window per model such as Fable). The * one that runs out first is the "binding" window, so that is what decides both * how healthy an account is and when to rotate. Judging by the 5-hour number * alone is exactly how an account looks fine while its weekly model window is * what actually stops you. */ export interface UsageLike { /** 0..1 utilization, or null when unknown. */ fiveHour: number | null; sevenDay: number | null; /** When each account-wide window resets, so a spent one can be seen to lift. */ fiveHourReset?: number | null; sevenDayReset?: number | null; models?: Array<{ name: string; utilization: number; resetsAt?: number | null; }> | null; } /** * The worst (binding) utilization across every window still in force, or null * when nothing is known. * * Windows whose reset has passed are skipped rather than counted: their number * describes a limit that has already lifted, and treating it as live is how an * account gets abandoned for a cap it no longer has. */ export declare function bindingUtilization(usage: UsageLike | undefined | null, model?: string, now?: number): number | null; /** Remaining room (0..1) on the binding window, or null when unknown. */ export declare function headroom(usage: UsageLike | undefined | null, model?: string, now?: number): number | null; export interface ProactiveCandidate { name: string; enabled: boolean; loggedIn: boolean; capped?: boolean; } export interface ProactiveInput { /** The account in use right now. */ current: string; candidates: ProactiveCandidate[]; /** Account name -> its usage snapshot. */ usage: Map; /** Move off an account once its binding window reaches this percent (0..100). */ thresholdPercent: number; /** * Only move when the target has at least this many percentage points more * room than the current account, so two similar accounts cannot ping-pong. */ hysteresisPercent?: number; /** Restrict the decision to one model's window (e.g. "Fable"). */ model?: string; /** Evaluated against this instant, so a window that has reset is not a limit. */ now?: number; } export interface ProactiveDecision { switchTo: string | null; reason: string; } /** * Decide whether to move off the current account before it runs out. Fail-safe * by construction: unknown usage never triggers a switch, and a target is only * chosen when it is both under the threshold and meaningfully roomier. */ export declare function decideProactiveSwitch(input: ProactiveInput): ProactiveDecision;