import type { BudgetLedger, BudgetPeriod, BudgetReservation, BudgetReservationOutcome, BudgetReservationRecord, OperationDescriptor } from '../types/lifecycle.js'; import { type BudgetReservationChange } from './reservations.js'; export type { BudgetReservationChange } from './reservations.js'; /** * Where a ledger keeps its totals: a sum per key that resets at a given time. Two operations, so any * shared store can implement it. `MemoryTenantUsage` and `RedisTenantUsage` from * `nexus-ai-pro/server/tenancy` already do, so one Redis counts both tenant runs and model spend. * * A store that also records reservations implements the five optional methods together. A ledger * over it records each reservation under a lease, so one a dead process held is found and settled, * and moves each reservation and its total in one step, so a crash never applies one without the * other. Every bundled store does. */ export interface BudgetUsageStore { /** Adds to a total that resets at `resetAt`, epoch milliseconds, and resolves to the new total. */ add(key: string, amount: number, resetAt: number): number | Promise; /** A total, or 0. */ total(key: string): number | Promise; /** Records a reservation and adds what it charges to its total, together: resolves to the new total. */ hold?(record: BudgetReservationRecord): number | Promise; /** * Moves a reservation and its total, by the difference in what it charges, together, if it stands * where `change.from` allows. Resolves whether it moved. */ settle?(id: string, change: BudgetReservationChange): boolean | Promise; /** Extends a reserved reservation's lease. Resolves whether it was still reserved. */ renew?(id: string, leasedUntil: number): boolean | Promise; /** Reserved reservations whose lease lapsed by `now`, soonest lapsed first, optionally for one operation. */ lapsed?(now: number, limit: number, operation?: string): BudgetReservationRecord[] | Promise; /** One reservation, while the store keeps it. */ reservation?(id: string): BudgetReservationRecord | undefined | Promise; } /** Options for `budgetLedger()`. */ export interface BudgetLedgerOptions { /** * US dollars each budget may spend per period: one number for every budget, or a function that * returns each budget's own. A budget whose limit is `undefined` is not tracked. */ limit: number | ((key: string) => number | undefined | Promise); /** How long a budget lasts before it resets. Defaults to `month`, in UTC. */ period?: BudgetPeriod; /** * Which budget an operation draws on. Defaults to its tenant, and to `default` for operations * without one. Return `undefined` to leave an operation untracked. */ key?: (operation: OperationDescriptor) => string | undefined; /** Where totals are kept. Defaults to process memory, which is enough for one replica. */ store?: BudgetUsageStore; /** * How long a recorded reservation is held without `renew()` before `recover()` may settle it. * Defaults to 10 minutes: longer than the operations it covers take. */ leaseMs?: number; /** * What a reservation whose operation's outcome is unknown costs until its operation says: `charge` * keeps it charged, as a provider may have billed the call; `release` gives it back. Defaults to * `charge`. */ uncertain?: 'charge' | 'release'; /** Replaces the clock, for tests. */ now?: () => Date; } /** A ledger that can also say what a budget has spent this period, and settle what a dead process held. */ export interface BudgetLedgerWithReport extends BudgetLedger { /** Holds `estimate` for the operation, or rejects with `BudgetExceededError` when it does not fit. */ reserve(operation: OperationDescriptor, estimate: number): Promise; /** What a budget has spent so far this period, in US dollars, reservations included. */ spent(key: string): Promise; /** A recorded reservation, while the store keeps it. */ reservation(id: string): Promise; /** Extends a recorded reservation's lease. Does nothing for one the store does not record. */ renew(reservation: BudgetReservation): Promise; /** Settles a recorded reservation by its id. Resolves `false` on a store that does not record them. */ settle(id: string, outcome: BudgetReservationOutcome): Promise; /** Settles reservations whose lease lapsed. Settles nothing on a store that does not record them. */ recover: NonNullable; } /** * A spend budget shared by every family of a client. * * Before an operation runs, its estimated cost is added to the budget's total; when that would pass * the limit, the addition is undone and `BudgetExceededError` stops the call. Afterwards the estimate * is replaced by what the call actually cost, or given back when it failed. Adding first and checking * the new total is what keeps concurrent calls from overshooting together, on one process or many. * * Over a store that records reservations, each is held under a lease. One whose process died is * found by `recover()` when its lease lapses and settled by what became of its operation: charged at * its cost if it finished, given back if it never started, and charged or given back by `uncertain` * when no one knows. */ export declare function budgetLedger(options: BudgetLedgerOptions): BudgetLedgerWithReport; /** Budget totals, and the reservations against them, in process memory. The default store, and enough for one replica. */ export declare class MemoryBudgetUsage implements BudgetUsageStore { private readonly now; private readonly totals; private readonly book; constructor(now?: () => number); /** Adds to a total that resets at `resetAt`. */ add(key: string, amount: number, resetAt: number): number; /** A total, or 0 once it has reset. */ total(key: string): number; /** Records a reservation and adds it to its total. */ hold(record: BudgetReservationRecord): number; /** Moves a reservation and its total together. */ settle(id: string, change: BudgetReservationChange): boolean; /** Extends a reserved reservation's lease. */ renew(id: string, leasedUntil: number): boolean; /** Reserved reservations whose lease lapsed. */ lapsed(now: number, limit: number, operation?: string): BudgetReservationRecord[]; /** One reservation. */ reservation(id: string): BudgetReservationRecord | undefined; }