apteva 0.2.3 → 0.2.5
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/App.ggy88vnx.js +213 -0
- package/dist/index.html +1 -1
- package/dist/styles.css +1 -1
- package/package.json +6 -6
- package/src/binary.ts +271 -1
- package/src/crypto.ts +53 -0
- package/src/db.ts +492 -3
- package/src/mcp-client.ts +599 -0
- package/src/providers.ts +31 -0
- package/src/routes/api.ts +786 -63
- package/src/server.ts +122 -5
- package/src/web/App.tsx +36 -1
- package/src/web/components/agents/AgentCard.tsx +22 -1
- package/src/web/components/agents/AgentPanel.tsx +381 -0
- package/src/web/components/agents/AgentsView.tsx +27 -10
- package/src/web/components/agents/CreateAgentModal.tsx +7 -7
- package/src/web/components/agents/index.ts +1 -1
- package/src/web/components/common/Icons.tsx +8 -0
- package/src/web/components/common/Modal.tsx +2 -2
- package/src/web/components/common/Select.tsx +1 -1
- package/src/web/components/common/index.ts +1 -0
- package/src/web/components/dashboard/Dashboard.tsx +74 -25
- package/src/web/components/index.ts +5 -2
- package/src/web/components/layout/Sidebar.tsx +22 -2
- package/src/web/components/mcp/McpPage.tsx +1144 -0
- package/src/web/components/mcp/index.ts +1 -0
- package/src/web/components/onboarding/OnboardingWizard.tsx +5 -1
- package/src/web/components/settings/SettingsPage.tsx +312 -82
- package/src/web/components/tasks/TasksPage.tsx +129 -0
- package/src/web/components/tasks/index.ts +1 -0
- package/src/web/components/telemetry/TelemetryPage.tsx +316 -0
- package/src/web/hooks/useAgents.ts +23 -0
- package/src/web/styles.css +18 -0
- package/src/web/types.ts +75 -1
- package/dist/App.wfhmfhx7.js +0 -213
- package/src/web/components/agents/ChatPanel.tsx +0 -63
package/src/db.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Database } from "bun:sqlite";
|
|
2
2
|
import { join } from "path";
|
|
3
3
|
import { mkdirSync, existsSync } from "fs";
|
|
4
|
+
import { encryptObject, decryptObject } from "./crypto";
|
|
4
5
|
|
|
5
6
|
// Types
|
|
6
7
|
export interface AgentFeatures {
|
|
@@ -30,6 +31,7 @@ export interface Agent {
|
|
|
30
31
|
status: "stopped" | "running";
|
|
31
32
|
port: number | null;
|
|
32
33
|
features: AgentFeatures;
|
|
34
|
+
mcp_servers: string[]; // Array of MCP server IDs
|
|
33
35
|
created_at: string;
|
|
34
36
|
updated_at: string;
|
|
35
37
|
}
|
|
@@ -43,6 +45,7 @@ export interface AgentRow {
|
|
|
43
45
|
status: string;
|
|
44
46
|
port: number | null;
|
|
45
47
|
features: string | null;
|
|
48
|
+
mcp_servers: string | null;
|
|
46
49
|
created_at: string;
|
|
47
50
|
updated_at: string;
|
|
48
51
|
}
|
|
@@ -72,6 +75,32 @@ export interface ProviderKeyRow {
|
|
|
72
75
|
created_at: string;
|
|
73
76
|
}
|
|
74
77
|
|
|
78
|
+
export interface McpServer {
|
|
79
|
+
id: string;
|
|
80
|
+
name: string;
|
|
81
|
+
type: "npm" | "github" | "http" | "custom";
|
|
82
|
+
package: string | null;
|
|
83
|
+
command: string | null;
|
|
84
|
+
args: string | null;
|
|
85
|
+
env: Record<string, string>;
|
|
86
|
+
port: number | null;
|
|
87
|
+
status: "stopped" | "running";
|
|
88
|
+
created_at: string;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export interface McpServerRow {
|
|
92
|
+
id: string;
|
|
93
|
+
name: string;
|
|
94
|
+
type: string;
|
|
95
|
+
package: string | null;
|
|
96
|
+
command: string | null;
|
|
97
|
+
args: string | null;
|
|
98
|
+
env: string | null;
|
|
99
|
+
port: number | null;
|
|
100
|
+
status: string;
|
|
101
|
+
created_at: string;
|
|
102
|
+
}
|
|
103
|
+
|
|
75
104
|
// Database instance
|
|
76
105
|
let db: Database;
|
|
77
106
|
|
|
@@ -193,6 +222,56 @@ function runMigrations() {
|
|
|
193
222
|
ALTER TABLE agents ADD COLUMN features TEXT DEFAULT '{"memory":true,"tasks":false,"vision":true,"operator":false,"mcp":false,"realtime":false}';
|
|
194
223
|
`,
|
|
195
224
|
},
|
|
225
|
+
{
|
|
226
|
+
name: "007_create_mcp_servers",
|
|
227
|
+
sql: `
|
|
228
|
+
CREATE TABLE IF NOT EXISTS mcp_servers (
|
|
229
|
+
id TEXT PRIMARY KEY,
|
|
230
|
+
name TEXT NOT NULL,
|
|
231
|
+
type TEXT NOT NULL DEFAULT 'npm',
|
|
232
|
+
package TEXT,
|
|
233
|
+
command TEXT,
|
|
234
|
+
args TEXT,
|
|
235
|
+
env TEXT DEFAULT '{}',
|
|
236
|
+
port INTEGER,
|
|
237
|
+
status TEXT NOT NULL DEFAULT 'stopped',
|
|
238
|
+
created_at TEXT DEFAULT CURRENT_TIMESTAMP
|
|
239
|
+
);
|
|
240
|
+
CREATE INDEX IF NOT EXISTS idx_mcp_servers_status ON mcp_servers(status);
|
|
241
|
+
`,
|
|
242
|
+
},
|
|
243
|
+
{
|
|
244
|
+
name: "008_add_agent_mcp_servers",
|
|
245
|
+
sql: `
|
|
246
|
+
ALTER TABLE agents ADD COLUMN mcp_servers TEXT DEFAULT '[]';
|
|
247
|
+
`,
|
|
248
|
+
},
|
|
249
|
+
{
|
|
250
|
+
name: "009_create_telemetry",
|
|
251
|
+
sql: `
|
|
252
|
+
CREATE TABLE IF NOT EXISTS telemetry_events (
|
|
253
|
+
id TEXT PRIMARY KEY,
|
|
254
|
+
agent_id TEXT NOT NULL,
|
|
255
|
+
timestamp TEXT NOT NULL,
|
|
256
|
+
category TEXT NOT NULL,
|
|
257
|
+
type TEXT NOT NULL,
|
|
258
|
+
level TEXT NOT NULL,
|
|
259
|
+
trace_id TEXT,
|
|
260
|
+
span_id TEXT,
|
|
261
|
+
thread_id TEXT,
|
|
262
|
+
data TEXT,
|
|
263
|
+
metadata TEXT,
|
|
264
|
+
duration_ms INTEGER,
|
|
265
|
+
error TEXT,
|
|
266
|
+
received_at TEXT NOT NULL
|
|
267
|
+
);
|
|
268
|
+
CREATE INDEX IF NOT EXISTS idx_telemetry_agent ON telemetry_events(agent_id);
|
|
269
|
+
CREATE INDEX IF NOT EXISTS idx_telemetry_time ON telemetry_events(timestamp);
|
|
270
|
+
CREATE INDEX IF NOT EXISTS idx_telemetry_category ON telemetry_events(category);
|
|
271
|
+
CREATE INDEX IF NOT EXISTS idx_telemetry_level ON telemetry_events(level);
|
|
272
|
+
CREATE INDEX IF NOT EXISTS idx_telemetry_trace ON telemetry_events(trace_id);
|
|
273
|
+
`,
|
|
274
|
+
},
|
|
196
275
|
];
|
|
197
276
|
|
|
198
277
|
// Check which migrations have been applied
|
|
@@ -218,11 +297,12 @@ export const AgentDB = {
|
|
|
218
297
|
create(agent: Omit<Agent, "created_at" | "updated_at" | "status" | "port">): Agent {
|
|
219
298
|
const now = new Date().toISOString();
|
|
220
299
|
const featuresJson = JSON.stringify(agent.features || DEFAULT_FEATURES);
|
|
300
|
+
const mcpServersJson = JSON.stringify(agent.mcp_servers || []);
|
|
221
301
|
const stmt = db.prepare(`
|
|
222
|
-
INSERT INTO agents (id, name, model, provider, system_prompt, features, status, port, created_at, updated_at)
|
|
223
|
-
VALUES (?, ?, ?, ?, ?, ?, 'stopped', NULL, ?, ?)
|
|
302
|
+
INSERT INTO agents (id, name, model, provider, system_prompt, features, mcp_servers, status, port, created_at, updated_at)
|
|
303
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, 'stopped', NULL, ?, ?)
|
|
224
304
|
`);
|
|
225
|
-
stmt.run(agent.id, agent.name, agent.model, agent.provider, agent.system_prompt, featuresJson, now, now);
|
|
305
|
+
stmt.run(agent.id, agent.name, agent.model, agent.provider, agent.system_prompt, featuresJson, mcpServersJson, now, now);
|
|
226
306
|
return this.findById(agent.id)!;
|
|
227
307
|
},
|
|
228
308
|
|
|
@@ -238,6 +318,12 @@ export const AgentDB = {
|
|
|
238
318
|
return rows.map(rowToAgent);
|
|
239
319
|
},
|
|
240
320
|
|
|
321
|
+
// Get running agents (for auto-restart)
|
|
322
|
+
findRunning(): Agent[] {
|
|
323
|
+
const rows = db.query("SELECT * FROM agents WHERE status = 'running'").all() as AgentRow[];
|
|
324
|
+
return rows.map(rowToAgent);
|
|
325
|
+
},
|
|
326
|
+
|
|
241
327
|
// Update agent
|
|
242
328
|
update(id: string, updates: Partial<Omit<Agent, "id" | "created_at">>): Agent | null {
|
|
243
329
|
const agent = this.findById(id);
|
|
@@ -274,6 +360,10 @@ export const AgentDB = {
|
|
|
274
360
|
fields.push("features = ?");
|
|
275
361
|
values.push(JSON.stringify(updates.features));
|
|
276
362
|
}
|
|
363
|
+
if (updates.mcp_servers !== undefined) {
|
|
364
|
+
fields.push("mcp_servers = ?");
|
|
365
|
+
values.push(JSON.stringify(updates.mcp_servers));
|
|
366
|
+
}
|
|
277
367
|
|
|
278
368
|
if (fields.length > 0) {
|
|
279
369
|
fields.push("updated_at = ?");
|
|
@@ -385,6 +475,14 @@ function rowToAgent(row: AgentRow): Agent {
|
|
|
385
475
|
// Use defaults if parsing fails
|
|
386
476
|
}
|
|
387
477
|
}
|
|
478
|
+
let mcp_servers: string[] = [];
|
|
479
|
+
if (row.mcp_servers) {
|
|
480
|
+
try {
|
|
481
|
+
mcp_servers = JSON.parse(row.mcp_servers);
|
|
482
|
+
} catch {
|
|
483
|
+
// Use empty array if parsing fails
|
|
484
|
+
}
|
|
485
|
+
}
|
|
388
486
|
return {
|
|
389
487
|
id: row.id,
|
|
390
488
|
name: row.name,
|
|
@@ -394,6 +492,7 @@ function rowToAgent(row: AgentRow): Agent {
|
|
|
394
492
|
status: row.status as "stopped" | "running",
|
|
395
493
|
port: row.port,
|
|
396
494
|
features,
|
|
495
|
+
mcp_servers,
|
|
397
496
|
created_at: row.created_at,
|
|
398
497
|
updated_at: row.updated_at,
|
|
399
498
|
};
|
|
@@ -480,6 +579,396 @@ function rowToProviderKey(row: ProviderKeyRow): ProviderKey {
|
|
|
480
579
|
};
|
|
481
580
|
}
|
|
482
581
|
|
|
582
|
+
// MCP Server operations
|
|
583
|
+
export const McpServerDB = {
|
|
584
|
+
create(server: Omit<McpServer, "created_at" | "status" | "port">): McpServer {
|
|
585
|
+
const now = new Date().toISOString();
|
|
586
|
+
// Encrypt env vars (credentials) before storing
|
|
587
|
+
const envEncrypted = encryptObject(server.env || {});
|
|
588
|
+
const stmt = db.prepare(`
|
|
589
|
+
INSERT INTO mcp_servers (id, name, type, package, command, args, env, status, port, created_at)
|
|
590
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, 'stopped', NULL, ?)
|
|
591
|
+
`);
|
|
592
|
+
stmt.run(server.id, server.name, server.type, server.package, server.command, server.args, envEncrypted, now);
|
|
593
|
+
return this.findById(server.id)!;
|
|
594
|
+
},
|
|
595
|
+
|
|
596
|
+
findById(id: string): McpServer | null {
|
|
597
|
+
const row = db.query("SELECT * FROM mcp_servers WHERE id = ?").get(id) as McpServerRow | null;
|
|
598
|
+
return row ? rowToMcpServer(row) : null;
|
|
599
|
+
},
|
|
600
|
+
|
|
601
|
+
findAll(): McpServer[] {
|
|
602
|
+
const rows = db.query("SELECT * FROM mcp_servers ORDER BY created_at DESC").all() as McpServerRow[];
|
|
603
|
+
return rows.map(rowToMcpServer);
|
|
604
|
+
},
|
|
605
|
+
|
|
606
|
+
findRunning(): McpServer[] {
|
|
607
|
+
const rows = db.query("SELECT * FROM mcp_servers WHERE status = 'running'").all() as McpServerRow[];
|
|
608
|
+
return rows.map(rowToMcpServer);
|
|
609
|
+
},
|
|
610
|
+
|
|
611
|
+
update(id: string, updates: Partial<Omit<McpServer, "id" | "created_at">>): McpServer | null {
|
|
612
|
+
const server = this.findById(id);
|
|
613
|
+
if (!server) return null;
|
|
614
|
+
|
|
615
|
+
const fields: string[] = [];
|
|
616
|
+
const values: unknown[] = [];
|
|
617
|
+
|
|
618
|
+
if (updates.name !== undefined) {
|
|
619
|
+
fields.push("name = ?");
|
|
620
|
+
values.push(updates.name);
|
|
621
|
+
}
|
|
622
|
+
if (updates.type !== undefined) {
|
|
623
|
+
fields.push("type = ?");
|
|
624
|
+
values.push(updates.type);
|
|
625
|
+
}
|
|
626
|
+
if (updates.package !== undefined) {
|
|
627
|
+
fields.push("package = ?");
|
|
628
|
+
values.push(updates.package);
|
|
629
|
+
}
|
|
630
|
+
if (updates.command !== undefined) {
|
|
631
|
+
fields.push("command = ?");
|
|
632
|
+
values.push(updates.command);
|
|
633
|
+
}
|
|
634
|
+
if (updates.args !== undefined) {
|
|
635
|
+
fields.push("args = ?");
|
|
636
|
+
values.push(updates.args);
|
|
637
|
+
}
|
|
638
|
+
if (updates.env !== undefined) {
|
|
639
|
+
fields.push("env = ?");
|
|
640
|
+
// Encrypt env vars (credentials) before storing
|
|
641
|
+
values.push(encryptObject(updates.env));
|
|
642
|
+
}
|
|
643
|
+
if (updates.port !== undefined) {
|
|
644
|
+
fields.push("port = ?");
|
|
645
|
+
values.push(updates.port);
|
|
646
|
+
}
|
|
647
|
+
if (updates.status !== undefined) {
|
|
648
|
+
fields.push("status = ?");
|
|
649
|
+
values.push(updates.status);
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
if (fields.length > 0) {
|
|
653
|
+
values.push(id);
|
|
654
|
+
db.run(`UPDATE mcp_servers SET ${fields.join(", ")} WHERE id = ?`, values);
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
return this.findById(id);
|
|
658
|
+
},
|
|
659
|
+
|
|
660
|
+
setStatus(id: string, status: "stopped" | "running", port?: number): McpServer | null {
|
|
661
|
+
return this.update(id, { status, port: port ?? null });
|
|
662
|
+
},
|
|
663
|
+
|
|
664
|
+
delete(id: string): boolean {
|
|
665
|
+
const result = db.run("DELETE FROM mcp_servers WHERE id = ?", [id]);
|
|
666
|
+
return result.changes > 0;
|
|
667
|
+
},
|
|
668
|
+
|
|
669
|
+
resetAllStatus(): void {
|
|
670
|
+
db.run("UPDATE mcp_servers SET status = 'stopped', port = NULL");
|
|
671
|
+
},
|
|
672
|
+
|
|
673
|
+
count(): number {
|
|
674
|
+
const row = db.query("SELECT COUNT(*) as count FROM mcp_servers").get() as { count: number };
|
|
675
|
+
return row.count;
|
|
676
|
+
},
|
|
677
|
+
};
|
|
678
|
+
|
|
679
|
+
// Helper to convert DB row to McpServer type
|
|
680
|
+
function rowToMcpServer(row: McpServerRow): McpServer {
|
|
681
|
+
// Decrypt env vars (handles both encrypted and legacy unencrypted data)
|
|
682
|
+
const env = row.env ? decryptObject(row.env) : {};
|
|
683
|
+
return {
|
|
684
|
+
id: row.id,
|
|
685
|
+
name: row.name,
|
|
686
|
+
type: row.type as McpServer["type"],
|
|
687
|
+
package: row.package,
|
|
688
|
+
command: row.command,
|
|
689
|
+
args: row.args,
|
|
690
|
+
env,
|
|
691
|
+
port: row.port,
|
|
692
|
+
status: row.status as "stopped" | "running",
|
|
693
|
+
created_at: row.created_at,
|
|
694
|
+
};
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
// Telemetry Event types
|
|
698
|
+
export interface TelemetryEvent {
|
|
699
|
+
id: string;
|
|
700
|
+
agent_id: string;
|
|
701
|
+
timestamp: string;
|
|
702
|
+
category: string;
|
|
703
|
+
type: string;
|
|
704
|
+
level: string;
|
|
705
|
+
trace_id: string | null;
|
|
706
|
+
span_id: string | null;
|
|
707
|
+
thread_id: string | null;
|
|
708
|
+
data: Record<string, unknown> | null;
|
|
709
|
+
metadata: Record<string, unknown> | null;
|
|
710
|
+
duration_ms: number | null;
|
|
711
|
+
error: string | null;
|
|
712
|
+
received_at: string;
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
interface TelemetryEventRow {
|
|
716
|
+
id: string;
|
|
717
|
+
agent_id: string;
|
|
718
|
+
timestamp: string;
|
|
719
|
+
category: string;
|
|
720
|
+
type: string;
|
|
721
|
+
level: string;
|
|
722
|
+
trace_id: string | null;
|
|
723
|
+
span_id: string | null;
|
|
724
|
+
thread_id: string | null;
|
|
725
|
+
data: string | null;
|
|
726
|
+
metadata: string | null;
|
|
727
|
+
duration_ms: number | null;
|
|
728
|
+
error: string | null;
|
|
729
|
+
received_at: string;
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
// Telemetry operations
|
|
733
|
+
export const TelemetryDB = {
|
|
734
|
+
// Insert batch of events
|
|
735
|
+
insertBatch(agentId: string, events: Array<{
|
|
736
|
+
id: string;
|
|
737
|
+
timestamp: string;
|
|
738
|
+
category: string;
|
|
739
|
+
type: string;
|
|
740
|
+
level: string;
|
|
741
|
+
trace_id?: string;
|
|
742
|
+
span_id?: string;
|
|
743
|
+
thread_id?: string;
|
|
744
|
+
data?: Record<string, unknown>;
|
|
745
|
+
metadata?: Record<string, unknown>;
|
|
746
|
+
duration_ms?: number;
|
|
747
|
+
error?: string;
|
|
748
|
+
}>): number {
|
|
749
|
+
const now = new Date().toISOString();
|
|
750
|
+
const stmt = db.prepare(`
|
|
751
|
+
INSERT OR IGNORE INTO telemetry_events
|
|
752
|
+
(id, agent_id, timestamp, category, type, level, trace_id, span_id, thread_id, data, metadata, duration_ms, error, received_at)
|
|
753
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
754
|
+
`);
|
|
755
|
+
|
|
756
|
+
let inserted = 0;
|
|
757
|
+
for (const event of events) {
|
|
758
|
+
const result = stmt.run(
|
|
759
|
+
event.id,
|
|
760
|
+
agentId,
|
|
761
|
+
event.timestamp,
|
|
762
|
+
event.category,
|
|
763
|
+
event.type,
|
|
764
|
+
event.level,
|
|
765
|
+
event.trace_id || null,
|
|
766
|
+
event.span_id || null,
|
|
767
|
+
event.thread_id || null,
|
|
768
|
+
event.data ? JSON.stringify(event.data) : null,
|
|
769
|
+
event.metadata ? JSON.stringify(event.metadata) : null,
|
|
770
|
+
event.duration_ms || null,
|
|
771
|
+
event.error || null,
|
|
772
|
+
now
|
|
773
|
+
);
|
|
774
|
+
if (result.changes > 0) inserted++;
|
|
775
|
+
}
|
|
776
|
+
return inserted;
|
|
777
|
+
},
|
|
778
|
+
|
|
779
|
+
// Query events with filters
|
|
780
|
+
query(filters: {
|
|
781
|
+
agent_id?: string;
|
|
782
|
+
category?: string;
|
|
783
|
+
level?: string;
|
|
784
|
+
trace_id?: string;
|
|
785
|
+
since?: string;
|
|
786
|
+
until?: string;
|
|
787
|
+
limit?: number;
|
|
788
|
+
offset?: number;
|
|
789
|
+
} = {}): TelemetryEvent[] {
|
|
790
|
+
const conditions: string[] = [];
|
|
791
|
+
const params: unknown[] = [];
|
|
792
|
+
|
|
793
|
+
if (filters.agent_id) {
|
|
794
|
+
conditions.push("agent_id = ?");
|
|
795
|
+
params.push(filters.agent_id);
|
|
796
|
+
}
|
|
797
|
+
if (filters.category) {
|
|
798
|
+
conditions.push("category = ?");
|
|
799
|
+
params.push(filters.category);
|
|
800
|
+
}
|
|
801
|
+
if (filters.level) {
|
|
802
|
+
conditions.push("level = ?");
|
|
803
|
+
params.push(filters.level);
|
|
804
|
+
}
|
|
805
|
+
if (filters.trace_id) {
|
|
806
|
+
conditions.push("trace_id = ?");
|
|
807
|
+
params.push(filters.trace_id);
|
|
808
|
+
}
|
|
809
|
+
if (filters.since) {
|
|
810
|
+
conditions.push("timestamp >= ?");
|
|
811
|
+
params.push(filters.since);
|
|
812
|
+
}
|
|
813
|
+
if (filters.until) {
|
|
814
|
+
conditions.push("timestamp <= ?");
|
|
815
|
+
params.push(filters.until);
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
const where = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
819
|
+
const limit = filters.limit || 100;
|
|
820
|
+
const offset = filters.offset || 0;
|
|
821
|
+
|
|
822
|
+
const sql = `SELECT * FROM telemetry_events ${where} ORDER BY timestamp DESC LIMIT ? OFFSET ?`;
|
|
823
|
+
params.push(limit, offset);
|
|
824
|
+
|
|
825
|
+
const rows = db.query(sql).all(...params) as TelemetryEventRow[];
|
|
826
|
+
return rows.map(rowToTelemetryEvent);
|
|
827
|
+
},
|
|
828
|
+
|
|
829
|
+
// Get usage stats
|
|
830
|
+
getUsage(filters: {
|
|
831
|
+
agent_id?: string;
|
|
832
|
+
since?: string;
|
|
833
|
+
until?: string;
|
|
834
|
+
group_by?: "agent" | "day";
|
|
835
|
+
} = {}): Array<{
|
|
836
|
+
agent_id?: string;
|
|
837
|
+
date?: string;
|
|
838
|
+
input_tokens: number;
|
|
839
|
+
output_tokens: number;
|
|
840
|
+
llm_calls: number;
|
|
841
|
+
tool_calls: number;
|
|
842
|
+
errors: number;
|
|
843
|
+
}> {
|
|
844
|
+
const conditions: string[] = [];
|
|
845
|
+
const params: unknown[] = [];
|
|
846
|
+
|
|
847
|
+
if (filters.agent_id) {
|
|
848
|
+
conditions.push("agent_id = ?");
|
|
849
|
+
params.push(filters.agent_id);
|
|
850
|
+
}
|
|
851
|
+
if (filters.since) {
|
|
852
|
+
conditions.push("timestamp >= ?");
|
|
853
|
+
params.push(filters.since);
|
|
854
|
+
}
|
|
855
|
+
if (filters.until) {
|
|
856
|
+
conditions.push("timestamp <= ?");
|
|
857
|
+
params.push(filters.until);
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
const where = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
861
|
+
|
|
862
|
+
let groupBy = "";
|
|
863
|
+
let selectFields = "";
|
|
864
|
+
|
|
865
|
+
if (filters.group_by === "day") {
|
|
866
|
+
groupBy = "GROUP BY date(timestamp)";
|
|
867
|
+
selectFields = "date(timestamp) as date,";
|
|
868
|
+
} else if (filters.group_by === "agent") {
|
|
869
|
+
groupBy = "GROUP BY agent_id";
|
|
870
|
+
selectFields = "agent_id,";
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
const sql = `
|
|
874
|
+
SELECT
|
|
875
|
+
${selectFields}
|
|
876
|
+
COALESCE(SUM(CASE WHEN category = 'LLM' THEN json_extract(data, '$.input_tokens') ELSE 0 END), 0) as input_tokens,
|
|
877
|
+
COALESCE(SUM(CASE WHEN category = 'LLM' THEN json_extract(data, '$.output_tokens') ELSE 0 END), 0) as output_tokens,
|
|
878
|
+
COALESCE(SUM(CASE WHEN category = 'LLM' THEN 1 ELSE 0 END), 0) as llm_calls,
|
|
879
|
+
COALESCE(SUM(CASE WHEN category = 'TOOL' THEN 1 ELSE 0 END), 0) as tool_calls,
|
|
880
|
+
COALESCE(SUM(CASE WHEN level = 'error' THEN 1 ELSE 0 END), 0) as errors
|
|
881
|
+
FROM telemetry_events
|
|
882
|
+
${where}
|
|
883
|
+
${groupBy}
|
|
884
|
+
`;
|
|
885
|
+
|
|
886
|
+
return db.query(sql).all(...params) as Array<{
|
|
887
|
+
agent_id?: string;
|
|
888
|
+
date?: string;
|
|
889
|
+
input_tokens: number;
|
|
890
|
+
output_tokens: number;
|
|
891
|
+
llm_calls: number;
|
|
892
|
+
tool_calls: number;
|
|
893
|
+
errors: number;
|
|
894
|
+
}>;
|
|
895
|
+
},
|
|
896
|
+
|
|
897
|
+
// Get summary stats
|
|
898
|
+
getStats(agentId?: string): {
|
|
899
|
+
total_events: number;
|
|
900
|
+
total_llm_calls: number;
|
|
901
|
+
total_tool_calls: number;
|
|
902
|
+
total_errors: number;
|
|
903
|
+
total_input_tokens: number;
|
|
904
|
+
total_output_tokens: number;
|
|
905
|
+
} {
|
|
906
|
+
const where = agentId ? "WHERE agent_id = ?" : "";
|
|
907
|
+
const params = agentId ? [agentId] : [];
|
|
908
|
+
|
|
909
|
+
const sql = `
|
|
910
|
+
SELECT
|
|
911
|
+
COUNT(*) as total_events,
|
|
912
|
+
COALESCE(SUM(CASE WHEN category = 'LLM' THEN 1 ELSE 0 END), 0) as total_llm_calls,
|
|
913
|
+
COALESCE(SUM(CASE WHEN category = 'TOOL' THEN 1 ELSE 0 END), 0) as total_tool_calls,
|
|
914
|
+
COALESCE(SUM(CASE WHEN level = 'error' THEN 1 ELSE 0 END), 0) as total_errors,
|
|
915
|
+
COALESCE(SUM(CASE WHEN category = 'LLM' THEN json_extract(data, '$.input_tokens') ELSE 0 END), 0) as total_input_tokens,
|
|
916
|
+
COALESCE(SUM(CASE WHEN category = 'LLM' THEN json_extract(data, '$.output_tokens') ELSE 0 END), 0) as total_output_tokens
|
|
917
|
+
FROM telemetry_events
|
|
918
|
+
${where}
|
|
919
|
+
`;
|
|
920
|
+
|
|
921
|
+
return db.query(sql).get(...params) as {
|
|
922
|
+
total_events: number;
|
|
923
|
+
total_llm_calls: number;
|
|
924
|
+
total_tool_calls: number;
|
|
925
|
+
total_errors: number;
|
|
926
|
+
total_input_tokens: number;
|
|
927
|
+
total_output_tokens: number;
|
|
928
|
+
};
|
|
929
|
+
},
|
|
930
|
+
|
|
931
|
+
// Delete old events (retention)
|
|
932
|
+
deleteOlderThan(days: number): number {
|
|
933
|
+
const cutoff = new Date();
|
|
934
|
+
cutoff.setDate(cutoff.getDate() - days);
|
|
935
|
+
const result = db.run(
|
|
936
|
+
"DELETE FROM telemetry_events WHERE timestamp < ?",
|
|
937
|
+
[cutoff.toISOString()]
|
|
938
|
+
);
|
|
939
|
+
return result.changes;
|
|
940
|
+
},
|
|
941
|
+
|
|
942
|
+
// Count events
|
|
943
|
+
count(agentId?: string): number {
|
|
944
|
+
if (agentId) {
|
|
945
|
+
const row = db.query("SELECT COUNT(*) as count FROM telemetry_events WHERE agent_id = ?").get(agentId) as { count: number };
|
|
946
|
+
return row.count;
|
|
947
|
+
}
|
|
948
|
+
const row = db.query("SELECT COUNT(*) as count FROM telemetry_events").get() as { count: number };
|
|
949
|
+
return row.count;
|
|
950
|
+
},
|
|
951
|
+
};
|
|
952
|
+
|
|
953
|
+
function rowToTelemetryEvent(row: TelemetryEventRow): TelemetryEvent {
|
|
954
|
+
return {
|
|
955
|
+
id: row.id,
|
|
956
|
+
agent_id: row.agent_id,
|
|
957
|
+
timestamp: row.timestamp,
|
|
958
|
+
category: row.category,
|
|
959
|
+
type: row.type,
|
|
960
|
+
level: row.level,
|
|
961
|
+
trace_id: row.trace_id,
|
|
962
|
+
span_id: row.span_id,
|
|
963
|
+
thread_id: row.thread_id,
|
|
964
|
+
data: row.data ? JSON.parse(row.data) : null,
|
|
965
|
+
metadata: row.metadata ? JSON.parse(row.metadata) : null,
|
|
966
|
+
duration_ms: row.duration_ms,
|
|
967
|
+
error: row.error,
|
|
968
|
+
received_at: row.received_at,
|
|
969
|
+
};
|
|
970
|
+
}
|
|
971
|
+
|
|
483
972
|
// Generate unique ID
|
|
484
973
|
export function generateId(): string {
|
|
485
974
|
return Math.random().toString(36).substring(2, 15);
|