import { type DenialReason } from "./allowance.js"; import { type CycleWindow, type PlanCatalog } from "./plan-model.js"; import { type UsageLedger } from "./usage-ledger.js"; import type { Notify } from "./notifications/index.js"; import type { BillingAdapter, ResolvedConfig } from "./types.js"; export interface MeterCaller { /** `user` = a human via session/OAuth (their seat); `api` = an API key / agent * (the shared API seat). Decides which pack the execution draws — the surface * (ui/api/mcp/cli) is irrelevant. */ kind: "user" | "api"; /** WorkOS member id (user) or API key id (api) — for per-caller attribution. */ id?: string; /** The caller's seat type, resolved by the consumer (e.g. `standard`, * `premium`, `api`). Used for the per-seat pack cap in `per_seat` plans. */ seatType?: string; } export interface MeterInput { orgId: string; action: string; /** Credit cost for this execution (rate card × units), resolved by the consumer. */ cost: number; plans: PlanCatalog; /** The org's current plan key (consumer resolves + caches it). */ plan: string | null; /** Start of the current billing cycle, unix SECONDS (unchanged). Prefer * `cycle`, which also carries the END — an annual window needs both. */ cycleStart?: number; /** The window usage is measured over. Derived from the subscription period * when omitted. */ cycle?: CycleWindow; /** Where usage is counted. Defaults to `stripeUsageLedger()` — the composite, * the same default `createBilling` and `createMeter` apply. */ ledger?: UsageLedger; caller?: MeterCaller; /** Say "you are nearly out" when this call crosses a threshold. See `alerts.ts`. */ notify?: Notify; /** Percentages of an included allowance worth an email. Default `[80, 100]`. */ alertThresholds?: readonly number[]; } export type MeterResult = { ok: true; funded?: "pool" | "pack" | "wallet" | null; } | { ok: false; /** * `DenialReason` plus the one refusal that happens before any allowance is * read. Spelled as a reference rather than a copy: this union was a * duplicate of that one, so every new reason had to be added twice and the * compiler only caught it because a value flowed between them. * * `pool_exhausted` exists because an org whose included package is used up * used to be told "insufficient balance", which pointed at the wrong * problem and the wrong remedy. `rate_limit_reached` is a declared * per-window limit — waiting fixes it, buying does not — while * `spend_limit_reached` is the customer's own ceiling, which they can raise. */ reason: DenialReason | "no_billing"; message: string; /** When a rate limit refused: epoch ms at which it resets. */ retryAt?: number; }; export declare function meterUsage(adapter: BillingAdapter, config: ResolvedConfig, input: MeterInput): Promise; export interface MeterConfig = Record> { /** Plan catalog (shapes, packs, pools, limits). */ plans: PlanCatalog; /** action → credit cost (per unit). Consumer-authored product data. Omit to * always pass an explicit `cost` at the call site. */ rateCard?: R; /** Resolve the org's current plan key. Source varies per app (subscription * metadata, WorkOS org metadata, …) so the consumer supplies it; the result * is cached here for `planCacheTtlMs`. */ resolvePlan: (orgId: string) => Promise; /** Seat-type keys a caller maps to by identity. Default standard / api. */ seatDefaults?: { user?: string; api?: string; }; /** Say "you are nearly out" — `createBilling` passes its emitter. See `alerts.ts`. */ notify?: Notify; /** Percentages of an included allowance worth an email. Default `[80, 100]`. */ alertThresholds?: readonly number[]; /** Plan-cache TTL (ms). Default 60_000. The plan changes rarely; a brief stale * read only affects which allowance mode applies, never the debit. */ planCacheTtlMs?: number; /** * Start of the current billing cycle, unix seconds. * * No longer needed: the window is derived from the SUBSCRIPTION period when the * adapter can report one, falling back to the 1st of the month UTC — which is * what an included allowance requires (an annual package measured over calendar * months would reset twelve times). Override only to impose your own window. */ cycleStart?: () => number; /** Cycle key top-up grants are stored under. Default "YYYY-MM" UTC. Derived * from the same window as the usage measurement, so the two cannot drift — * which they could when this was the caller's obligation. */ cycleKey?: () => string; /** * Where usage is counted. Default: `stripeUsageLedger()`, the composite — every * ORG-wide window on a Stripe meter (included usage too, one request at any * volume) and per-CALLER windows from balance-transaction metadata. * * Pass one for the pair the bare composite can't see: a window that is both * INCLUDED and PER-MEMBER (`cap: per_seat`, a `scope: "caller"` limit) — * `stripeUsageLedger({ perCaller: stripeScopeUsageLedger() })`, which counts it * in Stripe too rather than in a database. */ ledger?: UsageLedger; } export interface MeterCallOpts { /** Who is spending — decides which seat pack the debit draws (by auth identity, * NOT surface). Omit for an org-level debit with no seat cap. */ caller?: { kind: "user" | "api"; id?: string; }; /** Executions this call represents (cost = rateCard[action] × units). Default 1. */ units?: number; /** Explicit credit cost, bypassing the rate card (e.g. a dynamically-priced op). */ cost?: number; } export type Meter> = (orgId: string, action: keyof R & string, opts?: MeterCallOpts) => Promise; export declare function createMeter = Record>(adapter: BillingAdapter, config: ResolvedConfig, meterCfg: MeterConfig): Meter; export type ApiMeterGuard> = (req: Request, action: keyof R & string, opts?: { units?: number; }) => Promise; export declare function createApiMeterGuard>(adapter: BillingAdapter, meter: Meter, cfg?: { realm?: string; /** * The MPP gate, when the app accepts machine payments. A money refusal (never a * waitable one) is re-issued as a `WWW-Authenticate: Payment` challenge, and a * caller that settles it is metered again — for real, because paying funds the * meter rather than skipping it. * * The tool surface gets this from `createToolDispatchHandler`; this is the same * offer for a route that is not a tool. Both consumers had written it by hand. */ payment?: { requirePayment(request: Request): Promise; }; }): ApiMeterGuard; //# sourceMappingURL=metering.d.ts.map