paygate-mcp 1.6.0 → 1.8.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.
Files changed (46) hide show
  1. package/README.md +134 -1
  2. package/dist/alerts.d.ts +85 -0
  3. package/dist/alerts.d.ts.map +1 -0
  4. package/dist/alerts.js +213 -0
  5. package/dist/alerts.js.map +1 -0
  6. package/dist/analytics.d.ts +117 -0
  7. package/dist/analytics.d.ts.map +1 -0
  8. package/dist/analytics.js +211 -0
  9. package/dist/analytics.js.map +1 -0
  10. package/dist/audit.d.ts +1 -1
  11. package/dist/audit.d.ts.map +1 -1
  12. package/dist/audit.js.map +1 -1
  13. package/dist/gate.d.ts +1 -1
  14. package/dist/gate.d.ts.map +1 -1
  15. package/dist/gate.js +12 -1
  16. package/dist/gate.js.map +1 -1
  17. package/dist/http-proxy.d.ts +1 -1
  18. package/dist/http-proxy.d.ts.map +1 -1
  19. package/dist/http-proxy.js +2 -2
  20. package/dist/http-proxy.js.map +1 -1
  21. package/dist/index.d.ts +4 -0
  22. package/dist/index.d.ts.map +1 -1
  23. package/dist/index.js +5 -1
  24. package/dist/index.js.map +1 -1
  25. package/dist/proxy.d.ts +1 -1
  26. package/dist/proxy.d.ts.map +1 -1
  27. package/dist/proxy.js +2 -2
  28. package/dist/proxy.js.map +1 -1
  29. package/dist/router.d.ts +1 -1
  30. package/dist/router.d.ts.map +1 -1
  31. package/dist/router.js +6 -6
  32. package/dist/router.js.map +1 -1
  33. package/dist/server.d.ts +12 -0
  34. package/dist/server.d.ts.map +1 -1
  35. package/dist/server.js +265 -1
  36. package/dist/server.js.map +1 -1
  37. package/dist/store.d.ts +37 -0
  38. package/dist/store.d.ts.map +1 -1
  39. package/dist/store.js +146 -0
  40. package/dist/store.js.map +1 -1
  41. package/dist/types.d.ts +10 -0
  42. package/dist/types.d.ts.map +1 -1
  43. package/dist/types.js.map +1 -1
  44. package/dist/webhook.d.ts +1 -1
  45. package/dist/webhook.d.ts.map +1 -1
  46. package/package.json +1 -1
package/README.md CHANGED
@@ -48,6 +48,10 @@ Agent → PayGate (auth + billing) → Your MCP Server (stdio or HTTP)
48
48
  - **Rate Limit Headers** — `X-RateLimit-*` and `X-Credits-Remaining` on every `/mcp` response
49
49
  - **Webhook Signatures** — HMAC-SHA256 signed webhook payloads (`X-PayGate-Signature`) for tamper-proof delivery
50
50
  - **Admin Lifecycle Events** — Webhook notifications for key.created, key.revoked, key.rotated, key.topup
51
+ - **IP Allowlisting** — Restrict API keys to specific IPs or CIDR ranges (IPv4)
52
+ - **Key Tags/Metadata** — Attach arbitrary key-value tags to API keys for external system integration
53
+ - **Usage Analytics** — Time-series analytics API with tool breakdown, top consumers, and trend comparison
54
+ - **Alert Webhooks** — Configurable alerts for spending thresholds, low credits, quota warnings, key expiry, rate limit spikes
51
55
  - **Refund on Failure** — Automatically refund credits when downstream tool calls fail
52
56
  - **Webhook Events** — POST batched usage events to any URL for external billing/alerting
53
57
  - **Config File Mode** — Load all settings from a JSON file (`--config`)
@@ -267,6 +271,9 @@ A real-time admin UI for managing keys, viewing usage, and monitoring tool calls
267
271
  | `/keys/acl` | POST | `X-Admin-Key` | Set tool ACL (whitelist/blacklist) on a key |
268
272
  | `/keys/expiry` | POST | `X-Admin-Key` | Set or remove key expiry (TTL) |
269
273
  | `/keys/quota` | POST | `X-Admin-Key` | Set usage quota (daily/monthly limits) |
274
+ | `/keys/tags` | POST | `X-Admin-Key` | Set key tags/metadata (merge semantics) |
275
+ | `/keys/ip` | POST | `X-Admin-Key` | Set IP allowlist (CIDR + exact match) |
276
+ | `/keys/search` | POST | `X-Admin-Key` | Search keys by tag values |
270
277
  | `/limits` | POST | `X-Admin-Key` | Set spending limit on a key |
271
278
  | `/usage` | GET | `X-Admin-Key` | Export usage data (JSON or CSV) |
272
279
  | `/status` | GET | `X-Admin-Key` | Full dashboard with usage stats |
@@ -281,6 +288,9 @@ A real-time admin UI for managing keys, viewing usage, and monitoring tool calls
281
288
  | `/.well-known/mcp-payment` | GET | None | Server payment metadata (SEP-2007) |
282
289
  | `/pricing` | GET | None | Full per-tool pricing breakdown |
283
290
  | `/metrics` | GET | None | Prometheus metrics (counters, gauges, uptime) |
291
+ | `/analytics` | GET | `X-Admin-Key` | Usage analytics (time-series, tool breakdown, trends) |
292
+ | `/alerts` | GET | `X-Admin-Key` | Consume pending alerts |
293
+ | `/alerts` | POST | `X-Admin-Key` | Configure alert rules |
284
294
  | `/audit` | GET | `X-Admin-Key` | Query audit log (filter by type, actor, time) |
285
295
  | `/audit/export` | GET | `X-Admin-Key` | Export full audit log (JSON or CSV) |
286
296
  | `/audit/stats` | GET | `X-Admin-Key` | Audit log statistics |
@@ -505,6 +515,8 @@ When webhooks are enabled, admin operations also fire webhook events:
505
515
  | `key.topup` | POST /topup | keyMasked, creditsAdded, newBalance |
506
516
  | `key.revoked` | POST /keys/revoke | keyMasked |
507
517
  | `key.rotated` | POST /keys/rotate | oldKeyMasked, newKeyMasked |
518
+ | `key.expired` | Gate evaluation | keyMasked |
519
+ | `alert.fired` | Gate evaluation | alertType, keyPrefix, message, value, threshold |
508
520
 
509
521
  Admin events appear in the `adminEvents` array of the webhook payload (separate from usage `events`). Both arrays can be present in the same batch.
510
522
 
@@ -644,7 +656,7 @@ curl http://localhost:3402/audit/stats \
644
656
  -H "X-Admin-Key: YOUR_ADMIN_KEY"
645
657
  ```
646
658
 
647
- **Tracked events:** `key.created`, `key.revoked`, `key.topup`, `key.acl_updated`, `key.expiry_updated`, `key.quota_updated`, `key.limit_updated`, `gate.allow`, `gate.deny`, `session.created`, `session.destroyed`, `oauth.client_registered`, `oauth.token_issued`, `oauth.token_revoked`, `admin.auth_failed`, `billing.refund`.
659
+ **Tracked events:** `key.created`, `key.revoked`, `key.topup`, `key.acl_updated`, `key.expiry_updated`, `key.quota_updated`, `key.limit_updated`, `key.tags_updated`, `key.ip_updated`, `gate.allow`, `gate.deny`, `session.created`, `session.destroyed`, `oauth.client_registered`, `oauth.token_issued`, `oauth.token_revoked`, `admin.auth_failed`, `admin.alerts_configured`, `billing.refund`.
648
660
 
649
661
  **Retention:** Ring buffer (default 10,000 events), age-based cleanup (default 30 days), automatic periodic enforcement.
650
662
 
@@ -726,6 +738,123 @@ curl -X POST http://localhost:3402/keys/rotate \
726
738
 
727
739
  The old key is immediately invalidated. All state (credits, totalSpent, totalCalls, ACL, quota, expiry, spending limit) transfers to the new key. Use this for periodic key rotation policies, compromised key response, or key migration.
728
740
 
741
+ ### IP Allowlisting
742
+
743
+ Restrict API keys to specific IP addresses or CIDR ranges:
744
+
745
+ ```bash
746
+ # Set IP allowlist on a key (replaces existing list)
747
+ curl -X POST http://localhost:3402/keys/ip \
748
+ -H "X-Admin-Key: YOUR_ADMIN_KEY" \
749
+ -d '{"key": "pg_...", "ips": ["192.168.1.0/24", "10.0.0.5"]}'
750
+
751
+ # Clear allowlist (allow all IPs)
752
+ curl -X POST http://localhost:3402/keys/ip \
753
+ -H "X-Admin-Key: YOUR_ADMIN_KEY" \
754
+ -d '{"key": "pg_...", "ips": []}'
755
+ ```
756
+
757
+ You can also set the allowlist at key creation time:
758
+
759
+ ```bash
760
+ curl -X POST http://localhost:3402/keys \
761
+ -H "X-Admin-Key: YOUR_ADMIN_KEY" \
762
+ -d '{"name": "prod-agent", "credits": 1000, "ipAllowlist": ["10.0.0.0/8"]}'
763
+ ```
764
+
765
+ Supports exact IPv4 matching and CIDR notation (`/8`, `/16`, `/24`, `/32`, etc.). When the allowlist is empty, all IPs are allowed. Client IP is extracted from `X-Forwarded-For` header (first value) or socket remote address.
766
+
767
+ ### Key Tags / Metadata
768
+
769
+ Attach arbitrary key-value tags to API keys for external system integration:
770
+
771
+ ```bash
772
+ # Set tags (merge semantics — existing tags preserved, new ones added/updated)
773
+ curl -X POST http://localhost:3402/keys/tags \
774
+ -H "X-Admin-Key: YOUR_ADMIN_KEY" \
775
+ -d '{"key": "pg_...", "tags": {"team": "backend", "env": "production", "customer_id": "cus_123"}}'
776
+
777
+ # Remove a tag (set value to null)
778
+ curl -X POST http://localhost:3402/keys/tags \
779
+ -H "X-Admin-Key: YOUR_ADMIN_KEY" \
780
+ -d '{"key": "pg_...", "tags": {"env": null}}'
781
+
782
+ # Search keys by tags
783
+ curl -X POST http://localhost:3402/keys/search \
784
+ -H "X-Admin-Key: YOUR_ADMIN_KEY" \
785
+ -d '{"tags": {"team": "backend"}}'
786
+ # → { "keys": [...], "count": 3 }
787
+ ```
788
+
789
+ Tags can also be set at key creation:
790
+
791
+ ```bash
792
+ curl -X POST http://localhost:3402/keys \
793
+ -H "X-Admin-Key: YOUR_ADMIN_KEY" \
794
+ -d '{"name": "backend-prod", "credits": 5000, "tags": {"team": "backend", "env": "production"}}'
795
+ ```
796
+
797
+ Limits: max 50 tags per key, max 100 chars per key/value. Tags appear in `/balance` responses and key listings.
798
+
799
+ ### Usage Analytics
800
+
801
+ Query aggregated usage data for dashboards, reports, and trend analysis:
802
+
803
+ ```bash
804
+ # Get analytics for the last 24 hours (hourly buckets)
805
+ curl "http://localhost:3402/analytics?from=2026-02-25T00:00:00Z&to=2026-02-26T00:00:00Z&granularity=hourly" \
806
+ -H "X-Admin-Key: YOUR_ADMIN_KEY"
807
+
808
+ # Daily granularity with top 5 consumers
809
+ curl "http://localhost:3402/analytics?granularity=daily&topN=5" \
810
+ -H "X-Admin-Key: YOUR_ADMIN_KEY"
811
+ ```
812
+
813
+ Returns:
814
+ - **timeSeries** — Bucketed call counts, credits charged, and denials per time window
815
+ - **toolBreakdown** — Per-tool stats (calls, credits, average cost) sorted by usage
816
+ - **topConsumers** — Top N API keys by credits spent, with each key's most-used tool
817
+ - **trend** — Current vs previous period comparison with percentage changes (calls, credits, denials)
818
+ - **summary** — Total calls, credits, unique keys, and unique tools
819
+
820
+ Query parameters: `from` (ISO date), `to` (ISO date), `granularity` (`hourly` or `daily`, default: `hourly`), `topN` (number, default: `10`).
821
+
822
+ ### Alert Webhooks
823
+
824
+ Configure rules to fire alerts when usage thresholds are crossed. Alerts are delivered via webhooks as `alert.fired` admin events:
825
+
826
+ ```bash
827
+ # Configure alert rules
828
+ curl -X POST http://localhost:3402/alerts \
829
+ -H "Content-Type: application/json" \
830
+ -H "X-Admin-Key: YOUR_ADMIN_KEY" \
831
+ -d '{"rules": [
832
+ {"type": "spending_threshold", "threshold": 80},
833
+ {"type": "credits_low", "threshold": 50},
834
+ {"type": "quota_warning", "threshold": 90},
835
+ {"type": "key_expiry_soon", "threshold": 86400},
836
+ {"type": "rate_limit_spike", "threshold": 10}
837
+ ]}'
838
+
839
+ # Consume pending alerts (returns and clears queue)
840
+ curl http://localhost:3402/alerts \
841
+ -H "X-Admin-Key: YOUR_ADMIN_KEY"
842
+ ```
843
+
844
+ **Alert types:**
845
+
846
+ | Type | Threshold Meaning | Fires When |
847
+ |------|-------------------|------------|
848
+ | `spending_threshold` | Percentage (0–100) | Key has spent ≥ threshold% of its initial credits |
849
+ | `credits_low` | Absolute credits | Key's remaining credits drop below threshold |
850
+ | `quota_warning` | Percentage (0–100) | Key's daily call usage exceeds threshold% of quota |
851
+ | `key_expiry_soon` | Seconds | Key expires within threshold seconds |
852
+ | `rate_limit_spike` | Count | Key has ≥ threshold rate-limit denials in 5 minutes |
853
+
854
+ Each rule has an optional `cooldownSeconds` (default: 300) to prevent alert storms. Alerts are automatically checked on every gate evaluation (tool call).
855
+
856
+ When webhooks are enabled (`--webhook-url`), alerts fire as `alert.fired` events in the `adminEvents` webhook payload with full context (key, rule type, current value, threshold).
857
+
729
858
  ### Rate Limit Response Headers
730
859
 
731
860
  Every `/mcp` response includes rate limit and credits headers when an API key is provided:
@@ -876,6 +1005,10 @@ const result = await client.callTool('search', { query: 'hello' });
876
1005
  - [x] Rate limit headers — X-RateLimit-* and X-Credits-Remaining on /mcp responses
877
1006
  - [x] Webhook signatures — HMAC-SHA256 signed payloads with timing-safe verification
878
1007
  - [x] Admin lifecycle events — Webhook notifications for key management operations
1008
+ - [x] IP allowlisting — Restrict API keys to specific IPs or CIDR ranges
1009
+ - [x] Key tags/metadata — Attach key-value tags for external system integration
1010
+ - [x] Usage analytics — Time-series analytics API with tool breakdown, trends, and top consumers
1011
+ - [x] Alert webhooks — Configurable threshold alerts (spending, credits, quota, expiry, rate limits)
879
1012
  - [ ] Horizontal scaling — Redis-backed state for multi-process deployments
880
1013
 
881
1014
  ## Requirements
@@ -0,0 +1,85 @@
1
+ /**
2
+ * AlertEngine — Configurable alerts for proactive monitoring.
3
+ *
4
+ * Alert types:
5
+ * - spending_threshold: Fires when a key's totalSpent crosses X% of its credits
6
+ * - credits_low: Fires when remaining credits fall below threshold
7
+ * - quota_warning: Fires when daily/monthly quota usage exceeds X%
8
+ * - key_expiry_soon: Fires when a key will expire within N seconds
9
+ * - rate_limit_spike: Fires when rate limit denials exceed N in a window
10
+ *
11
+ * Each alert fires at most once per key per cooldown period (default 1 hour).
12
+ */
13
+ import { ApiKeyRecord } from './types';
14
+ export type AlertType = 'spending_threshold' | 'credits_low' | 'quota_warning' | 'key_expiry_soon' | 'rate_limit_spike';
15
+ export interface AlertRule {
16
+ type: AlertType;
17
+ /** Threshold value — meaning depends on type:
18
+ * - spending_threshold: percentage (0-100) of initial credits spent
19
+ * - credits_low: absolute credits remaining
20
+ * - quota_warning: percentage (0-100) of quota used
21
+ * - key_expiry_soon: seconds before expiry to alert
22
+ * - rate_limit_spike: number of denials in a 5-min window
23
+ */
24
+ threshold: number;
25
+ /** Cooldown in seconds between repeated alerts for same key (default: 3600) */
26
+ cooldownSeconds?: number;
27
+ }
28
+ export interface Alert {
29
+ type: AlertType;
30
+ timestamp: string;
31
+ keyPrefix: string;
32
+ keyName: string;
33
+ message: string;
34
+ threshold: number;
35
+ currentValue: number;
36
+ metadata: Record<string, unknown>;
37
+ }
38
+ export interface AlertEngineConfig {
39
+ rules: AlertRule[];
40
+ /** If true, alerts are only logged but not emitted */
41
+ dryRun?: boolean;
42
+ }
43
+ export declare class AlertEngine {
44
+ private readonly rules;
45
+ private readonly dryRun;
46
+ /** Map of "alertType:keyPrefix" → last alert timestamp */
47
+ private readonly cooldowns;
48
+ /** Rate limit denial counter: "keyPrefix" → timestamps of recent denials */
49
+ private readonly rateLimitDenials;
50
+ /** Pending alerts waiting to be consumed */
51
+ private pendingAlerts;
52
+ constructor(config?: AlertEngineConfig);
53
+ /**
54
+ * Check a key record against all alert rules.
55
+ * Call this after every gate evaluation.
56
+ */
57
+ check(key: string, record: ApiKeyRecord, context?: {
58
+ rateLimitDenied?: boolean;
59
+ }): Alert[];
60
+ /**
61
+ * Record a rate limit denial for spike detection.
62
+ */
63
+ recordRateLimitDenial(key: string): void;
64
+ /**
65
+ * Consume pending alerts (returns and clears the queue).
66
+ */
67
+ consumeAlerts(): Alert[];
68
+ /**
69
+ * Get pending alert count.
70
+ */
71
+ get pendingCount(): number;
72
+ /**
73
+ * Get configured rules.
74
+ */
75
+ get configuredRules(): AlertRule[];
76
+ /**
77
+ * Evaluate a single rule against a key record.
78
+ */
79
+ private evaluateRule;
80
+ /**
81
+ * Clear cooldowns (for testing).
82
+ */
83
+ clearCooldowns(): void;
84
+ }
85
+ //# sourceMappingURL=alerts.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"alerts.d.ts","sourceRoot":"","sources":["../src/alerts.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAIvC,MAAM,MAAM,SAAS,GACjB,oBAAoB,GACpB,aAAa,GACb,eAAe,GACf,iBAAiB,GACjB,kBAAkB,CAAC;AAEvB,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,SAAS,CAAC;IAChB;;;;;;OAMG;IACH,SAAS,EAAE,MAAM,CAAC;IAClB,+EAA+E;IAC/E,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,KAAK;IACpB,IAAI,EAAE,SAAS,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACnC;AAED,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,SAAS,EAAE,CAAC;IACnB,sDAAsD;IACtD,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAID,qBAAa,WAAW;IACtB,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAc;IACpC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAU;IACjC,0DAA0D;IAC1D,OAAO,CAAC,QAAQ,CAAC,SAAS,CAA6B;IACvD,4EAA4E;IAC5E,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAA+B;IAChE,4CAA4C;IAC5C,OAAO,CAAC,aAAa,CAAe;gBAExB,MAAM,CAAC,EAAE,iBAAiB;IAKtC;;;OAGG;IACH,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,YAAY,EAAE,OAAO,CAAC,EAAE;QAAE,eAAe,CAAC,EAAE,OAAO,CAAA;KAAE,GAAG,KAAK,EAAE;IAyB1F;;OAEG;IACH,qBAAqB,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI;IAcxC;;OAEG;IACH,aAAa,IAAI,KAAK,EAAE;IAMxB;;OAEG;IACH,IAAI,YAAY,IAAI,MAAM,CAEzB;IAED;;OAEG;IACH,IAAI,eAAe,IAAI,SAAS,EAAE,CAEjC;IAED;;OAEG;IACH,OAAO,CAAC,YAAY;IAuHpB;;OAEG;IACH,cAAc,IAAI,IAAI;CAKvB"}
package/dist/alerts.js ADDED
@@ -0,0 +1,213 @@
1
+ "use strict";
2
+ /**
3
+ * AlertEngine — Configurable alerts for proactive monitoring.
4
+ *
5
+ * Alert types:
6
+ * - spending_threshold: Fires when a key's totalSpent crosses X% of its credits
7
+ * - credits_low: Fires when remaining credits fall below threshold
8
+ * - quota_warning: Fires when daily/monthly quota usage exceeds X%
9
+ * - key_expiry_soon: Fires when a key will expire within N seconds
10
+ * - rate_limit_spike: Fires when rate limit denials exceed N in a window
11
+ *
12
+ * Each alert fires at most once per key per cooldown period (default 1 hour).
13
+ */
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.AlertEngine = void 0;
16
+ // ─── Engine ───────────────────────────────────────────────────────────────────
17
+ class AlertEngine {
18
+ rules;
19
+ dryRun;
20
+ /** Map of "alertType:keyPrefix" → last alert timestamp */
21
+ cooldowns = new Map();
22
+ /** Rate limit denial counter: "keyPrefix" → timestamps of recent denials */
23
+ rateLimitDenials = new Map();
24
+ /** Pending alerts waiting to be consumed */
25
+ pendingAlerts = [];
26
+ constructor(config) {
27
+ this.rules = config?.rules || [];
28
+ this.dryRun = config?.dryRun || false;
29
+ }
30
+ /**
31
+ * Check a key record against all alert rules.
32
+ * Call this after every gate evaluation.
33
+ */
34
+ check(key, record, context) {
35
+ const fired = [];
36
+ const keyPrefix = key.slice(0, 10) + '...';
37
+ for (const rule of this.rules) {
38
+ const cooldownKey = `${rule.type}:${keyPrefix}`;
39
+ const cooldownMs = (rule.cooldownSeconds || 3600) * 1000;
40
+ const lastFired = this.cooldowns.get(cooldownKey) || 0;
41
+ const now = Date.now();
42
+ if (now - lastFired < cooldownMs)
43
+ continue;
44
+ const alert = this.evaluateRule(rule, key, record, keyPrefix, context);
45
+ if (alert) {
46
+ this.cooldowns.set(cooldownKey, now);
47
+ fired.push(alert);
48
+ if (!this.dryRun) {
49
+ this.pendingAlerts.push(alert);
50
+ }
51
+ }
52
+ }
53
+ return fired;
54
+ }
55
+ /**
56
+ * Record a rate limit denial for spike detection.
57
+ */
58
+ recordRateLimitDenial(key) {
59
+ const keyPrefix = key.slice(0, 10) + '...';
60
+ let denials = this.rateLimitDenials.get(keyPrefix);
61
+ if (!denials) {
62
+ denials = [];
63
+ this.rateLimitDenials.set(keyPrefix, denials);
64
+ }
65
+ denials.push(Date.now());
66
+ // Keep only last 5 minutes
67
+ const cutoff = Date.now() - 5 * 60 * 1000;
68
+ this.rateLimitDenials.set(keyPrefix, denials.filter(t => t > cutoff));
69
+ }
70
+ /**
71
+ * Consume pending alerts (returns and clears the queue).
72
+ */
73
+ consumeAlerts() {
74
+ const alerts = [...this.pendingAlerts];
75
+ this.pendingAlerts = [];
76
+ return alerts;
77
+ }
78
+ /**
79
+ * Get pending alert count.
80
+ */
81
+ get pendingCount() {
82
+ return this.pendingAlerts.length;
83
+ }
84
+ /**
85
+ * Get configured rules.
86
+ */
87
+ get configuredRules() {
88
+ return [...this.rules];
89
+ }
90
+ /**
91
+ * Evaluate a single rule against a key record.
92
+ */
93
+ evaluateRule(rule, key, record, keyPrefix, context) {
94
+ const now = new Date().toISOString();
95
+ const base = {
96
+ type: rule.type,
97
+ timestamp: now,
98
+ keyPrefix,
99
+ keyName: record.name,
100
+ threshold: rule.threshold,
101
+ };
102
+ switch (rule.type) {
103
+ case 'spending_threshold': {
104
+ // Alert when totalSpent exceeds threshold% of (totalSpent + remaining credits)
105
+ const totalBudget = record.credits + record.totalSpent;
106
+ if (totalBudget <= 0)
107
+ return null;
108
+ const spentPct = (record.totalSpent / totalBudget) * 100;
109
+ if (spentPct >= rule.threshold) {
110
+ return {
111
+ ...base,
112
+ currentValue: Math.round(spentPct * 10) / 10,
113
+ message: `Key "${record.name}" has spent ${Math.round(spentPct)}% of total budget (${record.totalSpent}/${totalBudget} credits)`,
114
+ metadata: { credits: record.credits, totalSpent: record.totalSpent, totalBudget },
115
+ };
116
+ }
117
+ return null;
118
+ }
119
+ case 'credits_low': {
120
+ if (record.credits <= rule.threshold) {
121
+ return {
122
+ ...base,
123
+ currentValue: record.credits,
124
+ message: `Key "${record.name}" has only ${record.credits} credits remaining (threshold: ${rule.threshold})`,
125
+ metadata: { credits: record.credits },
126
+ };
127
+ }
128
+ return null;
129
+ }
130
+ case 'quota_warning': {
131
+ if (!record.quota)
132
+ return null;
133
+ // Check daily call quota
134
+ if (record.quota.dailyCallLimit > 0) {
135
+ const pct = (record.quotaDailyCalls / record.quota.dailyCallLimit) * 100;
136
+ if (pct >= rule.threshold) {
137
+ return {
138
+ ...base,
139
+ currentValue: Math.round(pct * 10) / 10,
140
+ message: `Key "${record.name}" daily call quota at ${Math.round(pct)}% (${record.quotaDailyCalls}/${record.quota.dailyCallLimit})`,
141
+ metadata: {
142
+ quotaType: 'dailyCalls',
143
+ used: record.quotaDailyCalls,
144
+ limit: record.quota.dailyCallLimit,
145
+ },
146
+ };
147
+ }
148
+ }
149
+ // Check monthly call quota
150
+ if (record.quota.monthlyCallLimit > 0) {
151
+ const pct = (record.quotaMonthlyCalls / record.quota.monthlyCallLimit) * 100;
152
+ if (pct >= rule.threshold) {
153
+ return {
154
+ ...base,
155
+ currentValue: Math.round(pct * 10) / 10,
156
+ message: `Key "${record.name}" monthly call quota at ${Math.round(pct)}% (${record.quotaMonthlyCalls}/${record.quota.monthlyCallLimit})`,
157
+ metadata: {
158
+ quotaType: 'monthlyCalls',
159
+ used: record.quotaMonthlyCalls,
160
+ limit: record.quota.monthlyCallLimit,
161
+ },
162
+ };
163
+ }
164
+ }
165
+ return null;
166
+ }
167
+ case 'key_expiry_soon': {
168
+ if (!record.expiresAt)
169
+ return null;
170
+ const expiresMs = new Date(record.expiresAt).getTime();
171
+ const remainingMs = expiresMs - Date.now();
172
+ const thresholdMs = rule.threshold * 1000;
173
+ if (remainingMs > 0 && remainingMs <= thresholdMs) {
174
+ const remainingHours = Math.round(remainingMs / 3600_000 * 10) / 10;
175
+ return {
176
+ ...base,
177
+ currentValue: Math.round(remainingMs / 1000),
178
+ message: `Key "${record.name}" expires in ${remainingHours} hours (${record.expiresAt})`,
179
+ metadata: { expiresAt: record.expiresAt, remainingSeconds: Math.round(remainingMs / 1000) },
180
+ };
181
+ }
182
+ return null;
183
+ }
184
+ case 'rate_limit_spike': {
185
+ if (!context?.rateLimitDenied)
186
+ return null;
187
+ const denials = this.rateLimitDenials.get(keyPrefix) || [];
188
+ const recentCount = denials.length;
189
+ if (recentCount >= rule.threshold) {
190
+ return {
191
+ ...base,
192
+ currentValue: recentCount,
193
+ message: `Key "${record.name}" hit ${recentCount} rate limit denials in 5 min (threshold: ${rule.threshold})`,
194
+ metadata: { denialCount: recentCount, windowMinutes: 5 },
195
+ };
196
+ }
197
+ return null;
198
+ }
199
+ default:
200
+ return null;
201
+ }
202
+ }
203
+ /**
204
+ * Clear cooldowns (for testing).
205
+ */
206
+ clearCooldowns() {
207
+ this.cooldowns.clear();
208
+ this.rateLimitDenials.clear();
209
+ this.pendingAlerts = [];
210
+ }
211
+ }
212
+ exports.AlertEngine = AlertEngine;
213
+ //# sourceMappingURL=alerts.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"alerts.js","sourceRoot":"","sources":["../src/alerts.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,117 @@
1
+ /**
2
+ * AnalyticsEngine — Time-series analytics over UsageEvents.
3
+ *
4
+ * Provides:
5
+ * - Time-bucketed aggregation (hourly, daily)
6
+ * - Per-tool breakdown with success/failure rates
7
+ * - Top consumers by credits or call count
8
+ * - Trend comparison (current vs previous period)
9
+ * - Revenue (credits) over time
10
+ */
11
+ import { UsageEvent } from './types';
12
+ export type BucketGranularity = 'hourly' | 'daily';
13
+ export interface TimeBucket {
14
+ /** Bucket start time (ISO 8601) */
15
+ start: string;
16
+ /** Bucket end time (ISO 8601) */
17
+ end: string;
18
+ /** Total calls in this bucket */
19
+ calls: number;
20
+ /** Allowed calls */
21
+ allowed: number;
22
+ /** Denied calls */
23
+ denied: number;
24
+ /** Credits charged */
25
+ credits: number;
26
+ }
27
+ export interface ToolBreakdown {
28
+ tool: string;
29
+ calls: number;
30
+ allowed: number;
31
+ denied: number;
32
+ credits: number;
33
+ /** Success rate (0-1) */
34
+ successRate: number;
35
+ /** Average credits per allowed call */
36
+ avgCreditsPerCall: number;
37
+ }
38
+ export interface TopConsumer {
39
+ /** Key name (or masked prefix) */
40
+ keyName: string;
41
+ calls: number;
42
+ credits: number;
43
+ denied: number;
44
+ /** Most-used tool */
45
+ topTool: string;
46
+ }
47
+ export interface TrendComparison {
48
+ /** Current period stats */
49
+ current: {
50
+ calls: number;
51
+ credits: number;
52
+ denied: number;
53
+ };
54
+ /** Previous period stats (same duration) */
55
+ previous: {
56
+ calls: number;
57
+ credits: number;
58
+ denied: number;
59
+ };
60
+ /** Percentage change (-100 to +Infinity) */
61
+ change: {
62
+ calls: number | null;
63
+ credits: number | null;
64
+ denied: number | null;
65
+ };
66
+ }
67
+ export interface AnalyticsReport {
68
+ /** Query time range */
69
+ from: string;
70
+ to: string;
71
+ granularity: BucketGranularity;
72
+ /** Time-series data */
73
+ timeSeries: TimeBucket[];
74
+ /** Per-tool breakdown (sorted by calls desc) */
75
+ tools: ToolBreakdown[];
76
+ /** Top consumers (sorted by credits desc) */
77
+ topConsumers: TopConsumer[];
78
+ /** Trend vs previous equivalent period */
79
+ trend: TrendComparison;
80
+ /** Summary stats */
81
+ summary: {
82
+ totalCalls: number;
83
+ totalCredits: number;
84
+ totalDenied: number;
85
+ uniqueKeys: number;
86
+ uniqueTools: number;
87
+ successRate: number;
88
+ };
89
+ }
90
+ export declare class AnalyticsEngine {
91
+ /**
92
+ * Generate a full analytics report from usage events.
93
+ */
94
+ report(events: UsageEvent[], options?: {
95
+ from?: string;
96
+ to?: string;
97
+ granularity?: BucketGranularity;
98
+ topN?: number;
99
+ }): AnalyticsReport;
100
+ /**
101
+ * Bucket events into time intervals.
102
+ */
103
+ private buildTimeSeries;
104
+ /**
105
+ * Breakdown per tool, sorted by calls descending.
106
+ */
107
+ private buildToolBreakdown;
108
+ /**
109
+ * Top consumers by credits spent, with their most-used tool.
110
+ */
111
+ private buildTopConsumers;
112
+ /**
113
+ * Compare current period vs previous period of same length.
114
+ */
115
+ private buildTrend;
116
+ }
117
+ //# sourceMappingURL=analytics.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"analytics.d.ts","sourceRoot":"","sources":["../src/analytics.ts"],"names":[],"mappings":"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"}