@agentgazer/server 0.2.0 → 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.
Files changed (51) hide show
  1. package/dist/alerts/evaluator.d.ts +21 -0
  2. package/dist/alerts/evaluator.d.ts.map +1 -1
  3. package/dist/alerts/evaluator.js +243 -79
  4. package/dist/alerts/evaluator.js.map +1 -1
  5. package/dist/db.d.ts +108 -2
  6. package/dist/db.d.ts.map +1 -1
  7. package/dist/db.js +536 -11
  8. package/dist/db.js.map +1 -1
  9. package/dist/index.d.ts +5 -3
  10. package/dist/index.d.ts.map +1 -1
  11. package/dist/index.js +8 -1
  12. package/dist/index.js.map +1 -1
  13. package/dist/middleware/auth.js +1 -1
  14. package/dist/middleware/auth.js.map +1 -1
  15. package/dist/routes/agents.d.ts.map +1 -1
  16. package/dist/routes/agents.js +116 -3
  17. package/dist/routes/agents.js.map +1 -1
  18. package/dist/routes/alerts.d.ts.map +1 -1
  19. package/dist/routes/alerts.js +55 -5
  20. package/dist/routes/alerts.js.map +1 -1
  21. package/dist/routes/events.d.ts.map +1 -1
  22. package/dist/routes/events.js +27 -14
  23. package/dist/routes/events.js.map +1 -1
  24. package/dist/routes/health.d.ts.map +1 -1
  25. package/dist/routes/health.js +13 -0
  26. package/dist/routes/health.js.map +1 -1
  27. package/dist/routes/openclaw.d.ts +3 -0
  28. package/dist/routes/openclaw.d.ts.map +1 -0
  29. package/dist/routes/openclaw.js +139 -0
  30. package/dist/routes/openclaw.js.map +1 -0
  31. package/dist/routes/overview.d.ts +9 -0
  32. package/dist/routes/overview.d.ts.map +1 -0
  33. package/dist/routes/overview.js +101 -0
  34. package/dist/routes/overview.js.map +1 -0
  35. package/dist/routes/providers.d.ts +15 -0
  36. package/dist/routes/providers.d.ts.map +1 -0
  37. package/dist/routes/providers.js +317 -0
  38. package/dist/routes/providers.js.map +1 -0
  39. package/dist/routes/settings.d.ts +7 -0
  40. package/dist/routes/settings.d.ts.map +1 -0
  41. package/dist/routes/settings.js +134 -0
  42. package/dist/routes/settings.js.map +1 -0
  43. package/dist/server.d.ts +8 -0
  44. package/dist/server.d.ts.map +1 -1
  45. package/dist/server.js +11 -0
  46. package/dist/server.js.map +1 -1
  47. package/dist/services/provider-validator.d.ts +2 -0
  48. package/dist/services/provider-validator.d.ts.map +1 -0
  49. package/dist/services/provider-validator.js +8 -0
  50. package/dist/services/provider-validator.js.map +1 -0
  51. 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 = ["agent_id = ?"];
264
- const params = [options.agent_id];
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 limit = options.limit ?? 1000;
295
- const sql = `SELECT * FROM agent_events WHERE ${conditions.join(" AND ")} ORDER BY timestamp DESC LIMIT ?`;
296
- params.push(limit);
297
- return db.prepare(sql).all(...params);
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