@tktideai/ai-api-cost-guard 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/guard.js ADDED
@@ -0,0 +1,310 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.ApiCostGuard = void 0;
7
+ exports.createMiddleware = createMiddleware;
8
+ const decimal_js_1 = __importDefault(require("decimal.js"));
9
+ const pricing_1 = require("./pricing");
10
+ const errors_1 = require("./errors");
11
+ // ─── Configure Decimal.js for money-safe arithmetic ───────────────────────
12
+ decimal_js_1.default.set({
13
+ precision: 28, // More than enough for USD amounts
14
+ rounding: decimal_js_1.default.ROUND_HALF_UP,
15
+ toExpPos: 18,
16
+ toExpNeg: -7,
17
+ });
18
+ class ApiCostGuard {
19
+ // ─────────────────────────────────────────────────────────────────────
20
+ constructor(options = {}) {
21
+ // ── Ledger ───────────────────────────────────────────────────────────
22
+ this.ledger = [];
23
+ this.totalSpend = new decimal_js_1.default(0);
24
+ this.hardLimit = options.hardLimit != null ? new decimal_js_1.default(options.hardLimit) : null;
25
+ this.softLimit = options.softLimit != null ? new decimal_js_1.default(options.softLimit) : null;
26
+ this.perRequestLimit = options.perRequestLimit != null ? new decimal_js_1.default(options.perRequestLimit) : null;
27
+ this.windowMs = options.windowMs ?? null;
28
+ this.throwOnBlock = options.throwOnBlock ?? true;
29
+ this.onWarning = options.onWarning ?? null;
30
+ this.onBlock = options.onBlock ?? null;
31
+ // Merge built-in + custom pricing (custom wins on conflicts)
32
+ this.pricing = { ...pricing_1.BUILT_IN_PRICING, ...(options.customPricing ?? {}) };
33
+ this.validateOptions();
34
+ }
35
+ // ─── Public: Estimate cost ────────────────────────────────────────────
36
+ /**
37
+ * Estimate the cost of a request without checking limits.
38
+ * Returns null if the model is not in the pricing table.
39
+ */
40
+ estimateCost(model, inputTokens, outputTokens = 0) {
41
+ const p = this.resolvePricing(model);
42
+ if (!p)
43
+ return null;
44
+ const inputCost = new decimal_js_1.default(inputTokens)
45
+ .div(p.unit)
46
+ .mul(new decimal_js_1.default(p.input));
47
+ const outputCost = new decimal_js_1.default(outputTokens)
48
+ .div(p.unit)
49
+ .mul(new decimal_js_1.default(p.output));
50
+ return inputCost.plus(outputCost);
51
+ }
52
+ // ─── Public: Preflight check ──────────────────────────────────────────
53
+ /**
54
+ * Check whether a request should be allowed based on current spend.
55
+ * Call this BEFORE making the API request.
56
+ *
57
+ * @throws {BudgetExceededError} if blocked and `throwOnBlock` is true
58
+ */
59
+ preflightCheck(model, estimatedInputTokens, estimatedOutputTokens = 0) {
60
+ const estimatedCost = this.estimateCost(model, estimatedInputTokens, estimatedOutputTokens);
61
+ const currentSpend = this.getWindowSpend();
62
+ const projectedTotal = estimatedCost
63
+ ? currentSpend.plus(estimatedCost)
64
+ : currentSpend;
65
+ // ── Per-request limit ─────────────────────────────────────────────
66
+ if (this.perRequestLimit && estimatedCost && estimatedCost.gt(this.perRequestLimit)) {
67
+ const error = new errors_1.BudgetExceededError(`Request estimated cost $${estimatedCost.toFixed(6)} exceeds per-request limit $${this.perRequestLimit.toFixed(6)}`, {
68
+ type: 'per_request_limit',
69
+ projectedTotal,
70
+ currentSpend,
71
+ limit: this.perRequestLimit,
72
+ estimatedCost,
73
+ });
74
+ return this.handleBlock(error);
75
+ }
76
+ // ── Hard limit ────────────────────────────────────────────────────
77
+ if (this.hardLimit && projectedTotal.gte(this.hardLimit)) {
78
+ const error = new errors_1.BudgetExceededError(`Projected spend $${projectedTotal.toFixed(6)} would reach hard limit $${this.hardLimit.toFixed(6)}`, {
79
+ type: 'hard_limit',
80
+ projectedTotal,
81
+ currentSpend,
82
+ limit: this.hardLimit,
83
+ estimatedCost,
84
+ });
85
+ return this.handleBlock(error);
86
+ }
87
+ // ── Soft limit warning ────────────────────────────────────────────
88
+ if (this.softLimit && projectedTotal.gte(this.softLimit) && this.onWarning) {
89
+ this.onWarning({ projectedTotal, currentSpend, softLimit: this.softLimit, estimatedCost });
90
+ }
91
+ return { allowed: true, estimatedCost, projectedTotal };
92
+ }
93
+ // ─── Public: Record actual usage ──────────────────────────────────────
94
+ /**
95
+ * Record the actual token usage AFTER a successful API response.
96
+ * This keeps the spend ledger accurate.
97
+ *
98
+ * @returns The actual cost of this request
99
+ */
100
+ recordUsage(model, inputTokens, outputTokens, metadata = {}) {
101
+ const cost = this.estimateCost(model, inputTokens, outputTokens) ?? new decimal_js_1.default(0);
102
+ const record = {
103
+ timestamp: Date.now(),
104
+ cost,
105
+ model,
106
+ inputTokens,
107
+ outputTokens,
108
+ metadata,
109
+ };
110
+ this.ledger.push(record);
111
+ this.totalSpend = this.totalSpend.plus(cost);
112
+ this.pruneOldEntries();
113
+ return cost;
114
+ }
115
+ // ─── Public: wrap() ───────────────────────────────────────────────────
116
+ /**
117
+ * Wrap any async API call with automatic preflight + usage recording.
118
+ *
119
+ * @returns The API result, or null if blocked (when throwOnBlock=false)
120
+ * @throws {BudgetExceededError} if blocked and throwOnBlock=true
121
+ *
122
+ * @example
123
+ * const response = await guard.wrap(
124
+ * () => openai.chat.completions.create({ model: 'gpt-4o', messages }),
125
+ * {
126
+ * model: 'gpt-4o',
127
+ * estimatedInputTokens: 500,
128
+ * estimatedOutputTokens: 500,
129
+ * extractUsage: (res) => ({
130
+ * inputTokens: res.usage.prompt_tokens,
131
+ * outputTokens: res.usage.completion_tokens,
132
+ * }),
133
+ * },
134
+ * );
135
+ */
136
+ async wrap(apiFn, options) {
137
+ const { model, estimatedInputTokens = 500, estimatedOutputTokens = 500, extractUsage, metadata = {}, } = options;
138
+ const check = this.preflightCheck(model, estimatedInputTokens, estimatedOutputTokens);
139
+ if (!check.allowed) {
140
+ // throwOnBlock=false path — handleBlock returned a result instead of throwing
141
+ return null;
142
+ }
143
+ const result = await apiFn();
144
+ // Record actual usage if extractor provided
145
+ if (extractUsage) {
146
+ try {
147
+ const { inputTokens, outputTokens } = extractUsage(result);
148
+ this.recordUsage(model, inputTokens, outputTokens, metadata);
149
+ }
150
+ catch {
151
+ // Non-fatal: estimation already tracked at preflight
152
+ }
153
+ }
154
+ return result;
155
+ }
156
+ // ─── Public: Stats ────────────────────────────────────────────────────
157
+ /**
158
+ * Get a snapshot of current spend and budget status.
159
+ */
160
+ getStats() {
161
+ const windowSpend = this.getWindowSpend();
162
+ const remainingBudget = this.hardLimit ? decimal_js_1.default.max(0, this.hardLimit.minus(windowSpend)) : null;
163
+ const percentUsed = this.hardLimit
164
+ ? windowSpend.div(this.hardLimit).mul(100).toFixed(2)
165
+ : null;
166
+ return {
167
+ totalSpend: this.totalSpend,
168
+ windowSpend,
169
+ requestCount: this.ledger.length,
170
+ hardLimit: this.hardLimit,
171
+ softLimit: this.softLimit,
172
+ remainingBudget,
173
+ percentUsed,
174
+ ledger: [...this.ledger], // defensive copy
175
+ };
176
+ }
177
+ /**
178
+ * Reset all spend records. Useful between test runs or billing periods.
179
+ */
180
+ reset() {
181
+ this.ledger = [];
182
+ this.totalSpend = new decimal_js_1.default(0);
183
+ }
184
+ // ─── Private helpers ──────────────────────────────────────────────────
185
+ getWindowSpend() {
186
+ if (!this.windowMs)
187
+ return this.totalSpend;
188
+ const cutoff = Date.now() - this.windowMs;
189
+ return this.ledger
190
+ .filter(e => e.timestamp >= cutoff)
191
+ .reduce((sum, e) => sum.plus(e.cost), new decimal_js_1.default(0));
192
+ }
193
+ pruneOldEntries() {
194
+ if (!this.windowMs)
195
+ return;
196
+ const cutoff = Date.now() - this.windowMs;
197
+ const before = this.ledger.length;
198
+ this.ledger = this.ledger.filter(e => e.timestamp >= cutoff);
199
+ const pruned = before - this.ledger.length;
200
+ // Keep totalSpend consistent when pruning
201
+ if (pruned > 0) {
202
+ this.totalSpend = this.ledger.reduce((sum, e) => sum.plus(e.cost), new decimal_js_1.default(0));
203
+ }
204
+ }
205
+ resolvePricing(model) {
206
+ const key = model.toLowerCase();
207
+ // Exact match first
208
+ if (this.pricing[key])
209
+ return this.pricing[key];
210
+ // Substring match — longest key wins to avoid 'gpt-4' matching 'gpt-4o'
211
+ const matches = Object.keys(this.pricing)
212
+ .filter(k => key.includes(k))
213
+ .sort((a, b) => b.length - a.length); // longest first
214
+ return matches.length > 0 ? (this.pricing[matches[0]] ?? null) : null;
215
+ }
216
+ handleBlock(error) {
217
+ if (this.onBlock) {
218
+ this.onBlock(error);
219
+ }
220
+ if (this.throwOnBlock) {
221
+ throw error;
222
+ }
223
+ return {
224
+ allowed: false,
225
+ reason: error.details.type,
226
+ estimatedCost: error.details.estimatedCost,
227
+ projectedTotal: error.details.projectedTotal,
228
+ currentSpend: error.details.currentSpend,
229
+ limit: error.details.limit,
230
+ };
231
+ }
232
+ validateOptions() {
233
+ if (this.hardLimit && this.hardLimit.lte(0)) {
234
+ throw new RangeError('hardLimit must be greater than 0');
235
+ }
236
+ if (this.softLimit && this.softLimit.lte(0)) {
237
+ throw new RangeError('softLimit must be greater than 0');
238
+ }
239
+ if (this.perRequestLimit && this.perRequestLimit.lte(0)) {
240
+ throw new RangeError('perRequestLimit must be greater than 0');
241
+ }
242
+ if (this.hardLimit && this.softLimit && this.softLimit.gte(this.hardLimit)) {
243
+ throw new RangeError('softLimit must be less than hardLimit');
244
+ }
245
+ if (this.windowMs !== null && this.windowMs <= 0) {
246
+ throw new RangeError('windowMs must be greater than 0');
247
+ }
248
+ }
249
+ }
250
+ exports.ApiCostGuard = ApiCostGuard;
251
+ /**
252
+ * Express/Connect-compatible middleware.
253
+ * Runs a preflight check on every incoming request.
254
+ * Returns 429 with JSON error body if budget is exceeded.
255
+ *
256
+ * @example
257
+ * const guard = new ApiCostGuard({ hardLimit: 50 });
258
+ * app.post('/api/chat', createMiddleware(guard), async (req, res) => {
259
+ * const response = await openai.chat.completions.create(req.body);
260
+ * guard.recordUsage(req.body.model,
261
+ * response.usage.prompt_tokens,
262
+ * response.usage.completion_tokens,
263
+ * );
264
+ * res.json(response);
265
+ * });
266
+ */
267
+ function createMiddleware(guard, options = {}) {
268
+ const getModel = options.getModel ?? ((req) => req.body?.model);
269
+ const getTokens = options.getTokens ?? ((req) => ({
270
+ input: Number(req.body?.max_tokens ?? 500),
271
+ output: Number(req.body?.max_tokens ?? 500),
272
+ }));
273
+ return (req, res, next) => {
274
+ const model = getModel(req);
275
+ const { input, output } = getTokens(req);
276
+ // Works regardless of throwOnBlock setting on the guard:
277
+ // - throwOnBlock:true → throws BudgetExceededError → caught here → 429
278
+ // - throwOnBlock:false → returns { allowed: false } → caught here → 429
279
+ try {
280
+ const check = guard.preflightCheck(model ?? 'unknown', input, output);
281
+ if (!check.allowed) {
282
+ res.status(429).json({
283
+ error: 'BudgetExceeded',
284
+ reason: check.reason,
285
+ projectedTotal: check.projectedTotal.toFixed(6),
286
+ currentSpend: check.currentSpend.toFixed(6),
287
+ limit: check.limit.toFixed(6),
288
+ });
289
+ return;
290
+ }
291
+ }
292
+ catch (err) {
293
+ if (err instanceof errors_1.BudgetExceededError) {
294
+ res.status(429).json({
295
+ error: 'BudgetExceeded',
296
+ reason: err.details.type,
297
+ projectedTotal: err.details.projectedTotal.toFixed(6),
298
+ currentSpend: err.details.currentSpend.toFixed(6),
299
+ limit: err.details.limit.toFixed(6),
300
+ });
301
+ return;
302
+ }
303
+ // Unknown error — let Express handle it
304
+ next(err);
305
+ return;
306
+ }
307
+ next();
308
+ };
309
+ }
310
+ //# sourceMappingURL=guard.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"guard.js","sourceRoot":"","sources":["../src/guard.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ export { ApiCostGuard, createMiddleware } from './guard';
2
+ export type { MiddlewareOptions } from './guard';
3
+ export { BudgetExceededError } from './errors';
4
+ export { BUILT_IN_PRICING } from './pricing';
5
+ export type { ApiCostGuardOptions, ModelPricing, PricingMap, PreflightResult, PreflightAllowed, PreflightBlocked, BlockReason, UsageRecord, GuardStats, WrapOptions, WarningDetails, } from './types';
6
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC;AACzD,YAAY,EAAE,iBAAiB,EAAE,MAAM,SAAS,CAAC;AACjD,OAAO,EAAE,mBAAmB,EAAE,MAAM,UAAU,CAAC;AAC/C,OAAO,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAC7C,YAAY,EACV,mBAAmB,EACnB,YAAY,EACZ,UAAU,EACV,eAAe,EACf,gBAAgB,EAChB,gBAAgB,EAChB,WAAW,EACX,WAAW,EACX,UAAU,EACV,WAAW,EACX,cAAc,GACf,MAAM,SAAS,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,11 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.BUILT_IN_PRICING = exports.BudgetExceededError = exports.createMiddleware = exports.ApiCostGuard = void 0;
4
+ var guard_1 = require("./guard");
5
+ Object.defineProperty(exports, "ApiCostGuard", { enumerable: true, get: function () { return guard_1.ApiCostGuard; } });
6
+ Object.defineProperty(exports, "createMiddleware", { enumerable: true, get: function () { return guard_1.createMiddleware; } });
7
+ var errors_1 = require("./errors");
8
+ Object.defineProperty(exports, "BudgetExceededError", { enumerable: true, get: function () { return errors_1.BudgetExceededError; } });
9
+ var pricing_1 = require("./pricing");
10
+ Object.defineProperty(exports, "BUILT_IN_PRICING", { enumerable: true, get: function () { return pricing_1.BUILT_IN_PRICING; } });
11
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;AAAA,iCAAyD;AAAhD,qGAAA,YAAY,OAAA;AAAE,yGAAA,gBAAgB,OAAA;AAEvC,mCAA+C;AAAtC,6GAAA,mBAAmB,OAAA;AAC5B,qCAA6C;AAApC,2GAAA,gBAAgB,OAAA"}
@@ -0,0 +1,36 @@
1
+ import type { WarningDetails } from '../types';
2
+ import { BudgetExceededError } from '../errors';
3
+ export interface EmailNotifierOptions {
4
+ /** Your Gmail address — used as the sender */
5
+ from: string;
6
+ /** Gmail app password for the sender account.
7
+ * Google Account → Security → 2-Step Verification → App Passwords
8
+ */
9
+ password: string;
10
+ /** Recipient email address — where alerts are sent */
11
+ to: string;
12
+ /** Optional custom message shown at the top of the email */
13
+ message?: string;
14
+ }
15
+ type NotifierFn = (details: WarningDetails | BudgetExceededError) => Promise<void>;
16
+ /**
17
+ * Creates an email notifier that sends real Gmail alerts when cost limits are hit.
18
+ *
19
+ * @example
20
+ * const notifier = createEmailNotifier({
21
+ * from: 'your@gmail.com',
22
+ * password: process.env.GMAIL_PASS!,
23
+ * to: 'alerts@yourdomain.com',
24
+ * message: 'Your AI spend needs attention.', // optional
25
+ * });
26
+ *
27
+ * const guard = new ApiCostGuard({
28
+ * softLimit: 8,
29
+ * hardLimit: 10,
30
+ * onWarning: notifier,
31
+ * onBlock: notifier,
32
+ * });
33
+ */
34
+ export declare function createEmailNotifier(options: EmailNotifierOptions): NotifierFn;
35
+ export {};
36
+ //# sourceMappingURL=email.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"email.d.ts","sourceRoot":"","sources":["../../src/notifiers/email.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAC/C,OAAO,EAAE,mBAAmB,EAAE,MAAM,WAAW,CAAC;AAOhD,MAAM,WAAW,oBAAoB;IACnC,8CAA8C;IAC9C,IAAI,EAAE,MAAM,CAAC;IACb;;OAEG;IACH,QAAQ,EAAE,MAAM,CAAC;IACjB,sDAAsD;IACtD,EAAE,EAAE,MAAM,CAAC;IACX,4DAA4D;IAC5D,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,KAAK,UAAU,GAAG,CAAC,OAAO,EAAE,cAAc,GAAG,mBAAmB,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;AA+OnF;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,oBAAoB,GAAG,UAAU,CAiC7E"}