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.
- package/README.md +134 -1
- package/dist/alerts.d.ts +85 -0
- package/dist/alerts.d.ts.map +1 -0
- package/dist/alerts.js +213 -0
- package/dist/alerts.js.map +1 -0
- package/dist/analytics.d.ts +117 -0
- package/dist/analytics.d.ts.map +1 -0
- package/dist/analytics.js +211 -0
- package/dist/analytics.js.map +1 -0
- package/dist/audit.d.ts +1 -1
- package/dist/audit.d.ts.map +1 -1
- package/dist/audit.js.map +1 -1
- package/dist/gate.d.ts +1 -1
- package/dist/gate.d.ts.map +1 -1
- package/dist/gate.js +12 -1
- package/dist/gate.js.map +1 -1
- package/dist/http-proxy.d.ts +1 -1
- package/dist/http-proxy.d.ts.map +1 -1
- package/dist/http-proxy.js +2 -2
- package/dist/http-proxy.js.map +1 -1
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -1
- package/dist/index.js.map +1 -1
- package/dist/proxy.d.ts +1 -1
- package/dist/proxy.d.ts.map +1 -1
- package/dist/proxy.js +2 -2
- package/dist/proxy.js.map +1 -1
- package/dist/router.d.ts +1 -1
- package/dist/router.d.ts.map +1 -1
- package/dist/router.js +6 -6
- package/dist/router.js.map +1 -1
- package/dist/server.d.ts +12 -0
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +265 -1
- package/dist/server.js.map +1 -1
- package/dist/store.d.ts +37 -0
- package/dist/store.d.ts.map +1 -1
- package/dist/store.js +146 -0
- package/dist/store.js.map +1 -1
- package/dist/types.d.ts +10 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/webhook.d.ts +1 -1
- package/dist/webhook.d.ts.map +1 -1
- 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
|
package/dist/alerts.d.ts
ADDED
|
@@ -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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"}
|