cost-limiter 0.1.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/index.cjs ADDED
@@ -0,0 +1,384 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var src_exports = {};
22
+ __export(src_exports, {
23
+ CostLimitError: () => CostLimitError,
24
+ CostLimiter: () => CostLimiter,
25
+ DEFAULT_PRICING: () => DEFAULT_PRICING,
26
+ MemoryCostStorage: () => MemoryCostStorage,
27
+ RedisCostStorage: () => RedisCostStorage,
28
+ WINDOWS: () => WINDOWS,
29
+ priceCall: () => priceCall,
30
+ windowBucket: () => windowBucket,
31
+ windowResetAt: () => windowResetAt
32
+ });
33
+ module.exports = __toCommonJS(src_exports);
34
+
35
+ // src/limiter.ts
36
+ var import_node_events = require("events");
37
+
38
+ // src/errors.ts
39
+ var CostLimitError = class _CostLimitError extends Error {
40
+ limit;
41
+ used;
42
+ remaining;
43
+ resetAt;
44
+ window;
45
+ dimension;
46
+ dimensionKey;
47
+ constructor(input) {
48
+ super(
49
+ `cost-limiter: budget exhausted for ${input.dimension}=${input.dimensionKey} (${input.window}: $${input.used.toFixed(4)} / $${input.limit.toFixed(4)})`
50
+ );
51
+ this.name = "CostLimitError";
52
+ this.limit = input.limit;
53
+ this.used = input.used;
54
+ this.remaining = Math.max(0, input.limit - input.used);
55
+ this.resetAt = input.resetAt;
56
+ this.window = input.window;
57
+ this.dimension = input.dimension;
58
+ this.dimensionKey = input.dimensionKey;
59
+ Object.setPrototypeOf(this, _CostLimitError.prototype);
60
+ }
61
+ };
62
+
63
+ // src/pricing.ts
64
+ var DEFAULT_PRICING = {
65
+ // OpenAI
66
+ "gpt-4o": { inputPerMTokens: 2.5, outputPerMTokens: 10 },
67
+ "gpt-4o-mini": { inputPerMTokens: 0.15, outputPerMTokens: 0.6 },
68
+ "gpt-4-turbo": { inputPerMTokens: 10, outputPerMTokens: 30 },
69
+ "gpt-3.5-turbo": { inputPerMTokens: 0.5, outputPerMTokens: 1.5 },
70
+ "o1": { inputPerMTokens: 15, outputPerMTokens: 60 },
71
+ "o1-mini": { inputPerMTokens: 3, outputPerMTokens: 12 },
72
+ "o3-mini": { inputPerMTokens: 1.1, outputPerMTokens: 4.4 },
73
+ // Anthropic
74
+ "claude-opus-4": { inputPerMTokens: 15, outputPerMTokens: 75 },
75
+ "claude-sonnet-4": { inputPerMTokens: 3, outputPerMTokens: 15 },
76
+ "claude-haiku-4": { inputPerMTokens: 1, outputPerMTokens: 5 },
77
+ // Gemini
78
+ "gemini-2.0-flash": { inputPerMTokens: 0.1, outputPerMTokens: 0.4 },
79
+ "gemini-1.5-pro": { inputPerMTokens: 1.25, outputPerMTokens: 5 },
80
+ "gemini-1.5-flash": { inputPerMTokens: 0.075, outputPerMTokens: 0.3 },
81
+ // Groq
82
+ "llama-3.3-70b": { inputPerMTokens: 0.59, outputPerMTokens: 0.79 },
83
+ "mixtral-8x7b": { inputPerMTokens: 0.24, outputPerMTokens: 0.24 }
84
+ };
85
+ function priceCall(model, inputTokens, outputTokens, custom = {}) {
86
+ const p = custom[model] ?? DEFAULT_PRICING[model];
87
+ if (!p) return 0;
88
+ return (inputTokens * p.inputPerMTokens + outputTokens * p.outputPerMTokens) / 1e6;
89
+ }
90
+
91
+ // src/storage.ts
92
+ var WINDOWS = ["minute", "hour", "day", "month"];
93
+ function windowResetAt(now, window) {
94
+ const d = new Date(now);
95
+ switch (window) {
96
+ case "minute":
97
+ d.setUTCSeconds(0, 0);
98
+ d.setUTCMinutes(d.getUTCMinutes() + 1);
99
+ return d;
100
+ case "hour":
101
+ d.setUTCMinutes(0, 0, 0);
102
+ d.setUTCHours(d.getUTCHours() + 1);
103
+ return d;
104
+ case "day":
105
+ d.setUTCHours(0, 0, 0, 0);
106
+ d.setUTCDate(d.getUTCDate() + 1);
107
+ return d;
108
+ case "month":
109
+ d.setUTCHours(0, 0, 0, 0);
110
+ d.setUTCDate(1);
111
+ d.setUTCMonth(d.getUTCMonth() + 1);
112
+ return d;
113
+ }
114
+ }
115
+ function windowBucket(now, window) {
116
+ const y = now.getUTCFullYear();
117
+ const m = String(now.getUTCMonth() + 1).padStart(2, "0");
118
+ const d = String(now.getUTCDate()).padStart(2, "0");
119
+ const h = String(now.getUTCHours()).padStart(2, "0");
120
+ const min = String(now.getUTCMinutes()).padStart(2, "0");
121
+ switch (window) {
122
+ case "minute":
123
+ return `${y}${m}${d}${h}${min}`;
124
+ case "hour":
125
+ return `${y}${m}${d}${h}`;
126
+ case "day":
127
+ return `${y}${m}${d}`;
128
+ case "month":
129
+ return `${y}${m}`;
130
+ }
131
+ }
132
+ var MemoryCostStorage = class {
133
+ store = /* @__PURE__ */ new Map();
134
+ async increment(key, amount, window, resetAt) {
135
+ const fullKey = `${key}:${window}`;
136
+ const entry = this.store.get(fullKey);
137
+ if (entry && entry.resetAt <= Date.now()) {
138
+ this.store.delete(fullKey);
139
+ }
140
+ const cur = this.store.get(fullKey) ?? { value: 0, resetAt: resetAt.getTime() };
141
+ cur.value = +(cur.value + amount).toFixed(6);
142
+ this.store.set(fullKey, cur);
143
+ return cur.value;
144
+ }
145
+ async get(key, window) {
146
+ const entry = this.store.get(`${key}:${window}`);
147
+ if (!entry) return 0;
148
+ if (entry.resetAt <= Date.now()) {
149
+ this.store.delete(`${key}:${window}`);
150
+ return 0;
151
+ }
152
+ return entry.value;
153
+ }
154
+ async reset(key) {
155
+ for (const w of WINDOWS) this.store.delete(`${key}:${w}`);
156
+ }
157
+ };
158
+ var RedisCostStorage = class {
159
+ constructor(redis, prefix = "cost-limiter") {
160
+ this.redis = redis;
161
+ this.prefix = prefix;
162
+ }
163
+ redis;
164
+ prefix;
165
+ key(k, w) {
166
+ return `${this.prefix}:${k}:${w}`;
167
+ }
168
+ async increment(key, amount, window, resetAt) {
169
+ const k = this.key(key, window);
170
+ const newVal = await this.redis.incrbyfloat(k, amount);
171
+ const ttl = Math.max(1, resetAt.getTime() - Date.now());
172
+ await this.redis.pexpire(k, ttl);
173
+ return Number(newVal);
174
+ }
175
+ async get(key, window) {
176
+ const v = await this.redis.get(this.key(key, window));
177
+ return v ? Number(v) : 0;
178
+ }
179
+ async reset(key) {
180
+ await this.redis.del(...WINDOWS.map((w) => this.key(key, w)));
181
+ }
182
+ };
183
+
184
+ // src/limiter.ts
185
+ var DIMENSION_PREFIX = {
186
+ user: "u",
187
+ team: "t",
188
+ apiKey: "k",
189
+ global: "g"
190
+ };
191
+ var CostLimiter = class extends import_node_events.EventEmitter {
192
+ storage;
193
+ budgets;
194
+ pricing;
195
+ warnThreshold;
196
+ constructor(opts = {}) {
197
+ super();
198
+ this.storage = opts.storage ?? new MemoryCostStorage();
199
+ this.budgets = opts.budgets ?? {};
200
+ this.pricing = opts.pricing === "auto" || !opts.pricing ? DEFAULT_PRICING : opts.pricing;
201
+ this.warnThreshold = opts.warnThreshold ?? 0.8;
202
+ }
203
+ dimensionKey(dim, key) {
204
+ return `${DIMENSION_PREFIX[dim]}:${key}`;
205
+ }
206
+ checks(input) {
207
+ const out = [];
208
+ if (input.userId && this.budgets.perUser) out.push({ dim: "user", key: input.userId, budget: this.budgets.perUser });
209
+ if (input.teamId && this.budgets.perTeam) out.push({ dim: "team", key: input.teamId, budget: this.budgets.perTeam });
210
+ if (input.apiKeyId && this.budgets.perApiKey) out.push({ dim: "apiKey", key: input.apiKeyId, budget: this.budgets.perApiKey });
211
+ if (this.budgets.global) out.push({ dim: "global", key: "global", budget: this.budgets.global });
212
+ return out;
213
+ }
214
+ /** Throws CostLimitError when any *remaining* budget can not absorb `costUsd`. */
215
+ async check(input) {
216
+ const cost = input.estimatedCostUsd ?? 0;
217
+ const now = /* @__PURE__ */ new Date();
218
+ for (const c of this.checks({ ...input, outputTokens: 0 })) {
219
+ for (const w of WINDOWS) {
220
+ const limit = c.budget[w];
221
+ if (limit === void 0) continue;
222
+ const fullKey = `${this.dimensionKey(c.dim, c.key)}:${windowBucket(now, w)}`;
223
+ const used = await this.storage.get(fullKey, w);
224
+ if (used + cost > limit) {
225
+ throw new CostLimitError({
226
+ limit,
227
+ used,
228
+ resetAt: windowResetAt(now, w),
229
+ window: w,
230
+ dimension: c.dim,
231
+ dimensionKey: c.key
232
+ });
233
+ }
234
+ }
235
+ }
236
+ }
237
+ async charge(input) {
238
+ const costUsd = input.customCostUsd ?? priceCall(input.model, input.inputTokens, input.outputTokens, this.pricing);
239
+ const now = /* @__PURE__ */ new Date();
240
+ for (const c of this.checks(input)) {
241
+ for (const w of WINDOWS) {
242
+ const limit = c.budget[w];
243
+ if (limit === void 0) continue;
244
+ const bucket = windowBucket(now, w);
245
+ const fullKey = `${this.dimensionKey(c.dim, c.key)}:${bucket}`;
246
+ const newTotal = await this.storage.increment(fullKey, costUsd, w, windowResetAt(now, w));
247
+ if (newTotal > limit) {
248
+ throw new CostLimitError({
249
+ limit,
250
+ used: newTotal,
251
+ resetAt: windowResetAt(now, w),
252
+ window: w,
253
+ dimension: c.dim,
254
+ dimensionKey: c.key
255
+ });
256
+ }
257
+ if (newTotal / limit >= this.warnThreshold) {
258
+ const ev = {
259
+ dimension: c.dim,
260
+ key: c.key,
261
+ window: w,
262
+ used: newTotal,
263
+ limit,
264
+ percent: newTotal / limit
265
+ };
266
+ this.emit("BudgetWarning", ev);
267
+ }
268
+ }
269
+ }
270
+ return { costUsd };
271
+ }
272
+ async getUsage(userId) {
273
+ const now = /* @__PURE__ */ new Date();
274
+ const spend = { minute: 0, hour: 0, day: 0, month: 0 };
275
+ const limit = { minute: void 0, hour: void 0, day: void 0, month: void 0 };
276
+ for (const w of WINDOWS) {
277
+ const key = `${this.dimensionKey("user", userId)}:${windowBucket(now, w)}`;
278
+ spend[w] = await this.storage.get(key, w);
279
+ limit[w] = this.budgets.perUser?.[w];
280
+ }
281
+ return { dimension: "user", key: userId, spend, limit };
282
+ }
283
+ async resetUsage(userId) {
284
+ const now = /* @__PURE__ */ new Date();
285
+ for (const w of WINDOWS) {
286
+ const key = `${this.dimensionKey("user", userId)}:${windowBucket(now, w)}`;
287
+ await this.storage.reset(key);
288
+ }
289
+ }
290
+ /** Wrap an OpenAI v4 client so usage is charged automatically. */
291
+ wrap(client, ctx) {
292
+ return new Proxy(client, this.makeHandler("openai"));
293
+ }
294
+ /** Wrap an Anthropic SDK client. */
295
+ wrapAnthropic(client) {
296
+ return new Proxy(client, this.makeHandler("anthropic"));
297
+ }
298
+ /** Express/Hono/Fastify-style middleware: parses `userId`/`teamId` off `req` before allowing the request. */
299
+ middleware(getDims) {
300
+ return async (req, _res, next) => {
301
+ try {
302
+ const { estimatedCostUsd, ...dims } = getDims(req);
303
+ await this.check({
304
+ ...dims,
305
+ provider: "openai",
306
+ model: "gpt-4o-mini",
307
+ inputTokens: 0,
308
+ ...estimatedCostUsd !== void 0 ? { estimatedCostUsd } : {}
309
+ });
310
+ next();
311
+ } catch (err) {
312
+ next(err);
313
+ }
314
+ };
315
+ }
316
+ makeHandler(provider) {
317
+ const limiter = this;
318
+ const handler = {
319
+ get(target, prop, receiver) {
320
+ const value = Reflect.get(target, prop, receiver);
321
+ if (typeof value === "object" && value !== null) {
322
+ return new Proxy(value, handler);
323
+ }
324
+ if (typeof value === "function") {
325
+ return async (req) => {
326
+ const result = await value.call(target, stripDims(req));
327
+ try {
328
+ const usage = extractUsage(result, provider);
329
+ if (usage) {
330
+ await limiter.charge({
331
+ ...req.userId !== void 0 ? { userId: req.userId } : {},
332
+ ...req.teamId !== void 0 ? { teamId: req.teamId } : {},
333
+ ...req.apiKeyId !== void 0 ? { apiKeyId: req.apiKeyId } : {},
334
+ provider,
335
+ model: String(req.model ?? usage.model ?? "unknown"),
336
+ inputTokens: usage.input,
337
+ outputTokens: usage.output
338
+ });
339
+ }
340
+ } catch (err) {
341
+ if (err instanceof CostLimitError) throw err;
342
+ }
343
+ return result;
344
+ };
345
+ }
346
+ return value;
347
+ }
348
+ };
349
+ return handler;
350
+ }
351
+ };
352
+ function stripDims(req) {
353
+ const { userId: _u, teamId: _t, apiKeyId: _k, ...rest } = req;
354
+ return rest;
355
+ }
356
+ function extractUsage(result, provider) {
357
+ const r = result;
358
+ if (!r?.usage) return void 0;
359
+ if (provider === "openai") {
360
+ return {
361
+ input: r.usage.prompt_tokens ?? 0,
362
+ output: r.usage.completion_tokens ?? 0,
363
+ ...r.model ? { model: r.model } : {}
364
+ };
365
+ }
366
+ return {
367
+ input: r.usage.input_tokens ?? 0,
368
+ output: r.usage.output_tokens ?? 0,
369
+ ...r.model ? { model: r.model } : {}
370
+ };
371
+ }
372
+ // Annotate the CommonJS export names for ESM import in node:
373
+ 0 && (module.exports = {
374
+ CostLimitError,
375
+ CostLimiter,
376
+ DEFAULT_PRICING,
377
+ MemoryCostStorage,
378
+ RedisCostStorage,
379
+ WINDOWS,
380
+ priceCall,
381
+ windowBucket,
382
+ windowResetAt
383
+ });
384
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts","../src/limiter.ts","../src/errors.ts","../src/pricing.ts","../src/storage.ts"],"sourcesContent":["export { CostLimiter } from \"./limiter.js\";\nexport type { CostLimiterOptions, ChargeInput } from \"./limiter.js\";\nexport { CostLimitError } from \"./errors.js\";\nexport {\n MemoryCostStorage,\n RedisCostStorage,\n WINDOWS,\n windowBucket,\n windowResetAt,\n} from \"./storage.js\";\nexport type { StorageAdapter } from \"./storage.js\";\nexport {\n DEFAULT_PRICING,\n priceCall,\n} from \"./pricing.js\";\nexport type { ModelPrice, PricingConfig } from \"./pricing.js\";\nexport type {\n BudgetConfig,\n WindowBudget,\n Window,\n Dimension,\n UsageReport,\n BudgetWarningEvent,\n} from \"./types.js\";\n","import { EventEmitter } from \"node:events\";\nimport { CostLimitError } from \"./errors.js\";\nimport { DEFAULT_PRICING, priceCall, type ModelPrice } from \"./pricing.js\";\nimport {\n MemoryCostStorage,\n type StorageAdapter,\n WINDOWS,\n windowBucket,\n windowResetAt,\n} from \"./storage.js\";\nimport type {\n BudgetConfig,\n BudgetWarningEvent,\n Dimension,\n UsageReport,\n Window,\n WindowBudget,\n} from \"./types.js\";\n\nexport interface CostLimiterOptions {\n budgets?: BudgetConfig;\n storage?: StorageAdapter;\n pricing?: Record<string, ModelPrice> | \"auto\";\n /** Soft-warn threshold (0-1). Defaults to 0.8 (80%). */\n warnThreshold?: number;\n}\n\nexport interface ChargeInput {\n userId?: string;\n teamId?: string;\n apiKeyId?: string;\n provider: \"openai\" | \"anthropic\" | string;\n model: string;\n inputTokens: number;\n outputTokens: number;\n customCostUsd?: number;\n}\n\nconst DIMENSION_PREFIX: Record<\"user\" | \"team\" | \"apiKey\" | \"global\", string> = {\n user: \"u\",\n team: \"t\",\n apiKey: \"k\",\n global: \"g\",\n};\n\nexport class CostLimiter extends EventEmitter {\n readonly storage: StorageAdapter;\n readonly budgets: BudgetConfig;\n readonly pricing: Record<string, ModelPrice>;\n readonly warnThreshold: number;\n\n constructor(opts: CostLimiterOptions = {}) {\n super();\n this.storage = opts.storage ?? new MemoryCostStorage();\n this.budgets = opts.budgets ?? {};\n this.pricing = opts.pricing === \"auto\" || !opts.pricing ? DEFAULT_PRICING : opts.pricing;\n this.warnThreshold = opts.warnThreshold ?? 0.8;\n }\n\n private dimensionKey(dim: keyof typeof DIMENSION_PREFIX, key: string): string {\n return `${DIMENSION_PREFIX[dim]}:${key}`;\n }\n\n private checks(input: ChargeInput): { dim: keyof typeof DIMENSION_PREFIX; key: string; budget: WindowBudget }[] {\n const out: { dim: keyof typeof DIMENSION_PREFIX; key: string; budget: WindowBudget }[] = [];\n if (input.userId && this.budgets.perUser) out.push({ dim: \"user\", key: input.userId, budget: this.budgets.perUser });\n if (input.teamId && this.budgets.perTeam) out.push({ dim: \"team\", key: input.teamId, budget: this.budgets.perTeam });\n if (input.apiKeyId && this.budgets.perApiKey) out.push({ dim: \"apiKey\", key: input.apiKeyId, budget: this.budgets.perApiKey });\n if (this.budgets.global) out.push({ dim: \"global\", key: \"global\", budget: this.budgets.global });\n return out;\n }\n\n /** Throws CostLimitError when any *remaining* budget can not absorb `costUsd`. */\n async check(input: Omit<ChargeInput, \"outputTokens\"> & { estimatedCostUsd?: number }): Promise<void> {\n const cost = input.estimatedCostUsd ?? 0;\n const now = new Date();\n for (const c of this.checks({ ...input, outputTokens: 0 })) {\n for (const w of WINDOWS) {\n const limit = c.budget[w];\n if (limit === undefined) continue;\n const fullKey = `${this.dimensionKey(c.dim, c.key)}:${windowBucket(now, w)}`;\n const used = await this.storage.get(fullKey, w);\n if (used + cost > limit) {\n throw new CostLimitError({\n limit,\n used,\n resetAt: windowResetAt(now, w),\n window: w,\n dimension: c.dim,\n dimensionKey: c.key,\n });\n }\n }\n }\n }\n\n async charge(input: ChargeInput): Promise<{ costUsd: number }> {\n const costUsd = input.customCostUsd ?? priceCall(input.model, input.inputTokens, input.outputTokens, this.pricing);\n const now = new Date();\n for (const c of this.checks(input)) {\n for (const w of WINDOWS) {\n const limit = c.budget[w];\n if (limit === undefined) continue;\n const bucket = windowBucket(now, w);\n const fullKey = `${this.dimensionKey(c.dim, c.key)}:${bucket}`;\n const newTotal = await this.storage.increment(fullKey, costUsd, w, windowResetAt(now, w));\n if (newTotal > limit) {\n throw new CostLimitError({\n limit,\n used: newTotal,\n resetAt: windowResetAt(now, w),\n window: w,\n dimension: c.dim,\n dimensionKey: c.key,\n });\n }\n if (newTotal / limit >= this.warnThreshold) {\n const ev: BudgetWarningEvent = {\n dimension: c.dim,\n key: c.key,\n window: w,\n used: newTotal,\n limit,\n percent: newTotal / limit,\n };\n this.emit(\"BudgetWarning\", ev);\n }\n }\n }\n return { costUsd };\n }\n\n async getUsage(userId: string): Promise<UsageReport> {\n const now = new Date();\n const spend: Record<Window, number> = { minute: 0, hour: 0, day: 0, month: 0 };\n const limit: Record<Window, number | undefined> = { minute: undefined, hour: undefined, day: undefined, month: undefined };\n for (const w of WINDOWS) {\n const key = `${this.dimensionKey(\"user\", userId)}:${windowBucket(now, w)}`;\n spend[w] = await this.storage.get(key, w);\n limit[w] = this.budgets.perUser?.[w];\n }\n return { dimension: \"user\", key: userId, spend, limit };\n }\n\n async resetUsage(userId: string): Promise<void> {\n const now = new Date();\n for (const w of WINDOWS) {\n const key = `${this.dimensionKey(\"user\", userId)}:${windowBucket(now, w)}`;\n await this.storage.reset(key);\n }\n }\n\n /** Wrap an OpenAI v4 client so usage is charged automatically. */\n wrap<T extends OpenAILike>(client: T, ctx?: { provider?: \"openai\" }): T {\n return new Proxy(client, this.makeHandler(\"openai\")) as T;\n }\n\n /** Wrap an Anthropic SDK client. */\n wrapAnthropic<T extends AnthropicLike>(client: T): T {\n return new Proxy(client, this.makeHandler(\"anthropic\")) as T;\n }\n\n /** Express/Hono/Fastify-style middleware: parses `userId`/`teamId` off `req` before allowing the request. */\n middleware(getDims: (req: unknown) => { userId?: string; teamId?: string; apiKeyId?: string; estimatedCostUsd?: number }) {\n return async (req: unknown, _res: unknown, next: (err?: unknown) => void) => {\n try {\n const { estimatedCostUsd, ...dims } = getDims(req);\n await this.check({\n ...dims,\n provider: \"openai\",\n model: \"gpt-4o-mini\",\n inputTokens: 0,\n ...(estimatedCostUsd !== undefined ? { estimatedCostUsd } : {}),\n });\n next();\n } catch (err) {\n next(err);\n }\n };\n }\n\n private makeHandler(provider: \"openai\" | \"anthropic\"): ProxyHandler<object> {\n const limiter = this;\n const handler: ProxyHandler<object> = {\n get(target, prop, receiver) {\n const value = Reflect.get(target, prop, receiver);\n if (typeof value === \"object\" && value !== null) {\n return new Proxy(value, handler);\n }\n if (typeof value === \"function\") {\n return async (req: Record<string, unknown> & { userId?: string; teamId?: string; apiKeyId?: string; model?: string }) => {\n const result = await (value as Function).call(target, stripDims(req));\n try {\n const usage = extractUsage(result, provider);\n if (usage) {\n await limiter.charge({\n ...(req.userId !== undefined ? { userId: req.userId } : {}),\n ...(req.teamId !== undefined ? { teamId: req.teamId } : {}),\n ...(req.apiKeyId !== undefined ? { apiKeyId: req.apiKeyId } : {}),\n provider,\n model: String(req.model ?? usage.model ?? \"unknown\"),\n inputTokens: usage.input,\n outputTokens: usage.output,\n });\n }\n } catch (err) {\n if (err instanceof CostLimitError) throw err;\n }\n return result;\n };\n }\n return value;\n },\n };\n return handler;\n }\n}\n\ninterface OpenAILike { chat?: unknown }\ninterface AnthropicLike { messages?: unknown }\n\nfunction stripDims<T extends Record<string, unknown>>(req: T): Omit<T, \"userId\" | \"teamId\" | \"apiKeyId\"> {\n const { userId: _u, teamId: _t, apiKeyId: _k, ...rest } = req;\n return rest as Omit<T, \"userId\" | \"teamId\" | \"apiKeyId\">;\n}\n\nfunction extractUsage(result: unknown, provider: \"openai\" | \"anthropic\"): { input: number; output: number; model?: string } | undefined {\n const r = result as { usage?: { prompt_tokens?: number; completion_tokens?: number; input_tokens?: number; output_tokens?: number }; model?: string } | null;\n if (!r?.usage) return undefined;\n if (provider === \"openai\") {\n return {\n input: r.usage.prompt_tokens ?? 0,\n output: r.usage.completion_tokens ?? 0,\n ...(r.model ? { model: r.model } : {}),\n };\n }\n return {\n input: r.usage.input_tokens ?? 0,\n output: r.usage.output_tokens ?? 0,\n ...(r.model ? { model: r.model } : {}),\n };\n}\n","import type { Window, Dimension } from \"./types.js\";\n\nexport class CostLimitError extends Error {\n readonly limit: number;\n readonly used: number;\n readonly remaining: number;\n readonly resetAt: Date;\n readonly window: Window;\n readonly dimension: Dimension;\n readonly dimensionKey: string;\n\n constructor(input: {\n limit: number;\n used: number;\n resetAt: Date;\n window: Window;\n dimension: Dimension;\n dimensionKey: string;\n }) {\n super(\n `cost-limiter: budget exhausted for ${input.dimension}=${input.dimensionKey} ` +\n `(${input.window}: $${input.used.toFixed(4)} / $${input.limit.toFixed(4)})`,\n );\n this.name = \"CostLimitError\";\n this.limit = input.limit;\n this.used = input.used;\n this.remaining = Math.max(0, input.limit - input.used);\n this.resetAt = input.resetAt;\n this.window = input.window;\n this.dimension = input.dimension;\n this.dimensionKey = input.dimensionKey;\n Object.setPrototypeOf(this, CostLimitError.prototype);\n }\n}\n","export interface ModelPrice {\n /** Dollar cost per 1,000,000 input tokens. */\n inputPerMTokens: number;\n /** Dollar cost per 1,000,000 output tokens. */\n outputPerMTokens: number;\n}\n\nexport const DEFAULT_PRICING: Record<string, ModelPrice> = {\n // OpenAI\n \"gpt-4o\": { inputPerMTokens: 2.50, outputPerMTokens: 10.00 },\n \"gpt-4o-mini\": { inputPerMTokens: 0.15, outputPerMTokens: 0.60 },\n \"gpt-4-turbo\": { inputPerMTokens: 10.00, outputPerMTokens: 30.00 },\n \"gpt-3.5-turbo\": { inputPerMTokens: 0.50, outputPerMTokens: 1.50 },\n \"o1\": { inputPerMTokens: 15.00, outputPerMTokens: 60.00 },\n \"o1-mini\": { inputPerMTokens: 3.00, outputPerMTokens: 12.00 },\n \"o3-mini\": { inputPerMTokens: 1.10, outputPerMTokens: 4.40 },\n // Anthropic\n \"claude-opus-4\": { inputPerMTokens: 15.00, outputPerMTokens: 75.00 },\n \"claude-sonnet-4\": { inputPerMTokens: 3.00, outputPerMTokens: 15.00 },\n \"claude-haiku-4\": { inputPerMTokens: 1.00, outputPerMTokens: 5.00 },\n // Gemini\n \"gemini-2.0-flash\": { inputPerMTokens: 0.10, outputPerMTokens: 0.40 },\n \"gemini-1.5-pro\": { inputPerMTokens: 1.25, outputPerMTokens: 5.00 },\n \"gemini-1.5-flash\": { inputPerMTokens: 0.075, outputPerMTokens: 0.30 },\n // Groq\n \"llama-3.3-70b\": { inputPerMTokens: 0.59, outputPerMTokens: 0.79 },\n \"mixtral-8x7b\": { inputPerMTokens: 0.24, outputPerMTokens: 0.24 },\n};\n\nexport interface PricingConfig {\n pricing?: Record<string, ModelPrice>;\n}\n\nexport function priceCall(model: string, inputTokens: number, outputTokens: number, custom: Record<string, ModelPrice> = {}): number {\n const p = custom[model] ?? DEFAULT_PRICING[model];\n if (!p) return 0;\n return (inputTokens * p.inputPerMTokens + outputTokens * p.outputPerMTokens) / 1_000_000;\n}\n","import type { Window } from \"./types.js\";\n\nexport interface StorageAdapter {\n /** Increment the spend counter for the given key/window and return the new total. */\n increment(key: string, amount: number, window: Window, resetAt: Date): Promise<number>;\n /** Read the current spend for the given key/window. */\n get(key: string, window: Window): Promise<number>;\n /** Reset (clear) all windows for the given key. */\n reset(key: string): Promise<void>;\n}\n\nexport const WINDOWS: Window[] = [\"minute\", \"hour\", \"day\", \"month\"];\n\nexport function windowResetAt(now: Date, window: Window): Date {\n const d = new Date(now);\n switch (window) {\n case \"minute\":\n d.setUTCSeconds(0, 0);\n d.setUTCMinutes(d.getUTCMinutes() + 1);\n return d;\n case \"hour\":\n d.setUTCMinutes(0, 0, 0);\n d.setUTCHours(d.getUTCHours() + 1);\n return d;\n case \"day\":\n d.setUTCHours(0, 0, 0, 0);\n d.setUTCDate(d.getUTCDate() + 1);\n return d;\n case \"month\":\n d.setUTCHours(0, 0, 0, 0);\n d.setUTCDate(1);\n d.setUTCMonth(d.getUTCMonth() + 1);\n return d;\n }\n}\n\nexport function windowBucket(now: Date, window: Window): string {\n const y = now.getUTCFullYear();\n const m = String(now.getUTCMonth() + 1).padStart(2, \"0\");\n const d = String(now.getUTCDate()).padStart(2, \"0\");\n const h = String(now.getUTCHours()).padStart(2, \"0\");\n const min = String(now.getUTCMinutes()).padStart(2, \"0\");\n switch (window) {\n case \"minute\": return `${y}${m}${d}${h}${min}`;\n case \"hour\": return `${y}${m}${d}${h}`;\n case \"day\": return `${y}${m}${d}`;\n case \"month\": return `${y}${m}`;\n }\n}\n\nexport class MemoryCostStorage implements StorageAdapter {\n private store = new Map<string, { value: number; resetAt: number }>();\n\n async increment(key: string, amount: number, window: Window, resetAt: Date) {\n const fullKey = `${key}:${window}`;\n const entry = this.store.get(fullKey);\n if (entry && entry.resetAt <= Date.now()) {\n this.store.delete(fullKey);\n }\n const cur = this.store.get(fullKey) ?? { value: 0, resetAt: resetAt.getTime() };\n cur.value = +(cur.value + amount).toFixed(6);\n this.store.set(fullKey, cur);\n return cur.value;\n }\n\n async get(key: string, window: Window): Promise<number> {\n const entry = this.store.get(`${key}:${window}`);\n if (!entry) return 0;\n if (entry.resetAt <= Date.now()) {\n this.store.delete(`${key}:${window}`);\n return 0;\n }\n return entry.value;\n }\n\n async reset(key: string): Promise<void> {\n for (const w of WINDOWS) this.store.delete(`${key}:${w}`);\n }\n}\n\ninterface RedisLike {\n incrbyfloat(key: string, amount: number): Promise<string>;\n pexpire(key: string, ms: number): Promise<number>;\n get(key: string): Promise<string | null>;\n del(...keys: string[]): Promise<number>;\n}\n\nexport class RedisCostStorage implements StorageAdapter {\n constructor(private redis: RedisLike, private prefix = \"cost-limiter\") {}\n\n private key(k: string, w: Window) { return `${this.prefix}:${k}:${w}`; }\n\n async increment(key: string, amount: number, window: Window, resetAt: Date) {\n const k = this.key(key, window);\n const newVal = await this.redis.incrbyfloat(k, amount);\n const ttl = Math.max(1, resetAt.getTime() - Date.now());\n await this.redis.pexpire(k, ttl);\n return Number(newVal);\n }\n\n async get(key: string, window: Window) {\n const v = await this.redis.get(this.key(key, window));\n return v ? Number(v) : 0;\n }\n\n async reset(key: string) {\n await this.redis.del(...WINDOWS.map((w) => this.key(key, w)));\n }\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,151 @@
1
+ import { EventEmitter } from 'node:events';
2
+
3
+ interface ModelPrice {
4
+ /** Dollar cost per 1,000,000 input tokens. */
5
+ inputPerMTokens: number;
6
+ /** Dollar cost per 1,000,000 output tokens. */
7
+ outputPerMTokens: number;
8
+ }
9
+ declare const DEFAULT_PRICING: Record<string, ModelPrice>;
10
+ interface PricingConfig {
11
+ pricing?: Record<string, ModelPrice>;
12
+ }
13
+ declare function priceCall(model: string, inputTokens: number, outputTokens: number, custom?: Record<string, ModelPrice>): number;
14
+
15
+ type Dimension = "user" | "team" | "apiKey" | "global" | string;
16
+ type Window = "minute" | "hour" | "day" | "month";
17
+ interface WindowBudget {
18
+ minute?: number;
19
+ hour?: number;
20
+ day?: number;
21
+ month?: number;
22
+ }
23
+ interface BudgetConfig {
24
+ perUser?: WindowBudget;
25
+ perTeam?: WindowBudget;
26
+ perApiKey?: WindowBudget;
27
+ global?: WindowBudget;
28
+ }
29
+ interface UsageReport {
30
+ dimension: Dimension;
31
+ key: string;
32
+ spend: Record<Window, number>;
33
+ limit: Record<Window, number | undefined>;
34
+ }
35
+ interface BudgetWarningEvent {
36
+ dimension: Dimension;
37
+ key: string;
38
+ window: Window;
39
+ used: number;
40
+ limit: number;
41
+ percent: number;
42
+ }
43
+
44
+ interface StorageAdapter {
45
+ /** Increment the spend counter for the given key/window and return the new total. */
46
+ increment(key: string, amount: number, window: Window, resetAt: Date): Promise<number>;
47
+ /** Read the current spend for the given key/window. */
48
+ get(key: string, window: Window): Promise<number>;
49
+ /** Reset (clear) all windows for the given key. */
50
+ reset(key: string): Promise<void>;
51
+ }
52
+ declare const WINDOWS: Window[];
53
+ declare function windowResetAt(now: Date, window: Window): Date;
54
+ declare function windowBucket(now: Date, window: Window): string;
55
+ declare class MemoryCostStorage implements StorageAdapter {
56
+ private store;
57
+ increment(key: string, amount: number, window: Window, resetAt: Date): Promise<number>;
58
+ get(key: string, window: Window): Promise<number>;
59
+ reset(key: string): Promise<void>;
60
+ }
61
+ interface RedisLike {
62
+ incrbyfloat(key: string, amount: number): Promise<string>;
63
+ pexpire(key: string, ms: number): Promise<number>;
64
+ get(key: string): Promise<string | null>;
65
+ del(...keys: string[]): Promise<number>;
66
+ }
67
+ declare class RedisCostStorage implements StorageAdapter {
68
+ private redis;
69
+ private prefix;
70
+ constructor(redis: RedisLike, prefix?: string);
71
+ private key;
72
+ increment(key: string, amount: number, window: Window, resetAt: Date): Promise<number>;
73
+ get(key: string, window: Window): Promise<number>;
74
+ reset(key: string): Promise<void>;
75
+ }
76
+
77
+ interface CostLimiterOptions {
78
+ budgets?: BudgetConfig;
79
+ storage?: StorageAdapter;
80
+ pricing?: Record<string, ModelPrice> | "auto";
81
+ /** Soft-warn threshold (0-1). Defaults to 0.8 (80%). */
82
+ warnThreshold?: number;
83
+ }
84
+ interface ChargeInput {
85
+ userId?: string;
86
+ teamId?: string;
87
+ apiKeyId?: string;
88
+ provider: "openai" | "anthropic" | string;
89
+ model: string;
90
+ inputTokens: number;
91
+ outputTokens: number;
92
+ customCostUsd?: number;
93
+ }
94
+ declare class CostLimiter extends EventEmitter {
95
+ readonly storage: StorageAdapter;
96
+ readonly budgets: BudgetConfig;
97
+ readonly pricing: Record<string, ModelPrice>;
98
+ readonly warnThreshold: number;
99
+ constructor(opts?: CostLimiterOptions);
100
+ private dimensionKey;
101
+ private checks;
102
+ /** Throws CostLimitError when any *remaining* budget can not absorb `costUsd`. */
103
+ check(input: Omit<ChargeInput, "outputTokens"> & {
104
+ estimatedCostUsd?: number;
105
+ }): Promise<void>;
106
+ charge(input: ChargeInput): Promise<{
107
+ costUsd: number;
108
+ }>;
109
+ getUsage(userId: string): Promise<UsageReport>;
110
+ resetUsage(userId: string): Promise<void>;
111
+ /** Wrap an OpenAI v4 client so usage is charged automatically. */
112
+ wrap<T extends OpenAILike>(client: T, ctx?: {
113
+ provider?: "openai";
114
+ }): T;
115
+ /** Wrap an Anthropic SDK client. */
116
+ wrapAnthropic<T extends AnthropicLike>(client: T): T;
117
+ /** Express/Hono/Fastify-style middleware: parses `userId`/`teamId` off `req` before allowing the request. */
118
+ middleware(getDims: (req: unknown) => {
119
+ userId?: string;
120
+ teamId?: string;
121
+ apiKeyId?: string;
122
+ estimatedCostUsd?: number;
123
+ }): (req: unknown, _res: unknown, next: (err?: unknown) => void) => Promise<void>;
124
+ private makeHandler;
125
+ }
126
+ interface OpenAILike {
127
+ chat?: unknown;
128
+ }
129
+ interface AnthropicLike {
130
+ messages?: unknown;
131
+ }
132
+
133
+ declare class CostLimitError extends Error {
134
+ readonly limit: number;
135
+ readonly used: number;
136
+ readonly remaining: number;
137
+ readonly resetAt: Date;
138
+ readonly window: Window;
139
+ readonly dimension: Dimension;
140
+ readonly dimensionKey: string;
141
+ constructor(input: {
142
+ limit: number;
143
+ used: number;
144
+ resetAt: Date;
145
+ window: Window;
146
+ dimension: Dimension;
147
+ dimensionKey: string;
148
+ });
149
+ }
150
+
151
+ export { type BudgetConfig, type BudgetWarningEvent, type ChargeInput, CostLimitError, CostLimiter, type CostLimiterOptions, DEFAULT_PRICING, type Dimension, MemoryCostStorage, type ModelPrice, type PricingConfig, RedisCostStorage, type StorageAdapter, type UsageReport, WINDOWS, type Window, type WindowBudget, priceCall, windowBucket, windowResetAt };