paygate-mcp 9.6.0 → 9.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 +9 -3
- package/dist/adaptive-rate-limiter.d.ts +126 -0
- package/dist/adaptive-rate-limiter.d.ts.map +1 -0
- package/dist/adaptive-rate-limiter.js +260 -0
- package/dist/adaptive-rate-limiter.js.map +1 -0
- package/dist/cost-tags.d.ts +138 -0
- package/dist/cost-tags.d.ts.map +1 -0
- package/dist/cost-tags.js +307 -0
- package/dist/cost-tags.js.map +1 -0
- package/dist/dedup.d.ts +97 -0
- package/dist/dedup.d.ts.map +1 -0
- package/dist/dedup.js +235 -0
- package/dist/dedup.js.map +1 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +15 -1
- package/dist/index.js.map +1 -1
- package/dist/priority-queue.d.ts +113 -0
- package/dist/priority-queue.d.ts.map +1 -0
- package/dist/priority-queue.js +276 -0
- package/dist/priority-queue.js.map +1 -0
- package/dist/retry-policy.d.ts +99 -0
- package/dist/retry-policy.d.ts.map +1 -0
- package/dist/retry-policy.js +224 -0
- package/dist/retry-policy.js.map +1 -0
- package/dist/server.d.ts +24 -0
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +319 -0
- package/dist/server.js.map +1 -1
- package/dist/transforms.d.ts +125 -0
- package/dist/transforms.d.ts.map +1 -0
- package/dist/transforms.js +270 -0
- package/dist/transforms.js.map +1 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -11,7 +11,7 @@ Monetize any MCP server with one command. Add API key auth, per-tool pricing, ra
|
|
|
11
11
|
- [Quick Start](#quick-start)
|
|
12
12
|
- [What It Does](#what-it-does)
|
|
13
13
|
- [Usage](#usage) — Local stdio, remote HTTP, multi-server, client SDK
|
|
14
|
-
- [API Reference](#api-reference) — All
|
|
14
|
+
- [API Reference](#api-reference) — All 172+ endpoints
|
|
15
15
|
- [CLI Options](#cli-options)
|
|
16
16
|
- [Deployment](#deployment) — Docker, docker-compose, systemd, PM2
|
|
17
17
|
- [Load Testing](#load-testing) — k6 benchmarking for production
|
|
@@ -66,7 +66,7 @@ Agent → PayGate (auth + billing) → Your MCP Server (stdio or HTTP)
|
|
|
66
66
|
- **SSE Streaming** — Full MCP Streamable HTTP transport (POST SSE, GET notifications, DELETE sessions)
|
|
67
67
|
- **Audit Log** — Structured audit trail with retention policies, query API, CSV/JSON export
|
|
68
68
|
- **Registry/Discovery** — Agent-discoverable pricing via `/.well-known/mcp-payment`, `/pricing`, and `/.well-known/mcp.json` identity card
|
|
69
|
-
- **OpenAPI 3.1 + Interactive Docs** — Auto-generated spec at `/openapi.json`, Swagger UI at `/docs` — all
|
|
69
|
+
- **OpenAPI 3.1 + Interactive Docs** — Auto-generated spec at `/openapi.json`, Swagger UI at `/docs` — all 172+ endpoints documented
|
|
70
70
|
- **Public Endpoint Rate Limiting** — Configurable per-IP rate limit (default 300/min) on `/health`, `/info`, `/pricing`, `/docs`, `/openapi.json`, `/.well-known/*`, `/robots.txt`, `/` — 429 with Retry-After header
|
|
71
71
|
- **Robots.txt + HEAD Support** — Standard `/robots.txt` (allow public, disallow admin/keys), HEAD method on all public endpoints for uptime monitoring
|
|
72
72
|
- **Prometheus Metrics** — `/metrics` endpoint with counters, gauges, and uptime in standard text format
|
|
@@ -165,6 +165,12 @@ Agent → PayGate (auth + billing) → Your MCP Server (stdio or HTTP)
|
|
|
165
165
|
- **Usage Plans** — Tiered key policies (free/pro/enterprise) — bundle rate limits, quotas, credit multipliers, and tool ACL into reusable templates, assign keys to plans via `POST /admin/keys/plan`, denied tools rejected with error code `-32403`, CRUD via `GET/POST/DELETE /admin/plans`
|
|
166
166
|
- **Tool Input Schema Validation** — Per-tool JSON Schema validation at the gateway — register schemas to reject invalid payloads before they reach downstream, zero-dependency JSON Schema subset (type, required, enum, minLength, pattern, items), error code `-32602` with detailed errors, manage via `GET/POST/DELETE /admin/tools/schema`
|
|
167
167
|
- **Canary Routing** — Weighted traffic splitting between primary and canary MCP servers — enable zero-downtime upgrades with percentage-based routing (0-100%), unbiased `crypto.randomInt` decisions, per-backend call/error tracking, weight updates without restart, manage via `GET/POST/DELETE /admin/canary`
|
|
168
|
+
- **Request/Response Transforms** — Declarative rewriting of tool call arguments and responses — inject defaults, strip fields, rename keys, and template `{{variables}}` from context, wildcard tool matching, priority ordering, deep clone on apply, import/export for backup, manage via `GET/POST/PUT/DELETE /admin/transforms`
|
|
169
|
+
- **Backend Retry Policy** — Automatic retry with exponential backoff for transient failures — configurable max retries, base/max backoff, full jitter, retry budget (max % of traffic as retries with cold-start grace), per-tool stats, retryable error pattern matching, manage via `GET/POST /admin/retry-policy`
|
|
170
|
+
- **Adaptive Rate Limiting** — Dynamic rate adjustment based on key behavior — auto-tighten for high error rates, auto-boost for good actors, cooldown periods, configurable thresholds, per-key behavior tracking, LRU eviction, batch evaluation, manage via `GET/POST /admin/adaptive-rates`
|
|
171
|
+
- **Request Deduplication** — Idempotency layer preventing duplicate billing from agent retries — `X-Idempotency-Key` header with auto-generation fallback (SHA-256), in-flight request coalescing, configurable TTL window, LRU eviction, credits-saved tracking, manage via `GET/POST/DELETE /admin/dedup`
|
|
172
|
+
- **Priority Queue** — Tiered request prioritization (critical/high/normal/low/background) with fair scheduling — per-key priority assignment, configurable max wait times per tier, starvation prevention via automatic promotion, max queue depth limiting, manage via `GET/POST /admin/priority-queue`
|
|
173
|
+
- **Cost Allocation Tags** — Per-request cost attribution via `X-Cost-Tags` header (JSON) for enterprise chargeback — aggregated reports by any tag dimension, cross-tabulation, CSV export, required tag enforcement per key, cardinality limits, manage via `GET/POST/DELETE /admin/cost-tags`
|
|
168
174
|
- **Anomaly Detection** — `GET /admin/anomalies` identifies unusual patterns: keys with high denial rates, rapid credit depletion, low remaining credits, with severity ratings and detailed descriptions
|
|
169
175
|
- **Usage Forecasting** — `GET /admin/forecast` predicts future credit consumption with per-key depletion estimates, calls remaining, at-risk key identification, system-wide consumption aggregates, and per-tool cost breakdown
|
|
170
176
|
- **Compliance Report** — `GET /admin/compliance` generates compliance-ready report with key governance (expiry coverage), access control (ACL/IP/spending limit coverage), audit trail completeness, weighted overall score, and actionable recommendations
|
|
@@ -478,7 +484,7 @@ A real-time admin UI for managing keys, viewing usage, and monitoring tool calls
|
|
|
478
484
|
| `/.well-known/mcp-payment` | GET | None | Server payment metadata (SEP-2007) |
|
|
479
485
|
| `/.well-known/mcp.json` | GET | None | MCP Server Identity card (discovery) |
|
|
480
486
|
| `/pricing` | GET | None | Full per-tool pricing breakdown |
|
|
481
|
-
| `/openapi.json` | GET | None | OpenAPI 3.1 spec (all
|
|
487
|
+
| `/openapi.json` | GET | None | OpenAPI 3.1 spec (all 172+ endpoints) |
|
|
482
488
|
| `/docs` | GET | None | Interactive API docs (Swagger UI) |
|
|
483
489
|
| `/robots.txt` | GET | None | Crawler directives (allow public, disallow admin/keys) |
|
|
484
490
|
| `/portal` | GET | None | Self-service API key portal (browser UI, auth via X-API-Key prompt) |
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AdaptiveRateLimiter — Dynamic rate limit adjustment based on traffic behavior.
|
|
3
|
+
*
|
|
4
|
+
* Automatically tightens limits for misbehaving keys (high error rates,
|
|
5
|
+
* excessive credit velocity) and loosens limits for well-behaved keys
|
|
6
|
+
* (zero denials, steady usage). Wraps the existing static RateLimiter
|
|
7
|
+
* with a per-key multiplier.
|
|
8
|
+
*
|
|
9
|
+
* Inspired by Apigee adaptive rate limiting and Tyk request throttling.
|
|
10
|
+
* Zero external dependencies.
|
|
11
|
+
*/
|
|
12
|
+
export interface AdaptiveRateConfig {
|
|
13
|
+
/** Enable adaptive rate limiting. */
|
|
14
|
+
enabled: boolean;
|
|
15
|
+
/** Sliding window size in seconds for evaluation. Default 300 (5 min). */
|
|
16
|
+
evaluationWindowSeconds: number;
|
|
17
|
+
/** Error rate threshold (0-1) above which rate is tightened. Default 0.3. */
|
|
18
|
+
errorRateThreshold: number;
|
|
19
|
+
/** If credit spend rate exceeds this × average, tighten. Default 1.5. */
|
|
20
|
+
creditVelocityMultiplier: number;
|
|
21
|
+
/** Never reduce below this % of configured limit. Default 25. */
|
|
22
|
+
minRatePercent: number;
|
|
23
|
+
/** Allow burst up to this % for good behavior. Default 200. */
|
|
24
|
+
maxRatePercent: number;
|
|
25
|
+
/** Hold adjusted rate for at least this many seconds. Default 60. */
|
|
26
|
+
cooldownSeconds: number;
|
|
27
|
+
}
|
|
28
|
+
export interface KeyBehavior {
|
|
29
|
+
totalCalls: number;
|
|
30
|
+
errorCalls: number;
|
|
31
|
+
deniedCalls: number;
|
|
32
|
+
creditsSpent: number;
|
|
33
|
+
/** Current effective multiplier (1.0 = nominal). */
|
|
34
|
+
multiplier: number;
|
|
35
|
+
/** Last time the multiplier was adjusted. */
|
|
36
|
+
lastAdjusted: number;
|
|
37
|
+
/** Sliding window of call timestamps. */
|
|
38
|
+
callTimestamps: number[];
|
|
39
|
+
/** Sliding window of error timestamps. */
|
|
40
|
+
errorTimestamps: number[];
|
|
41
|
+
}
|
|
42
|
+
export interface AdaptiveRateStats {
|
|
43
|
+
enabled: boolean;
|
|
44
|
+
config: AdaptiveRateConfig;
|
|
45
|
+
totalKeys: number;
|
|
46
|
+
tightenedKeys: number;
|
|
47
|
+
boostedKeys: number;
|
|
48
|
+
normalKeys: number;
|
|
49
|
+
keyDetails: Array<{
|
|
50
|
+
key: string;
|
|
51
|
+
multiplier: number;
|
|
52
|
+
effectiveRate: string;
|
|
53
|
+
recentCalls: number;
|
|
54
|
+
recentErrors: number;
|
|
55
|
+
errorRate: number;
|
|
56
|
+
}>;
|
|
57
|
+
}
|
|
58
|
+
export interface AdaptiveRateAdjustment {
|
|
59
|
+
key: string;
|
|
60
|
+
previousMultiplier: number;
|
|
61
|
+
newMultiplier: number;
|
|
62
|
+
reason: string;
|
|
63
|
+
effectiveRate: number;
|
|
64
|
+
baseRate: number;
|
|
65
|
+
}
|
|
66
|
+
export declare class AdaptiveRateLimiter {
|
|
67
|
+
private config;
|
|
68
|
+
private readonly keyBehaviors;
|
|
69
|
+
private readonly maxTrackedKeys;
|
|
70
|
+
constructor(config?: Partial<AdaptiveRateConfig>);
|
|
71
|
+
/**
|
|
72
|
+
* Update configuration at runtime.
|
|
73
|
+
*/
|
|
74
|
+
configure(updates: Partial<AdaptiveRateConfig>): AdaptiveRateConfig;
|
|
75
|
+
/**
|
|
76
|
+
* Record a call for a key.
|
|
77
|
+
*/
|
|
78
|
+
recordCall(apiKey: string): void;
|
|
79
|
+
/**
|
|
80
|
+
* Record an error for a key.
|
|
81
|
+
*/
|
|
82
|
+
recordError(apiKey: string): void;
|
|
83
|
+
/**
|
|
84
|
+
* Record a denied request for a key.
|
|
85
|
+
*/
|
|
86
|
+
recordDenied(apiKey: string): void;
|
|
87
|
+
/**
|
|
88
|
+
* Record credits spent by a key.
|
|
89
|
+
*/
|
|
90
|
+
recordCredits(apiKey: string, amount: number): void;
|
|
91
|
+
/**
|
|
92
|
+
* Get the effective rate multiplier for a key.
|
|
93
|
+
* Returns 1.0 if adaptive limiting is disabled.
|
|
94
|
+
*/
|
|
95
|
+
getMultiplier(apiKey: string): number;
|
|
96
|
+
/**
|
|
97
|
+
* Get the effective rate limit for a key.
|
|
98
|
+
*/
|
|
99
|
+
getEffectiveRate(apiKey: string, baseRate: number): number;
|
|
100
|
+
/**
|
|
101
|
+
* Evaluate and adjust the rate multiplier for a key.
|
|
102
|
+
* Call this periodically (e.g., every minute) or on every Nth request.
|
|
103
|
+
*/
|
|
104
|
+
evaluate(apiKey: string): AdaptiveRateAdjustment | null;
|
|
105
|
+
/**
|
|
106
|
+
* Evaluate all tracked keys.
|
|
107
|
+
*/
|
|
108
|
+
evaluateAll(): AdaptiveRateAdjustment[];
|
|
109
|
+
private pruneTimestamps;
|
|
110
|
+
private getOrCreate;
|
|
111
|
+
/**
|
|
112
|
+
* Get adaptive rate statistics.
|
|
113
|
+
*/
|
|
114
|
+
stats(): AdaptiveRateStats;
|
|
115
|
+
/**
|
|
116
|
+
* Reset tracking for a specific key.
|
|
117
|
+
*/
|
|
118
|
+
resetKey(apiKey: string): boolean;
|
|
119
|
+
/**
|
|
120
|
+
* Clear all tracking data.
|
|
121
|
+
*/
|
|
122
|
+
clear(): void;
|
|
123
|
+
/** Number of tracked keys. */
|
|
124
|
+
get size(): number;
|
|
125
|
+
}
|
|
126
|
+
//# sourceMappingURL=adaptive-rate-limiter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"adaptive-rate-limiter.d.ts","sourceRoot":"","sources":["../src/adaptive-rate-limiter.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* AdaptiveRateLimiter — Dynamic rate limit adjustment based on traffic behavior.
|
|
4
|
+
*
|
|
5
|
+
* Automatically tightens limits for misbehaving keys (high error rates,
|
|
6
|
+
* excessive credit velocity) and loosens limits for well-behaved keys
|
|
7
|
+
* (zero denials, steady usage). Wraps the existing static RateLimiter
|
|
8
|
+
* with a per-key multiplier.
|
|
9
|
+
*
|
|
10
|
+
* Inspired by Apigee adaptive rate limiting and Tyk request throttling.
|
|
11
|
+
* Zero external dependencies.
|
|
12
|
+
*/
|
|
13
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
14
|
+
exports.AdaptiveRateLimiter = void 0;
|
|
15
|
+
// ─── Default config ─────────────────────────────────────────────────────────
|
|
16
|
+
const DEFAULT_ADAPTIVE_CONFIG = {
|
|
17
|
+
enabled: false,
|
|
18
|
+
evaluationWindowSeconds: 300,
|
|
19
|
+
errorRateThreshold: 0.3,
|
|
20
|
+
creditVelocityMultiplier: 1.5,
|
|
21
|
+
minRatePercent: 25,
|
|
22
|
+
maxRatePercent: 200,
|
|
23
|
+
cooldownSeconds: 60,
|
|
24
|
+
};
|
|
25
|
+
// ─── AdaptiveRateLimiter Class ──────────────────────────────────────────────
|
|
26
|
+
class AdaptiveRateLimiter {
|
|
27
|
+
config;
|
|
28
|
+
keyBehaviors = new Map();
|
|
29
|
+
maxTrackedKeys = 5000;
|
|
30
|
+
constructor(config) {
|
|
31
|
+
this.config = { ...DEFAULT_ADAPTIVE_CONFIG, ...config };
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Update configuration at runtime.
|
|
35
|
+
*/
|
|
36
|
+
configure(updates) {
|
|
37
|
+
if (updates.enabled !== undefined)
|
|
38
|
+
this.config.enabled = updates.enabled;
|
|
39
|
+
if (updates.evaluationWindowSeconds !== undefined)
|
|
40
|
+
this.config.evaluationWindowSeconds = Math.max(30, updates.evaluationWindowSeconds);
|
|
41
|
+
if (updates.errorRateThreshold !== undefined)
|
|
42
|
+
this.config.errorRateThreshold = Math.min(1, Math.max(0, updates.errorRateThreshold));
|
|
43
|
+
if (updates.creditVelocityMultiplier !== undefined)
|
|
44
|
+
this.config.creditVelocityMultiplier = Math.max(1, updates.creditVelocityMultiplier);
|
|
45
|
+
if (updates.minRatePercent !== undefined)
|
|
46
|
+
this.config.minRatePercent = Math.min(100, Math.max(1, updates.minRatePercent));
|
|
47
|
+
if (updates.maxRatePercent !== undefined)
|
|
48
|
+
this.config.maxRatePercent = Math.max(100, updates.maxRatePercent);
|
|
49
|
+
if (updates.cooldownSeconds !== undefined)
|
|
50
|
+
this.config.cooldownSeconds = Math.max(0, updates.cooldownSeconds);
|
|
51
|
+
return { ...this.config };
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Record a call for a key.
|
|
55
|
+
*/
|
|
56
|
+
recordCall(apiKey) {
|
|
57
|
+
if (!this.config.enabled)
|
|
58
|
+
return;
|
|
59
|
+
const behavior = this.getOrCreate(apiKey);
|
|
60
|
+
behavior.totalCalls++;
|
|
61
|
+
behavior.callTimestamps.push(Date.now());
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Record an error for a key.
|
|
65
|
+
*/
|
|
66
|
+
recordError(apiKey) {
|
|
67
|
+
if (!this.config.enabled)
|
|
68
|
+
return;
|
|
69
|
+
const behavior = this.getOrCreate(apiKey);
|
|
70
|
+
behavior.errorCalls++;
|
|
71
|
+
behavior.errorTimestamps.push(Date.now());
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Record a denied request for a key.
|
|
75
|
+
*/
|
|
76
|
+
recordDenied(apiKey) {
|
|
77
|
+
if (!this.config.enabled)
|
|
78
|
+
return;
|
|
79
|
+
const behavior = this.getOrCreate(apiKey);
|
|
80
|
+
behavior.deniedCalls++;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Record credits spent by a key.
|
|
84
|
+
*/
|
|
85
|
+
recordCredits(apiKey, amount) {
|
|
86
|
+
if (!this.config.enabled)
|
|
87
|
+
return;
|
|
88
|
+
const behavior = this.getOrCreate(apiKey);
|
|
89
|
+
behavior.creditsSpent += amount;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Get the effective rate multiplier for a key.
|
|
93
|
+
* Returns 1.0 if adaptive limiting is disabled.
|
|
94
|
+
*/
|
|
95
|
+
getMultiplier(apiKey) {
|
|
96
|
+
if (!this.config.enabled)
|
|
97
|
+
return 1.0;
|
|
98
|
+
const behavior = this.keyBehaviors.get(apiKey);
|
|
99
|
+
if (!behavior)
|
|
100
|
+
return 1.0;
|
|
101
|
+
return behavior.multiplier;
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Get the effective rate limit for a key.
|
|
105
|
+
*/
|
|
106
|
+
getEffectiveRate(apiKey, baseRate) {
|
|
107
|
+
return Math.round(baseRate * this.getMultiplier(apiKey));
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Evaluate and adjust the rate multiplier for a key.
|
|
111
|
+
* Call this periodically (e.g., every minute) or on every Nth request.
|
|
112
|
+
*/
|
|
113
|
+
evaluate(apiKey) {
|
|
114
|
+
if (!this.config.enabled)
|
|
115
|
+
return null;
|
|
116
|
+
const behavior = this.keyBehaviors.get(apiKey);
|
|
117
|
+
if (!behavior)
|
|
118
|
+
return null;
|
|
119
|
+
// Check cooldown
|
|
120
|
+
const now = Date.now();
|
|
121
|
+
const cooldownMs = this.config.cooldownSeconds * 1000;
|
|
122
|
+
if (now - behavior.lastAdjusted < cooldownMs)
|
|
123
|
+
return null;
|
|
124
|
+
// Prune old timestamps
|
|
125
|
+
this.pruneTimestamps(behavior);
|
|
126
|
+
const recentCalls = behavior.callTimestamps.length;
|
|
127
|
+
const recentErrors = behavior.errorTimestamps.length;
|
|
128
|
+
// Need minimum traffic to evaluate
|
|
129
|
+
if (recentCalls < 5)
|
|
130
|
+
return null;
|
|
131
|
+
const previousMultiplier = behavior.multiplier;
|
|
132
|
+
let newMultiplier = 1.0;
|
|
133
|
+
let reason = 'normal behavior';
|
|
134
|
+
// Check error rate
|
|
135
|
+
const errorRate = recentErrors / recentCalls;
|
|
136
|
+
if (errorRate > this.config.errorRateThreshold) {
|
|
137
|
+
// Tighten: reduce to proportional amount
|
|
138
|
+
const tightenFactor = 1 - (errorRate * 0.5); // 30% errors → 0.85x, 60% → 0.7x
|
|
139
|
+
newMultiplier = Math.max(this.config.minRatePercent / 100, tightenFactor);
|
|
140
|
+
reason = `high error rate: ${(errorRate * 100).toFixed(1)}%`;
|
|
141
|
+
}
|
|
142
|
+
else if (errorRate < 0.05 && behavior.deniedCalls === 0) {
|
|
143
|
+
// Boost: reward good behavior
|
|
144
|
+
const boostFactor = 1 + ((1 - errorRate) * 0.3); // up to 1.3x for perfect keys
|
|
145
|
+
newMultiplier = Math.min(this.config.maxRatePercent / 100, boostFactor);
|
|
146
|
+
reason = `good behavior: ${(errorRate * 100).toFixed(1)}% error rate`;
|
|
147
|
+
}
|
|
148
|
+
// Clamp
|
|
149
|
+
newMultiplier = Math.max(this.config.minRatePercent / 100, Math.min(this.config.maxRatePercent / 100, newMultiplier));
|
|
150
|
+
// Only update if significantly different (>5% change)
|
|
151
|
+
if (Math.abs(newMultiplier - previousMultiplier) < 0.05)
|
|
152
|
+
return null;
|
|
153
|
+
behavior.multiplier = newMultiplier;
|
|
154
|
+
behavior.lastAdjusted = now;
|
|
155
|
+
return {
|
|
156
|
+
key: apiKey.slice(0, 8) + '...',
|
|
157
|
+
previousMultiplier,
|
|
158
|
+
newMultiplier,
|
|
159
|
+
reason,
|
|
160
|
+
effectiveRate: 0, // caller fills in with base rate
|
|
161
|
+
baseRate: 0,
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Evaluate all tracked keys.
|
|
166
|
+
*/
|
|
167
|
+
evaluateAll() {
|
|
168
|
+
const adjustments = [];
|
|
169
|
+
for (const key of this.keyBehaviors.keys()) {
|
|
170
|
+
const adj = this.evaluate(key);
|
|
171
|
+
if (adj)
|
|
172
|
+
adjustments.push(adj);
|
|
173
|
+
}
|
|
174
|
+
return adjustments;
|
|
175
|
+
}
|
|
176
|
+
pruneTimestamps(behavior) {
|
|
177
|
+
const cutoff = Date.now() - (this.config.evaluationWindowSeconds * 1000);
|
|
178
|
+
behavior.callTimestamps = behavior.callTimestamps.filter(t => t > cutoff);
|
|
179
|
+
behavior.errorTimestamps = behavior.errorTimestamps.filter(t => t > cutoff);
|
|
180
|
+
}
|
|
181
|
+
getOrCreate(apiKey) {
|
|
182
|
+
let behavior = this.keyBehaviors.get(apiKey);
|
|
183
|
+
if (!behavior) {
|
|
184
|
+
// Evict oldest if at capacity
|
|
185
|
+
if (this.keyBehaviors.size >= this.maxTrackedKeys) {
|
|
186
|
+
const firstKey = this.keyBehaviors.keys().next().value;
|
|
187
|
+
if (firstKey)
|
|
188
|
+
this.keyBehaviors.delete(firstKey);
|
|
189
|
+
}
|
|
190
|
+
behavior = {
|
|
191
|
+
totalCalls: 0,
|
|
192
|
+
errorCalls: 0,
|
|
193
|
+
deniedCalls: 0,
|
|
194
|
+
creditsSpent: 0,
|
|
195
|
+
multiplier: 1.0,
|
|
196
|
+
lastAdjusted: 0,
|
|
197
|
+
callTimestamps: [],
|
|
198
|
+
errorTimestamps: [],
|
|
199
|
+
};
|
|
200
|
+
this.keyBehaviors.set(apiKey, behavior);
|
|
201
|
+
}
|
|
202
|
+
return behavior;
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* Get adaptive rate statistics.
|
|
206
|
+
*/
|
|
207
|
+
stats() {
|
|
208
|
+
const keyDetails = [];
|
|
209
|
+
let tightened = 0;
|
|
210
|
+
let boosted = 0;
|
|
211
|
+
let normal = 0;
|
|
212
|
+
for (const [key, behavior] of this.keyBehaviors) {
|
|
213
|
+
this.pruneTimestamps(behavior);
|
|
214
|
+
const recentCalls = behavior.callTimestamps.length;
|
|
215
|
+
const recentErrors = behavior.errorTimestamps.length;
|
|
216
|
+
const errorRate = recentCalls > 0 ? recentErrors / recentCalls : 0;
|
|
217
|
+
if (behavior.multiplier < 0.95)
|
|
218
|
+
tightened++;
|
|
219
|
+
else if (behavior.multiplier > 1.05)
|
|
220
|
+
boosted++;
|
|
221
|
+
else
|
|
222
|
+
normal++;
|
|
223
|
+
keyDetails.push({
|
|
224
|
+
key: key.slice(0, 8) + '...',
|
|
225
|
+
multiplier: Math.round(behavior.multiplier * 100) / 100,
|
|
226
|
+
effectiveRate: `${Math.round(behavior.multiplier * 100)}%`,
|
|
227
|
+
recentCalls,
|
|
228
|
+
recentErrors,
|
|
229
|
+
errorRate: Math.round(errorRate * 100) / 100,
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
return {
|
|
233
|
+
enabled: this.config.enabled,
|
|
234
|
+
config: { ...this.config },
|
|
235
|
+
totalKeys: this.keyBehaviors.size,
|
|
236
|
+
tightenedKeys: tightened,
|
|
237
|
+
boostedKeys: boosted,
|
|
238
|
+
normalKeys: normal,
|
|
239
|
+
keyDetails,
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* Reset tracking for a specific key.
|
|
244
|
+
*/
|
|
245
|
+
resetKey(apiKey) {
|
|
246
|
+
return this.keyBehaviors.delete(apiKey);
|
|
247
|
+
}
|
|
248
|
+
/**
|
|
249
|
+
* Clear all tracking data.
|
|
250
|
+
*/
|
|
251
|
+
clear() {
|
|
252
|
+
this.keyBehaviors.clear();
|
|
253
|
+
}
|
|
254
|
+
/** Number of tracked keys. */
|
|
255
|
+
get size() {
|
|
256
|
+
return this.keyBehaviors.size;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
exports.AdaptiveRateLimiter = AdaptiveRateLimiter;
|
|
260
|
+
//# sourceMappingURL=adaptive-rate-limiter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"adaptive-rate-limiter.js","sourceRoot":"","sources":["../src/adaptive-rate-limiter.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CostAllocator — Per-request cost allocation tags with chargeback reporting.
|
|
3
|
+
*
|
|
4
|
+
* Allows clients to attach key-value tags to individual tool calls via
|
|
5
|
+
* an `X-Cost-Tags` header. Tags flow through the metering pipeline and
|
|
6
|
+
* enable aggregated chargeback reports by any dimension.
|
|
7
|
+
*
|
|
8
|
+
* Features:
|
|
9
|
+
* - X-Cost-Tags header parsing with validation
|
|
10
|
+
* - Tag cardinality limits to prevent memory exhaustion
|
|
11
|
+
* - Aggregated chargeback reports by any tag dimension
|
|
12
|
+
* - Cross-tabulation (group by two dimensions)
|
|
13
|
+
* - CSV export support
|
|
14
|
+
* - Per-key required tag enforcement
|
|
15
|
+
*
|
|
16
|
+
* Zero external dependencies.
|
|
17
|
+
*/
|
|
18
|
+
export interface CostTagConfig {
|
|
19
|
+
/** Enable cost tags. Default true. */
|
|
20
|
+
enabled: boolean;
|
|
21
|
+
/** Max tags per request. Default 10. */
|
|
22
|
+
maxTagsPerRequest: number;
|
|
23
|
+
/** Max key length. Default 64. */
|
|
24
|
+
maxKeyLength: number;
|
|
25
|
+
/** Max value length. Default 64. */
|
|
26
|
+
maxValueLength: number;
|
|
27
|
+
/** Max unique tag keys. Default 1_000. */
|
|
28
|
+
maxUniqueKeys: number;
|
|
29
|
+
/** Max unique values per key. Default 10_000. */
|
|
30
|
+
maxValuesPerKey: number;
|
|
31
|
+
}
|
|
32
|
+
export interface CostTagEntry {
|
|
33
|
+
tags: Record<string, string>;
|
|
34
|
+
apiKey: string;
|
|
35
|
+
toolName: string;
|
|
36
|
+
credits: number;
|
|
37
|
+
timestamp: number;
|
|
38
|
+
}
|
|
39
|
+
export interface ChargebackRow {
|
|
40
|
+
dimension: string;
|
|
41
|
+
value: string;
|
|
42
|
+
totalCredits: number;
|
|
43
|
+
totalCalls: number;
|
|
44
|
+
avgCreditsPerCall: number;
|
|
45
|
+
}
|
|
46
|
+
export interface CrossTabRow {
|
|
47
|
+
dim1Value: string;
|
|
48
|
+
dim2Value: string;
|
|
49
|
+
totalCredits: number;
|
|
50
|
+
totalCalls: number;
|
|
51
|
+
}
|
|
52
|
+
export interface ChargebackReport {
|
|
53
|
+
dimension: string;
|
|
54
|
+
rows: ChargebackRow[];
|
|
55
|
+
totalCredits: number;
|
|
56
|
+
totalCalls: number;
|
|
57
|
+
generatedAt: string;
|
|
58
|
+
}
|
|
59
|
+
export interface CrossTabReport {
|
|
60
|
+
dim1: string;
|
|
61
|
+
dim2: string;
|
|
62
|
+
rows: CrossTabRow[];
|
|
63
|
+
totalCredits: number;
|
|
64
|
+
totalCalls: number;
|
|
65
|
+
generatedAt: string;
|
|
66
|
+
}
|
|
67
|
+
export interface CostTagStats {
|
|
68
|
+
enabled: boolean;
|
|
69
|
+
config: CostTagConfig;
|
|
70
|
+
totalEntries: number;
|
|
71
|
+
uniqueKeys: number;
|
|
72
|
+
valuesPerKey: Record<string, number>;
|
|
73
|
+
totalCreditsTracked: number;
|
|
74
|
+
totalCallsTracked: number;
|
|
75
|
+
requiredTagKeys: number;
|
|
76
|
+
}
|
|
77
|
+
export declare class CostAllocator {
|
|
78
|
+
private config;
|
|
79
|
+
private entries;
|
|
80
|
+
private readonly maxEntries;
|
|
81
|
+
private knownKeys;
|
|
82
|
+
private knownValuesPerKey;
|
|
83
|
+
private requiredTags;
|
|
84
|
+
constructor(config?: Partial<CostTagConfig>);
|
|
85
|
+
/**
|
|
86
|
+
* Parse and validate tags from X-Cost-Tags header value.
|
|
87
|
+
* Header format: JSON-encoded object, e.g. {"project":"acme","dept":"eng"}
|
|
88
|
+
*/
|
|
89
|
+
parseTags(headerValue: string): Record<string, string>;
|
|
90
|
+
/**
|
|
91
|
+
* Record a tagged usage event.
|
|
92
|
+
*/
|
|
93
|
+
record(tags: Record<string, string>, apiKey: string, toolName: string, credits: number): void;
|
|
94
|
+
/**
|
|
95
|
+
* Check if a request has all required tags for the given API key.
|
|
96
|
+
*/
|
|
97
|
+
validateRequired(apiKey: string, tags: Record<string, string>): string[];
|
|
98
|
+
/**
|
|
99
|
+
* Set required tags for an API key.
|
|
100
|
+
*/
|
|
101
|
+
setRequiredTags(apiKey: string, tagKeys: string[]): void;
|
|
102
|
+
/**
|
|
103
|
+
* Get required tags for an API key.
|
|
104
|
+
*/
|
|
105
|
+
getRequiredTags(apiKey: string): string[];
|
|
106
|
+
/**
|
|
107
|
+
* Generate a chargeback report grouped by a single dimension.
|
|
108
|
+
*/
|
|
109
|
+
report(dimension: string, startMs?: number, endMs?: number): ChargebackReport;
|
|
110
|
+
/**
|
|
111
|
+
* Generate a cross-tabulation report (group by two dimensions).
|
|
112
|
+
*/
|
|
113
|
+
crossTab(dim1: string, dim2: string, startMs?: number, endMs?: number): CrossTabReport;
|
|
114
|
+
/**
|
|
115
|
+
* Export report as CSV string.
|
|
116
|
+
*/
|
|
117
|
+
reportToCsv(report: ChargebackReport): string;
|
|
118
|
+
/**
|
|
119
|
+
* Update configuration at runtime.
|
|
120
|
+
*/
|
|
121
|
+
configure(updates: Partial<CostTagConfig>): CostTagConfig;
|
|
122
|
+
/**
|
|
123
|
+
* Clear all recorded entries.
|
|
124
|
+
*/
|
|
125
|
+
clear(): void;
|
|
126
|
+
/**
|
|
127
|
+
* Get statistics.
|
|
128
|
+
*/
|
|
129
|
+
stats(): CostTagStats;
|
|
130
|
+
/** Is cost allocation enabled? */
|
|
131
|
+
get enabled(): boolean;
|
|
132
|
+
/** Current config (copy). */
|
|
133
|
+
get currentConfig(): CostTagConfig;
|
|
134
|
+
/** Number of recorded entries. */
|
|
135
|
+
get size(): number;
|
|
136
|
+
private filterEntries;
|
|
137
|
+
}
|
|
138
|
+
//# sourceMappingURL=cost-tags.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cost-tags.d.ts","sourceRoot":"","sources":["../src/cost-tags.ts"],"names":[],"mappings":"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"}
|