@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/LICENSE +21 -0
- package/README.md +360 -0
- package/dist/errors.d.ts +14 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +14 -0
- package/dist/errors.js.map +1 -0
- package/dist/guard.d.ts +99 -0
- package/dist/guard.d.ts.map +1 -0
- package/dist/guard.js +310 -0
- package/dist/guard.js.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +11 -0
- package/dist/index.js.map +1 -0
- package/dist/notifiers/email.d.ts +36 -0
- package/dist/notifiers/email.d.ts.map +1 -0
- package/dist/notifiers/email.js +284 -0
- package/dist/notifiers/email.js.map +1 -0
- package/dist/notifiers/index.d.ts +3 -0
- package/dist/notifiers/index.d.ts.map +1 -0
- package/dist/notifiers/index.js +6 -0
- package/dist/notifiers/index.js.map +1 -0
- package/dist/pricing.d.ts +11 -0
- package/dist/pricing.d.ts.map +1 -0
- package/dist/pricing.js +36 -0
- package/dist/pricing.js.map +1 -0
- package/dist/types.d.ts +127 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +3 -0
- package/dist/types.js.map +1 -0
- package/package.json +78 -0
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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"}
|
package/dist/index.d.ts
ADDED
|
@@ -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"}
|