@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.
- package/README.md +297 -20
- package/assets/relayplane-proxy.service +20 -0
- package/dist/alerts.d.ts +72 -0
- package/dist/alerts.d.ts.map +1 -0
- package/dist/alerts.js +290 -0
- package/dist/alerts.js.map +1 -0
- package/dist/anomaly.d.ts +65 -0
- package/dist/anomaly.d.ts.map +1 -0
- package/dist/anomaly.js +193 -0
- package/dist/anomaly.js.map +1 -0
- package/dist/budget.d.ts +98 -0
- package/dist/budget.d.ts.map +1 -0
- package/dist/budget.js +356 -0
- package/dist/budget.js.map +1 -0
- package/dist/cli.js +512 -93
- package/dist/cli.js.map +1 -1
- package/dist/config.d.ts +28 -2
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +122 -24
- package/dist/config.js.map +1 -1
- package/dist/downgrade.d.ts +37 -0
- package/dist/downgrade.d.ts.map +1 -0
- package/dist/downgrade.js +79 -0
- package/dist/downgrade.js.map +1 -0
- package/dist/mesh/capture.d.ts +11 -0
- package/dist/mesh/capture.d.ts.map +1 -0
- package/dist/mesh/capture.js +43 -0
- package/dist/mesh/capture.js.map +1 -0
- package/dist/mesh/fitness.d.ts +14 -0
- package/dist/mesh/fitness.d.ts.map +1 -0
- package/dist/mesh/fitness.js +40 -0
- package/dist/mesh/fitness.js.map +1 -0
- package/dist/mesh/index.d.ts +39 -0
- package/dist/mesh/index.d.ts.map +1 -0
- package/dist/mesh/index.js +118 -0
- package/dist/mesh/index.js.map +1 -0
- package/dist/mesh/store.d.ts +30 -0
- package/dist/mesh/store.d.ts.map +1 -0
- package/dist/mesh/store.js +174 -0
- package/dist/mesh/store.js.map +1 -0
- package/dist/mesh/sync.d.ts +37 -0
- package/dist/mesh/sync.d.ts.map +1 -0
- package/dist/mesh/sync.js +154 -0
- package/dist/mesh/sync.js.map +1 -0
- package/dist/mesh/types.d.ts +57 -0
- package/dist/mesh/types.d.ts.map +1 -0
- package/dist/mesh/types.js +7 -0
- package/dist/mesh/types.js.map +1 -0
- package/dist/rate-limiter.d.ts +64 -0
- package/dist/rate-limiter.d.ts.map +1 -0
- package/dist/rate-limiter.js +159 -0
- package/dist/rate-limiter.js.map +1 -0
- package/dist/relay-config.d.ts +9 -0
- package/dist/relay-config.d.ts.map +1 -1
- package/dist/relay-config.js +2 -0
- package/dist/relay-config.js.map +1 -1
- package/dist/response-cache.d.ts +139 -0
- package/dist/response-cache.d.ts.map +1 -0
- package/dist/response-cache.js +515 -0
- package/dist/response-cache.js.map +1 -0
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +5 -1
- package/dist/server.js.map +1 -1
- package/dist/standalone-proxy.d.ts +2 -1
- package/dist/standalone-proxy.d.ts.map +1 -1
- package/dist/standalone-proxy.js +736 -50
- package/dist/standalone-proxy.js.map +1 -1
- package/dist/telemetry.d.ts.map +1 -1
- package/dist/telemetry.js +21 -5
- package/dist/telemetry.js.map +1 -1
- package/dist/utils/model-suggestions.d.ts.map +1 -1
- package/dist/utils/model-suggestions.js +19 -2
- package/dist/utils/model-suggestions.js.map +1 -1
- package/dist/utils/version-status.d.ts +9 -0
- package/dist/utils/version-status.d.ts.map +1 -0
- package/dist/utils/version-status.js +28 -0
- package/dist/utils/version-status.js.map +1 -0
- 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"}
|
package/dist/anomaly.js
ADDED
|
@@ -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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"}
|
package/dist/budget.d.ts
ADDED
|
@@ -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
|