An application that checks its remaining AI token allowance only after a response arrives can admit several overlapping requests against the same balance. Reserve an upper bound before dispatch, then settle the reservation when usage is known. The reservation must reduce the available balance immediately.
This tutorial implements a local accounting component independently of any provider. Its units are integer tokens under an application-defined accounting policy. It does not tokenize prompts, calculate currency charges, or assume a particular provider's usage fields. ECMAScript defines Number.isSafeInteger, which the component uses to reject fractional and unsafe numeric amounts.
Prerequisites
Use a supported Node.js release with ES modules and private class fields. Save this complete fixture test as budget.mjs. It requires no packages, credentials, network connection, or model.
import assert from 'node:assert/strict';
class Budget {
#remaining;
constructor(limit) {
this.#check(limit);
this.#remaining = limit;
}
#check(n) {
if (!Number.isSafeInteger(n) || n < 0) throw new Error('invalid units');
}
get remaining() { return this.#remaining; }
reserve(maximum) {
this.#check(maximum);
if (maximum > this.#remaining) throw new Error('budget exhausted');
this.#remaining -= maximum;
let settled = false;
return actual => {
this.#check(actual);
if (settled || actual > maximum) throw new Error('invalid settlement');
this.#remaining += maximum - actual;
settled = true;
};
}
}
const budget = new Budget(100);
const settleA = budget.reserve(70);
assert.throws(() => budget.reserve(40), /exhausted/);
assert.equal(budget.remaining, 30);
assert.throws(() => settleA(71), /settlement/);
settleA(45);
assert.equal(budget.remaining, 55);
assert.throws(() => settleA(45), /settlement/);
const settleB = budget.reserve(55);
settleB(55); // Unknown usage: conservatively retain the whole reservation.
assert.equal(budget.remaining, 0);
assert.throws(() => new Budget(1.5), /units/);
assert.throws(() => new Budget(Number.MAX_SAFE_INTEGER + 1), /units/);
console.log('remaining:', budget.remaining);
console.log('AI budget fixture checks passed');
Run and expected output
node budget.mjs
remaining: 0
AI budget fixture checks passed
Follow the accounting
The first reservation removes 70 from an initial balance of 100. A second request for 40 is rejected even though the first request has not settled. When actual usage is 45, settlement returns the unused 25, leaving 55 available. Settling the same reservation twice throws instead of crediting the balance twice.
An attempted settlement above the reservation also throws. Because that attempt changes no state, the valid settlement can still follow. The final reservation consumes the remaining 55. Its settlement retains all 55 units, illustrating a conservative policy when a dispatched request has an uncertain outcome.
Connect it to generation
Call reserve synchronously before starting an asynchronous provider operation. The reservation must cover every token component counted by your application, including input and the permitted output, with any additional provider-specific usage handled explicitly. Settle using trustworthy usage metadata after translating it into the same units.
Do not automatically refund a timeout: the remote request may have run. If dispatch definitely never occurred, application policy can settle at zero. If the maximum was not actually an upper bound, this component cannot guarantee the allowance; fix the estimation and dispatch contract rather than ignoring an over-limit settlement.
Edge cases and troubleshooting
This component works within one JavaScript instance because reserve contains no await point. Multiple processes need a shared atomic reservation mechanism. It also has no persistence, expiry, or provider-billing reconciliation. A process restart resets its state. Tests cover accounting fixtures, not these production concerns.
If the balance never recovers, check which requests were dispatched without a subsequent settlement. Keep reservations identifiable in a real adapter so uncertain outcomes can be reconciled deliberately. Review the related Javascript tutorials and AI tutorials.