@lov3kaizen/agentsea-costs 0.5.1

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.js ADDED
@@ -0,0 +1,2867 @@
1
+ import { EventEmitter } from 'eventemitter3';
2
+ import { nanoid } from 'nanoid';
3
+ import { Cron } from 'croner';
4
+
5
+ // src/core/CostManager.ts
6
+
7
+ // src/pricing/ModelPricingRegistry.ts
8
+ var DEFAULT_PRICING = [
9
+ // Anthropic Models
10
+ {
11
+ model: "claude-3-5-sonnet-20241022",
12
+ provider: "anthropic",
13
+ displayName: "Claude 3.5 Sonnet",
14
+ inputPricePerMillion: 3,
15
+ outputPricePerMillion: 15,
16
+ cacheReadPricePerMillion: 0.3,
17
+ cacheWritePricePerMillion: 3.75,
18
+ contextWindow: 2e5,
19
+ maxOutputTokens: 8192,
20
+ currency: "USD",
21
+ capabilities: {
22
+ vision: true,
23
+ functionCalling: true,
24
+ streaming: true,
25
+ jsonMode: true,
26
+ systemMessage: true,
27
+ computerUse: true
28
+ }
29
+ },
30
+ {
31
+ model: "claude-3-5-haiku-20241022",
32
+ provider: "anthropic",
33
+ displayName: "Claude 3.5 Haiku",
34
+ inputPricePerMillion: 0.8,
35
+ outputPricePerMillion: 4,
36
+ cacheReadPricePerMillion: 0.08,
37
+ cacheWritePricePerMillion: 1,
38
+ contextWindow: 2e5,
39
+ maxOutputTokens: 8192,
40
+ currency: "USD",
41
+ capabilities: {
42
+ vision: true,
43
+ functionCalling: true,
44
+ streaming: true,
45
+ jsonMode: true,
46
+ systemMessage: true
47
+ }
48
+ },
49
+ {
50
+ model: "claude-3-opus-20240229",
51
+ provider: "anthropic",
52
+ displayName: "Claude 3 Opus",
53
+ inputPricePerMillion: 15,
54
+ outputPricePerMillion: 75,
55
+ cacheReadPricePerMillion: 1.5,
56
+ cacheWritePricePerMillion: 18.75,
57
+ contextWindow: 2e5,
58
+ maxOutputTokens: 4096,
59
+ currency: "USD",
60
+ capabilities: {
61
+ vision: true,
62
+ functionCalling: true,
63
+ streaming: true,
64
+ jsonMode: true,
65
+ systemMessage: true
66
+ }
67
+ },
68
+ {
69
+ model: "claude-3-sonnet-20240229",
70
+ provider: "anthropic",
71
+ displayName: "Claude 3 Sonnet",
72
+ inputPricePerMillion: 3,
73
+ outputPricePerMillion: 15,
74
+ contextWindow: 2e5,
75
+ maxOutputTokens: 4096,
76
+ currency: "USD",
77
+ capabilities: {
78
+ vision: true,
79
+ functionCalling: true,
80
+ streaming: true,
81
+ jsonMode: true,
82
+ systemMessage: true
83
+ }
84
+ },
85
+ {
86
+ model: "claude-3-haiku-20240307",
87
+ provider: "anthropic",
88
+ displayName: "Claude 3 Haiku",
89
+ inputPricePerMillion: 0.25,
90
+ outputPricePerMillion: 1.25,
91
+ cacheReadPricePerMillion: 0.03,
92
+ cacheWritePricePerMillion: 0.3,
93
+ contextWindow: 2e5,
94
+ maxOutputTokens: 4096,
95
+ currency: "USD",
96
+ capabilities: {
97
+ vision: true,
98
+ functionCalling: true,
99
+ streaming: true,
100
+ jsonMode: true,
101
+ systemMessage: true
102
+ }
103
+ },
104
+ // OpenAI Models
105
+ {
106
+ model: "gpt-4o",
107
+ provider: "openai",
108
+ displayName: "GPT-4o",
109
+ inputPricePerMillion: 2.5,
110
+ outputPricePerMillion: 10,
111
+ contextWindow: 128e3,
112
+ maxOutputTokens: 16384,
113
+ currency: "USD",
114
+ capabilities: {
115
+ vision: true,
116
+ functionCalling: true,
117
+ streaming: true,
118
+ jsonMode: true,
119
+ systemMessage: true
120
+ }
121
+ },
122
+ {
123
+ model: "gpt-4o-mini",
124
+ provider: "openai",
125
+ displayName: "GPT-4o Mini",
126
+ inputPricePerMillion: 0.15,
127
+ outputPricePerMillion: 0.6,
128
+ contextWindow: 128e3,
129
+ maxOutputTokens: 16384,
130
+ currency: "USD",
131
+ capabilities: {
132
+ vision: true,
133
+ functionCalling: true,
134
+ streaming: true,
135
+ jsonMode: true,
136
+ systemMessage: true
137
+ }
138
+ },
139
+ {
140
+ model: "gpt-4-turbo",
141
+ provider: "openai",
142
+ displayName: "GPT-4 Turbo",
143
+ inputPricePerMillion: 10,
144
+ outputPricePerMillion: 30,
145
+ contextWindow: 128e3,
146
+ maxOutputTokens: 4096,
147
+ currency: "USD",
148
+ capabilities: {
149
+ vision: true,
150
+ functionCalling: true,
151
+ streaming: true,
152
+ jsonMode: true,
153
+ systemMessage: true
154
+ }
155
+ },
156
+ {
157
+ model: "gpt-3.5-turbo",
158
+ provider: "openai",
159
+ displayName: "GPT-3.5 Turbo",
160
+ inputPricePerMillion: 0.5,
161
+ outputPricePerMillion: 1.5,
162
+ contextWindow: 16385,
163
+ maxOutputTokens: 4096,
164
+ currency: "USD",
165
+ capabilities: {
166
+ functionCalling: true,
167
+ streaming: true,
168
+ jsonMode: true,
169
+ systemMessage: true
170
+ }
171
+ },
172
+ {
173
+ model: "o1-preview",
174
+ provider: "openai",
175
+ displayName: "o1 Preview",
176
+ inputPricePerMillion: 15,
177
+ outputPricePerMillion: 60,
178
+ contextWindow: 128e3,
179
+ maxOutputTokens: 32768,
180
+ currency: "USD",
181
+ capabilities: {
182
+ streaming: true,
183
+ extendedThinking: true
184
+ }
185
+ },
186
+ {
187
+ model: "o1-mini",
188
+ provider: "openai",
189
+ displayName: "o1 Mini",
190
+ inputPricePerMillion: 3,
191
+ outputPricePerMillion: 12,
192
+ contextWindow: 128e3,
193
+ maxOutputTokens: 65536,
194
+ currency: "USD",
195
+ capabilities: {
196
+ streaming: true,
197
+ extendedThinking: true
198
+ }
199
+ },
200
+ // Google Models
201
+ {
202
+ model: "gemini-1.5-pro",
203
+ provider: "google",
204
+ displayName: "Gemini 1.5 Pro",
205
+ inputPricePerMillion: 1.25,
206
+ outputPricePerMillion: 5,
207
+ contextWindow: 2e6,
208
+ maxOutputTokens: 8192,
209
+ currency: "USD",
210
+ capabilities: {
211
+ vision: true,
212
+ functionCalling: true,
213
+ streaming: true,
214
+ jsonMode: true,
215
+ systemMessage: true
216
+ }
217
+ },
218
+ {
219
+ model: "gemini-1.5-flash",
220
+ provider: "google",
221
+ displayName: "Gemini 1.5 Flash",
222
+ inputPricePerMillion: 0.075,
223
+ outputPricePerMillion: 0.3,
224
+ contextWindow: 1e6,
225
+ maxOutputTokens: 8192,
226
+ currency: "USD",
227
+ capabilities: {
228
+ vision: true,
229
+ functionCalling: true,
230
+ streaming: true,
231
+ jsonMode: true,
232
+ systemMessage: true
233
+ }
234
+ },
235
+ {
236
+ model: "gemini-2.0-flash-exp",
237
+ provider: "google",
238
+ displayName: "Gemini 2.0 Flash",
239
+ inputPricePerMillion: 0,
240
+ outputPricePerMillion: 0,
241
+ contextWindow: 1e6,
242
+ maxOutputTokens: 8192,
243
+ currency: "USD",
244
+ capabilities: {
245
+ vision: true,
246
+ functionCalling: true,
247
+ streaming: true,
248
+ jsonMode: true,
249
+ systemMessage: true
250
+ }
251
+ },
252
+ // Mistral Models
253
+ {
254
+ model: "mistral-large-latest",
255
+ provider: "mistral",
256
+ displayName: "Mistral Large",
257
+ inputPricePerMillion: 2,
258
+ outputPricePerMillion: 6,
259
+ contextWindow: 128e3,
260
+ maxOutputTokens: 4096,
261
+ currency: "USD",
262
+ capabilities: {
263
+ functionCalling: true,
264
+ streaming: true,
265
+ jsonMode: true,
266
+ systemMessage: true
267
+ }
268
+ },
269
+ {
270
+ model: "mistral-small-latest",
271
+ provider: "mistral",
272
+ displayName: "Mistral Small",
273
+ inputPricePerMillion: 0.2,
274
+ outputPricePerMillion: 0.6,
275
+ contextWindow: 32e3,
276
+ maxOutputTokens: 4096,
277
+ currency: "USD",
278
+ capabilities: {
279
+ functionCalling: true,
280
+ streaming: true,
281
+ jsonMode: true,
282
+ systemMessage: true
283
+ }
284
+ },
285
+ {
286
+ model: "codestral-latest",
287
+ provider: "mistral",
288
+ displayName: "Codestral",
289
+ inputPricePerMillion: 0.2,
290
+ outputPricePerMillion: 0.6,
291
+ contextWindow: 32e3,
292
+ maxOutputTokens: 4096,
293
+ currency: "USD",
294
+ capabilities: {
295
+ streaming: true,
296
+ systemMessage: true
297
+ }
298
+ },
299
+ // Cohere Models
300
+ {
301
+ model: "command-r-plus",
302
+ provider: "cohere",
303
+ displayName: "Command R+",
304
+ inputPricePerMillion: 2.5,
305
+ outputPricePerMillion: 10,
306
+ contextWindow: 128e3,
307
+ maxOutputTokens: 4096,
308
+ currency: "USD",
309
+ capabilities: {
310
+ functionCalling: true,
311
+ streaming: true,
312
+ systemMessage: true
313
+ }
314
+ },
315
+ {
316
+ model: "command-r",
317
+ provider: "cohere",
318
+ displayName: "Command R",
319
+ inputPricePerMillion: 0.15,
320
+ outputPricePerMillion: 0.6,
321
+ contextWindow: 128e3,
322
+ maxOutputTokens: 4096,
323
+ currency: "USD",
324
+ capabilities: {
325
+ functionCalling: true,
326
+ streaming: true,
327
+ systemMessage: true
328
+ }
329
+ }
330
+ ];
331
+ var ModelPricingRegistry = class {
332
+ pricing = /* @__PURE__ */ new Map();
333
+ config;
334
+ updateTimer;
335
+ constructor(config = {}) {
336
+ this.config = {
337
+ autoUpdate: config.autoUpdate ?? false,
338
+ updateInterval: config.updateInterval ?? 24 * 60 * 60 * 1e3,
339
+ // 24 hours
340
+ defaultCurrency: config.defaultCurrency ?? "USD",
341
+ ...config
342
+ };
343
+ this.loadDefaultPricing();
344
+ if (config.customPricing) {
345
+ for (const pricing of config.customPricing) {
346
+ this.registerModel(pricing);
347
+ }
348
+ }
349
+ if (this.config.autoUpdate && this.config.remotePricingUrl) {
350
+ this.startAutoUpdate();
351
+ }
352
+ }
353
+ /**
354
+ * Load default pricing data
355
+ */
356
+ loadDefaultPricing() {
357
+ for (const pricing of DEFAULT_PRICING) {
358
+ const key = this.getKey(pricing.provider, pricing.model);
359
+ this.pricing.set(key, pricing);
360
+ }
361
+ }
362
+ /**
363
+ * Generate key for pricing lookup
364
+ */
365
+ getKey(provider, model) {
366
+ return `${provider}:${model}`;
367
+ }
368
+ /**
369
+ * Register or update model pricing
370
+ */
371
+ registerModel(pricing) {
372
+ const key = this.getKey(pricing.provider, pricing.model);
373
+ this.pricing.set(key, {
374
+ ...pricing,
375
+ effectiveDate: pricing.effectiveDate ?? /* @__PURE__ */ new Date()
376
+ });
377
+ }
378
+ /**
379
+ * Get pricing for a model
380
+ */
381
+ getPricing(provider, model) {
382
+ const key = this.getKey(provider, model);
383
+ return this.pricing.get(key) ?? null;
384
+ }
385
+ /**
386
+ * Get pricing by model name (auto-detect provider)
387
+ */
388
+ getPricingByModel(model) {
389
+ for (const pricing of this.pricing.values()) {
390
+ if (pricing.model === model) {
391
+ return pricing;
392
+ }
393
+ }
394
+ for (const pricing of this.pricing.values()) {
395
+ if (pricing.model.includes(model) || model.includes(pricing.model)) {
396
+ return pricing;
397
+ }
398
+ }
399
+ return null;
400
+ }
401
+ /**
402
+ * Calculate cost for token usage
403
+ */
404
+ calculateCost(provider, model, inputTokens, outputTokens, options) {
405
+ const pricing = this.getPricing(provider, model);
406
+ if (!pricing) {
407
+ throw new Error(`No pricing found for ${provider}:${model}`);
408
+ }
409
+ const inputCost = inputTokens / 1e6 * pricing.inputPricePerMillion;
410
+ const outputCost = outputTokens / 1e6 * pricing.outputPricePerMillion;
411
+ const cacheReadCost = options?.cacheReadTokens ? options.cacheReadTokens / 1e6 * (pricing.cacheReadPricePerMillion ?? 0) : 0;
412
+ const cacheCost = options?.cacheWriteTokens ? options.cacheWriteTokens / 1e6 * (pricing.cacheWritePricePerMillion ?? 0) : 0;
413
+ return {
414
+ inputCost,
415
+ outputCost,
416
+ cacheReadCost,
417
+ cacheCost,
418
+ totalCost: inputCost + outputCost + cacheReadCost + cacheCost,
419
+ currency: pricing.currency
420
+ };
421
+ }
422
+ /**
423
+ * List all models for a provider
424
+ */
425
+ listModels(provider) {
426
+ const models = [];
427
+ for (const pricing of this.pricing.values()) {
428
+ if (!provider || pricing.provider === provider) {
429
+ models.push(pricing);
430
+ }
431
+ }
432
+ return models.sort((a, b) => a.model.localeCompare(b.model));
433
+ }
434
+ /**
435
+ * List all providers
436
+ */
437
+ listProviders() {
438
+ const providers = /* @__PURE__ */ new Set();
439
+ for (const pricing of this.pricing.values()) {
440
+ providers.add(pricing.provider);
441
+ }
442
+ return Array.from(providers).sort();
443
+ }
444
+ /**
445
+ * Get provider pricing summary
446
+ */
447
+ getProviderSummary(provider) {
448
+ const models = this.listModels(provider);
449
+ if (models.length === 0) {
450
+ return null;
451
+ }
452
+ const inputPrices = models.map((m) => m.inputPricePerMillion);
453
+ const outputPrices = models.map((m) => m.outputPricePerMillion);
454
+ return {
455
+ provider,
456
+ modelCount: models.length,
457
+ minInputPrice: Math.min(...inputPrices),
458
+ maxInputPrice: Math.max(...inputPrices),
459
+ minOutputPrice: Math.min(...outputPrices),
460
+ maxOutputPrice: Math.max(...outputPrices),
461
+ models: models.map((m) => m.model)
462
+ };
463
+ }
464
+ /**
465
+ * Compare pricing between two models
466
+ */
467
+ comparePricing(modelA, modelB, sampleTokens) {
468
+ const pricingA = this.getPricingByModel(modelA);
469
+ const pricingB = this.getPricingByModel(modelB);
470
+ if (!pricingA || !pricingB) {
471
+ return null;
472
+ }
473
+ const inputDiff = pricingA.inputPricePerMillion - pricingB.inputPricePerMillion;
474
+ const outputDiff = pricingA.outputPricePerMillion - pricingB.outputPricePerMillion;
475
+ const avgPriceA = (pricingA.inputPricePerMillion + pricingA.outputPricePerMillion) / 2;
476
+ const avgPriceB = (pricingB.inputPricePerMillion + pricingB.outputPricePerMillion) / 2;
477
+ const percentageDiff = (avgPriceA - avgPriceB) / avgPriceB * 100;
478
+ let estimatedSavings;
479
+ if (sampleTokens) {
480
+ const costA = sampleTokens.input / 1e6 * pricingA.inputPricePerMillion + sampleTokens.output / 1e6 * pricingA.outputPricePerMillion;
481
+ const costB = sampleTokens.input / 1e6 * pricingB.inputPricePerMillion + sampleTokens.output / 1e6 * pricingB.outputPricePerMillion;
482
+ estimatedSavings = Math.abs(costA - costB);
483
+ }
484
+ return {
485
+ modelA,
486
+ modelB,
487
+ inputPriceDiff: inputDiff,
488
+ outputPriceDiff: outputDiff,
489
+ percentageDiff,
490
+ cheaperModel: avgPriceA < avgPriceB ? modelA : modelB,
491
+ estimatedSavings
492
+ };
493
+ }
494
+ /**
495
+ * Find cheapest model with required capabilities
496
+ */
497
+ findCheapestModel(options) {
498
+ const weightInput = options?.weightInput ?? 0.5;
499
+ const weightOutput = options?.weightOutput ?? 0.5;
500
+ let cheapest = null;
501
+ let cheapestScore = Infinity;
502
+ for (const pricing of this.pricing.values()) {
503
+ if (options?.provider && pricing.provider !== options.provider) {
504
+ continue;
505
+ }
506
+ if (options?.minContextWindow && pricing.contextWindow && pricing.contextWindow < options.minContextWindow) {
507
+ continue;
508
+ }
509
+ if (options?.requireVision && !pricing.capabilities?.vision) {
510
+ continue;
511
+ }
512
+ if (options?.requireFunctionCalling && !pricing.capabilities?.functionCalling) {
513
+ continue;
514
+ }
515
+ if (pricing.deprecated) {
516
+ continue;
517
+ }
518
+ const score = pricing.inputPricePerMillion * weightInput + pricing.outputPricePerMillion * weightOutput;
519
+ if (score < cheapestScore) {
520
+ cheapestScore = score;
521
+ cheapest = pricing;
522
+ }
523
+ }
524
+ return cheapest;
525
+ }
526
+ /**
527
+ * Start auto-update timer
528
+ */
529
+ startAutoUpdate() {
530
+ if (this.updateTimer) {
531
+ return;
532
+ }
533
+ this.updateTimer = setInterval(() => {
534
+ void (async () => {
535
+ try {
536
+ await this.updateFromRemote();
537
+ } catch {
538
+ }
539
+ })();
540
+ }, this.config.updateInterval);
541
+ }
542
+ /**
543
+ * Stop auto-update timer
544
+ */
545
+ stopAutoUpdate() {
546
+ if (this.updateTimer) {
547
+ clearInterval(this.updateTimer);
548
+ this.updateTimer = void 0;
549
+ }
550
+ }
551
+ /**
552
+ * Update pricing from remote source
553
+ */
554
+ async updateFromRemote() {
555
+ if (!this.config.remotePricingUrl) {
556
+ throw new Error("No remote pricing URL configured");
557
+ }
558
+ const response = await fetch(this.config.remotePricingUrl);
559
+ if (!response.ok) {
560
+ throw new Error(`Failed to fetch pricing: ${response.statusText}`);
561
+ }
562
+ const data = await response.json();
563
+ for (const pricing of data) {
564
+ this.registerModel(pricing);
565
+ }
566
+ }
567
+ /**
568
+ * Export all pricing data
569
+ */
570
+ exportPricing() {
571
+ return Array.from(this.pricing.values());
572
+ }
573
+ /**
574
+ * Import pricing data
575
+ */
576
+ importPricing(data, replace = false) {
577
+ if (replace) {
578
+ this.pricing.clear();
579
+ }
580
+ for (const pricing of data) {
581
+ this.registerModel(pricing);
582
+ }
583
+ }
584
+ /**
585
+ * Clear all pricing data
586
+ */
587
+ clear() {
588
+ this.pricing.clear();
589
+ }
590
+ /**
591
+ * Reload default pricing
592
+ */
593
+ reset() {
594
+ this.clear();
595
+ this.loadDefaultPricing();
596
+ }
597
+ };
598
+
599
+ // src/pricing/TokenCounter.ts
600
+ var cl100kEncoder = null;
601
+ async function getEncoder() {
602
+ if (!cl100kEncoder) {
603
+ try {
604
+ const tiktoken = await import('tiktoken');
605
+ cl100kEncoder = tiktoken.get_encoding("cl100k_base");
606
+ } catch {
607
+ return null;
608
+ }
609
+ }
610
+ return cl100kEncoder;
611
+ }
612
+ var PROVIDER_STRATEGIES = {
613
+ openai: { encoding: "tiktoken", charsPerToken: 4 },
614
+ anthropic: { encoding: "approximate", charsPerToken: 3.5 },
615
+ google: { encoding: "approximate", charsPerToken: 4 },
616
+ azure: { encoding: "tiktoken", charsPerToken: 4 },
617
+ bedrock: { encoding: "approximate", charsPerToken: 3.5 },
618
+ cohere: { encoding: "approximate", charsPerToken: 4 },
619
+ mistral: { encoding: "approximate", charsPerToken: 4 },
620
+ replicate: { encoding: "approximate", charsPerToken: 4 },
621
+ custom: { encoding: "approximate", charsPerToken: 4 }
622
+ };
623
+ var TokenCounter = class {
624
+ pricingRegistry;
625
+ cache = /* @__PURE__ */ new Map();
626
+ maxCacheSize;
627
+ constructor(pricingRegistry, options) {
628
+ this.pricingRegistry = pricingRegistry;
629
+ this.maxCacheSize = options?.maxCacheSize ?? 1e3;
630
+ }
631
+ /**
632
+ * Count tokens in text
633
+ */
634
+ async countTokens(request) {
635
+ const { text, model, provider } = request;
636
+ const effectiveProvider = provider ?? this.detectProvider(model);
637
+ const cacheKey = `${effectiveProvider}:${text.substring(0, 100)}:${text.length}`;
638
+ const cached = this.cache.get(cacheKey);
639
+ if (cached !== void 0) {
640
+ return this.buildResult(
641
+ text,
642
+ cached,
643
+ model ?? "default",
644
+ effectiveProvider
645
+ );
646
+ }
647
+ let tokens;
648
+ const strategy = PROVIDER_STRATEGIES[effectiveProvider];
649
+ if (strategy.encoding === "tiktoken") {
650
+ tokens = await this.countWithTiktoken(text, strategy.charsPerToken);
651
+ } else {
652
+ tokens = this.countApproximate(text, strategy.charsPerToken);
653
+ }
654
+ this.setCached(cacheKey, tokens);
655
+ return this.buildResult(
656
+ text,
657
+ tokens,
658
+ model ?? "default",
659
+ effectiveProvider
660
+ );
661
+ }
662
+ /**
663
+ * Count tokens using tiktoken
664
+ */
665
+ async countWithTiktoken(text, fallbackCharsPerToken) {
666
+ const encoder = await getEncoder();
667
+ if (encoder) {
668
+ try {
669
+ const encoded = encoder.encode(text);
670
+ return encoded.length;
671
+ } catch {
672
+ }
673
+ }
674
+ return this.countApproximate(text, fallbackCharsPerToken);
675
+ }
676
+ /**
677
+ * Count tokens using approximation
678
+ */
679
+ countApproximate(text, charsPerToken) {
680
+ let adjustedLength = 0;
681
+ for (let i = 0; i < text.length; i++) {
682
+ const char = text[i];
683
+ const code = char.charCodeAt(0);
684
+ if (/[\s.,!?;:'"]/.test(char)) {
685
+ adjustedLength += 0.5;
686
+ } else if (/\d/.test(char)) {
687
+ adjustedLength += 0.7;
688
+ } else if (code > 127) {
689
+ adjustedLength += 2;
690
+ } else {
691
+ adjustedLength += 1;
692
+ }
693
+ }
694
+ return Math.ceil(adjustedLength / charsPerToken);
695
+ }
696
+ /**
697
+ * Build token count result
698
+ */
699
+ buildResult(text, tokens, model, _provider) {
700
+ const words = text.split(/\s+/).filter((w) => w.length > 0).length;
701
+ const characters = text.length;
702
+ let estimatedInputCost;
703
+ const pricing = this.pricingRegistry.getPricingByModel(model);
704
+ if (pricing) {
705
+ estimatedInputCost = tokens / 1e6 * pricing.inputPricePerMillion;
706
+ }
707
+ return {
708
+ tokens,
709
+ model,
710
+ estimatedInputCost,
711
+ characters,
712
+ words
713
+ };
714
+ }
715
+ /**
716
+ * Detect provider from model name
717
+ */
718
+ detectProvider(model) {
719
+ if (!model) return "openai";
720
+ const modelLower = model.toLowerCase();
721
+ if (modelLower.includes("claude")) return "anthropic";
722
+ if (modelLower.includes("gpt") || modelLower.includes("o1"))
723
+ return "openai";
724
+ if (modelLower.includes("gemini")) return "google";
725
+ if (modelLower.includes("mistral") || modelLower.includes("codestral"))
726
+ return "mistral";
727
+ if (modelLower.includes("command")) return "cohere";
728
+ return "openai";
729
+ }
730
+ /**
731
+ * Estimate cost for a request
732
+ */
733
+ async estimateCost(request) {
734
+ const { input, estimatedOutputTokens = 500, model, provider } = request;
735
+ let inputTokens;
736
+ if (typeof input === "number") {
737
+ inputTokens = input;
738
+ } else {
739
+ const countResult = await this.countTokens({
740
+ text: input,
741
+ model,
742
+ provider
743
+ });
744
+ inputTokens = countResult.tokens;
745
+ }
746
+ const effectiveProvider = provider ?? this.detectProvider(model);
747
+ const pricing = this.pricingRegistry.getPricing(effectiveProvider, model);
748
+ if (!pricing) {
749
+ throw new Error(`No pricing found for ${effectiveProvider}:${model}`);
750
+ }
751
+ const inputCost = inputTokens / 1e6 * pricing.inputPricePerMillion;
752
+ const outputCost = estimatedOutputTokens / 1e6 * pricing.outputPricePerMillion;
753
+ const cacheCost = request.includeCache ? inputTokens / 1e6 * (pricing.cacheWritePricePerMillion ?? 0) : void 0;
754
+ const estimatedCost = inputCost + outputCost + (cacheCost ?? 0);
755
+ const confidence = estimatedOutputTokens === 500 ? 0.7 : 0.85;
756
+ return {
757
+ estimatedCost,
758
+ inputTokens,
759
+ outputTokens: estimatedOutputTokens,
760
+ breakdown: {
761
+ inputCost,
762
+ outputCost,
763
+ cacheCost
764
+ },
765
+ model,
766
+ provider: effectiveProvider,
767
+ currency: pricing.currency,
768
+ confidence
769
+ };
770
+ }
771
+ /**
772
+ * Batch count tokens
773
+ */
774
+ async countTokensBatch(texts, options) {
775
+ return Promise.all(
776
+ texts.map(
777
+ (text) => this.countTokens({
778
+ text,
779
+ model: options?.model,
780
+ provider: options?.provider
781
+ })
782
+ )
783
+ );
784
+ }
785
+ /**
786
+ * Count tokens for messages (chat format)
787
+ */
788
+ async countMessagesTokens(messages, options) {
789
+ const perMessage = [];
790
+ let totalContent = 0;
791
+ for (const message of messages) {
792
+ const result = await this.countTokens({
793
+ text: message.content,
794
+ model: options?.model,
795
+ provider: options?.provider
796
+ });
797
+ perMessage.push({ role: message.role, tokens: result.tokens });
798
+ totalContent += result.tokens;
799
+ }
800
+ const overhead = messages.length * 4 + 3;
801
+ const totalTokens = totalContent + overhead;
802
+ return {
803
+ totalTokens,
804
+ perMessage,
805
+ overhead
806
+ };
807
+ }
808
+ /**
809
+ * Set cached value
810
+ */
811
+ setCached(key, value) {
812
+ if (this.cache.size >= this.maxCacheSize) {
813
+ const firstKey = this.cache.keys().next().value;
814
+ if (firstKey) {
815
+ this.cache.delete(firstKey);
816
+ }
817
+ }
818
+ this.cache.set(key, value);
819
+ }
820
+ /**
821
+ * Clear cache
822
+ */
823
+ clearCache() {
824
+ this.cache.clear();
825
+ }
826
+ /**
827
+ * Get cache stats
828
+ */
829
+ getCacheStats() {
830
+ return {
831
+ size: this.cache.size,
832
+ maxSize: this.maxCacheSize,
833
+ hitRate: 0
834
+ // Would need to track hits/misses for accurate rate
835
+ };
836
+ }
837
+ };
838
+ async function countTokens(text, options) {
839
+ const provider = options?.provider ?? "openai";
840
+ const strategy = PROVIDER_STRATEGIES[provider];
841
+ if (strategy.encoding === "tiktoken") {
842
+ const encoder = await getEncoder();
843
+ if (encoder) {
844
+ try {
845
+ return encoder.encode(text).length;
846
+ } catch {
847
+ }
848
+ }
849
+ }
850
+ return Math.ceil(text.length / strategy.charsPerToken);
851
+ }
852
+ function countTokensApprox(text, charsPerToken = 4) {
853
+ return Math.ceil(text.length / charsPerToken);
854
+ }
855
+ var CostTracker = class extends EventEmitter {
856
+ pricingRegistry;
857
+ storage;
858
+ defaultAttribution;
859
+ buffer = [];
860
+ bufferSize;
861
+ autoFlushTimer;
862
+ realTimeEvents;
863
+ constructor(config) {
864
+ super();
865
+ this.pricingRegistry = config.pricingRegistry;
866
+ this.storage = config.storage;
867
+ this.defaultAttribution = config.defaultAttribution;
868
+ this.bufferSize = config.bufferSize ?? 100;
869
+ this.realTimeEvents = config.realTimeEvents ?? true;
870
+ if (config.autoFlushInterval && config.autoFlushInterval > 0) {
871
+ this.autoFlushTimer = setInterval(() => {
872
+ this.flush().catch((err) => {
873
+ this.emit("error", { message: "Auto-flush failed", cause: err });
874
+ });
875
+ }, config.autoFlushInterval);
876
+ }
877
+ }
878
+ /**
879
+ * Track an API call
880
+ */
881
+ async track(options) {
882
+ const cost = this.calculateCost(
883
+ options.provider,
884
+ options.model,
885
+ options.tokens
886
+ );
887
+ const record = {
888
+ id: nanoid(),
889
+ timestamp: options.timestamp ?? /* @__PURE__ */ new Date(),
890
+ provider: options.provider,
891
+ model: options.model,
892
+ tokens: options.tokens,
893
+ cost,
894
+ latencyMs: options.latencyMs,
895
+ success: options.success ?? true,
896
+ error: options.error,
897
+ attribution: this.mergeAttribution(options.attribution),
898
+ metadata: options.metadata
899
+ };
900
+ this.buffer.push(record);
901
+ if (this.realTimeEvents) {
902
+ this.emit("cost:recorded", record);
903
+ }
904
+ if (this.buffer.length >= this.bufferSize) {
905
+ await this.flush();
906
+ }
907
+ return record;
908
+ }
909
+ /**
910
+ * Track from Anthropic API response
911
+ */
912
+ async trackAnthropicResponse(response, options) {
913
+ const usage = response.usage ?? { input_tokens: 0, output_tokens: 0 };
914
+ return this.track({
915
+ provider: "anthropic",
916
+ model: response.model,
917
+ tokens: {
918
+ inputTokens: usage.input_tokens,
919
+ outputTokens: usage.output_tokens,
920
+ totalTokens: usage.input_tokens + usage.output_tokens,
921
+ cacheReadTokens: usage.cache_read_input_tokens,
922
+ cacheWriteTokens: usage.cache_creation_input_tokens
923
+ },
924
+ latencyMs: options?.latencyMs,
925
+ attribution: options?.attribution,
926
+ metadata: options?.metadata
927
+ });
928
+ }
929
+ /**
930
+ * Track from OpenAI API response
931
+ */
932
+ async trackOpenAIResponse(response, options) {
933
+ const usage = response.usage ?? {
934
+ prompt_tokens: 0,
935
+ completion_tokens: 0,
936
+ total_tokens: 0
937
+ };
938
+ return this.track({
939
+ provider: "openai",
940
+ model: response.model,
941
+ tokens: {
942
+ inputTokens: usage.prompt_tokens,
943
+ outputTokens: usage.completion_tokens,
944
+ totalTokens: usage.total_tokens
945
+ },
946
+ latencyMs: options?.latencyMs,
947
+ attribution: options?.attribution,
948
+ metadata: options?.metadata
949
+ });
950
+ }
951
+ /**
952
+ * Track a failed request
953
+ */
954
+ async trackError(options) {
955
+ return this.track({
956
+ provider: options.provider,
957
+ model: options.model,
958
+ tokens: {
959
+ inputTokens: options.estimatedInputTokens ?? 0,
960
+ outputTokens: 0,
961
+ totalTokens: options.estimatedInputTokens ?? 0
962
+ },
963
+ latencyMs: options.latencyMs,
964
+ success: false,
965
+ error: options.error,
966
+ attribution: options.attribution,
967
+ metadata: options.metadata
968
+ });
969
+ }
970
+ /**
971
+ * Calculate cost for token usage
972
+ */
973
+ calculateCost(provider, model, tokens) {
974
+ try {
975
+ const result = this.pricingRegistry.calculateCost(
976
+ provider,
977
+ model,
978
+ tokens.inputTokens,
979
+ tokens.outputTokens,
980
+ {
981
+ cacheReadTokens: tokens.cacheReadTokens,
982
+ cacheWriteTokens: tokens.cacheWriteTokens
983
+ }
984
+ );
985
+ return {
986
+ inputCost: result.inputCost,
987
+ outputCost: result.outputCost,
988
+ cacheReadCost: result.cacheReadCost,
989
+ cacheCost: result.cacheCost,
990
+ totalCost: result.totalCost,
991
+ currency: result.currency
992
+ };
993
+ } catch {
994
+ return {
995
+ inputCost: 0,
996
+ outputCost: 0,
997
+ totalCost: 0,
998
+ currency: "USD"
999
+ };
1000
+ }
1001
+ }
1002
+ /**
1003
+ * Merge attribution with defaults
1004
+ */
1005
+ mergeAttribution(attribution) {
1006
+ if (!attribution && !this.defaultAttribution) {
1007
+ return void 0;
1008
+ }
1009
+ return {
1010
+ ...this.defaultAttribution,
1011
+ ...attribution,
1012
+ labels: {
1013
+ ...this.defaultAttribution?.labels,
1014
+ ...attribution?.labels
1015
+ }
1016
+ };
1017
+ }
1018
+ /**
1019
+ * Flush buffer to storage
1020
+ */
1021
+ async flush() {
1022
+ if (this.buffer.length === 0) {
1023
+ return 0;
1024
+ }
1025
+ const records = [...this.buffer];
1026
+ this.buffer = [];
1027
+ if (this.storage) {
1028
+ await this.storage.saveCostRecords(records);
1029
+ }
1030
+ this.emit("cost:batch", { records });
1031
+ return records.length;
1032
+ }
1033
+ /**
1034
+ * Get buffered records
1035
+ */
1036
+ getBuffer() {
1037
+ return [...this.buffer];
1038
+ }
1039
+ /**
1040
+ * Get buffer size
1041
+ */
1042
+ getBufferSize() {
1043
+ return this.buffer.length;
1044
+ }
1045
+ /**
1046
+ * Clear buffer without flushing
1047
+ */
1048
+ clearBuffer() {
1049
+ this.buffer = [];
1050
+ }
1051
+ /**
1052
+ * Set default attribution
1053
+ */
1054
+ setDefaultAttribution(attribution) {
1055
+ this.defaultAttribution = attribution;
1056
+ }
1057
+ /**
1058
+ * Create a scoped tracker with preset attribution
1059
+ */
1060
+ scoped(attribution) {
1061
+ return new ScopedCostTracker(this, attribution);
1062
+ }
1063
+ /**
1064
+ * Close tracker
1065
+ */
1066
+ async close() {
1067
+ if (this.autoFlushTimer) {
1068
+ clearInterval(this.autoFlushTimer);
1069
+ this.autoFlushTimer = void 0;
1070
+ }
1071
+ await this.flush();
1072
+ }
1073
+ };
1074
+ var ScopedCostTracker = class _ScopedCostTracker {
1075
+ parent;
1076
+ scopeAttribution;
1077
+ constructor(parent, attribution) {
1078
+ this.parent = parent;
1079
+ this.scopeAttribution = attribution;
1080
+ }
1081
+ /**
1082
+ * Track an API call with scoped attribution
1083
+ */
1084
+ async track(options) {
1085
+ return this.parent.track({
1086
+ ...options,
1087
+ attribution: {
1088
+ ...this.scopeAttribution,
1089
+ ...options.attribution,
1090
+ labels: {
1091
+ ...this.scopeAttribution.labels,
1092
+ ...options.attribution?.labels
1093
+ }
1094
+ }
1095
+ });
1096
+ }
1097
+ /**
1098
+ * Track Anthropic response
1099
+ */
1100
+ async trackAnthropicResponse(response, options) {
1101
+ return this.parent.trackAnthropicResponse(response, {
1102
+ ...options,
1103
+ attribution: {
1104
+ ...this.scopeAttribution,
1105
+ ...options?.attribution
1106
+ }
1107
+ });
1108
+ }
1109
+ /**
1110
+ * Track OpenAI response
1111
+ */
1112
+ async trackOpenAIResponse(response, options) {
1113
+ return this.parent.trackOpenAIResponse(response, {
1114
+ ...options,
1115
+ attribution: {
1116
+ ...this.scopeAttribution,
1117
+ ...options?.attribution
1118
+ }
1119
+ });
1120
+ }
1121
+ /**
1122
+ * Create a nested scope
1123
+ */
1124
+ scoped(attribution) {
1125
+ return new _ScopedCostTracker(this.parent, {
1126
+ ...this.scopeAttribution,
1127
+ ...attribution,
1128
+ labels: {
1129
+ ...this.scopeAttribution.labels,
1130
+ ...attribution.labels
1131
+ }
1132
+ });
1133
+ }
1134
+ };
1135
+
1136
+ // src/core/CostManager.ts
1137
+ var CostManager = class extends EventEmitter {
1138
+ storage;
1139
+ pricingRegistry;
1140
+ tokenCounter;
1141
+ tracker;
1142
+ config;
1143
+ initialized = false;
1144
+ constructor(options = {}) {
1145
+ super();
1146
+ this.config = {
1147
+ currency: options.currency ?? "USD",
1148
+ autoFlushInterval: options.autoFlushInterval ?? 3e4,
1149
+ // 30 seconds
1150
+ bufferSize: options.bufferSize ?? 100,
1151
+ realTimeTracking: options.realTimeTracking ?? true,
1152
+ defaultAttribution: options.defaultAttribution
1153
+ };
1154
+ this.storage = options.storage;
1155
+ this.pricingRegistry = options.pricingRegistry ?? new ModelPricingRegistry();
1156
+ this.tokenCounter = new TokenCounter(this.pricingRegistry);
1157
+ this.tracker = new CostTracker({
1158
+ pricingRegistry: this.pricingRegistry,
1159
+ storage: this.storage,
1160
+ defaultAttribution: this.config.defaultAttribution,
1161
+ autoFlushInterval: this.config.autoFlushInterval,
1162
+ bufferSize: this.config.bufferSize,
1163
+ realTimeEvents: this.config.realTimeTracking
1164
+ });
1165
+ this.tracker.on("cost:recorded", (record) => {
1166
+ this.emit("cost:recorded", record);
1167
+ });
1168
+ this.tracker.on("cost:batch", (records) => {
1169
+ this.emit("cost:batch", records);
1170
+ });
1171
+ this.tracker.on("error", (error) => {
1172
+ this.emit("error", error);
1173
+ });
1174
+ }
1175
+ /**
1176
+ * Initialize the cost manager
1177
+ */
1178
+ async initialize() {
1179
+ if (this.initialized) return;
1180
+ if (this.storage) {
1181
+ await this.storage.initialize();
1182
+ }
1183
+ this.initialized = true;
1184
+ }
1185
+ /**
1186
+ * Close the cost manager
1187
+ */
1188
+ async close() {
1189
+ await this.tracker.close();
1190
+ if (this.storage) {
1191
+ await this.storage.close();
1192
+ }
1193
+ this.initialized = false;
1194
+ }
1195
+ // ==================== Tracking ====================
1196
+ /**
1197
+ * Track an API call
1198
+ */
1199
+ async track(options) {
1200
+ return this.tracker.track(options);
1201
+ }
1202
+ /**
1203
+ * Track from Anthropic API response
1204
+ */
1205
+ async trackAnthropicResponse(response, options) {
1206
+ return this.tracker.trackAnthropicResponse(response, options);
1207
+ }
1208
+ /**
1209
+ * Track from OpenAI API response
1210
+ */
1211
+ async trackOpenAIResponse(response, options) {
1212
+ return this.tracker.trackOpenAIResponse(response, options);
1213
+ }
1214
+ /**
1215
+ * Track a failed request
1216
+ */
1217
+ async trackError(options) {
1218
+ return this.tracker.trackError(options);
1219
+ }
1220
+ /**
1221
+ * Create a scoped tracker
1222
+ */
1223
+ scoped(attribution) {
1224
+ return this.tracker.scoped(attribution);
1225
+ }
1226
+ /**
1227
+ * Flush pending records to storage
1228
+ */
1229
+ async flush() {
1230
+ return this.tracker.flush();
1231
+ }
1232
+ // ==================== Token Counting ====================
1233
+ /**
1234
+ * Count tokens in text
1235
+ */
1236
+ async countTokens(text, options) {
1237
+ const result = await this.tokenCounter.countTokens({
1238
+ text,
1239
+ model: options?.model,
1240
+ provider: options?.provider
1241
+ });
1242
+ return result.tokens;
1243
+ }
1244
+ /**
1245
+ * Estimate cost before making a request
1246
+ */
1247
+ async estimateCost(input, options) {
1248
+ const result = await this.tokenCounter.estimateCost({
1249
+ input,
1250
+ model: options.model,
1251
+ provider: options.provider,
1252
+ estimatedOutputTokens: options.estimatedOutputTokens
1253
+ });
1254
+ return {
1255
+ estimatedCost: result.estimatedCost,
1256
+ inputTokens: result.inputTokens,
1257
+ outputTokens: result.outputTokens,
1258
+ currency: result.currency
1259
+ };
1260
+ }
1261
+ // ==================== Pricing ====================
1262
+ /**
1263
+ * Get pricing registry
1264
+ */
1265
+ getPricingRegistry() {
1266
+ return this.pricingRegistry;
1267
+ }
1268
+ /**
1269
+ * Get token counter
1270
+ */
1271
+ getTokenCounter() {
1272
+ return this.tokenCounter;
1273
+ }
1274
+ /**
1275
+ * Calculate cost for token usage
1276
+ */
1277
+ calculateCost(provider, model, tokens) {
1278
+ const result = this.pricingRegistry.calculateCost(
1279
+ provider,
1280
+ model,
1281
+ tokens.inputTokens,
1282
+ tokens.outputTokens,
1283
+ {
1284
+ cacheReadTokens: tokens.cacheReadTokens,
1285
+ cacheWriteTokens: tokens.cacheWriteTokens
1286
+ }
1287
+ );
1288
+ return {
1289
+ totalCost: result.totalCost,
1290
+ inputCost: result.inputCost,
1291
+ outputCost: result.outputCost,
1292
+ currency: result.currency
1293
+ };
1294
+ }
1295
+ // ==================== Queries ====================
1296
+ /**
1297
+ * Get cost summary
1298
+ */
1299
+ async getSummary(options = {}) {
1300
+ if (!this.storage) {
1301
+ throw new Error("Storage adapter required for queries");
1302
+ }
1303
+ return this.storage.getCostSummary(options);
1304
+ }
1305
+ /**
1306
+ * Get costs by dimension
1307
+ */
1308
+ async getCostsByDimension(dimension, options = {}) {
1309
+ if (!this.storage) {
1310
+ throw new Error("Storage adapter required for queries");
1311
+ }
1312
+ return this.storage.getCostsByDimension(dimension, options);
1313
+ }
1314
+ /**
1315
+ * Get cost trends
1316
+ */
1317
+ async getCostTrends(options = {}) {
1318
+ if (!this.storage) {
1319
+ throw new Error("Storage adapter required for queries");
1320
+ }
1321
+ return this.storage.getCostTrends(options);
1322
+ }
1323
+ /**
1324
+ * Query cost records
1325
+ */
1326
+ async queryRecords(options = {}) {
1327
+ if (!this.storage) {
1328
+ throw new Error("Storage adapter required for queries");
1329
+ }
1330
+ return this.storage.queryCostRecords(options);
1331
+ }
1332
+ /**
1333
+ * Get a specific cost record
1334
+ */
1335
+ async getRecord(id) {
1336
+ if (!this.storage) {
1337
+ throw new Error("Storage adapter required for queries");
1338
+ }
1339
+ return this.storage.getCostRecord(id);
1340
+ }
1341
+ // ==================== Statistics ====================
1342
+ /**
1343
+ * Get total cost for a time period
1344
+ */
1345
+ async getTotalCost(options = {}) {
1346
+ const summary = await this.getSummary(options);
1347
+ return summary.totalCost;
1348
+ }
1349
+ /**
1350
+ * Get total tokens for a time period
1351
+ */
1352
+ async getTotalTokens(options = {}) {
1353
+ const summary = await this.getSummary(options);
1354
+ return summary.totalTokens;
1355
+ }
1356
+ /**
1357
+ * Get request count for a time period
1358
+ */
1359
+ async getRequestCount(options = {}) {
1360
+ const summary = await this.getSummary(options);
1361
+ return summary.requestCount;
1362
+ }
1363
+ /**
1364
+ * Get error rate for a time period
1365
+ */
1366
+ async getErrorRate(options = {}) {
1367
+ const summary = await this.getSummary(options);
1368
+ if (summary.requestCount === 0) return 0;
1369
+ return summary.errorCount / summary.requestCount;
1370
+ }
1371
+ /**
1372
+ * Get average cost per request
1373
+ */
1374
+ async getAvgCostPerRequest(options = {}) {
1375
+ const summary = await this.getSummary(options);
1376
+ return summary.avgCostPerRequest;
1377
+ }
1378
+ // ==================== Top Consumers ====================
1379
+ /**
1380
+ * Get top models by cost
1381
+ */
1382
+ async getTopModels(options = {}) {
1383
+ const results = await this.getCostsByDimension("model", options);
1384
+ return results.sort((a, b) => b.totalCost - a.totalCost).slice(0, options.limit ?? 10);
1385
+ }
1386
+ /**
1387
+ * Get top users by cost
1388
+ */
1389
+ async getTopUsers(options = {}) {
1390
+ const results = await this.getCostsByDimension("user", options);
1391
+ return results.sort((a, b) => b.totalCost - a.totalCost).slice(0, options.limit ?? 10);
1392
+ }
1393
+ /**
1394
+ * Get top features by cost
1395
+ */
1396
+ async getTopFeatures(options = {}) {
1397
+ const results = await this.getCostsByDimension("feature", options);
1398
+ return results.sort((a, b) => b.totalCost - a.totalCost).slice(0, options.limit ?? 10);
1399
+ }
1400
+ // ==================== Maintenance ====================
1401
+ /**
1402
+ * Cleanup old records
1403
+ */
1404
+ async cleanup(olderThan) {
1405
+ if (!this.storage) {
1406
+ throw new Error("Storage adapter required for cleanup");
1407
+ }
1408
+ return this.storage.cleanup(olderThan);
1409
+ }
1410
+ /**
1411
+ * Get storage stats
1412
+ */
1413
+ async getStorageStats() {
1414
+ if (!this.storage) {
1415
+ return { recordCount: 0 };
1416
+ }
1417
+ const stats = await this.storage.getStats();
1418
+ return {
1419
+ recordCount: stats.costRecordCount,
1420
+ storageSizeBytes: stats.storageSizeBytes,
1421
+ oldestRecord: stats.oldestRecord,
1422
+ newestRecord: stats.newestRecord
1423
+ };
1424
+ }
1425
+ /**
1426
+ * Optimize storage
1427
+ */
1428
+ async optimizeStorage() {
1429
+ if (!this.storage) {
1430
+ throw new Error("Storage adapter required for optimization");
1431
+ }
1432
+ await this.storage.optimize();
1433
+ }
1434
+ // ==================== Export/Import ====================
1435
+ /**
1436
+ * Export cost records
1437
+ */
1438
+ async exportRecords(options = {}) {
1439
+ const [records, summary] = await Promise.all([
1440
+ this.queryRecords(options),
1441
+ this.getSummary(options)
1442
+ ]);
1443
+ return {
1444
+ records,
1445
+ summary,
1446
+ exportedAt: /* @__PURE__ */ new Date()
1447
+ };
1448
+ }
1449
+ /**
1450
+ * Import cost records
1451
+ */
1452
+ async importRecords(records) {
1453
+ if (!this.storage) {
1454
+ throw new Error("Storage adapter required for import");
1455
+ }
1456
+ await this.storage.saveCostRecords(records);
1457
+ return records.length;
1458
+ }
1459
+ };
1460
+ function createCostManager(options) {
1461
+ return new CostManager(options);
1462
+ }
1463
+ var BudgetManager = class extends EventEmitter {
1464
+ budgets = /* @__PURE__ */ new Map();
1465
+ usage = /* @__PURE__ */ new Map();
1466
+ resetJobs = /* @__PURE__ */ new Map();
1467
+ storage;
1468
+ config;
1469
+ constructor(config = {}, storage) {
1470
+ super();
1471
+ this.config = {
1472
+ enforceOnRequest: config.enforceOnRequest ?? true,
1473
+ defaultAction: config.defaultAction ?? "allow",
1474
+ checkInterval: config.checkInterval ?? 6e4,
1475
+ // 1 minute
1476
+ enableProjections: config.enableProjections ?? true,
1477
+ ...config
1478
+ };
1479
+ this.storage = storage;
1480
+ }
1481
+ /**
1482
+ * Initialize from storage
1483
+ */
1484
+ async initialize() {
1485
+ if (this.storage) {
1486
+ const budgets = await this.storage.listBudgets();
1487
+ for (const budget of budgets) {
1488
+ this.budgets.set(budget.id, budget);
1489
+ this.scheduleReset(budget);
1490
+ }
1491
+ for (const budget of this.budgets.values()) {
1492
+ await this.refreshUsage(budget.id);
1493
+ }
1494
+ }
1495
+ }
1496
+ /**
1497
+ * Create a new budget
1498
+ */
1499
+ async createBudget(request) {
1500
+ const budget = {
1501
+ id: nanoid(),
1502
+ name: request.name,
1503
+ description: request.description,
1504
+ limit: request.limit,
1505
+ currency: request.currency ?? "USD",
1506
+ period: request.period,
1507
+ scope: request.scope,
1508
+ scopeId: request.scopeId,
1509
+ warningThresholds: request.warningThresholds ?? [50, 80, 90],
1510
+ actions: request.actions,
1511
+ filters: request.filters,
1512
+ rollover: request.rollover ?? false,
1513
+ maxRollover: request.maxRollover,
1514
+ enabled: true,
1515
+ createdAt: /* @__PURE__ */ new Date(),
1516
+ updatedAt: /* @__PURE__ */ new Date()
1517
+ };
1518
+ this.budgets.set(budget.id, budget);
1519
+ const usage = this.initializeUsage(budget);
1520
+ this.usage.set(budget.id, usage);
1521
+ this.scheduleReset(budget);
1522
+ if (this.storage) {
1523
+ await this.storage.saveBudget(budget);
1524
+ }
1525
+ this.emit("budget:created", budget);
1526
+ return budget;
1527
+ }
1528
+ /**
1529
+ * Update a budget
1530
+ */
1531
+ async updateBudget(budgetId, updates) {
1532
+ const budget = this.budgets.get(budgetId);
1533
+ if (!budget) {
1534
+ throw new Error(`Budget not found: ${budgetId}`);
1535
+ }
1536
+ const updated = {
1537
+ ...budget,
1538
+ ...updates,
1539
+ updatedAt: /* @__PURE__ */ new Date()
1540
+ };
1541
+ this.budgets.set(budgetId, updated);
1542
+ if (updates.enabled !== void 0 || updates.limit) {
1543
+ this.scheduleReset(updated);
1544
+ }
1545
+ if (this.storage) {
1546
+ await this.storage.updateBudget(budgetId, updated);
1547
+ }
1548
+ this.emit("budget:updated", { budgetId, updates });
1549
+ return updated;
1550
+ }
1551
+ /**
1552
+ * Delete a budget
1553
+ */
1554
+ async deleteBudget(budgetId) {
1555
+ const budget = this.budgets.get(budgetId);
1556
+ if (!budget) {
1557
+ return false;
1558
+ }
1559
+ const job = this.resetJobs.get(budgetId);
1560
+ if (job) {
1561
+ job.stop();
1562
+ this.resetJobs.delete(budgetId);
1563
+ }
1564
+ this.budgets.delete(budgetId);
1565
+ this.usage.delete(budgetId);
1566
+ if (this.storage) {
1567
+ await this.storage.deleteBudget(budgetId);
1568
+ }
1569
+ this.emit("budget:deleted", budgetId);
1570
+ return true;
1571
+ }
1572
+ /**
1573
+ * Get a budget
1574
+ */
1575
+ getBudget(budgetId) {
1576
+ return this.budgets.get(budgetId) ?? null;
1577
+ }
1578
+ /**
1579
+ * List all budgets
1580
+ */
1581
+ listBudgets(options) {
1582
+ let budgets = Array.from(this.budgets.values());
1583
+ if (options?.scope) {
1584
+ budgets = budgets.filter((b) => b.scope === options.scope);
1585
+ }
1586
+ if (options?.scopeId) {
1587
+ budgets = budgets.filter((b) => b.scopeId === options.scopeId);
1588
+ }
1589
+ if (options?.enabled !== void 0) {
1590
+ budgets = budgets.filter((b) => b.enabled === options.enabled);
1591
+ }
1592
+ return budgets;
1593
+ }
1594
+ /**
1595
+ * Get budget usage
1596
+ */
1597
+ async getUsage(budgetId) {
1598
+ const budget = this.budgets.get(budgetId);
1599
+ if (!budget) {
1600
+ return null;
1601
+ }
1602
+ await this.refreshUsage(budgetId);
1603
+ return this.usage.get(budgetId) ?? null;
1604
+ }
1605
+ /**
1606
+ * Check if a request is within budget
1607
+ */
1608
+ async checkBudget(request) {
1609
+ const matchingBudgets = [];
1610
+ const exceededBudgets = [];
1611
+ const warningBudgets = [];
1612
+ const budgetsToCheck = request.budgetIds ? request.budgetIds.map((id) => this.budgets.get(id)).filter(Boolean) : this.findMatchingBudgets(request.attribution);
1613
+ for (const budget of budgetsToCheck) {
1614
+ if (!budget.enabled) continue;
1615
+ await this.refreshUsage(budget.id);
1616
+ const usage = this.usage.get(budget.id);
1617
+ if (!usage) continue;
1618
+ matchingBudgets.push(usage);
1619
+ const projectedUsage = usage.currentUsage + request.estimatedCost;
1620
+ const projectedPercentage = projectedUsage / usage.limit * 100;
1621
+ if (projectedPercentage >= 100) {
1622
+ exceededBudgets.push(budget.id);
1623
+ } else if (projectedPercentage >= (budget.warningThresholds?.[0] ?? 50)) {
1624
+ warningBudgets.push(budget.id);
1625
+ }
1626
+ }
1627
+ let action = "allow";
1628
+ let reason;
1629
+ if (exceededBudgets.length > 0) {
1630
+ const budget = this.budgets.get(exceededBudgets[0]);
1631
+ const budgetAction = budget?.actions?.find((a) => a.threshold >= 100);
1632
+ if (budgetAction?.action === "block") {
1633
+ action = "block";
1634
+ reason = `Budget limit exceeded for "${budget?.name}"`;
1635
+ } else if (budgetAction?.action === "throttle") {
1636
+ action = "throttle";
1637
+ reason = `Budget limit exceeded for "${budget?.name}"`;
1638
+ } else {
1639
+ action = this.config.defaultAction ?? "allow";
1640
+ if (action !== "allow") {
1641
+ reason = `Budget limit exceeded for "${budget?.name}"`;
1642
+ }
1643
+ }
1644
+ }
1645
+ return {
1646
+ allowed: action === "allow" || action === "warn",
1647
+ reason,
1648
+ matchingBudgets,
1649
+ exceededBudgets,
1650
+ warningBudgets,
1651
+ action
1652
+ };
1653
+ }
1654
+ /**
1655
+ * Record cost against budgets
1656
+ */
1657
+ async recordCost(cost, attribution) {
1658
+ const matchingBudgets = this.findMatchingBudgets(attribution);
1659
+ for (const budget of matchingBudgets) {
1660
+ if (!budget.enabled) continue;
1661
+ const usage = this.usage.get(budget.id);
1662
+ if (!usage) continue;
1663
+ usage.currentUsage += cost;
1664
+ usage.remaining = Math.max(0, usage.limit - usage.currentUsage);
1665
+ usage.usagePercentage = usage.currentUsage / usage.limit * 100;
1666
+ await this.checkThresholds(budget, usage);
1667
+ if (this.config.enableProjections) {
1668
+ this.updateProjections(usage);
1669
+ }
1670
+ }
1671
+ }
1672
+ /**
1673
+ * Find budgets matching attribution
1674
+ */
1675
+ findMatchingBudgets(attribution) {
1676
+ const matching = [];
1677
+ for (const budget of this.budgets.values()) {
1678
+ if (!budget.enabled) continue;
1679
+ if (budget.scope === "global") {
1680
+ matching.push(budget);
1681
+ continue;
1682
+ }
1683
+ if (!attribution) continue;
1684
+ let matches = false;
1685
+ switch (budget.scope) {
1686
+ case "user":
1687
+ matches = budget.scopeId === attribution.userId;
1688
+ break;
1689
+ case "agent":
1690
+ matches = budget.scopeId === attribution.agentId;
1691
+ break;
1692
+ case "project":
1693
+ matches = budget.scopeId === attribution.projectId;
1694
+ break;
1695
+ case "team":
1696
+ matches = budget.scopeId === attribution.teamId;
1697
+ break;
1698
+ case "feature":
1699
+ matches = budget.scopeId === attribution.feature;
1700
+ break;
1701
+ }
1702
+ if (!matches) continue;
1703
+ if (budget.filters) {
1704
+ if (budget.filters.environment && budget.filters.environment !== attribution.environment) {
1705
+ continue;
1706
+ }
1707
+ }
1708
+ matching.push(budget);
1709
+ }
1710
+ return matching;
1711
+ }
1712
+ /**
1713
+ * Initialize usage for a budget
1714
+ */
1715
+ initializeUsage(budget) {
1716
+ const period = this.getPeriodDates(budget.period);
1717
+ return {
1718
+ budgetId: budget.id,
1719
+ currentUsage: 0,
1720
+ limit: budget.limit,
1721
+ usagePercentage: 0,
1722
+ remaining: budget.limit,
1723
+ periodStart: period.start,
1724
+ periodEnd: period.end,
1725
+ timeRemaining: this.getTimeRemaining(period.end),
1726
+ status: "active",
1727
+ triggeredThresholds: []
1728
+ };
1729
+ }
1730
+ /**
1731
+ * Refresh usage from storage
1732
+ */
1733
+ async refreshUsage(budgetId) {
1734
+ const budget = this.budgets.get(budgetId);
1735
+ if (!budget) return;
1736
+ let usage = this.usage.get(budgetId);
1737
+ if (!usage) {
1738
+ usage = this.initializeUsage(budget);
1739
+ this.usage.set(budgetId, usage);
1740
+ }
1741
+ usage.timeRemaining = this.getTimeRemaining(usage.periodEnd);
1742
+ if (this.storage) {
1743
+ const queryOptions = {
1744
+ startDate: usage.periodStart,
1745
+ endDate: usage.periodEnd
1746
+ };
1747
+ if (budget.scope !== "global" && budget.scopeId) {
1748
+ switch (budget.scope) {
1749
+ case "user":
1750
+ queryOptions.userIds = [budget.scopeId];
1751
+ break;
1752
+ case "agent":
1753
+ queryOptions.agentIds = [budget.scopeId];
1754
+ break;
1755
+ case "project":
1756
+ queryOptions.projectIds = [budget.scopeId];
1757
+ break;
1758
+ case "team":
1759
+ queryOptions.teamIds = [budget.scopeId];
1760
+ break;
1761
+ case "feature":
1762
+ queryOptions.features = [budget.scopeId];
1763
+ break;
1764
+ }
1765
+ }
1766
+ try {
1767
+ const summary = await this.storage.getCostSummary(queryOptions);
1768
+ usage.currentUsage = summary.totalCost;
1769
+ usage.remaining = Math.max(0, budget.limit - usage.currentUsage);
1770
+ usage.usagePercentage = usage.currentUsage / budget.limit * 100;
1771
+ if (usage.usagePercentage >= 100) {
1772
+ usage.status = "exceeded";
1773
+ } else if (!budget.enabled) {
1774
+ usage.status = "paused";
1775
+ } else {
1776
+ usage.status = "active";
1777
+ }
1778
+ } catch {
1779
+ }
1780
+ }
1781
+ }
1782
+ /**
1783
+ * Check thresholds and emit alerts
1784
+ */
1785
+ async checkThresholds(budget, usage) {
1786
+ if (!budget.warningThresholds) return;
1787
+ for (const threshold of budget.warningThresholds) {
1788
+ if (usage.usagePercentage >= threshold && !usage.triggeredThresholds.includes(threshold)) {
1789
+ usage.triggeredThresholds.push(threshold);
1790
+ const alert = {
1791
+ id: nanoid(),
1792
+ budgetId: budget.id,
1793
+ budgetName: budget.name,
1794
+ type: threshold >= 100 ? "exceeded" : "warning",
1795
+ threshold,
1796
+ usage: usage.currentUsage,
1797
+ limit: usage.limit,
1798
+ percentage: usage.usagePercentage,
1799
+ message: threshold >= 100 ? `Budget "${budget.name}" has been exceeded (${usage.usagePercentage.toFixed(1)}%)` : `Budget "${budget.name}" has reached ${threshold}% (${usage.usagePercentage.toFixed(1)}%)`,
1800
+ timestamp: /* @__PURE__ */ new Date(),
1801
+ acknowledged: false
1802
+ };
1803
+ if (this.storage) {
1804
+ await this.storage.saveBudgetAlert(alert);
1805
+ }
1806
+ if (threshold >= 100) {
1807
+ this.emit("budget:exceeded", alert);
1808
+ } else {
1809
+ this.emit("budget:warning", alert);
1810
+ }
1811
+ const action = budget.actions?.find((a) => a.threshold === threshold);
1812
+ if (action) {
1813
+ await this.executeAction(action, alert);
1814
+ }
1815
+ }
1816
+ }
1817
+ }
1818
+ /**
1819
+ * Execute threshold action
1820
+ */
1821
+ async executeAction(action, alert) {
1822
+ if (action.notifyEmails && action.notifyEmails.length > 0) {
1823
+ console.log(
1824
+ `Would send email to ${action.notifyEmails.join(", ")}:`,
1825
+ alert.message
1826
+ );
1827
+ }
1828
+ if (action.webhookUrl) {
1829
+ try {
1830
+ await fetch(action.webhookUrl, {
1831
+ method: "POST",
1832
+ headers: { "Content-Type": "application/json" },
1833
+ body: JSON.stringify(alert)
1834
+ });
1835
+ } catch {
1836
+ this.emit("error", {
1837
+ message: `Failed to send webhook to ${action.webhookUrl}`
1838
+ });
1839
+ }
1840
+ }
1841
+ }
1842
+ /**
1843
+ * Update projections
1844
+ */
1845
+ updateProjections(usage) {
1846
+ if (usage.timeRemaining <= 0) return;
1847
+ const elapsedMs = Date.now() - usage.periodStart.getTime();
1848
+ const totalMs = usage.periodEnd.getTime() - usage.periodStart.getTime();
1849
+ if (elapsedMs <= 0) return;
1850
+ const rate = usage.currentUsage / elapsedMs;
1851
+ usage.projectedUsage = rate * totalMs;
1852
+ usage.projectedExceed = usage.projectedUsage > usage.limit;
1853
+ }
1854
+ /**
1855
+ * Schedule budget reset
1856
+ */
1857
+ scheduleReset(budget) {
1858
+ const existingJob = this.resetJobs.get(budget.id);
1859
+ if (existingJob) {
1860
+ existingJob.stop();
1861
+ }
1862
+ if (!budget.enabled) return;
1863
+ const pattern = this.getCronPattern(budget.period, budget.resetSchedule);
1864
+ if (!pattern) return;
1865
+ const job = new Cron(pattern, async () => {
1866
+ await this.resetBudget(budget.id);
1867
+ });
1868
+ this.resetJobs.set(budget.id, job);
1869
+ }
1870
+ /**
1871
+ * Reset a budget
1872
+ */
1873
+ async resetBudget(budgetId) {
1874
+ const budget = this.budgets.get(budgetId);
1875
+ if (!budget) return;
1876
+ const usage = this.usage.get(budgetId);
1877
+ if (!usage) return;
1878
+ const previousUsage = usage.currentUsage;
1879
+ if (this.storage) {
1880
+ const historyEntry = {
1881
+ budgetId,
1882
+ periodStart: usage.periodStart,
1883
+ periodEnd: usage.periodEnd,
1884
+ usage: previousUsage,
1885
+ limit: usage.limit,
1886
+ usagePercentage: usage.usagePercentage,
1887
+ exceeded: previousUsage > usage.limit,
1888
+ rolloverIn: usage.rolloverIn,
1889
+ rolloverOut: budget.rollover ? Math.min(usage.remaining, budget.maxRollover ?? Infinity) : void 0
1890
+ };
1891
+ await this.storage.saveBudgetHistory(historyEntry);
1892
+ }
1893
+ let newLimit = budget.limit;
1894
+ if (budget.rollover && usage.remaining > 0) {
1895
+ const rolloverAmount = Math.min(
1896
+ usage.remaining,
1897
+ budget.maxRollover ?? Infinity
1898
+ );
1899
+ newLimit += rolloverAmount;
1900
+ }
1901
+ const period = this.getPeriodDates(budget.period);
1902
+ usage.currentUsage = 0;
1903
+ usage.limit = newLimit;
1904
+ usage.remaining = newLimit;
1905
+ usage.usagePercentage = 0;
1906
+ usage.periodStart = period.start;
1907
+ usage.periodEnd = period.end;
1908
+ usage.timeRemaining = this.getTimeRemaining(period.end);
1909
+ usage.status = "active";
1910
+ usage.triggeredThresholds = [];
1911
+ usage.projectedUsage = void 0;
1912
+ usage.projectedExceed = void 0;
1913
+ this.emit("budget:reset", { budgetId, previousUsage });
1914
+ }
1915
+ /**
1916
+ * Get period dates
1917
+ */
1918
+ getPeriodDates(period) {
1919
+ const now = /* @__PURE__ */ new Date();
1920
+ let start;
1921
+ let end;
1922
+ switch (period) {
1923
+ case "hourly":
1924
+ start = new Date(
1925
+ now.getFullYear(),
1926
+ now.getMonth(),
1927
+ now.getDate(),
1928
+ now.getHours()
1929
+ );
1930
+ end = new Date(start.getTime() + 60 * 60 * 1e3);
1931
+ break;
1932
+ case "daily":
1933
+ start = new Date(now.getFullYear(), now.getMonth(), now.getDate());
1934
+ end = new Date(start.getTime() + 24 * 60 * 60 * 1e3);
1935
+ break;
1936
+ case "weekly": {
1937
+ const dayOfWeek = now.getDay();
1938
+ start = new Date(
1939
+ now.getFullYear(),
1940
+ now.getMonth(),
1941
+ now.getDate() - dayOfWeek
1942
+ );
1943
+ end = new Date(start.getTime() + 7 * 24 * 60 * 60 * 1e3);
1944
+ break;
1945
+ }
1946
+ case "monthly":
1947
+ start = new Date(now.getFullYear(), now.getMonth(), 1);
1948
+ end = new Date(now.getFullYear(), now.getMonth() + 1, 1);
1949
+ break;
1950
+ case "quarterly": {
1951
+ const quarter = Math.floor(now.getMonth() / 3);
1952
+ start = new Date(now.getFullYear(), quarter * 3, 1);
1953
+ end = new Date(now.getFullYear(), (quarter + 1) * 3, 1);
1954
+ break;
1955
+ }
1956
+ case "yearly":
1957
+ start = new Date(now.getFullYear(), 0, 1);
1958
+ end = new Date(now.getFullYear() + 1, 0, 1);
1959
+ break;
1960
+ default:
1961
+ start = new Date(now.getFullYear(), now.getMonth(), 1);
1962
+ end = new Date(now.getFullYear(), now.getMonth() + 1, 1);
1963
+ }
1964
+ return { start, end };
1965
+ }
1966
+ /**
1967
+ * Get time remaining in period
1968
+ */
1969
+ getTimeRemaining(endDate) {
1970
+ return Math.max(0, endDate.getTime() - Date.now());
1971
+ }
1972
+ /**
1973
+ * Get cron pattern for period
1974
+ */
1975
+ getCronPattern(period, customSchedule) {
1976
+ if (customSchedule) return customSchedule;
1977
+ switch (period) {
1978
+ case "hourly":
1979
+ return "0 * * * *";
1980
+ // Every hour at minute 0
1981
+ case "daily":
1982
+ return "0 0 * * *";
1983
+ // Every day at midnight
1984
+ case "weekly":
1985
+ return "0 0 * * 0";
1986
+ // Every Sunday at midnight
1987
+ case "monthly":
1988
+ return "0 0 1 * *";
1989
+ // First of every month
1990
+ case "quarterly":
1991
+ return "0 0 1 1,4,7,10 *";
1992
+ // First of Jan, Apr, Jul, Oct
1993
+ case "yearly":
1994
+ return "0 0 1 1 *";
1995
+ // First of January
1996
+ default:
1997
+ return null;
1998
+ }
1999
+ }
2000
+ /**
2001
+ * Close budget manager
2002
+ */
2003
+ close() {
2004
+ for (const job of this.resetJobs.values()) {
2005
+ job.stop();
2006
+ }
2007
+ this.resetJobs.clear();
2008
+ }
2009
+ };
2010
+
2011
+ // src/storage/adapters/BufferStorage.ts
2012
+ var BufferStorage = class {
2013
+ records = /* @__PURE__ */ new Map();
2014
+ budgets = /* @__PURE__ */ new Map();
2015
+ budgetHistory = /* @__PURE__ */ new Map();
2016
+ budgetAlerts = /* @__PURE__ */ new Map();
2017
+ attributedCosts = [];
2018
+ alertRules = /* @__PURE__ */ new Map();
2019
+ alerts = /* @__PURE__ */ new Map();
2020
+ config;
2021
+ flushTimer;
2022
+ constructor(config = {}) {
2023
+ this.config = {
2024
+ maxRecords: config.maxRecords ?? 1e4,
2025
+ autoFlushInterval: config.autoFlushInterval ?? 0,
2026
+ onFlush: config.onFlush
2027
+ };
2028
+ if (this.config.autoFlushInterval && this.config.autoFlushInterval > 0) {
2029
+ this.flushTimer = setInterval(() => {
2030
+ void (async () => {
2031
+ if (this.config.onFlush) {
2032
+ const records = Array.from(this.records.values());
2033
+ await this.config.onFlush(records);
2034
+ }
2035
+ })();
2036
+ }, this.config.autoFlushInterval);
2037
+ }
2038
+ }
2039
+ async initialize() {
2040
+ }
2041
+ close() {
2042
+ if (this.flushTimer) {
2043
+ clearInterval(this.flushTimer);
2044
+ }
2045
+ return Promise.resolve();
2046
+ }
2047
+ // ==================== Cost Records ====================
2048
+ saveCostRecord(record) {
2049
+ this.enforceLimit();
2050
+ this.records.set(record.id, record);
2051
+ return Promise.resolve();
2052
+ }
2053
+ async saveCostRecords(records) {
2054
+ for (const record of records) {
2055
+ await this.saveCostRecord(record);
2056
+ }
2057
+ }
2058
+ getCostRecord(id) {
2059
+ return Promise.resolve(this.records.get(id) ?? null);
2060
+ }
2061
+ queryCostRecords(options) {
2062
+ let results = Array.from(this.records.values());
2063
+ results = this.applyFilters(results, options);
2064
+ results = this.applySort(results, options);
2065
+ results = this.applyPagination(results, options);
2066
+ return Promise.resolve(results);
2067
+ }
2068
+ async getCostSummary(options) {
2069
+ const records = await this.queryCostRecords(options);
2070
+ const summary = {
2071
+ periodStart: options.startDate ?? /* @__PURE__ */ new Date(0),
2072
+ periodEnd: options.endDate ?? /* @__PURE__ */ new Date(),
2073
+ totalCost: 0,
2074
+ totalTokens: 0,
2075
+ inputTokens: 0,
2076
+ outputTokens: 0,
2077
+ requestCount: records.length,
2078
+ successCount: 0,
2079
+ errorCount: 0,
2080
+ avgCostPerRequest: 0,
2081
+ avgTokensPerRequest: 0,
2082
+ currency: "USD"
2083
+ };
2084
+ let totalLatency = 0;
2085
+ let latencyCount = 0;
2086
+ for (const record of records) {
2087
+ summary.totalCost += record.cost.totalCost;
2088
+ summary.totalTokens += record.tokens.totalTokens;
2089
+ summary.inputTokens += record.tokens.inputTokens;
2090
+ summary.outputTokens += record.tokens.outputTokens;
2091
+ if (record.success) {
2092
+ summary.successCount++;
2093
+ } else {
2094
+ summary.errorCount++;
2095
+ }
2096
+ if (record.latencyMs) {
2097
+ totalLatency += record.latencyMs;
2098
+ latencyCount++;
2099
+ }
2100
+ }
2101
+ if (summary.requestCount > 0) {
2102
+ summary.avgCostPerRequest = summary.totalCost / summary.requestCount;
2103
+ summary.avgTokensPerRequest = summary.totalTokens / summary.requestCount;
2104
+ }
2105
+ if (latencyCount > 0) {
2106
+ summary.avgLatencyMs = totalLatency / latencyCount;
2107
+ }
2108
+ return summary;
2109
+ }
2110
+ async getCostsByDimension(dimension, options) {
2111
+ const records = await this.queryCostRecords(options);
2112
+ const groups = /* @__PURE__ */ new Map();
2113
+ let totalCost = 0;
2114
+ for (const record of records) {
2115
+ const value = this.getDimensionValue(record, dimension);
2116
+ if (!value) continue;
2117
+ const existing = groups.get(value) ?? { cost: 0, tokens: 0, count: 0 };
2118
+ existing.cost += record.cost.totalCost;
2119
+ existing.tokens += record.tokens.totalTokens;
2120
+ existing.count++;
2121
+ groups.set(value, existing);
2122
+ totalCost += record.cost.totalCost;
2123
+ }
2124
+ return Array.from(groups.entries()).map(([value, data]) => ({
2125
+ dimension,
2126
+ value,
2127
+ totalCost: data.cost,
2128
+ totalTokens: data.tokens,
2129
+ requestCount: data.count,
2130
+ percentage: totalCost > 0 ? data.cost / totalCost * 100 : 0
2131
+ }));
2132
+ }
2133
+ async getCostTrends(options) {
2134
+ const records = await this.queryCostRecords(options);
2135
+ const granularity = options.granularity ?? "day";
2136
+ const buckets = /* @__PURE__ */ new Map();
2137
+ for (const record of records) {
2138
+ const bucketTime = this.getBucketTime(record.timestamp, granularity);
2139
+ const existing = buckets.get(bucketTime) ?? {
2140
+ timestamp: new Date(bucketTime),
2141
+ cost: 0,
2142
+ tokens: 0,
2143
+ requests: 0
2144
+ };
2145
+ existing.cost += record.cost.totalCost;
2146
+ existing.tokens += record.tokens.totalTokens;
2147
+ existing.requests++;
2148
+ buckets.set(bucketTime, existing);
2149
+ }
2150
+ return Array.from(buckets.values()).sort(
2151
+ (a, b) => a.timestamp.getTime() - b.timestamp.getTime()
2152
+ );
2153
+ }
2154
+ deleteCostRecords(ids) {
2155
+ let deleted = 0;
2156
+ for (const id of ids) {
2157
+ if (this.records.delete(id)) {
2158
+ deleted++;
2159
+ }
2160
+ }
2161
+ return Promise.resolve(deleted);
2162
+ }
2163
+ async deleteCostRecordsByFilter(options) {
2164
+ const toDelete = await this.queryCostRecords(options);
2165
+ return this.deleteCostRecords(toDelete.map((r) => r.id));
2166
+ }
2167
+ // ==================== Budgets ====================
2168
+ saveBudget(budget) {
2169
+ this.budgets.set(budget.id, budget);
2170
+ return Promise.resolve();
2171
+ }
2172
+ getBudget(id) {
2173
+ return Promise.resolve(this.budgets.get(id) ?? null);
2174
+ }
2175
+ listBudgets(options) {
2176
+ let budgets = Array.from(this.budgets.values());
2177
+ if (options?.scope) {
2178
+ budgets = budgets.filter((b) => b.scope === options.scope);
2179
+ }
2180
+ if (options?.scopeId) {
2181
+ budgets = budgets.filter((b) => b.scopeId === options.scopeId);
2182
+ }
2183
+ if (options?.enabled !== void 0) {
2184
+ budgets = budgets.filter((b) => b.enabled === options.enabled);
2185
+ }
2186
+ return Promise.resolve(budgets);
2187
+ }
2188
+ updateBudget(id, updates) {
2189
+ const budget = this.budgets.get(id);
2190
+ if (budget) {
2191
+ this.budgets.set(id, { ...budget, ...updates });
2192
+ }
2193
+ return Promise.resolve();
2194
+ }
2195
+ deleteBudget(id) {
2196
+ return Promise.resolve(this.budgets.delete(id));
2197
+ }
2198
+ getBudgetUsage(budgetId) {
2199
+ const budget = this.budgets.get(budgetId);
2200
+ if (!budget) {
2201
+ throw new Error(`Budget not found: ${budgetId}`);
2202
+ }
2203
+ const now = /* @__PURE__ */ new Date();
2204
+ const periodStart = this.getPeriodStart(budget.period, now);
2205
+ const records = Array.from(this.records.values()).filter(
2206
+ (r) => r.timestamp >= periodStart && this.matchesBudgetScope(r, budget)
2207
+ );
2208
+ const currentUsage = records.reduce((sum, r) => sum + r.cost.totalCost, 0);
2209
+ return Promise.resolve({
2210
+ budgetId,
2211
+ currentUsage,
2212
+ limit: budget.limit,
2213
+ usagePercentage: currentUsage / budget.limit * 100,
2214
+ remaining: Math.max(0, budget.limit - currentUsage),
2215
+ periodStart,
2216
+ periodEnd: this.getPeriodEnd(budget.period, periodStart),
2217
+ timeRemaining: this.getPeriodEnd(budget.period, periodStart).getTime() - now.getTime(),
2218
+ status: currentUsage >= budget.limit ? "exceeded" : "active",
2219
+ triggeredThresholds: []
2220
+ });
2221
+ }
2222
+ saveBudgetHistory(entry) {
2223
+ const existing = this.budgetHistory.get(entry.budgetId) ?? [];
2224
+ existing.push(entry);
2225
+ this.budgetHistory.set(entry.budgetId, existing);
2226
+ return Promise.resolve();
2227
+ }
2228
+ getBudgetHistory(budgetId, limit) {
2229
+ const history = this.budgetHistory.get(budgetId) ?? [];
2230
+ return Promise.resolve(limit ? history.slice(-limit) : history);
2231
+ }
2232
+ saveBudgetAlert(alert) {
2233
+ const existing = this.budgetAlerts.get(alert.budgetId) ?? [];
2234
+ existing.push(alert);
2235
+ this.budgetAlerts.set(alert.budgetId, existing);
2236
+ return Promise.resolve();
2237
+ }
2238
+ getBudgetAlerts(budgetId) {
2239
+ return Promise.resolve(this.budgetAlerts.get(budgetId) ?? []);
2240
+ }
2241
+ // ==================== Attribution ====================
2242
+ saveAttributedCost(attributed) {
2243
+ this.attributedCosts.push(attributed);
2244
+ return Promise.resolve();
2245
+ }
2246
+ saveAttributedCosts(attributed) {
2247
+ this.attributedCosts.push(...attributed);
2248
+ return Promise.resolve();
2249
+ }
2250
+ getAttributionSummary(dimension, options) {
2251
+ const costs = this.attributedCosts.filter(
2252
+ (c) => c.dimension === dimension && (!options.startDate || c.timestamp >= options.startDate) && (!options.endDate || c.timestamp <= options.endDate)
2253
+ );
2254
+ const groups = /* @__PURE__ */ new Map();
2255
+ let totalCost = 0;
2256
+ for (const cost of costs) {
2257
+ const existing = groups.get(cost.dimensionValue) ?? {
2258
+ cost: 0,
2259
+ tokens: 0,
2260
+ requests: 0
2261
+ };
2262
+ existing.cost += cost.attributedCost;
2263
+ existing.requests++;
2264
+ groups.set(cost.dimensionValue, existing);
2265
+ totalCost += cost.attributedCost;
2266
+ }
2267
+ return Promise.resolve({
2268
+ dimension,
2269
+ breakdown: Array.from(groups.entries()).map(([value, data]) => ({
2270
+ value,
2271
+ cost: data.cost,
2272
+ tokens: data.tokens,
2273
+ requests: data.requests,
2274
+ percentage: totalCost > 0 ? data.cost / totalCost * 100 : 0
2275
+ })),
2276
+ totalCost,
2277
+ periodStart: options.startDate ?? /* @__PURE__ */ new Date(0),
2278
+ periodEnd: options.endDate ?? /* @__PURE__ */ new Date()
2279
+ });
2280
+ }
2281
+ // ==================== Alerts ====================
2282
+ saveAlertRule(rule) {
2283
+ this.alertRules.set(rule.id, rule);
2284
+ return Promise.resolve();
2285
+ }
2286
+ getAlertRule(id) {
2287
+ return Promise.resolve(this.alertRules.get(id) ?? null);
2288
+ }
2289
+ listAlertRules(options) {
2290
+ let rules = Array.from(this.alertRules.values());
2291
+ if (options?.enabled !== void 0) {
2292
+ rules = rules.filter((r) => r.enabled === options.enabled);
2293
+ }
2294
+ return Promise.resolve(rules);
2295
+ }
2296
+ updateAlertRule(id, updates) {
2297
+ const rule = this.alertRules.get(id);
2298
+ if (rule) {
2299
+ this.alertRules.set(id, { ...rule, ...updates });
2300
+ }
2301
+ return Promise.resolve();
2302
+ }
2303
+ deleteAlertRule(id) {
2304
+ return Promise.resolve(this.alertRules.delete(id));
2305
+ }
2306
+ saveAlert(alert) {
2307
+ this.alerts.set(alert.id, alert);
2308
+ return Promise.resolve();
2309
+ }
2310
+ getAlert(id) {
2311
+ return Promise.resolve(this.alerts.get(id) ?? null);
2312
+ }
2313
+ queryAlerts(options) {
2314
+ let alerts = Array.from(this.alerts.values());
2315
+ if (options?.status) {
2316
+ alerts = alerts.filter((a) => options.status.includes(a.status));
2317
+ }
2318
+ if (options?.severity) {
2319
+ alerts = alerts.filter((a) => options.severity.includes(a.severity));
2320
+ }
2321
+ if (options?.startDate) {
2322
+ alerts = alerts.filter((a) => a.triggeredAt >= options.startDate);
2323
+ }
2324
+ if (options?.endDate) {
2325
+ alerts = alerts.filter((a) => a.triggeredAt <= options.endDate);
2326
+ }
2327
+ alerts.sort((a, b) => b.triggeredAt.getTime() - a.triggeredAt.getTime());
2328
+ if (options?.offset) {
2329
+ alerts = alerts.slice(options.offset);
2330
+ }
2331
+ if (options?.limit) {
2332
+ alerts = alerts.slice(0, options.limit);
2333
+ }
2334
+ return Promise.resolve(alerts);
2335
+ }
2336
+ updateAlert(id, updates) {
2337
+ const alert = this.alerts.get(id);
2338
+ if (alert) {
2339
+ this.alerts.set(id, { ...alert, ...updates });
2340
+ }
2341
+ return Promise.resolve();
2342
+ }
2343
+ // ==================== Maintenance ====================
2344
+ cleanup(olderThan) {
2345
+ const toDelete = [];
2346
+ for (const [id, record] of this.records) {
2347
+ if (record.timestamp < olderThan) {
2348
+ toDelete.push(id);
2349
+ }
2350
+ }
2351
+ for (const id of toDelete) {
2352
+ this.records.delete(id);
2353
+ }
2354
+ return Promise.resolve(toDelete.length);
2355
+ }
2356
+ getStats() {
2357
+ const records = Array.from(this.records.values());
2358
+ let oldestRecord;
2359
+ let newestRecord;
2360
+ for (const record of records) {
2361
+ if (!oldestRecord || record.timestamp < oldestRecord) {
2362
+ oldestRecord = record.timestamp;
2363
+ }
2364
+ if (!newestRecord || record.timestamp > newestRecord) {
2365
+ newestRecord = record.timestamp;
2366
+ }
2367
+ }
2368
+ return Promise.resolve({
2369
+ costRecordCount: this.records.size,
2370
+ budgetCount: this.budgets.size,
2371
+ alertRuleCount: this.alertRules.size,
2372
+ alertCount: this.alerts.size,
2373
+ oldestRecord,
2374
+ newestRecord
2375
+ });
2376
+ }
2377
+ async optimize() {
2378
+ }
2379
+ // ==================== Helper Methods ====================
2380
+ enforceLimit() {
2381
+ if (this.config.maxRecords && this.records.size >= this.config.maxRecords) {
2382
+ const sorted = Array.from(this.records.entries()).sort(
2383
+ (a, b) => a[1].timestamp.getTime() - b[1].timestamp.getTime()
2384
+ );
2385
+ const toRemove = sorted.slice(
2386
+ 0,
2387
+ Math.floor(this.config.maxRecords * 0.1)
2388
+ );
2389
+ for (const [id] of toRemove) {
2390
+ this.records.delete(id);
2391
+ }
2392
+ }
2393
+ }
2394
+ applyFilters(records, options) {
2395
+ return records.filter((record) => {
2396
+ if (options.startDate && record.timestamp < options.startDate)
2397
+ return false;
2398
+ if (options.endDate && record.timestamp > options.endDate) return false;
2399
+ if (options.providers && !options.providers.includes(record.provider))
2400
+ return false;
2401
+ if (options.models && !options.models.includes(record.model))
2402
+ return false;
2403
+ if (options.userIds && !options.userIds.includes(record.attribution?.userId ?? ""))
2404
+ return false;
2405
+ if (options.agentIds && !options.agentIds.includes(record.attribution?.agentId ?? ""))
2406
+ return false;
2407
+ if (options.sessionIds && !options.sessionIds.includes(record.attribution?.sessionId ?? ""))
2408
+ return false;
2409
+ if (options.projectIds && !options.projectIds.includes(record.attribution?.projectId ?? ""))
2410
+ return false;
2411
+ if (options.teamIds && !options.teamIds.includes(record.attribution?.teamId ?? ""))
2412
+ return false;
2413
+ if (options.features && !options.features.includes(record.attribution?.feature ?? ""))
2414
+ return false;
2415
+ if (options.environment && record.attribution?.environment !== options.environment)
2416
+ return false;
2417
+ if (options.success !== void 0 && record.success !== options.success)
2418
+ return false;
2419
+ return true;
2420
+ });
2421
+ }
2422
+ applySort(records, options) {
2423
+ const sortBy = options.sortBy ?? "timestamp";
2424
+ const sortOrder = options.sortOrder ?? "desc";
2425
+ return records.sort((a, b) => {
2426
+ let comparison = 0;
2427
+ switch (sortBy) {
2428
+ case "cost":
2429
+ comparison = a.cost.totalCost - b.cost.totalCost;
2430
+ break;
2431
+ case "tokens":
2432
+ comparison = a.tokens.totalTokens - b.tokens.totalTokens;
2433
+ break;
2434
+ case "timestamp":
2435
+ default:
2436
+ comparison = a.timestamp.getTime() - b.timestamp.getTime();
2437
+ break;
2438
+ }
2439
+ return sortOrder === "asc" ? comparison : -comparison;
2440
+ });
2441
+ }
2442
+ applyPagination(records, options) {
2443
+ let result = records;
2444
+ if (options.offset) {
2445
+ result = result.slice(options.offset);
2446
+ }
2447
+ if (options.limit) {
2448
+ result = result.slice(0, options.limit);
2449
+ }
2450
+ return result;
2451
+ }
2452
+ getDimensionValue(record, dimension) {
2453
+ switch (dimension) {
2454
+ case "provider":
2455
+ return record.provider;
2456
+ case "model":
2457
+ return record.model;
2458
+ case "user":
2459
+ return record.attribution?.userId;
2460
+ case "agent":
2461
+ return record.attribution?.agentId;
2462
+ case "session":
2463
+ return record.attribution?.sessionId;
2464
+ case "project":
2465
+ return record.attribution?.projectId;
2466
+ case "team":
2467
+ return record.attribution?.teamId;
2468
+ case "feature":
2469
+ return record.attribution?.feature;
2470
+ case "environment":
2471
+ return record.attribution?.environment;
2472
+ default:
2473
+ return record.attribution?.labels?.[dimension];
2474
+ }
2475
+ }
2476
+ getBucketTime(date, granularity) {
2477
+ const d = new Date(date);
2478
+ switch (granularity) {
2479
+ case "minute":
2480
+ d.setSeconds(0, 0);
2481
+ break;
2482
+ case "hour":
2483
+ d.setMinutes(0, 0, 0);
2484
+ break;
2485
+ case "day":
2486
+ d.setHours(0, 0, 0, 0);
2487
+ break;
2488
+ case "week": {
2489
+ const day = d.getDay();
2490
+ d.setDate(d.getDate() - day);
2491
+ d.setHours(0, 0, 0, 0);
2492
+ break;
2493
+ }
2494
+ case "month":
2495
+ d.setDate(1);
2496
+ d.setHours(0, 0, 0, 0);
2497
+ break;
2498
+ }
2499
+ return d.getTime();
2500
+ }
2501
+ matchesBudgetScope(record, budget) {
2502
+ if (budget.scope === "global") return true;
2503
+ switch (budget.scope) {
2504
+ case "user":
2505
+ return record.attribution?.userId === budget.scopeId;
2506
+ case "agent":
2507
+ return record.attribution?.agentId === budget.scopeId;
2508
+ case "project":
2509
+ return record.attribution?.projectId === budget.scopeId;
2510
+ case "team":
2511
+ return record.attribution?.teamId === budget.scopeId;
2512
+ case "feature":
2513
+ return record.attribution?.feature === budget.scopeId;
2514
+ case "model":
2515
+ return record.model === budget.scopeId;
2516
+ case "provider":
2517
+ return record.provider === budget.scopeId;
2518
+ default:
2519
+ return false;
2520
+ }
2521
+ }
2522
+ getPeriodStart(period, now) {
2523
+ const d = new Date(now);
2524
+ switch (period) {
2525
+ case "hourly":
2526
+ d.setMinutes(0, 0, 0);
2527
+ break;
2528
+ case "daily":
2529
+ d.setHours(0, 0, 0, 0);
2530
+ break;
2531
+ case "weekly":
2532
+ d.setDate(d.getDate() - d.getDay());
2533
+ d.setHours(0, 0, 0, 0);
2534
+ break;
2535
+ case "monthly":
2536
+ d.setDate(1);
2537
+ d.setHours(0, 0, 0, 0);
2538
+ break;
2539
+ case "quarterly":
2540
+ d.setMonth(Math.floor(d.getMonth() / 3) * 3, 1);
2541
+ d.setHours(0, 0, 0, 0);
2542
+ break;
2543
+ case "yearly":
2544
+ d.setMonth(0, 1);
2545
+ d.setHours(0, 0, 0, 0);
2546
+ break;
2547
+ }
2548
+ return d;
2549
+ }
2550
+ getPeriodEnd(period, start) {
2551
+ const d = new Date(start);
2552
+ switch (period) {
2553
+ case "hourly":
2554
+ d.setHours(d.getHours() + 1);
2555
+ break;
2556
+ case "daily":
2557
+ d.setDate(d.getDate() + 1);
2558
+ break;
2559
+ case "weekly":
2560
+ d.setDate(d.getDate() + 7);
2561
+ break;
2562
+ case "monthly":
2563
+ d.setMonth(d.getMonth() + 1);
2564
+ break;
2565
+ case "quarterly":
2566
+ d.setMonth(d.getMonth() + 3);
2567
+ break;
2568
+ case "yearly":
2569
+ d.setFullYear(d.getFullYear() + 1);
2570
+ break;
2571
+ }
2572
+ return d;
2573
+ }
2574
+ /**
2575
+ * Clear all data
2576
+ */
2577
+ clear() {
2578
+ this.records.clear();
2579
+ this.budgets.clear();
2580
+ this.budgetHistory.clear();
2581
+ this.budgetAlerts.clear();
2582
+ this.attributedCosts = [];
2583
+ this.alertRules.clear();
2584
+ this.alerts.clear();
2585
+ }
2586
+ };
2587
+ var CostProvider = class extends EventEmitter {
2588
+ costManager;
2589
+ budgetManager;
2590
+ enforceBudgets;
2591
+ defaultAttribution;
2592
+ initialized = false;
2593
+ constructor(config = {}) {
2594
+ super();
2595
+ this.enforceBudgets = config.enforceBudgets ?? false;
2596
+ this.defaultAttribution = config.defaultAttribution;
2597
+ this.costManager = new CostManager({
2598
+ ...config.costManagerOptions,
2599
+ storage: config.storage,
2600
+ defaultAttribution: config.defaultAttribution
2601
+ });
2602
+ if (this.enforceBudgets) {
2603
+ this.budgetManager = new BudgetManager(
2604
+ config.budgetManagerOptions ?? {},
2605
+ config.storage
2606
+ );
2607
+ }
2608
+ this.setupEventForwarding();
2609
+ }
2610
+ /**
2611
+ * Initialize the provider
2612
+ */
2613
+ async initialize() {
2614
+ if (this.initialized) return;
2615
+ await this.costManager.initialize();
2616
+ if (this.budgetManager) {
2617
+ await this.budgetManager.initialize();
2618
+ }
2619
+ this.initialized = true;
2620
+ }
2621
+ /**
2622
+ * Close the provider
2623
+ */
2624
+ async close() {
2625
+ await this.costManager.close();
2626
+ if (this.budgetManager) {
2627
+ this.budgetManager.close();
2628
+ }
2629
+ this.initialized = false;
2630
+ }
2631
+ /**
2632
+ * Create an agent cost tracker
2633
+ */
2634
+ createAgentTracker(context) {
2635
+ return new AgentCostTracker(
2636
+ this.costManager.scoped({
2637
+ agentId: context.agentId,
2638
+ sessionId: context.sessionId,
2639
+ userId: context.userId,
2640
+ labels: context.labels
2641
+ }),
2642
+ this.budgetManager,
2643
+ this.enforceBudgets
2644
+ );
2645
+ }
2646
+ /**
2647
+ * Track an API call
2648
+ */
2649
+ async track(provider, model, inputTokens, outputTokens, options) {
2650
+ return this.costManager.track({
2651
+ provider,
2652
+ model,
2653
+ tokens: {
2654
+ inputTokens,
2655
+ outputTokens,
2656
+ totalTokens: inputTokens + outputTokens
2657
+ },
2658
+ latencyMs: options?.latencyMs,
2659
+ success: options?.success ?? true,
2660
+ error: options?.error,
2661
+ attribution: options?.attribution,
2662
+ metadata: options?.metadata
2663
+ });
2664
+ }
2665
+ /**
2666
+ * Check budget before making a call
2667
+ */
2668
+ async checkBudget(estimatedCost, attribution) {
2669
+ if (!this.budgetManager) {
2670
+ return {
2671
+ allowed: true,
2672
+ matchingBudgets: [],
2673
+ exceededBudgets: [],
2674
+ warningBudgets: [],
2675
+ action: "allow"
2676
+ };
2677
+ }
2678
+ return this.budgetManager.checkBudget({
2679
+ estimatedCost,
2680
+ attribution: {
2681
+ ...this.defaultAttribution,
2682
+ ...attribution
2683
+ }
2684
+ });
2685
+ }
2686
+ /**
2687
+ * Get cost summary
2688
+ */
2689
+ async getSummary(options) {
2690
+ return this.costManager.getSummary({
2691
+ startDate: options?.startDate,
2692
+ endDate: options?.endDate,
2693
+ agentIds: options?.agentId ? [options.agentId] : void 0,
2694
+ userIds: options?.userId ? [options.userId] : void 0
2695
+ });
2696
+ }
2697
+ /**
2698
+ * Get cost manager
2699
+ */
2700
+ getCostManager() {
2701
+ return this.costManager;
2702
+ }
2703
+ /**
2704
+ * Get budget manager
2705
+ */
2706
+ getBudgetManager() {
2707
+ return this.budgetManager;
2708
+ }
2709
+ /**
2710
+ * Estimate cost for a request
2711
+ */
2712
+ async estimateCost(input, model, options) {
2713
+ const result = await this.costManager.estimateCost(input, {
2714
+ model,
2715
+ provider: options?.provider,
2716
+ estimatedOutputTokens: options?.estimatedOutputTokens
2717
+ });
2718
+ return result.estimatedCost;
2719
+ }
2720
+ /**
2721
+ * Setup event forwarding
2722
+ */
2723
+ setupEventForwarding() {
2724
+ this.costManager.on("cost:recorded", (record) => {
2725
+ this.emit("cost:recorded", record);
2726
+ });
2727
+ this.costManager.on("cost:batch", (batch) => {
2728
+ this.emit("cost:batch", batch);
2729
+ });
2730
+ this.costManager.on("error", (error) => {
2731
+ this.emit("error", error);
2732
+ });
2733
+ if (this.budgetManager) {
2734
+ this.budgetManager.on("budget:warning", (alert) => {
2735
+ this.emit("budget:warning", {
2736
+ budgetId: alert.budgetId,
2737
+ message: alert.message
2738
+ });
2739
+ });
2740
+ this.budgetManager.on("budget:exceeded", (alert) => {
2741
+ this.emit("budget:exceeded", {
2742
+ budgetId: alert.budgetId,
2743
+ message: alert.message
2744
+ });
2745
+ });
2746
+ }
2747
+ }
2748
+ };
2749
+ var AgentCostTracker = class _AgentCostTracker {
2750
+ tracker;
2751
+ budgetManager;
2752
+ enforceBudgets;
2753
+ constructor(tracker, budgetManager, enforceBudgets = false) {
2754
+ this.tracker = tracker;
2755
+ this.budgetManager = budgetManager;
2756
+ this.enforceBudgets = enforceBudgets;
2757
+ }
2758
+ /**
2759
+ * Track an API call
2760
+ */
2761
+ async track(provider, model, inputTokens, outputTokens, options) {
2762
+ return this.tracker.track({
2763
+ provider,
2764
+ model,
2765
+ tokens: {
2766
+ inputTokens,
2767
+ outputTokens,
2768
+ totalTokens: inputTokens + outputTokens
2769
+ },
2770
+ latencyMs: options?.latencyMs,
2771
+ success: options?.success ?? true,
2772
+ error: options?.error,
2773
+ metadata: options?.metadata
2774
+ });
2775
+ }
2776
+ /**
2777
+ * Track Anthropic response
2778
+ */
2779
+ async trackAnthropicResponse(response, options) {
2780
+ return this.tracker.trackAnthropicResponse(response, options);
2781
+ }
2782
+ /**
2783
+ * Track OpenAI response
2784
+ */
2785
+ async trackOpenAIResponse(response, options) {
2786
+ return this.tracker.trackOpenAIResponse(response, options);
2787
+ }
2788
+ /**
2789
+ * Check budget before making a call
2790
+ */
2791
+ async checkBudget(estimatedCost) {
2792
+ if (!this.budgetManager) {
2793
+ return {
2794
+ allowed: true,
2795
+ matchingBudgets: [],
2796
+ exceededBudgets: [],
2797
+ warningBudgets: [],
2798
+ action: "allow"
2799
+ };
2800
+ }
2801
+ return this.budgetManager.checkBudget({
2802
+ estimatedCost
2803
+ });
2804
+ }
2805
+ /**
2806
+ * Wrap an async function with cost tracking
2807
+ */
2808
+ wrap(fn, options) {
2809
+ return async (...args) => {
2810
+ const startTime = Date.now();
2811
+ try {
2812
+ const result = await fn(...args);
2813
+ const latencyMs = Date.now() - startTime;
2814
+ let inputTokens = 0;
2815
+ let outputTokens = 0;
2816
+ if (options.extractUsage) {
2817
+ const usage = options.extractUsage(result);
2818
+ inputTokens = usage.inputTokens;
2819
+ outputTokens = usage.outputTokens;
2820
+ } else if (result.usage) {
2821
+ inputTokens = result.usage.input_tokens ?? result.usage.prompt_tokens ?? 0;
2822
+ outputTokens = result.usage.output_tokens ?? result.usage.completion_tokens ?? 0;
2823
+ }
2824
+ await this.track(
2825
+ options.provider,
2826
+ result.model,
2827
+ inputTokens,
2828
+ outputTokens,
2829
+ { latencyMs }
2830
+ );
2831
+ return result;
2832
+ } catch (error) {
2833
+ const latencyMs = Date.now() - startTime;
2834
+ await this.tracker.track({
2835
+ provider: options.provider,
2836
+ model: "unknown",
2837
+ tokens: {
2838
+ inputTokens: 0,
2839
+ outputTokens: 0,
2840
+ totalTokens: 0
2841
+ },
2842
+ latencyMs,
2843
+ success: false,
2844
+ error: error instanceof Error ? error.message : String(error)
2845
+ });
2846
+ throw error;
2847
+ }
2848
+ };
2849
+ }
2850
+ /**
2851
+ * Create a nested scope
2852
+ */
2853
+ scoped(attribution) {
2854
+ return new _AgentCostTracker(
2855
+ this.tracker.scoped(attribution),
2856
+ this.budgetManager,
2857
+ this.enforceBudgets
2858
+ );
2859
+ }
2860
+ };
2861
+ function createCostProvider(config = {}) {
2862
+ return new CostProvider(config);
2863
+ }
2864
+
2865
+ export { AgentCostTracker, BudgetManager, BufferStorage, CostManager, CostProvider, CostTracker, ModelPricingRegistry, ScopedCostTracker, TokenCounter, countTokens, countTokensApprox, createCostManager, createCostProvider };
2866
+ //# sourceMappingURL=index.js.map
2867
+ //# sourceMappingURL=index.js.map