@relayplane/proxy 1.5.46 → 1.7.2

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 (78) hide show
  1. package/README.md +297 -20
  2. package/assets/relayplane-proxy.service +20 -0
  3. package/dist/alerts.d.ts +72 -0
  4. package/dist/alerts.d.ts.map +1 -0
  5. package/dist/alerts.js +290 -0
  6. package/dist/alerts.js.map +1 -0
  7. package/dist/anomaly.d.ts +65 -0
  8. package/dist/anomaly.d.ts.map +1 -0
  9. package/dist/anomaly.js +193 -0
  10. package/dist/anomaly.js.map +1 -0
  11. package/dist/budget.d.ts +98 -0
  12. package/dist/budget.d.ts.map +1 -0
  13. package/dist/budget.js +356 -0
  14. package/dist/budget.js.map +1 -0
  15. package/dist/cli.js +512 -93
  16. package/dist/cli.js.map +1 -1
  17. package/dist/config.d.ts +28 -2
  18. package/dist/config.d.ts.map +1 -1
  19. package/dist/config.js +122 -24
  20. package/dist/config.js.map +1 -1
  21. package/dist/downgrade.d.ts +37 -0
  22. package/dist/downgrade.d.ts.map +1 -0
  23. package/dist/downgrade.js +79 -0
  24. package/dist/downgrade.js.map +1 -0
  25. package/dist/mesh/capture.d.ts +11 -0
  26. package/dist/mesh/capture.d.ts.map +1 -0
  27. package/dist/mesh/capture.js +43 -0
  28. package/dist/mesh/capture.js.map +1 -0
  29. package/dist/mesh/fitness.d.ts +14 -0
  30. package/dist/mesh/fitness.d.ts.map +1 -0
  31. package/dist/mesh/fitness.js +40 -0
  32. package/dist/mesh/fitness.js.map +1 -0
  33. package/dist/mesh/index.d.ts +39 -0
  34. package/dist/mesh/index.d.ts.map +1 -0
  35. package/dist/mesh/index.js +118 -0
  36. package/dist/mesh/index.js.map +1 -0
  37. package/dist/mesh/store.d.ts +30 -0
  38. package/dist/mesh/store.d.ts.map +1 -0
  39. package/dist/mesh/store.js +174 -0
  40. package/dist/mesh/store.js.map +1 -0
  41. package/dist/mesh/sync.d.ts +37 -0
  42. package/dist/mesh/sync.d.ts.map +1 -0
  43. package/dist/mesh/sync.js +154 -0
  44. package/dist/mesh/sync.js.map +1 -0
  45. package/dist/mesh/types.d.ts +57 -0
  46. package/dist/mesh/types.d.ts.map +1 -0
  47. package/dist/mesh/types.js +7 -0
  48. package/dist/mesh/types.js.map +1 -0
  49. package/dist/rate-limiter.d.ts +64 -0
  50. package/dist/rate-limiter.d.ts.map +1 -0
  51. package/dist/rate-limiter.js +159 -0
  52. package/dist/rate-limiter.js.map +1 -0
  53. package/dist/relay-config.d.ts +9 -0
  54. package/dist/relay-config.d.ts.map +1 -1
  55. package/dist/relay-config.js +2 -0
  56. package/dist/relay-config.js.map +1 -1
  57. package/dist/response-cache.d.ts +139 -0
  58. package/dist/response-cache.d.ts.map +1 -0
  59. package/dist/response-cache.js +515 -0
  60. package/dist/response-cache.js.map +1 -0
  61. package/dist/server.d.ts.map +1 -1
  62. package/dist/server.js +5 -1
  63. package/dist/server.js.map +1 -1
  64. package/dist/standalone-proxy.d.ts +2 -1
  65. package/dist/standalone-proxy.d.ts.map +1 -1
  66. package/dist/standalone-proxy.js +736 -50
  67. package/dist/standalone-proxy.js.map +1 -1
  68. package/dist/telemetry.d.ts.map +1 -1
  69. package/dist/telemetry.js +21 -5
  70. package/dist/telemetry.js.map +1 -1
  71. package/dist/utils/model-suggestions.d.ts.map +1 -1
  72. package/dist/utils/model-suggestions.js +19 -2
  73. package/dist/utils/model-suggestions.js.map +1 -1
  74. package/dist/utils/version-status.d.ts +9 -0
  75. package/dist/utils/version-status.d.ts.map +1 -0
  76. package/dist/utils/version-status.js +28 -0
  77. package/dist/utils/version-status.js.map +1 -0
  78. package/package.json +7 -3
package/dist/alerts.js ADDED
@@ -0,0 +1,290 @@
1
+ "use strict";
2
+ /**
3
+ * RelayPlane Cost Alerts & Webhooks
4
+ *
5
+ * Alert types: threshold (budget %), anomaly, breach.
6
+ * Webhook delivery via POST to configured URL.
7
+ * SQLite storage for alert history.
8
+ * Alert deduplication per window.
9
+ *
10
+ * @packageDocumentation
11
+ */
12
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
13
+ if (k2 === undefined) k2 = k;
14
+ var desc = Object.getOwnPropertyDescriptor(m, k);
15
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
16
+ desc = { enumerable: true, get: function() { return m[k]; } };
17
+ }
18
+ Object.defineProperty(o, k2, desc);
19
+ }) : (function(o, m, k, k2) {
20
+ if (k2 === undefined) k2 = k;
21
+ o[k2] = m[k];
22
+ }));
23
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
24
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
25
+ }) : function(o, v) {
26
+ o["default"] = v;
27
+ });
28
+ var __importStar = (this && this.__importStar) || (function () {
29
+ var ownKeys = function(o) {
30
+ ownKeys = Object.getOwnPropertyNames || function (o) {
31
+ var ar = [];
32
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
33
+ return ar;
34
+ };
35
+ return ownKeys(o);
36
+ };
37
+ return function (mod) {
38
+ if (mod && mod.__esModule) return mod;
39
+ var result = {};
40
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
41
+ __setModuleDefault(result, mod);
42
+ return result;
43
+ };
44
+ })();
45
+ Object.defineProperty(exports, "__esModule", { value: true });
46
+ exports.AlertManager = exports.DEFAULT_ALERTS_CONFIG = void 0;
47
+ exports.getAlertManager = getAlertManager;
48
+ exports.resetAlertManager = resetAlertManager;
49
+ const path = __importStar(require("node:path"));
50
+ const os = __importStar(require("node:os"));
51
+ const fs = __importStar(require("node:fs"));
52
+ // ─── Defaults ────────────────────────────────────────────────────────
53
+ exports.DEFAULT_ALERTS_CONFIG = {
54
+ enabled: false,
55
+ cooldownMs: 300_000,
56
+ maxHistory: 500,
57
+ };
58
+ function openDatabase(dbPath) {
59
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
60
+ const Database = require('better-sqlite3');
61
+ const db = new Database(dbPath);
62
+ db.pragma('journal_mode = WAL');
63
+ db.pragma('synchronous = NORMAL');
64
+ return db;
65
+ }
66
+ // ─── AlertManager ───────────────────────────────────────────────────
67
+ class AlertManager {
68
+ config;
69
+ db = null;
70
+ _initialized = false;
71
+ // In-memory deduplication: key → last fired timestamp
72
+ dedup = new Map();
73
+ // In-memory alert history for when DB is unavailable
74
+ memoryAlerts = [];
75
+ alertCounter = 0;
76
+ constructor(config) {
77
+ this.config = { ...exports.DEFAULT_ALERTS_CONFIG, ...config };
78
+ }
79
+ /** Initialize SQLite storage */
80
+ init() {
81
+ if (this._initialized)
82
+ return;
83
+ if (!this.config.enabled)
84
+ return;
85
+ this._initialized = true;
86
+ const dir = path.join(os.homedir(), '.relayplane');
87
+ fs.mkdirSync(dir, { recursive: true });
88
+ try {
89
+ const dbPath = path.join(dir, 'alerts.db');
90
+ this.db = openDatabase(dbPath);
91
+ this.db.exec(`
92
+ CREATE TABLE IF NOT EXISTS alerts (
93
+ id TEXT PRIMARY KEY,
94
+ type TEXT NOT NULL,
95
+ message TEXT NOT NULL,
96
+ severity TEXT NOT NULL,
97
+ timestamp INTEGER NOT NULL,
98
+ data TEXT NOT NULL DEFAULT '{}',
99
+ delivered INTEGER NOT NULL DEFAULT 0
100
+ );
101
+ CREATE INDEX IF NOT EXISTS idx_alerts_timestamp ON alerts(timestamp);
102
+ CREATE INDEX IF NOT EXISTS idx_alerts_type ON alerts(type);
103
+ `);
104
+ // Prune old alerts
105
+ const cutoff = Date.now() - 7 * 86400_000;
106
+ this.db.prepare('DELETE FROM alerts WHERE timestamp < ?').run(cutoff);
107
+ }
108
+ catch (err) {
109
+ console.warn('[RelayPlane Alerts] SQLite unavailable, memory-only mode:', err.message);
110
+ this.db = null;
111
+ }
112
+ }
113
+ updateConfig(config) {
114
+ this.config = { ...this.config, ...config };
115
+ }
116
+ getConfig() {
117
+ return { ...this.config };
118
+ }
119
+ /**
120
+ * Fire a threshold alert (budget % crossed)
121
+ */
122
+ fireThreshold(threshold, currentPercent, currentSpend, limit) {
123
+ if (!this.config.enabled)
124
+ return null;
125
+ const dedupKey = `threshold:${threshold}`;
126
+ if (this.isDuplicate(dedupKey))
127
+ return null;
128
+ const severity = threshold >= 95 ? 'critical' : threshold >= 80 ? 'warning' : 'info';
129
+ return this.createAlert('threshold', `Budget ${threshold}% threshold crossed (${currentPercent.toFixed(1)}% used: $${currentSpend.toFixed(2)} / $${limit})`, severity, {
130
+ threshold, currentPercent, currentSpend, limit,
131
+ });
132
+ }
133
+ /**
134
+ * Fire an anomaly alert
135
+ */
136
+ fireAnomaly(anomaly) {
137
+ if (!this.config.enabled)
138
+ return null;
139
+ const dedupKey = `anomaly:${anomaly.type}`;
140
+ if (this.isDuplicate(dedupKey))
141
+ return null;
142
+ return this.createAlert('anomaly', `Anomaly detected: ${anomaly.message}`, anomaly.severity === 'critical' ? 'critical' : 'warning', {
143
+ anomalyType: anomaly.type, ...anomaly.data,
144
+ });
145
+ }
146
+ /**
147
+ * Fire a breach alert (budget limit exceeded)
148
+ */
149
+ fireBreach(breachType, currentSpend, limit) {
150
+ if (!this.config.enabled)
151
+ return null;
152
+ const dedupKey = `breach:${breachType}`;
153
+ if (this.isDuplicate(dedupKey))
154
+ return null;
155
+ return this.createAlert('breach', `Budget breach: ${breachType} limit exceeded ($${currentSpend.toFixed(2)} / $${limit})`, 'critical', {
156
+ breachType, currentSpend, limit,
157
+ });
158
+ }
159
+ /**
160
+ * Get recent alerts
161
+ */
162
+ getRecent(limit = 20) {
163
+ if (this.db) {
164
+ const rows = this.db.prepare('SELECT id, type, message, severity, timestamp, data, delivered FROM alerts ORDER BY timestamp DESC LIMIT ?').all(limit);
165
+ return rows.map(r => ({
166
+ id: r.id,
167
+ type: r.type,
168
+ message: r.message,
169
+ severity: r.severity,
170
+ timestamp: r.timestamp,
171
+ data: JSON.parse(r.data),
172
+ delivered: r.delivered === 1,
173
+ }));
174
+ }
175
+ return this.memoryAlerts.slice(-limit).reverse();
176
+ }
177
+ /**
178
+ * Get alert count by type
179
+ */
180
+ getCounts() {
181
+ const counts = { threshold: 0, anomaly: 0, breach: 0 };
182
+ if (this.db) {
183
+ const rows = this.db.prepare('SELECT type, COUNT(*) as c FROM alerts GROUP BY type').all();
184
+ for (const r of rows) {
185
+ if (r.type in counts)
186
+ counts[r.type] = r.c;
187
+ }
188
+ }
189
+ else {
190
+ for (const a of this.memoryAlerts) {
191
+ counts[a.type]++;
192
+ }
193
+ }
194
+ return counts;
195
+ }
196
+ close() {
197
+ if (this.db) {
198
+ this.db.close();
199
+ this.db = null;
200
+ }
201
+ }
202
+ // ─── Private ──────────────────────────────────────────────────────
203
+ isDuplicate(key) {
204
+ const last = this.dedup.get(key);
205
+ if (last && (Date.now() - last) < this.config.cooldownMs) {
206
+ return true;
207
+ }
208
+ this.dedup.set(key, Date.now());
209
+ return false;
210
+ }
211
+ createAlert(type, message, severity, data) {
212
+ const alert = {
213
+ id: `alert-${++this.alertCounter}-${Date.now()}`,
214
+ type,
215
+ message,
216
+ severity,
217
+ timestamp: Date.now(),
218
+ data,
219
+ delivered: false,
220
+ };
221
+ // Store
222
+ this.storeAlert(alert);
223
+ // Deliver webhook (non-blocking)
224
+ this.deliverWebhook(alert);
225
+ return alert;
226
+ }
227
+ storeAlert(alert) {
228
+ if (this.db) {
229
+ this.db.prepare('INSERT INTO alerts (id, type, message, severity, timestamp, data, delivered) VALUES (?, ?, ?, ?, ?, ?, ?)').run(alert.id, alert.type, alert.message, alert.severity, alert.timestamp, JSON.stringify(alert.data), alert.delivered ? 1 : 0);
230
+ // Prune excess
231
+ const count = this.db.prepare('SELECT COUNT(*) as c FROM alerts').get().c;
232
+ if (count > this.config.maxHistory) {
233
+ this.db.prepare('DELETE FROM alerts WHERE id IN (SELECT id FROM alerts ORDER BY timestamp ASC LIMIT ?)').run(count - this.config.maxHistory);
234
+ }
235
+ }
236
+ else {
237
+ this.memoryAlerts.push(alert);
238
+ if (this.memoryAlerts.length > this.config.maxHistory) {
239
+ this.memoryAlerts.shift();
240
+ }
241
+ }
242
+ }
243
+ deliverWebhook(alert) {
244
+ if (!this.config.webhookUrl)
245
+ return;
246
+ const url = this.config.webhookUrl;
247
+ // Fire and forget
248
+ fetch(url, {
249
+ method: 'POST',
250
+ headers: { 'Content-Type': 'application/json' },
251
+ body: JSON.stringify({
252
+ source: 'relayplane',
253
+ alert: {
254
+ id: alert.id,
255
+ type: alert.type,
256
+ message: alert.message,
257
+ severity: alert.severity,
258
+ timestamp: new Date(alert.timestamp).toISOString(),
259
+ data: alert.data,
260
+ },
261
+ }),
262
+ }).then(() => {
263
+ alert.delivered = true;
264
+ try {
265
+ if (this.db) {
266
+ this.db.prepare('UPDATE alerts SET delivered = 1 WHERE id = ?').run(alert.id);
267
+ }
268
+ }
269
+ catch { /* SQLite failure non-fatal */ }
270
+ }).catch(() => {
271
+ // Webhook delivery failure — non-fatal
272
+ });
273
+ }
274
+ }
275
+ exports.AlertManager = AlertManager;
276
+ // ─── Singleton ──────────────────────────────────────────────────────
277
+ let _instance = null;
278
+ function getAlertManager(config) {
279
+ if (!_instance) {
280
+ _instance = new AlertManager(config);
281
+ }
282
+ return _instance;
283
+ }
284
+ function resetAlertManager() {
285
+ if (_instance) {
286
+ _instance.close();
287
+ _instance = null;
288
+ }
289
+ }
290
+ //# 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,65 @@
1
+ /**
2
+ * RelayPlane Anomaly Detection
3
+ *
4
+ * Sliding window analysis to detect runaway agent loops and cost spikes.
5
+ * Maintains an in-memory circular buffer of last 100 requests.
6
+ *
7
+ * Detection types:
8
+ * - Velocity spike: >10x normal request rate in 5-minute window
9
+ * - Cost acceleration: spend rate doubling every minute
10
+ * - Repetition detection: same model + similar token count >20 times in 5 min
11
+ * - Token explosion: single request >$5 estimated cost
12
+ *
13
+ * @packageDocumentation
14
+ */
15
+ export interface AnomalyConfig {
16
+ enabled: boolean;
17
+ /** Max requests in 5-minute window before velocity spike (default: 50) */
18
+ velocityThreshold: number;
19
+ /** Cost threshold for single request token explosion (default: 5.0 USD) */
20
+ tokenExplosionUsd: number;
21
+ /** Same model+token pattern count in 5 min before repetition flag (default: 20) */
22
+ repetitionThreshold: number;
23
+ /** Window size in ms for analysis (default: 300000 = 5 min) */
24
+ windowMs: number;
25
+ }
26
+ export type AnomalyType = 'velocity_spike' | 'cost_acceleration' | 'repetition' | 'token_explosion';
27
+ export interface AnomalyResult {
28
+ detected: boolean;
29
+ anomalies: AnomalyDetail[];
30
+ }
31
+ export interface AnomalyDetail {
32
+ type: AnomalyType;
33
+ message: string;
34
+ severity: 'warning' | 'critical';
35
+ data: Record<string, number | string>;
36
+ }
37
+ export declare const DEFAULT_ANOMALY_CONFIG: AnomalyConfig;
38
+ export declare class AnomalyDetector {
39
+ private config;
40
+ private buffer;
41
+ private readonly maxBufferSize;
42
+ private minuteBuckets;
43
+ constructor(config?: Partial<AnomalyConfig>);
44
+ updateConfig(config: Partial<AnomalyConfig>): void;
45
+ getConfig(): AnomalyConfig;
46
+ /**
47
+ * Record a completed request for analysis.
48
+ * Call this post-response. Returns anomalies detected.
49
+ */
50
+ recordAndAnalyze(entry: {
51
+ model: string;
52
+ tokensIn: number;
53
+ tokensOut: number;
54
+ costUsd: number;
55
+ }): AnomalyResult;
56
+ /** Get current buffer size (for testing) */
57
+ getBufferSize(): number;
58
+ /** Clear the buffer */
59
+ clear(): void;
60
+ private analyze;
61
+ private getBaselineRpm;
62
+ }
63
+ export declare function getAnomalyDetector(config?: Partial<AnomalyConfig>): AnomalyDetector;
64
+ export declare function resetAnomalyDetector(): void;
65
+ //# sourceMappingURL=anomaly.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"anomaly.d.ts","sourceRoot":"","sources":["../src/anomaly.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAIH,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,OAAO,CAAC;IACjB,0EAA0E;IAC1E,iBAAiB,EAAE,MAAM,CAAC;IAC1B,2EAA2E;IAC3E,iBAAiB,EAAE,MAAM,CAAC;IAC1B,mFAAmF;IACnF,mBAAmB,EAAE,MAAM,CAAC;IAC5B,+DAA+D;IAC/D,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,MAAM,WAAW,GAAG,gBAAgB,GAAG,mBAAmB,GAAG,YAAY,GAAG,iBAAiB,CAAC;AAEpG,MAAM,WAAW,aAAa;IAC5B,QAAQ,EAAE,OAAO,CAAC;IAClB,SAAS,EAAE,aAAa,EAAE,CAAC;CAC5B;AAED,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,WAAW,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,SAAS,GAAG,UAAU,CAAC;IACjC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC,CAAC;CACvC;AAYD,eAAO,MAAM,sBAAsB,EAAE,aAMpC,CAAC;AAIF,qBAAa,eAAe;IAC1B,OAAO,CAAC,MAAM,CAAgB;IAC9B,OAAO,CAAC,MAAM,CAAsB;IACpC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAO;IAGrC,OAAO,CAAC,aAAa,CAAkC;gBAE3C,MAAM,CAAC,EAAE,OAAO,CAAC,aAAa,CAAC;IAI3C,YAAY,CAAC,MAAM,EAAE,OAAO,CAAC,aAAa,CAAC,GAAG,IAAI;IAIlD,SAAS,IAAI,aAAa;IAI1B;;;OAGG;IACH,gBAAgB,CAAC,KAAK,EAAE;QACtB,KAAK,EAAE,MAAM,CAAC;QACd,QAAQ,EAAE,MAAM,CAAC;QACjB,SAAS,EAAE,MAAM,CAAC;QAClB,OAAO,EAAE,MAAM,CAAC;KACjB,GAAG,aAAa;IA4BjB,4CAA4C;IAC5C,aAAa,IAAI,MAAM;IAIvB,uBAAuB;IACvB,KAAK,IAAI,IAAI;IAOb,OAAO,CAAC,OAAO;IA+Ff,OAAO,CAAC,cAAc;CAQvB;AAMD,wBAAgB,kBAAkB,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC,aAAa,CAAC,GAAG,eAAe,CAKnF;AAED,wBAAgB,oBAAoB,IAAI,IAAI,CAE3C"}
@@ -0,0 +1,193 @@
1
+ "use strict";
2
+ /**
3
+ * RelayPlane Anomaly Detection
4
+ *
5
+ * Sliding window analysis to detect runaway agent loops and cost spikes.
6
+ * Maintains an in-memory circular buffer of last 100 requests.
7
+ *
8
+ * Detection types:
9
+ * - Velocity spike: >10x normal request rate in 5-minute window
10
+ * - Cost acceleration: spend rate doubling every minute
11
+ * - Repetition detection: same model + similar token count >20 times in 5 min
12
+ * - Token explosion: single request >$5 estimated cost
13
+ *
14
+ * @packageDocumentation
15
+ */
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ exports.AnomalyDetector = exports.DEFAULT_ANOMALY_CONFIG = void 0;
18
+ exports.getAnomalyDetector = getAnomalyDetector;
19
+ exports.resetAnomalyDetector = resetAnomalyDetector;
20
+ // ─── Defaults ────────────────────────────────────────────────────────
21
+ exports.DEFAULT_ANOMALY_CONFIG = {
22
+ enabled: false,
23
+ velocityThreshold: 50,
24
+ tokenExplosionUsd: 5.0,
25
+ repetitionThreshold: 20,
26
+ windowMs: 300_000,
27
+ };
28
+ // ─── AnomalyDetector ────────────────────────────────────────────────
29
+ class AnomalyDetector {
30
+ config;
31
+ buffer = [];
32
+ maxBufferSize = 100;
33
+ // Baseline: rolling average requests per minute over last hour
34
+ minuteBuckets = new Map();
35
+ constructor(config) {
36
+ this.config = { ...exports.DEFAULT_ANOMALY_CONFIG, ...config };
37
+ }
38
+ updateConfig(config) {
39
+ this.config = { ...this.config, ...config };
40
+ }
41
+ getConfig() {
42
+ return { ...this.config };
43
+ }
44
+ /**
45
+ * Record a completed request for analysis.
46
+ * Call this post-response. Returns anomalies detected.
47
+ */
48
+ recordAndAnalyze(entry) {
49
+ if (!this.config.enabled) {
50
+ return { detected: false, anomalies: [] };
51
+ }
52
+ const record = {
53
+ ...entry,
54
+ timestamp: Date.now(),
55
+ };
56
+ // Add to circular buffer
57
+ this.buffer.push(record);
58
+ if (this.buffer.length > this.maxBufferSize) {
59
+ this.buffer.shift();
60
+ }
61
+ // Track minute buckets for baseline
62
+ const minuteKey = Math.floor(record.timestamp / 60_000);
63
+ this.minuteBuckets.set(minuteKey, (this.minuteBuckets.get(minuteKey) ?? 0) + 1);
64
+ // Cleanup old buckets (keep last 60 minutes)
65
+ const cutoff = minuteKey - 60;
66
+ for (const [key] of this.minuteBuckets) {
67
+ if (key < cutoff)
68
+ this.minuteBuckets.delete(key);
69
+ }
70
+ return this.analyze(record);
71
+ }
72
+ /** Get current buffer size (for testing) */
73
+ getBufferSize() {
74
+ return this.buffer.length;
75
+ }
76
+ /** Clear the buffer */
77
+ clear() {
78
+ this.buffer = [];
79
+ this.minuteBuckets.clear();
80
+ }
81
+ // ─── Private ──────────────────────────────────────────────────────
82
+ analyze(current) {
83
+ const anomalies = [];
84
+ const now = current.timestamp;
85
+ const windowStart = now - this.config.windowMs;
86
+ const recent = this.buffer.filter(r => r.timestamp >= windowStart);
87
+ // 1. Token explosion — single request cost
88
+ if (current.costUsd > this.config.tokenExplosionUsd) {
89
+ anomalies.push({
90
+ type: 'token_explosion',
91
+ message: `Single request cost $${current.costUsd.toFixed(4)} exceeds threshold $${this.config.tokenExplosionUsd}`,
92
+ severity: 'critical',
93
+ data: { costUsd: current.costUsd, threshold: this.config.tokenExplosionUsd, model: current.model },
94
+ });
95
+ }
96
+ // 2. Velocity spike — too many requests in window
97
+ if (recent.length >= this.config.velocityThreshold) {
98
+ // Compare to baseline (avg requests per 5 min over last hour)
99
+ const baselineRpm = this.getBaselineRpm();
100
+ const currentRate = recent.length;
101
+ const expectedIn5Min = baselineRpm * 5;
102
+ // Only flag if >10x normal OR above absolute threshold
103
+ if (expectedIn5Min > 0 && currentRate > expectedIn5Min * 10) {
104
+ anomalies.push({
105
+ type: 'velocity_spike',
106
+ message: `${currentRate} requests in ${this.config.windowMs / 1000}s (baseline: ~${Math.round(expectedIn5Min)}/5min)`,
107
+ severity: 'warning',
108
+ data: { currentRate, baseline: expectedIn5Min, windowMs: this.config.windowMs },
109
+ });
110
+ }
111
+ else if (currentRate >= this.config.velocityThreshold) {
112
+ anomalies.push({
113
+ type: 'velocity_spike',
114
+ message: `${currentRate} requests in ${this.config.windowMs / 1000}s exceeds threshold ${this.config.velocityThreshold}`,
115
+ severity: 'warning',
116
+ data: { currentRate, threshold: this.config.velocityThreshold, windowMs: this.config.windowMs },
117
+ });
118
+ }
119
+ }
120
+ // 3. Repetition detection — same model + similar token count
121
+ if (recent.length >= this.config.repetitionThreshold) {
122
+ const patternCounts = new Map();
123
+ for (const r of recent) {
124
+ // Bucket token counts by rounding to nearest 100
125
+ const tokenBucket = Math.round((r.tokensIn + r.tokensOut) / 100) * 100;
126
+ const key = `${r.model}:${tokenBucket}`;
127
+ patternCounts.set(key, (patternCounts.get(key) ?? 0) + 1);
128
+ }
129
+ for (const [pattern, count] of patternCounts) {
130
+ if (count >= this.config.repetitionThreshold) {
131
+ anomalies.push({
132
+ type: 'repetition',
133
+ message: `Pattern "${pattern}" repeated ${count} times in ${this.config.windowMs / 1000}s (possible agent loop)`,
134
+ severity: 'critical',
135
+ data: { pattern, count, threshold: this.config.repetitionThreshold },
136
+ });
137
+ break; // One repetition alert is enough
138
+ }
139
+ }
140
+ }
141
+ // 4. Cost acceleration — check if spend rate is doubling
142
+ if (recent.length >= 10) {
143
+ const mid = Math.floor(recent.length / 2);
144
+ const firstHalf = recent.slice(0, mid);
145
+ const secondHalf = recent.slice(mid);
146
+ const firstCost = firstHalf.reduce((s, r) => s + r.costUsd, 0);
147
+ const secondCost = secondHalf.reduce((s, r) => s + r.costUsd, 0);
148
+ // Time-normalize
149
+ const firstDuration = (firstHalf[firstHalf.length - 1].timestamp - firstHalf[0].timestamp) || 1;
150
+ const secondDuration = (secondHalf[secondHalf.length - 1].timestamp - secondHalf[0].timestamp) || 1;
151
+ const firstRate = firstCost / firstDuration;
152
+ const secondRate = secondCost / secondDuration;
153
+ if (firstRate > 0 && secondRate > firstRate * 2 && secondCost > 1) {
154
+ anomalies.push({
155
+ type: 'cost_acceleration',
156
+ message: `Cost rate doubled: $${(firstRate * 60000).toFixed(4)}/min → $${(secondRate * 60000).toFixed(4)}/min`,
157
+ severity: 'warning',
158
+ data: {
159
+ firstRatePerMin: firstRate * 60000,
160
+ secondRatePerMin: secondRate * 60000,
161
+ ratio: secondRate / firstRate,
162
+ },
163
+ });
164
+ }
165
+ }
166
+ return {
167
+ detected: anomalies.length > 0,
168
+ anomalies,
169
+ };
170
+ }
171
+ getBaselineRpm() {
172
+ if (this.minuteBuckets.size <= 1)
173
+ return 0;
174
+ let total = 0;
175
+ for (const [, count] of this.minuteBuckets) {
176
+ total += count;
177
+ }
178
+ return total / this.minuteBuckets.size;
179
+ }
180
+ }
181
+ exports.AnomalyDetector = AnomalyDetector;
182
+ // ─── Singleton ──────────────────────────────────────────────────────
183
+ let _instance = null;
184
+ function getAnomalyDetector(config) {
185
+ if (!_instance) {
186
+ _instance = new AnomalyDetector(config);
187
+ }
188
+ return _instance;
189
+ }
190
+ function resetAnomalyDetector() {
191
+ _instance = null;
192
+ }
193
+ //# sourceMappingURL=anomaly.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"anomaly.js","sourceRoot":"","sources":["../src/anomaly.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,98 @@
1
+ /**
2
+ * RelayPlane Budget Enforcement
3
+ *
4
+ * SQLite-based spend tracking with in-memory cache for <5ms hot-path checks.
5
+ * Tracks daily/hourly windows and per-request limits.
6
+ *
7
+ * @packageDocumentation
8
+ */
9
+ export interface BudgetConfig {
10
+ enabled: boolean;
11
+ /** Daily spend limit in USD (default: 50) */
12
+ dailyUsd: number;
13
+ /** Hourly spend limit in USD (default: 10) */
14
+ hourlyUsd: number;
15
+ /** Per-request spend limit in USD (default: 2) */
16
+ perRequestUsd: number;
17
+ /** Action on breach: block, warn, downgrade, alert */
18
+ onBreach: 'block' | 'warn' | 'downgrade' | 'alert';
19
+ /** Model to downgrade to when onBreach=downgrade */
20
+ downgradeTo: string;
21
+ /** Webhook URL for alerts */
22
+ alertWebhook?: string;
23
+ /** Alert thresholds as percentages of daily limit */
24
+ alertThresholds: number[];
25
+ }
26
+ export interface BudgetStatus {
27
+ dailySpend: number;
28
+ dailyLimit: number;
29
+ dailyPercent: number;
30
+ hourlySpend: number;
31
+ hourlyLimit: number;
32
+ hourlyPercent: number;
33
+ dailyWindow: string;
34
+ hourlyWindow: string;
35
+ breached: boolean;
36
+ breachType: 'none' | 'daily' | 'hourly';
37
+ }
38
+ export interface BudgetCheckResult {
39
+ allowed: boolean;
40
+ breached: boolean;
41
+ breachType: 'none' | 'daily' | 'hourly' | 'per-request';
42
+ action: 'allow' | 'block' | 'warn' | 'downgrade' | 'alert';
43
+ currentDailySpend: number;
44
+ currentHourlySpend: number;
45
+ thresholdsCrossed: number[];
46
+ }
47
+ export declare const DEFAULT_BUDGET_CONFIG: BudgetConfig;
48
+ export declare function getDailyWindow(timestamp?: number): string;
49
+ export declare function getHourlyWindow(timestamp?: number): string;
50
+ export declare class BudgetManager {
51
+ private config;
52
+ private db;
53
+ private _initialized;
54
+ private dailySpendCache;
55
+ private hourlySpendCache;
56
+ private cachedDailyWindow;
57
+ private cachedHourlyWindow;
58
+ private firedThresholds;
59
+ private pendingWrites;
60
+ private flushTimer;
61
+ constructor(config?: Partial<BudgetConfig>);
62
+ /** Initialize SQLite storage. Safe to call multiple times. */
63
+ init(): void;
64
+ /** Update config at runtime */
65
+ updateConfig(config: Partial<BudgetConfig>): void;
66
+ getConfig(): BudgetConfig;
67
+ /**
68
+ * Pre-request budget check. Must be <5ms.
69
+ * Uses in-memory cache, never touches SQLite.
70
+ */
71
+ checkBudget(estimatedCost?: number): BudgetCheckResult;
72
+ /**
73
+ * Record spend after a request completes.
74
+ * Updates in-memory cache immediately, writes to SQLite async.
75
+ */
76
+ recordSpend(amount: number, model: string): void;
77
+ /** Mark a threshold as fired (for deduplication) */
78
+ markThresholdFired(threshold: number): void;
79
+ /** Get current budget status */
80
+ getStatus(): BudgetStatus;
81
+ /** Reset current window spend */
82
+ reset(): void;
83
+ /** Set budget limits */
84
+ setLimits(limits: {
85
+ dailyUsd?: number;
86
+ hourlyUsd?: number;
87
+ perRequestUsd?: number;
88
+ }): void;
89
+ /** Shutdown: flush pending writes */
90
+ close(): void;
91
+ private ensureCacheWindows;
92
+ private refreshCache;
93
+ private scheduleFlush;
94
+ private flushPendingWrites;
95
+ }
96
+ export declare function getBudgetManager(config?: Partial<BudgetConfig>): BudgetManager;
97
+ export declare function resetBudgetManager(): void;
98
+ //# sourceMappingURL=budget.d.ts.map