@agentgazer/server 0.1.1 → 0.3.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/dist/alerts/evaluator.d.ts +21 -0
- package/dist/alerts/evaluator.d.ts.map +1 -1
- package/dist/alerts/evaluator.js +243 -79
- package/dist/alerts/evaluator.js.map +1 -1
- package/dist/db.d.ts +108 -2
- package/dist/db.d.ts.map +1 -1
- package/dist/db.js +536 -11
- package/dist/db.js.map +1 -1
- package/dist/index.d.ts +5 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -1
- package/dist/index.js.map +1 -1
- package/dist/middleware/auth.js +1 -1
- package/dist/middleware/auth.js.map +1 -1
- package/dist/routes/agents.d.ts.map +1 -1
- package/dist/routes/agents.js +116 -3
- package/dist/routes/agents.js.map +1 -1
- package/dist/routes/alerts.d.ts.map +1 -1
- package/dist/routes/alerts.js +55 -5
- package/dist/routes/alerts.js.map +1 -1
- package/dist/routes/events.d.ts.map +1 -1
- package/dist/routes/events.js +27 -14
- package/dist/routes/events.js.map +1 -1
- package/dist/routes/health.d.ts.map +1 -1
- package/dist/routes/health.js +13 -0
- package/dist/routes/health.js.map +1 -1
- package/dist/routes/openclaw.d.ts +3 -0
- package/dist/routes/openclaw.d.ts.map +1 -0
- package/dist/routes/openclaw.js +139 -0
- package/dist/routes/openclaw.js.map +1 -0
- package/dist/routes/overview.d.ts +9 -0
- package/dist/routes/overview.d.ts.map +1 -0
- package/dist/routes/overview.js +101 -0
- package/dist/routes/overview.js.map +1 -0
- package/dist/routes/providers.d.ts +15 -0
- package/dist/routes/providers.d.ts.map +1 -0
- package/dist/routes/providers.js +317 -0
- package/dist/routes/providers.js.map +1 -0
- package/dist/routes/settings.d.ts +7 -0
- package/dist/routes/settings.d.ts.map +1 -0
- package/dist/routes/settings.js +134 -0
- package/dist/routes/settings.js.map +1 -0
- package/dist/server.d.ts +8 -0
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +11 -0
- package/dist/server.js.map +1 -1
- package/dist/services/provider-validator.d.ts +2 -0
- package/dist/services/provider-validator.d.ts.map +1 -0
- package/dist/services/provider-validator.js +8 -0
- package/dist/services/provider-validator.js.map +1 -0
- package/package.json +2 -2
package/dist/db.js
CHANGED
|
@@ -23,6 +23,24 @@ exports.getAllRateLimits = getAllRateLimits;
|
|
|
23
23
|
exports.getRateLimit = getRateLimit;
|
|
24
24
|
exports.setRateLimit = setRateLimit;
|
|
25
25
|
exports.deleteRateLimit = deleteRateLimit;
|
|
26
|
+
exports.getProviderModels = getProviderModels;
|
|
27
|
+
exports.addProviderModel = addProviderModel;
|
|
28
|
+
exports.deleteProviderModel = deleteProviderModel;
|
|
29
|
+
exports.getProviderSettings = getProviderSettings;
|
|
30
|
+
exports.getAllProviderSettings = getAllProviderSettings;
|
|
31
|
+
exports.upsertProviderSettings = upsertProviderSettings;
|
|
32
|
+
exports.getProviderStats = getProviderStats;
|
|
33
|
+
exports.getProviderModelStats = getProviderModelStats;
|
|
34
|
+
exports.getAllProviderListStats = getAllProviderListStats;
|
|
35
|
+
exports.getKillSwitchConfig = getKillSwitchConfig;
|
|
36
|
+
exports.updateKillSwitchConfig = updateKillSwitchConfig;
|
|
37
|
+
exports.getOverviewStats = getOverviewStats;
|
|
38
|
+
exports.getTopAgentsByCost = getTopAgentsByCost;
|
|
39
|
+
exports.getTopModelsByTokens = getTopModelsByTokens;
|
|
40
|
+
exports.getDailyTrends = getDailyTrends;
|
|
41
|
+
exports.deleteAgent = deleteAgent;
|
|
42
|
+
exports.deleteProvider = deleteProvider;
|
|
43
|
+
exports.getRecentEvents = getRecentEvents;
|
|
26
44
|
const better_sqlite3_1 = __importDefault(require("better-sqlite3"));
|
|
27
45
|
const node_crypto_1 = require("node:crypto");
|
|
28
46
|
function initDatabase(options) {
|
|
@@ -71,12 +89,45 @@ function runMigrations(db) {
|
|
|
71
89
|
if (!alertColNames.includes("telegram_config")) {
|
|
72
90
|
db.exec("ALTER TABLE alert_rules ADD COLUMN telegram_config TEXT");
|
|
73
91
|
}
|
|
92
|
+
// Migration: Add repeat/recovery columns to alert_rules table
|
|
93
|
+
if (!alertColNames.includes("repeat_enabled")) {
|
|
94
|
+
db.exec("ALTER TABLE alert_rules ADD COLUMN repeat_enabled INTEGER NOT NULL DEFAULT 1");
|
|
95
|
+
}
|
|
96
|
+
if (!alertColNames.includes("repeat_interval_minutes")) {
|
|
97
|
+
db.exec("ALTER TABLE alert_rules ADD COLUMN repeat_interval_minutes INTEGER NOT NULL DEFAULT 15");
|
|
98
|
+
}
|
|
99
|
+
if (!alertColNames.includes("recovery_notify")) {
|
|
100
|
+
db.exec("ALTER TABLE alert_rules ADD COLUMN recovery_notify INTEGER NOT NULL DEFAULT 0");
|
|
101
|
+
}
|
|
102
|
+
if (!alertColNames.includes("state")) {
|
|
103
|
+
db.exec("ALTER TABLE alert_rules ADD COLUMN state TEXT NOT NULL DEFAULT 'normal'");
|
|
104
|
+
}
|
|
105
|
+
if (!alertColNames.includes("last_triggered_at")) {
|
|
106
|
+
db.exec("ALTER TABLE alert_rules ADD COLUMN last_triggered_at TEXT");
|
|
107
|
+
}
|
|
108
|
+
if (!alertColNames.includes("budget_period")) {
|
|
109
|
+
db.exec("ALTER TABLE alert_rules ADD COLUMN budget_period TEXT");
|
|
110
|
+
}
|
|
74
111
|
// Migration: Add requested_model column to agent_events table
|
|
75
112
|
const eventCols = db.prepare("PRAGMA table_info(agent_events)").all();
|
|
76
113
|
const eventColNames = eventCols.map((c) => c.name);
|
|
77
114
|
if (!eventColNames.includes("requested_model")) {
|
|
78
115
|
db.exec("ALTER TABLE agent_events ADD COLUMN requested_model TEXT");
|
|
79
116
|
}
|
|
117
|
+
// Migration: Add kill_switch columns to agents table
|
|
118
|
+
if (!colNames.includes("kill_switch_enabled")) {
|
|
119
|
+
db.exec("ALTER TABLE agents ADD COLUMN kill_switch_enabled INTEGER NOT NULL DEFAULT 0");
|
|
120
|
+
}
|
|
121
|
+
if (!colNames.includes("kill_switch_window_size")) {
|
|
122
|
+
db.exec("ALTER TABLE agents ADD COLUMN kill_switch_window_size INTEGER NOT NULL DEFAULT 20");
|
|
123
|
+
}
|
|
124
|
+
if (!colNames.includes("kill_switch_threshold")) {
|
|
125
|
+
db.exec("ALTER TABLE agents ADD COLUMN kill_switch_threshold REAL NOT NULL DEFAULT 10.0");
|
|
126
|
+
}
|
|
127
|
+
// Migration: Add deactivated_by column to agents table
|
|
128
|
+
if (!colNames.includes("deactivated_by")) {
|
|
129
|
+
db.exec("ALTER TABLE agents ADD COLUMN deactivated_by TEXT");
|
|
130
|
+
}
|
|
80
131
|
}
|
|
81
132
|
const SCHEMA = `
|
|
82
133
|
CREATE TABLE IF NOT EXISTS agents (
|
|
@@ -84,9 +135,13 @@ const SCHEMA = `
|
|
|
84
135
|
agent_id TEXT NOT NULL UNIQUE,
|
|
85
136
|
name TEXT,
|
|
86
137
|
active INTEGER NOT NULL DEFAULT 1,
|
|
138
|
+
deactivated_by TEXT,
|
|
87
139
|
budget_limit REAL,
|
|
88
140
|
allowed_hours_start INTEGER,
|
|
89
141
|
allowed_hours_end INTEGER,
|
|
142
|
+
kill_switch_enabled INTEGER NOT NULL DEFAULT 0,
|
|
143
|
+
kill_switch_window_size INTEGER NOT NULL DEFAULT 20,
|
|
144
|
+
kill_switch_threshold REAL NOT NULL DEFAULT 10.0,
|
|
90
145
|
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
91
146
|
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
92
147
|
);
|
|
@@ -96,7 +151,7 @@ const SCHEMA = `
|
|
|
96
151
|
CREATE TABLE IF NOT EXISTS agent_events (
|
|
97
152
|
id TEXT PRIMARY KEY DEFAULT (lower(hex(randomblob(16)))),
|
|
98
153
|
agent_id TEXT NOT NULL,
|
|
99
|
-
event_type TEXT NOT NULL CHECK (event_type IN ('llm_call', 'completion', 'heartbeat', 'error', 'custom', 'blocked')),
|
|
154
|
+
event_type TEXT NOT NULL CHECK (event_type IN ('llm_call', 'completion', 'heartbeat', 'error', 'custom', 'blocked', 'kill_switch')),
|
|
100
155
|
provider TEXT,
|
|
101
156
|
model TEXT,
|
|
102
157
|
requested_model TEXT,
|
|
@@ -124,7 +179,7 @@ const SCHEMA = `
|
|
|
124
179
|
CREATE TABLE IF NOT EXISTS alert_rules (
|
|
125
180
|
id TEXT PRIMARY KEY DEFAULT (lower(hex(randomblob(16)))),
|
|
126
181
|
agent_id TEXT NOT NULL,
|
|
127
|
-
rule_type TEXT NOT NULL CHECK (rule_type IN ('agent_down', 'error_rate', 'budget')),
|
|
182
|
+
rule_type TEXT NOT NULL CHECK (rule_type IN ('agent_down', 'error_rate', 'budget', 'kill_switch')),
|
|
128
183
|
config TEXT NOT NULL DEFAULT '{}',
|
|
129
184
|
enabled INTEGER NOT NULL DEFAULT 1,
|
|
130
185
|
notification_type TEXT NOT NULL DEFAULT 'webhook',
|
|
@@ -172,6 +227,28 @@ const SCHEMA = `
|
|
|
172
227
|
);
|
|
173
228
|
|
|
174
229
|
CREATE INDEX IF NOT EXISTS idx_agent_rate_limits_agent ON agent_rate_limits(agent_id);
|
|
230
|
+
|
|
231
|
+
-- Custom models added by user per provider
|
|
232
|
+
CREATE TABLE IF NOT EXISTS provider_models (
|
|
233
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
234
|
+
provider TEXT NOT NULL,
|
|
235
|
+
model_id TEXT NOT NULL,
|
|
236
|
+
display_name TEXT,
|
|
237
|
+
verified_at TEXT,
|
|
238
|
+
created_at TEXT DEFAULT (datetime('now')),
|
|
239
|
+
UNIQUE(provider, model_id)
|
|
240
|
+
);
|
|
241
|
+
|
|
242
|
+
CREATE INDEX IF NOT EXISTS idx_provider_models_provider ON provider_models(provider);
|
|
243
|
+
|
|
244
|
+
-- Provider-level settings (active toggle, rate limit)
|
|
245
|
+
CREATE TABLE IF NOT EXISTS provider_settings (
|
|
246
|
+
provider TEXT PRIMARY KEY,
|
|
247
|
+
active INTEGER DEFAULT 1,
|
|
248
|
+
rate_limit_max_requests INTEGER,
|
|
249
|
+
rate_limit_window_seconds INTEGER,
|
|
250
|
+
updated_at TEXT DEFAULT (datetime('now'))
|
|
251
|
+
);
|
|
175
252
|
`;
|
|
176
253
|
// ---------------------------------------------------------------------------
|
|
177
254
|
// Query helpers
|
|
@@ -218,7 +295,7 @@ function getAllAgents(db) {
|
|
|
218
295
|
const todayStart = todayUTC.toISOString();
|
|
219
296
|
return db.prepare(`
|
|
220
297
|
SELECT
|
|
221
|
-
a.id, a.agent_id, a.name, a.active, a.budget_limit,
|
|
298
|
+
a.id, a.agent_id, a.name, a.active, a.deactivated_by, a.budget_limit, a.kill_switch_enabled,
|
|
222
299
|
a.created_at, a.updated_at,
|
|
223
300
|
COALESCE(SUM(e.tokens_total), 0) AS total_tokens,
|
|
224
301
|
COALESCE(SUM(e.cost_usd), 0) AS total_cost,
|
|
@@ -235,7 +312,7 @@ function getAgentByAgentId(db, agentId) {
|
|
|
235
312
|
const todayStart = todayUTC.toISOString();
|
|
236
313
|
return db.prepare(`
|
|
237
314
|
SELECT
|
|
238
|
-
a.id, a.agent_id, a.name, a.active, a.budget_limit,
|
|
315
|
+
a.id, a.agent_id, a.name, a.active, a.deactivated_by, a.budget_limit, a.kill_switch_enabled,
|
|
239
316
|
a.created_at, a.updated_at,
|
|
240
317
|
COALESCE(SUM(e.tokens_total), 0) AS total_tokens,
|
|
241
318
|
COALESCE(SUM(e.cost_usd), 0) AS total_cost,
|
|
@@ -260,8 +337,12 @@ function purgeOldData(db, retentionDays) {
|
|
|
260
337
|
};
|
|
261
338
|
}
|
|
262
339
|
function queryEvents(db, options) {
|
|
263
|
-
const conditions = [
|
|
264
|
-
const params = [
|
|
340
|
+
const conditions = [];
|
|
341
|
+
const params = [];
|
|
342
|
+
if (options.agent_id) {
|
|
343
|
+
conditions.push("agent_id = ?");
|
|
344
|
+
params.push(options.agent_id);
|
|
345
|
+
}
|
|
265
346
|
if (options.from) {
|
|
266
347
|
conditions.push("timestamp >= ?");
|
|
267
348
|
params.push(options.from);
|
|
@@ -291,10 +372,16 @@ function queryEvents(db, options) {
|
|
|
291
372
|
const term = `%${options.search}%`;
|
|
292
373
|
params.push(term, term, term, term);
|
|
293
374
|
}
|
|
294
|
-
const
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
375
|
+
const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
376
|
+
// Get total count
|
|
377
|
+
const countSql = `SELECT COUNT(*) as total FROM agent_events ${whereClause}`;
|
|
378
|
+
const countResult = db.prepare(countSql).get(...params);
|
|
379
|
+
// Get paginated results
|
|
380
|
+
const limit = options.limit ?? 50;
|
|
381
|
+
const offset = options.offset ?? 0;
|
|
382
|
+
const sql = `SELECT * FROM agent_events ${whereClause} ORDER BY timestamp DESC LIMIT ? OFFSET ?`;
|
|
383
|
+
const events = db.prepare(sql).all(...params, limit, offset);
|
|
384
|
+
return { events, total: countResult.total };
|
|
298
385
|
}
|
|
299
386
|
/**
|
|
300
387
|
* Calculate an agent's total spending for today (server local time).
|
|
@@ -312,16 +399,21 @@ function getDailySpend(db, agentId) {
|
|
|
312
399
|
}
|
|
313
400
|
function getAgentPolicy(db, agentId) {
|
|
314
401
|
const row = db.prepare(`
|
|
315
|
-
SELECT active, budget_limit, allowed_hours_start, allowed_hours_end
|
|
402
|
+
SELECT active, deactivated_by, budget_limit, allowed_hours_start, allowed_hours_end,
|
|
403
|
+
kill_switch_enabled, kill_switch_window_size, kill_switch_threshold
|
|
316
404
|
FROM agents WHERE agent_id = ?
|
|
317
405
|
`).get(agentId);
|
|
318
406
|
if (!row)
|
|
319
407
|
return null;
|
|
320
408
|
return {
|
|
321
409
|
active: row.active === 1,
|
|
410
|
+
deactivated_by: row.deactivated_by,
|
|
322
411
|
budget_limit: row.budget_limit,
|
|
323
412
|
allowed_hours_start: row.allowed_hours_start,
|
|
324
413
|
allowed_hours_end: row.allowed_hours_end,
|
|
414
|
+
kill_switch_enabled: row.kill_switch_enabled,
|
|
415
|
+
kill_switch_window_size: row.kill_switch_window_size,
|
|
416
|
+
kill_switch_threshold: row.kill_switch_threshold,
|
|
325
417
|
};
|
|
326
418
|
}
|
|
327
419
|
function updateAgentPolicy(db, agentId, policy) {
|
|
@@ -331,6 +423,10 @@ function updateAgentPolicy(db, agentId, policy) {
|
|
|
331
423
|
updates.push("active = ?");
|
|
332
424
|
params.push(policy.active ? 1 : 0);
|
|
333
425
|
}
|
|
426
|
+
if ("deactivated_by" in policy) {
|
|
427
|
+
updates.push("deactivated_by = ?");
|
|
428
|
+
params.push(policy.deactivated_by);
|
|
429
|
+
}
|
|
334
430
|
if (policy.budget_limit !== undefined) {
|
|
335
431
|
updates.push("budget_limit = ?");
|
|
336
432
|
params.push(policy.budget_limit);
|
|
@@ -343,6 +439,18 @@ function updateAgentPolicy(db, agentId, policy) {
|
|
|
343
439
|
updates.push("allowed_hours_end = ?");
|
|
344
440
|
params.push(policy.allowed_hours_end);
|
|
345
441
|
}
|
|
442
|
+
if (policy.kill_switch_enabled !== undefined) {
|
|
443
|
+
updates.push("kill_switch_enabled = ?");
|
|
444
|
+
params.push(policy.kill_switch_enabled);
|
|
445
|
+
}
|
|
446
|
+
if (policy.kill_switch_window_size !== undefined) {
|
|
447
|
+
updates.push("kill_switch_window_size = ?");
|
|
448
|
+
params.push(policy.kill_switch_window_size);
|
|
449
|
+
}
|
|
450
|
+
if (policy.kill_switch_threshold !== undefined) {
|
|
451
|
+
updates.push("kill_switch_threshold = ?");
|
|
452
|
+
params.push(policy.kill_switch_threshold);
|
|
453
|
+
}
|
|
346
454
|
if (updates.length === 0)
|
|
347
455
|
return false;
|
|
348
456
|
updates.push("updated_at = ?");
|
|
@@ -422,4 +530,421 @@ function deleteRateLimit(db, agentId, provider) {
|
|
|
422
530
|
`).run(agentId, provider);
|
|
423
531
|
return result.changes > 0;
|
|
424
532
|
}
|
|
533
|
+
function getProviderModels(db, provider) {
|
|
534
|
+
return db.prepare(`
|
|
535
|
+
SELECT * FROM provider_models WHERE provider = ? ORDER BY model_id
|
|
536
|
+
`).all(provider);
|
|
537
|
+
}
|
|
538
|
+
function addProviderModel(db, provider, modelId, displayName, verifiedAt) {
|
|
539
|
+
db.prepare(`
|
|
540
|
+
INSERT INTO provider_models (provider, model_id, display_name, verified_at)
|
|
541
|
+
VALUES (?, ?, ?, ?)
|
|
542
|
+
ON CONFLICT(provider, model_id) DO UPDATE SET
|
|
543
|
+
display_name = COALESCE(excluded.display_name, display_name),
|
|
544
|
+
verified_at = COALESCE(excluded.verified_at, verified_at)
|
|
545
|
+
`).run(provider, modelId, displayName ?? null, verifiedAt ?? null);
|
|
546
|
+
return db.prepare(`
|
|
547
|
+
SELECT * FROM provider_models WHERE provider = ? AND model_id = ?
|
|
548
|
+
`).get(provider, modelId);
|
|
549
|
+
}
|
|
550
|
+
function deleteProviderModel(db, provider, modelId) {
|
|
551
|
+
const result = db.prepare(`
|
|
552
|
+
DELETE FROM provider_models WHERE provider = ? AND model_id = ?
|
|
553
|
+
`).run(provider, modelId);
|
|
554
|
+
return result.changes > 0;
|
|
555
|
+
}
|
|
556
|
+
function getProviderSettings(db, provider) {
|
|
557
|
+
return db.prepare(`
|
|
558
|
+
SELECT * FROM provider_settings WHERE provider = ?
|
|
559
|
+
`).get(provider);
|
|
560
|
+
}
|
|
561
|
+
function getAllProviderSettings(db) {
|
|
562
|
+
return db.prepare(`
|
|
563
|
+
SELECT * FROM provider_settings ORDER BY provider
|
|
564
|
+
`).all();
|
|
565
|
+
}
|
|
566
|
+
function upsertProviderSettings(db, provider, settings) {
|
|
567
|
+
const now = new Date().toISOString();
|
|
568
|
+
const existing = getProviderSettings(db, provider);
|
|
569
|
+
const active = settings.active !== undefined ? (settings.active ? 1 : 0) : (existing?.active ?? 1);
|
|
570
|
+
const maxReqs = settings.rate_limit_max_requests !== undefined
|
|
571
|
+
? settings.rate_limit_max_requests
|
|
572
|
+
: (existing?.rate_limit_max_requests ?? null);
|
|
573
|
+
const windowSecs = settings.rate_limit_window_seconds !== undefined
|
|
574
|
+
? settings.rate_limit_window_seconds
|
|
575
|
+
: (existing?.rate_limit_window_seconds ?? null);
|
|
576
|
+
db.prepare(`
|
|
577
|
+
INSERT INTO provider_settings (provider, active, rate_limit_max_requests, rate_limit_window_seconds, updated_at)
|
|
578
|
+
VALUES (?, ?, ?, ?, ?)
|
|
579
|
+
ON CONFLICT(provider) DO UPDATE SET
|
|
580
|
+
active = excluded.active,
|
|
581
|
+
rate_limit_max_requests = excluded.rate_limit_max_requests,
|
|
582
|
+
rate_limit_window_seconds = excluded.rate_limit_window_seconds,
|
|
583
|
+
updated_at = excluded.updated_at
|
|
584
|
+
`).run(provider, active, maxReqs, windowSecs, now);
|
|
585
|
+
return getProviderSettings(db, provider);
|
|
586
|
+
}
|
|
587
|
+
function getProviderStats(db, provider, from, to) {
|
|
588
|
+
const conditions = ["provider = ?"];
|
|
589
|
+
const params = [provider];
|
|
590
|
+
if (from) {
|
|
591
|
+
conditions.push("timestamp >= ?");
|
|
592
|
+
params.push(from);
|
|
593
|
+
}
|
|
594
|
+
if (to) {
|
|
595
|
+
conditions.push("timestamp <= ?");
|
|
596
|
+
params.push(to);
|
|
597
|
+
}
|
|
598
|
+
const result = db.prepare(`
|
|
599
|
+
SELECT
|
|
600
|
+
? as provider,
|
|
601
|
+
COUNT(*) as total_requests,
|
|
602
|
+
COALESCE(SUM(tokens_total), 0) as total_tokens,
|
|
603
|
+
COALESCE(SUM(cost_usd), 0) as total_cost
|
|
604
|
+
FROM agent_events
|
|
605
|
+
WHERE ${conditions.join(" AND ")}
|
|
606
|
+
`).get(provider, ...params);
|
|
607
|
+
return result;
|
|
608
|
+
}
|
|
609
|
+
function getProviderModelStats(db, provider, from, to) {
|
|
610
|
+
const conditions = ["provider = ?", "model IS NOT NULL"];
|
|
611
|
+
const params = [provider];
|
|
612
|
+
if (from) {
|
|
613
|
+
conditions.push("timestamp >= ?");
|
|
614
|
+
params.push(from);
|
|
615
|
+
}
|
|
616
|
+
if (to) {
|
|
617
|
+
conditions.push("timestamp <= ?");
|
|
618
|
+
params.push(to);
|
|
619
|
+
}
|
|
620
|
+
return db.prepare(`
|
|
621
|
+
SELECT
|
|
622
|
+
model,
|
|
623
|
+
COUNT(*) as requests,
|
|
624
|
+
COALESCE(SUM(tokens_total), 0) as tokens,
|
|
625
|
+
COALESCE(SUM(cost_usd), 0) as cost
|
|
626
|
+
FROM agent_events
|
|
627
|
+
WHERE ${conditions.join(" AND ")}
|
|
628
|
+
GROUP BY model
|
|
629
|
+
ORDER BY cost DESC
|
|
630
|
+
`).all(...params);
|
|
631
|
+
}
|
|
632
|
+
function getAllProviderListStats(db) {
|
|
633
|
+
const todayUTC = new Date();
|
|
634
|
+
todayUTC.setUTCHours(0, 0, 0, 0);
|
|
635
|
+
const todayStart = todayUTC.toISOString();
|
|
636
|
+
return db.prepare(`
|
|
637
|
+
SELECT
|
|
638
|
+
provider,
|
|
639
|
+
COUNT(DISTINCT agent_id) as agent_count,
|
|
640
|
+
COALESCE(SUM(tokens_total), 0) as total_tokens,
|
|
641
|
+
COALESCE(SUM(cost_usd), 0) as total_cost,
|
|
642
|
+
COALESCE(SUM(CASE WHEN timestamp >= ? THEN cost_usd ELSE 0 END), 0) as today_cost
|
|
643
|
+
FROM agent_events
|
|
644
|
+
WHERE provider IS NOT NULL
|
|
645
|
+
GROUP BY provider
|
|
646
|
+
ORDER BY total_cost DESC
|
|
647
|
+
`).all(todayStart);
|
|
648
|
+
}
|
|
649
|
+
function getKillSwitchConfig(db, agentId) {
|
|
650
|
+
const row = db.prepare(`
|
|
651
|
+
SELECT kill_switch_enabled, kill_switch_window_size, kill_switch_threshold
|
|
652
|
+
FROM agents WHERE agent_id = ?
|
|
653
|
+
`).get(agentId);
|
|
654
|
+
if (!row)
|
|
655
|
+
return null;
|
|
656
|
+
return {
|
|
657
|
+
enabled: row.kill_switch_enabled === 1,
|
|
658
|
+
window_size: row.kill_switch_window_size,
|
|
659
|
+
threshold: row.kill_switch_threshold,
|
|
660
|
+
};
|
|
661
|
+
}
|
|
662
|
+
function updateKillSwitchConfig(db, agentId, config) {
|
|
663
|
+
const updates = [];
|
|
664
|
+
const params = [];
|
|
665
|
+
if (config.enabled !== undefined) {
|
|
666
|
+
updates.push("kill_switch_enabled = ?");
|
|
667
|
+
params.push(config.enabled ? 1 : 0);
|
|
668
|
+
}
|
|
669
|
+
if (config.window_size !== undefined) {
|
|
670
|
+
updates.push("kill_switch_window_size = ?");
|
|
671
|
+
params.push(config.window_size);
|
|
672
|
+
}
|
|
673
|
+
if (config.threshold !== undefined) {
|
|
674
|
+
updates.push("kill_switch_threshold = ?");
|
|
675
|
+
params.push(config.threshold);
|
|
676
|
+
}
|
|
677
|
+
if (updates.length === 0)
|
|
678
|
+
return false;
|
|
679
|
+
updates.push("updated_at = ?");
|
|
680
|
+
params.push(new Date().toISOString());
|
|
681
|
+
params.push(agentId);
|
|
682
|
+
const result = db.prepare(`
|
|
683
|
+
UPDATE agents SET ${updates.join(", ")} WHERE agent_id = ?
|
|
684
|
+
`).run(...params);
|
|
685
|
+
return result.changes > 0;
|
|
686
|
+
}
|
|
687
|
+
function getOverviewStats(db) {
|
|
688
|
+
const now = new Date();
|
|
689
|
+
const todayUTC = new Date(now);
|
|
690
|
+
todayUTC.setUTCHours(0, 0, 0, 0);
|
|
691
|
+
const todayStart = todayUTC.toISOString();
|
|
692
|
+
const yesterdayUTC = new Date(todayUTC);
|
|
693
|
+
yesterdayUTC.setUTCDate(yesterdayUTC.getUTCDate() - 1);
|
|
694
|
+
const yesterdayStart = yesterdayUTC.toISOString();
|
|
695
|
+
// Active agents count
|
|
696
|
+
const activeAgentsResult = db.prepare(`
|
|
697
|
+
SELECT COUNT(*) as count FROM agents WHERE active = 1
|
|
698
|
+
`).get();
|
|
699
|
+
// Today's stats
|
|
700
|
+
const todayStatsResult = db.prepare(`
|
|
701
|
+
SELECT
|
|
702
|
+
COALESCE(SUM(cost_usd), 0) as cost,
|
|
703
|
+
COUNT(*) as requests,
|
|
704
|
+
SUM(CASE WHEN event_type = 'error' OR status_code >= 400 THEN 1 ELSE 0 END) as errors
|
|
705
|
+
FROM agent_events
|
|
706
|
+
WHERE timestamp >= ?
|
|
707
|
+
`).get(todayStart);
|
|
708
|
+
// Yesterday's stats
|
|
709
|
+
const yesterdayStatsResult = db.prepare(`
|
|
710
|
+
SELECT
|
|
711
|
+
COALESCE(SUM(cost_usd), 0) as cost,
|
|
712
|
+
COUNT(*) as requests,
|
|
713
|
+
SUM(CASE WHEN event_type = 'error' OR status_code >= 400 THEN 1 ELSE 0 END) as errors
|
|
714
|
+
FROM agent_events
|
|
715
|
+
WHERE timestamp >= ? AND timestamp < ?
|
|
716
|
+
`).get(yesterdayStart, todayStart);
|
|
717
|
+
const todayErrorRate = todayStatsResult.requests > 0
|
|
718
|
+
? (todayStatsResult.errors / todayStatsResult.requests) * 100
|
|
719
|
+
: 0;
|
|
720
|
+
const yesterdayErrorRate = yesterdayStatsResult.requests > 0
|
|
721
|
+
? (yesterdayStatsResult.errors / yesterdayStatsResult.requests) * 100
|
|
722
|
+
: 0;
|
|
723
|
+
return {
|
|
724
|
+
active_agents: activeAgentsResult.count,
|
|
725
|
+
today_cost: todayStatsResult.cost,
|
|
726
|
+
today_requests: todayStatsResult.requests,
|
|
727
|
+
error_rate: todayErrorRate,
|
|
728
|
+
yesterday_cost: yesterdayStatsResult.cost,
|
|
729
|
+
yesterday_requests: yesterdayStatsResult.requests,
|
|
730
|
+
yesterday_error_rate: yesterdayErrorRate,
|
|
731
|
+
};
|
|
732
|
+
}
|
|
733
|
+
function getTopAgentsByCost(db, limit = 5, days = 7) {
|
|
734
|
+
const startDate = new Date();
|
|
735
|
+
startDate.setUTCDate(startDate.getUTCDate() - days + 1);
|
|
736
|
+
startDate.setUTCHours(0, 0, 0, 0);
|
|
737
|
+
const periodStart = startDate.toISOString();
|
|
738
|
+
// Get total cost first
|
|
739
|
+
const totalResult = db.prepare(`
|
|
740
|
+
SELECT COALESCE(SUM(cost_usd), 0) as total
|
|
741
|
+
FROM agent_events
|
|
742
|
+
WHERE timestamp >= ?
|
|
743
|
+
`).get(periodStart);
|
|
744
|
+
const totalCost = totalResult.total || 1; // Avoid division by zero
|
|
745
|
+
// Get top agents by cost
|
|
746
|
+
const rows = db.prepare(`
|
|
747
|
+
SELECT
|
|
748
|
+
agent_id,
|
|
749
|
+
COALESCE(SUM(cost_usd), 0) as cost
|
|
750
|
+
FROM agent_events
|
|
751
|
+
WHERE timestamp >= ? AND cost_usd IS NOT NULL
|
|
752
|
+
GROUP BY agent_id
|
|
753
|
+
ORDER BY cost DESC
|
|
754
|
+
LIMIT ?
|
|
755
|
+
`).all(periodStart, limit);
|
|
756
|
+
return rows.map(row => ({
|
|
757
|
+
agent_id: row.agent_id,
|
|
758
|
+
cost: row.cost,
|
|
759
|
+
percentage: (row.cost / totalCost) * 100,
|
|
760
|
+
}));
|
|
761
|
+
}
|
|
762
|
+
function getTopModelsByTokens(db, limit = 5, days = 7) {
|
|
763
|
+
const startDate = new Date();
|
|
764
|
+
startDate.setUTCDate(startDate.getUTCDate() - days + 1);
|
|
765
|
+
startDate.setUTCHours(0, 0, 0, 0);
|
|
766
|
+
const periodStart = startDate.toISOString();
|
|
767
|
+
// Get total tokens first
|
|
768
|
+
const totalResult = db.prepare(`
|
|
769
|
+
SELECT COALESCE(SUM(tokens_total), 0) as total
|
|
770
|
+
FROM agent_events
|
|
771
|
+
WHERE timestamp >= ?
|
|
772
|
+
`).get(periodStart);
|
|
773
|
+
const totalTokens = totalResult.total || 1; // Avoid division by zero
|
|
774
|
+
// Get top models by tokens
|
|
775
|
+
const rows = db.prepare(`
|
|
776
|
+
SELECT
|
|
777
|
+
model,
|
|
778
|
+
COALESCE(SUM(tokens_total), 0) as tokens
|
|
779
|
+
FROM agent_events
|
|
780
|
+
WHERE timestamp >= ? AND model IS NOT NULL AND tokens_total IS NOT NULL
|
|
781
|
+
GROUP BY model
|
|
782
|
+
ORDER BY tokens DESC
|
|
783
|
+
LIMIT ?
|
|
784
|
+
`).all(periodStart, limit);
|
|
785
|
+
return rows.map(row => ({
|
|
786
|
+
model: row.model,
|
|
787
|
+
tokens: row.tokens,
|
|
788
|
+
percentage: (row.tokens / totalTokens) * 100,
|
|
789
|
+
}));
|
|
790
|
+
}
|
|
791
|
+
function getDailyTrends(db, days = 7) {
|
|
792
|
+
const startDate = new Date();
|
|
793
|
+
startDate.setUTCDate(startDate.getUTCDate() - days + 1);
|
|
794
|
+
startDate.setUTCHours(0, 0, 0, 0);
|
|
795
|
+
const startDateStr = startDate.toISOString();
|
|
796
|
+
const rows = db.prepare(`
|
|
797
|
+
SELECT
|
|
798
|
+
date(timestamp) as date,
|
|
799
|
+
COALESCE(SUM(cost_usd), 0) as cost,
|
|
800
|
+
COUNT(*) as requests
|
|
801
|
+
FROM agent_events
|
|
802
|
+
WHERE timestamp >= ?
|
|
803
|
+
GROUP BY date(timestamp)
|
|
804
|
+
ORDER BY date ASC
|
|
805
|
+
`).all(startDateStr);
|
|
806
|
+
// Create a map of existing data
|
|
807
|
+
const dataMap = new Map(rows.map(r => [r.date, r]));
|
|
808
|
+
// Generate all dates in range
|
|
809
|
+
const costTrend = [];
|
|
810
|
+
const requestsTrend = [];
|
|
811
|
+
for (let i = 0; i < days; i++) {
|
|
812
|
+
const d = new Date(startDate);
|
|
813
|
+
d.setUTCDate(d.getUTCDate() + i);
|
|
814
|
+
const dateStr = d.toISOString().split("T")[0];
|
|
815
|
+
const data = dataMap.get(dateStr);
|
|
816
|
+
costTrend.push({ date: dateStr, value: data?.cost ?? 0 });
|
|
817
|
+
requestsTrend.push({ date: dateStr, value: data?.requests ?? 0 });
|
|
818
|
+
}
|
|
819
|
+
return {
|
|
820
|
+
cost_trend: costTrend,
|
|
821
|
+
requests_trend: requestsTrend,
|
|
822
|
+
};
|
|
823
|
+
}
|
|
824
|
+
// ---------------------------------------------------------------------------
|
|
825
|
+
// Delete Agent (cascade)
|
|
826
|
+
// ---------------------------------------------------------------------------
|
|
827
|
+
function deleteAgent(db, agentId) {
|
|
828
|
+
const deleteAll = db.transaction(() => {
|
|
829
|
+
// 1. Delete events
|
|
830
|
+
db.prepare("DELETE FROM agent_events WHERE agent_id = ?").run(agentId);
|
|
831
|
+
// 2. Delete alert history (via alert_rules FK, but also direct delete for safety)
|
|
832
|
+
db.prepare(`
|
|
833
|
+
DELETE FROM alert_history WHERE alert_rule_id IN (
|
|
834
|
+
SELECT id FROM alert_rules WHERE agent_id = ?
|
|
835
|
+
)
|
|
836
|
+
`).run(agentId);
|
|
837
|
+
// 3. Delete alert rules
|
|
838
|
+
db.prepare("DELETE FROM alert_rules WHERE agent_id = ?").run(agentId);
|
|
839
|
+
// 4. Delete model rules
|
|
840
|
+
db.prepare("DELETE FROM agent_model_rules WHERE agent_id = ?").run(agentId);
|
|
841
|
+
// 5. Delete rate limits
|
|
842
|
+
db.prepare("DELETE FROM agent_rate_limits WHERE agent_id = ?").run(agentId);
|
|
843
|
+
// 6. Delete agent
|
|
844
|
+
const result = db.prepare("DELETE FROM agents WHERE agent_id = ?").run(agentId);
|
|
845
|
+
return result.changes > 0;
|
|
846
|
+
});
|
|
847
|
+
return deleteAll();
|
|
848
|
+
}
|
|
849
|
+
// ---------------------------------------------------------------------------
|
|
850
|
+
// Delete Provider (cascade)
|
|
851
|
+
// ---------------------------------------------------------------------------
|
|
852
|
+
function deleteProvider(db, provider) {
|
|
853
|
+
const deleteAll = db.transaction(() => {
|
|
854
|
+
// 1. Delete agent model rules for this provider
|
|
855
|
+
db.prepare("DELETE FROM agent_model_rules WHERE provider = ?").run(provider);
|
|
856
|
+
// 2. Delete agent rate limits for this provider
|
|
857
|
+
db.prepare("DELETE FROM agent_rate_limits WHERE provider = ?").run(provider);
|
|
858
|
+
// 3. Delete custom models for this provider
|
|
859
|
+
db.prepare("DELETE FROM provider_models WHERE provider = ?").run(provider);
|
|
860
|
+
// 4. Delete provider settings
|
|
861
|
+
const result = db.prepare("DELETE FROM provider_settings WHERE provider = ?").run(provider);
|
|
862
|
+
return result.changes > 0;
|
|
863
|
+
});
|
|
864
|
+
return deleteAll();
|
|
865
|
+
}
|
|
866
|
+
function getRecentEvents(db, limit = 10) {
|
|
867
|
+
const events = [];
|
|
868
|
+
const oneDayAgo = new Date(Date.now() - 24 * 60 * 60 * 1000).toISOString();
|
|
869
|
+
// 1. Kill switch events
|
|
870
|
+
const killSwitchEvents = db.prepare(`
|
|
871
|
+
SELECT agent_id, timestamp
|
|
872
|
+
FROM agent_events
|
|
873
|
+
WHERE event_type = 'kill_switch' AND timestamp >= ?
|
|
874
|
+
ORDER BY timestamp DESC
|
|
875
|
+
LIMIT 5
|
|
876
|
+
`).all(oneDayAgo);
|
|
877
|
+
for (const e of killSwitchEvents) {
|
|
878
|
+
events.push({
|
|
879
|
+
type: "kill_switch",
|
|
880
|
+
agent_id: e.agent_id,
|
|
881
|
+
message: "Loop detected, deactivated",
|
|
882
|
+
timestamp: e.timestamp,
|
|
883
|
+
});
|
|
884
|
+
}
|
|
885
|
+
// 2. New agents (created in last 24h)
|
|
886
|
+
const newAgents = db.prepare(`
|
|
887
|
+
SELECT agent_id, created_at
|
|
888
|
+
FROM agents
|
|
889
|
+
WHERE created_at >= ?
|
|
890
|
+
ORDER BY created_at DESC
|
|
891
|
+
LIMIT 5
|
|
892
|
+
`).all(oneDayAgo);
|
|
893
|
+
for (const a of newAgents) {
|
|
894
|
+
events.push({
|
|
895
|
+
type: "new_agent",
|
|
896
|
+
agent_id: a.agent_id,
|
|
897
|
+
message: "First request received",
|
|
898
|
+
timestamp: a.created_at,
|
|
899
|
+
});
|
|
900
|
+
}
|
|
901
|
+
// 3. Budget warnings (agents near budget limit)
|
|
902
|
+
const budgetWarnings = db.prepare(`
|
|
903
|
+
SELECT a.agent_id, a.budget_limit,
|
|
904
|
+
COALESCE(SUM(e.cost_usd), 0) as today_spend,
|
|
905
|
+
MAX(e.timestamp) as timestamp
|
|
906
|
+
FROM agents a
|
|
907
|
+
LEFT JOIN agent_events e ON e.agent_id = a.agent_id AND date(e.timestamp) = date('now')
|
|
908
|
+
WHERE a.budget_limit IS NOT NULL
|
|
909
|
+
GROUP BY a.agent_id
|
|
910
|
+
HAVING today_spend >= a.budget_limit * 0.8
|
|
911
|
+
`).all();
|
|
912
|
+
for (const b of budgetWarnings) {
|
|
913
|
+
if (b.timestamp) {
|
|
914
|
+
events.push({
|
|
915
|
+
type: "budget_warning",
|
|
916
|
+
agent_id: b.agent_id,
|
|
917
|
+
message: `Budget ${Math.round((b.today_spend / b.budget_limit) * 100)}%: $${b.today_spend.toFixed(2)} / $${b.budget_limit.toFixed(2)}`,
|
|
918
|
+
timestamp: b.timestamp,
|
|
919
|
+
details: { current_spend: b.today_spend, budget_limit: b.budget_limit },
|
|
920
|
+
});
|
|
921
|
+
}
|
|
922
|
+
}
|
|
923
|
+
// 4. High error rate (>5% in last hour)
|
|
924
|
+
const oneHourAgo = new Date(Date.now() - 60 * 60 * 1000).toISOString();
|
|
925
|
+
const errorRates = db.prepare(`
|
|
926
|
+
SELECT
|
|
927
|
+
agent_id,
|
|
928
|
+
COUNT(*) as total,
|
|
929
|
+
SUM(CASE WHEN event_type = 'error' OR status_code >= 400 THEN 1 ELSE 0 END) as errors,
|
|
930
|
+
MAX(timestamp) as timestamp
|
|
931
|
+
FROM agent_events
|
|
932
|
+
WHERE timestamp >= ?
|
|
933
|
+
GROUP BY agent_id
|
|
934
|
+
HAVING total >= 10 AND (errors * 1.0 / total) > 0.05
|
|
935
|
+
`).all(oneHourAgo);
|
|
936
|
+
for (const r of errorRates) {
|
|
937
|
+
const errorRate = (r.errors / r.total) * 100;
|
|
938
|
+
events.push({
|
|
939
|
+
type: "high_error_rate",
|
|
940
|
+
agent_id: r.agent_id,
|
|
941
|
+
message: `${errorRate.toFixed(1)}% errors in last hour`,
|
|
942
|
+
timestamp: r.timestamp,
|
|
943
|
+
details: { error_rate: errorRate },
|
|
944
|
+
});
|
|
945
|
+
}
|
|
946
|
+
// Sort all events by timestamp descending and limit
|
|
947
|
+
events.sort((a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime());
|
|
948
|
+
return events.slice(0, limit);
|
|
949
|
+
}
|
|
425
950
|
//# sourceMappingURL=db.js.map
|