@agentlensai/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.
- package/dist/__tests__/alerts.test.d.ts +5 -0
- package/dist/__tests__/alerts.test.d.ts.map +1 -0
- package/dist/__tests__/alerts.test.js +245 -0
- package/dist/__tests__/alerts.test.js.map +1 -0
- package/dist/__tests__/analytics.test.d.ts +5 -0
- package/dist/__tests__/analytics.test.d.ts.map +1 -0
- package/dist/__tests__/analytics.test.js +218 -0
- package/dist/__tests__/analytics.test.js.map +1 -0
- package/dist/__tests__/ingest.test.d.ts +8 -0
- package/dist/__tests__/ingest.test.d.ts.map +1 -0
- package/dist/__tests__/ingest.test.js +469 -0
- package/dist/__tests__/ingest.test.js.map +1 -0
- package/dist/__tests__/llm-tracking.test.d.ts +10 -0
- package/dist/__tests__/llm-tracking.test.d.ts.map +1 -0
- package/dist/__tests__/llm-tracking.test.js +602 -0
- package/dist/__tests__/llm-tracking.test.js.map +1 -0
- package/dist/__tests__/stream.test.d.ts +5 -0
- package/dist/__tests__/stream.test.d.ts.map +1 -0
- package/dist/__tests__/stream.test.js +352 -0
- package/dist/__tests__/stream.test.js.map +1 -0
- package/dist/db/__tests__/sqlite-store-read.test.js +1 -1
- package/dist/db/__tests__/sqlite-store-read.test.js.map +1 -1
- package/dist/db/__tests__/sqlite-store-write.test.js +1 -1
- package/dist/db/__tests__/sqlite-store-write.test.js.map +1 -1
- package/dist/db/migrate.d.ts.map +1 -1
- package/dist/db/migrate.js +17 -0
- package/dist/db/migrate.js.map +1 -1
- package/dist/db/schema.sqlite.d.ts +52 -1
- package/dist/db/schema.sqlite.d.ts.map +1 -1
- package/dist/db/schema.sqlite.js +3 -0
- package/dist/db/schema.sqlite.js.map +1 -1
- package/dist/db/sqlite-store.d.ts +12 -2
- package/dist/db/sqlite-store.d.ts.map +1 -1
- package/dist/db/sqlite-store.js +96 -5
- package/dist/db/sqlite-store.js.map +1 -1
- package/dist/index.d.ts +9 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +35 -2
- package/dist/index.js.map +1 -1
- package/dist/lib/__tests__/alert-engine.test.d.ts +5 -0
- package/dist/lib/__tests__/alert-engine.test.d.ts.map +1 -0
- package/dist/lib/__tests__/alert-engine.test.js +211 -0
- package/dist/lib/__tests__/alert-engine.test.js.map +1 -0
- package/dist/lib/__tests__/retention.test.js +1 -1
- package/dist/lib/__tests__/retention.test.js.map +1 -1
- package/dist/lib/alert-engine.d.ts +61 -0
- package/dist/lib/alert-engine.d.ts.map +1 -0
- package/dist/lib/alert-engine.js +280 -0
- package/dist/lib/alert-engine.js.map +1 -0
- package/dist/lib/event-bus.d.ts +48 -0
- package/dist/lib/event-bus.d.ts.map +1 -0
- package/dist/lib/event-bus.js +34 -0
- package/dist/lib/event-bus.js.map +1 -0
- package/dist/lib/retention.d.ts +1 -1
- package/dist/lib/retention.d.ts.map +1 -1
- package/dist/lib/sse.d.ts +21 -0
- package/dist/lib/sse.d.ts.map +1 -0
- package/dist/lib/sse.js +114 -0
- package/dist/lib/sse.js.map +1 -0
- package/dist/routes/agents.d.ts +1 -1
- package/dist/routes/agents.d.ts.map +1 -1
- package/dist/routes/alerts.d.ts +17 -0
- package/dist/routes/alerts.d.ts.map +1 -0
- package/dist/routes/alerts.js +133 -0
- package/dist/routes/alerts.js.map +1 -0
- package/dist/routes/analytics.d.ts +16 -0
- package/dist/routes/analytics.d.ts.map +1 -0
- package/dist/routes/analytics.js +293 -0
- package/dist/routes/analytics.js.map +1 -0
- package/dist/routes/config.d.ts +5 -0
- package/dist/routes/config.d.ts.map +1 -1
- package/dist/routes/config.js +27 -7
- package/dist/routes/config.js.map +1 -1
- package/dist/routes/events.d.ts +1 -1
- package/dist/routes/events.d.ts.map +1 -1
- package/dist/routes/events.js +17 -4
- package/dist/routes/events.js.map +1 -1
- package/dist/routes/ingest.d.ts +30 -0
- package/dist/routes/ingest.d.ts.map +1 -0
- package/dist/routes/ingest.js +261 -0
- package/dist/routes/ingest.js.map +1 -0
- package/dist/routes/sessions.d.ts +1 -1
- package/dist/routes/sessions.d.ts.map +1 -1
- package/dist/routes/sessions.js +1 -1
- package/dist/routes/sessions.js.map +1 -1
- package/dist/routes/stats.d.ts +1 -1
- package/dist/routes/stats.d.ts.map +1 -1
- package/dist/routes/stream.d.ts +16 -0
- package/dist/routes/stream.d.ts.map +1 -0
- package/dist/routes/stream.js +41 -0
- package/dist/routes/stream.js.map +1 -0
- package/package.json +2 -2
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Alert Endpoints (Story 12.1)
|
|
3
|
+
*
|
|
4
|
+
* POST /api/alerts/rules — create alert rule
|
|
5
|
+
* GET /api/alerts/rules — list alert rules
|
|
6
|
+
* GET /api/alerts/rules/:id — get single alert rule
|
|
7
|
+
* PUT /api/alerts/rules/:id — update alert rule
|
|
8
|
+
* DELETE /api/alerts/rules/:id — delete alert rule
|
|
9
|
+
* GET /api/alerts/history — list alert history
|
|
10
|
+
*/
|
|
11
|
+
import { Hono } from 'hono';
|
|
12
|
+
import { ulid } from 'ulid';
|
|
13
|
+
import { createAlertRuleSchema, updateAlertRuleSchema } from '@agentlensai/core';
|
|
14
|
+
import { NotFoundError } from '../db/errors.js';
|
|
15
|
+
export function alertsRoutes(store) {
|
|
16
|
+
const app = new Hono();
|
|
17
|
+
// POST /api/alerts/rules — create alert rule
|
|
18
|
+
app.post('/rules', async (c) => {
|
|
19
|
+
const rawBody = await c.req.json().catch(() => null);
|
|
20
|
+
if (!rawBody) {
|
|
21
|
+
return c.json({ error: 'Invalid JSON body', status: 400 }, 400);
|
|
22
|
+
}
|
|
23
|
+
const parseResult = createAlertRuleSchema.safeParse(rawBody);
|
|
24
|
+
if (!parseResult.success) {
|
|
25
|
+
return c.json({
|
|
26
|
+
error: 'Validation failed',
|
|
27
|
+
status: 400,
|
|
28
|
+
details: parseResult.error.issues.map((issue) => ({
|
|
29
|
+
path: issue.path.join('.'),
|
|
30
|
+
message: issue.message,
|
|
31
|
+
})),
|
|
32
|
+
}, 400);
|
|
33
|
+
}
|
|
34
|
+
const input = parseResult.data;
|
|
35
|
+
const now = new Date().toISOString();
|
|
36
|
+
const rule = {
|
|
37
|
+
id: ulid(),
|
|
38
|
+
name: input.name,
|
|
39
|
+
enabled: input.enabled,
|
|
40
|
+
condition: input.condition,
|
|
41
|
+
threshold: input.threshold,
|
|
42
|
+
windowMinutes: input.windowMinutes,
|
|
43
|
+
scope: input.scope,
|
|
44
|
+
notifyChannels: input.notifyChannels,
|
|
45
|
+
createdAt: now,
|
|
46
|
+
updatedAt: now,
|
|
47
|
+
};
|
|
48
|
+
await store.createAlertRule(rule);
|
|
49
|
+
return c.json(rule, 201);
|
|
50
|
+
});
|
|
51
|
+
// GET /api/alerts/rules — list all alert rules
|
|
52
|
+
app.get('/rules', async (c) => {
|
|
53
|
+
const rules = await store.listAlertRules();
|
|
54
|
+
return c.json({ rules });
|
|
55
|
+
});
|
|
56
|
+
// GET /api/alerts/rules/:id — get single alert rule
|
|
57
|
+
app.get('/rules/:id', async (c) => {
|
|
58
|
+
const id = c.req.param('id');
|
|
59
|
+
const rule = await store.getAlertRule(id);
|
|
60
|
+
if (!rule) {
|
|
61
|
+
return c.json({ error: 'Alert rule not found', status: 404 }, 404);
|
|
62
|
+
}
|
|
63
|
+
return c.json(rule);
|
|
64
|
+
});
|
|
65
|
+
// PUT /api/alerts/rules/:id — update alert rule
|
|
66
|
+
app.put('/rules/:id', async (c) => {
|
|
67
|
+
const id = c.req.param('id');
|
|
68
|
+
const rawBody = await c.req.json().catch(() => null);
|
|
69
|
+
if (!rawBody) {
|
|
70
|
+
return c.json({ error: 'Invalid JSON body', status: 400 }, 400);
|
|
71
|
+
}
|
|
72
|
+
const parseResult = updateAlertRuleSchema.safeParse(rawBody);
|
|
73
|
+
if (!parseResult.success) {
|
|
74
|
+
return c.json({
|
|
75
|
+
error: 'Validation failed',
|
|
76
|
+
status: 400,
|
|
77
|
+
details: parseResult.error.issues.map((issue) => ({
|
|
78
|
+
path: issue.path.join('.'),
|
|
79
|
+
message: issue.message,
|
|
80
|
+
})),
|
|
81
|
+
}, 400);
|
|
82
|
+
}
|
|
83
|
+
const updates = parseResult.data;
|
|
84
|
+
try {
|
|
85
|
+
await store.updateAlertRule(id, {
|
|
86
|
+
...updates,
|
|
87
|
+
updatedAt: new Date().toISOString(),
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
catch (err) {
|
|
91
|
+
if (err instanceof NotFoundError) {
|
|
92
|
+
return c.json({ error: 'Alert rule not found', status: 404 }, 404);
|
|
93
|
+
}
|
|
94
|
+
throw err;
|
|
95
|
+
}
|
|
96
|
+
const updated = await store.getAlertRule(id);
|
|
97
|
+
return c.json(updated);
|
|
98
|
+
});
|
|
99
|
+
// DELETE /api/alerts/rules/:id — delete alert rule
|
|
100
|
+
app.delete('/rules/:id', async (c) => {
|
|
101
|
+
const id = c.req.param('id');
|
|
102
|
+
try {
|
|
103
|
+
await store.deleteAlertRule(id);
|
|
104
|
+
}
|
|
105
|
+
catch (err) {
|
|
106
|
+
if (err instanceof NotFoundError) {
|
|
107
|
+
return c.json({ error: 'Alert rule not found', status: 404 }, 404);
|
|
108
|
+
}
|
|
109
|
+
throw err;
|
|
110
|
+
}
|
|
111
|
+
return c.json({ id, deleted: true });
|
|
112
|
+
});
|
|
113
|
+
// GET /api/alerts/history — list alert history
|
|
114
|
+
app.get('/history', async (c) => {
|
|
115
|
+
const ruleId = c.req.query('ruleId');
|
|
116
|
+
const limitStr = c.req.query('limit');
|
|
117
|
+
const offsetStr = c.req.query('offset');
|
|
118
|
+
const limit = limitStr ? Math.max(1, Math.min(parseInt(limitStr, 10) || 50, 500)) : 50;
|
|
119
|
+
const offset = offsetStr ? Math.max(0, parseInt(offsetStr, 10) || 0) : 0;
|
|
120
|
+
const result = await store.listAlertHistory({
|
|
121
|
+
ruleId: ruleId ?? undefined,
|
|
122
|
+
limit,
|
|
123
|
+
offset,
|
|
124
|
+
});
|
|
125
|
+
return c.json({
|
|
126
|
+
entries: result.entries,
|
|
127
|
+
total: result.total,
|
|
128
|
+
hasMore: offset + result.entries.length < result.total,
|
|
129
|
+
});
|
|
130
|
+
});
|
|
131
|
+
return app;
|
|
132
|
+
}
|
|
133
|
+
//# sourceMappingURL=alerts.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"alerts.js","sourceRoot":"","sources":["../../src/routes/alerts.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Analytics Endpoints (Story 11.2)
|
|
3
|
+
*
|
|
4
|
+
* GET /api/analytics — bucketed metrics over time
|
|
5
|
+
* GET /api/analytics/costs — cost breakdown by agent and time period
|
|
6
|
+
* GET /api/analytics/agents — per-agent metrics (session count, error rate, avg duration, total cost)
|
|
7
|
+
* GET /api/analytics/tools — tool usage statistics (frequency, avg duration, error rate per tool)
|
|
8
|
+
*/
|
|
9
|
+
import { Hono } from 'hono';
|
|
10
|
+
import type { IEventStore } from '@agentlensai/core';
|
|
11
|
+
import type { AuthVariables } from '../middleware/auth.js';
|
|
12
|
+
import type { SqliteDb } from '../db/index.js';
|
|
13
|
+
export declare function analyticsRoutes(store: IEventStore, db: SqliteDb): Hono<{
|
|
14
|
+
Variables: AuthVariables;
|
|
15
|
+
}, import("hono/types").BlankSchema, "/">;
|
|
16
|
+
//# sourceMappingURL=analytics.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"analytics.d.ts","sourceRoot":"","sources":["../../src/routes/analytics.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAE5B,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AACrD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAC3D,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAG/C,wBAAgB,eAAe,CAAC,KAAK,EAAE,WAAW,EAAE,EAAE,EAAE,QAAQ;eAC5B,aAAa;0CAuXhD"}
|
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Analytics Endpoints (Story 11.2)
|
|
3
|
+
*
|
|
4
|
+
* GET /api/analytics — bucketed metrics over time
|
|
5
|
+
* GET /api/analytics/costs — cost breakdown by agent and time period
|
|
6
|
+
* GET /api/analytics/agents — per-agent metrics (session count, error rate, avg duration, total cost)
|
|
7
|
+
* GET /api/analytics/tools — tool usage statistics (frequency, avg duration, error rate per tool)
|
|
8
|
+
*/
|
|
9
|
+
import { Hono } from 'hono';
|
|
10
|
+
import { sql, gte, lte, eq, and } from 'drizzle-orm';
|
|
11
|
+
import { events, sessions } from '../db/schema.sqlite.js';
|
|
12
|
+
export function analyticsRoutes(store, db) {
|
|
13
|
+
const app = new Hono();
|
|
14
|
+
// GET /api/analytics — bucketed metrics over time
|
|
15
|
+
app.get('/', async (c) => {
|
|
16
|
+
const from = c.req.query('from') ?? new Date(Date.now() - 86400_000).toISOString();
|
|
17
|
+
const to = c.req.query('to') ?? new Date().toISOString();
|
|
18
|
+
const granularity = (c.req.query('granularity') ?? 'hour');
|
|
19
|
+
const agentId = c.req.query('agentId');
|
|
20
|
+
if (!['hour', 'day', 'week'].includes(granularity)) {
|
|
21
|
+
return c.json({ error: 'Invalid granularity. Use: hour, day, week', status: 400 }, 400);
|
|
22
|
+
}
|
|
23
|
+
const result = await store.getAnalytics({ from, to, agentId, granularity });
|
|
24
|
+
return c.json(result);
|
|
25
|
+
});
|
|
26
|
+
// GET /api/analytics/costs — cost breakdown by agent and time
|
|
27
|
+
app.get('/costs', async (c) => {
|
|
28
|
+
const from = c.req.query('from') ?? new Date(Date.now() - 86400_000).toISOString();
|
|
29
|
+
const to = c.req.query('to') ?? new Date().toISOString();
|
|
30
|
+
const granularity = (c.req.query('granularity') ?? 'day');
|
|
31
|
+
const formatStr = granularity === 'hour'
|
|
32
|
+
? '%Y-%m-%dT%H:00:00Z'
|
|
33
|
+
: granularity === 'day'
|
|
34
|
+
? '%Y-%m-%dT00:00:00Z'
|
|
35
|
+
: '%Y-%W';
|
|
36
|
+
// Cost by agent
|
|
37
|
+
const byAgent = db
|
|
38
|
+
.all(sql `
|
|
39
|
+
SELECT
|
|
40
|
+
agent_id as agentId,
|
|
41
|
+
COALESCE(SUM(json_extract(payload, '$.costUsd')), 0) as totalCostUsd,
|
|
42
|
+
COALESCE(SUM(json_extract(payload, '$.inputTokens')), 0) as totalInputTokens,
|
|
43
|
+
COALESCE(SUM(json_extract(payload, '$.outputTokens')), 0) as totalOutputTokens,
|
|
44
|
+
COALESCE(SUM(json_extract(payload, '$.totalTokens')), 0) as totalTokens,
|
|
45
|
+
COUNT(*) as eventCount
|
|
46
|
+
FROM events
|
|
47
|
+
WHERE event_type = 'cost_tracked'
|
|
48
|
+
AND timestamp >= ${from}
|
|
49
|
+
AND timestamp <= ${to}
|
|
50
|
+
GROUP BY agent_id
|
|
51
|
+
ORDER BY totalCostUsd DESC
|
|
52
|
+
`);
|
|
53
|
+
// Cost over time — broken down by agent for stacked chart
|
|
54
|
+
const overTimeByAgent = db
|
|
55
|
+
.all(sql `
|
|
56
|
+
SELECT
|
|
57
|
+
strftime(${formatStr}, timestamp) as bucket,
|
|
58
|
+
agent_id as agentId,
|
|
59
|
+
COALESCE(SUM(json_extract(payload, '$.costUsd')), 0) as totalCostUsd,
|
|
60
|
+
COUNT(*) as eventCount
|
|
61
|
+
FROM events
|
|
62
|
+
WHERE event_type = 'cost_tracked'
|
|
63
|
+
AND timestamp >= ${from}
|
|
64
|
+
AND timestamp <= ${to}
|
|
65
|
+
GROUP BY bucket, agent_id
|
|
66
|
+
ORDER BY bucket ASC
|
|
67
|
+
`);
|
|
68
|
+
// Pivot: group by bucket, with per-agent cost fields
|
|
69
|
+
const bucketMap = new Map();
|
|
70
|
+
for (const row of overTimeByAgent) {
|
|
71
|
+
const existing = bucketMap.get(row.bucket) ?? {
|
|
72
|
+
bucket: row.bucket,
|
|
73
|
+
totalCostUsd: 0,
|
|
74
|
+
eventCount: 0,
|
|
75
|
+
byAgent: {},
|
|
76
|
+
};
|
|
77
|
+
existing.totalCostUsd += Number(row.totalCostUsd);
|
|
78
|
+
existing.eventCount += Number(row.eventCount);
|
|
79
|
+
existing.byAgent[row.agentId] = Number(row.totalCostUsd);
|
|
80
|
+
bucketMap.set(row.bucket, existing);
|
|
81
|
+
}
|
|
82
|
+
const overTime = Array.from(bucketMap.values());
|
|
83
|
+
// Total cost
|
|
84
|
+
const total = db.get(sql `
|
|
85
|
+
SELECT
|
|
86
|
+
COALESCE(SUM(json_extract(payload, '$.costUsd')), 0) as totalCostUsd,
|
|
87
|
+
COALESCE(SUM(json_extract(payload, '$.inputTokens')), 0) as totalInputTokens,
|
|
88
|
+
COALESCE(SUM(json_extract(payload, '$.outputTokens')), 0) as totalOutputTokens,
|
|
89
|
+
COALESCE(SUM(json_extract(payload, '$.totalTokens')), 0) as totalTokens
|
|
90
|
+
FROM events
|
|
91
|
+
WHERE event_type = 'cost_tracked'
|
|
92
|
+
AND timestamp >= ${from}
|
|
93
|
+
AND timestamp <= ${to}
|
|
94
|
+
`);
|
|
95
|
+
return c.json({
|
|
96
|
+
byAgent: byAgent.map((r) => ({
|
|
97
|
+
agentId: r.agentId,
|
|
98
|
+
totalCostUsd: Number(r.totalCostUsd),
|
|
99
|
+
totalInputTokens: Number(r.totalInputTokens),
|
|
100
|
+
totalOutputTokens: Number(r.totalOutputTokens),
|
|
101
|
+
totalTokens: Number(r.totalTokens),
|
|
102
|
+
eventCount: Number(r.eventCount),
|
|
103
|
+
})),
|
|
104
|
+
overTime: overTime.map((r) => ({
|
|
105
|
+
bucket: r.bucket,
|
|
106
|
+
totalCostUsd: Number(r.totalCostUsd),
|
|
107
|
+
eventCount: Number(r.eventCount),
|
|
108
|
+
byAgent: r.byAgent,
|
|
109
|
+
})),
|
|
110
|
+
totals: {
|
|
111
|
+
totalCostUsd: Number(total?.totalCostUsd ?? 0),
|
|
112
|
+
totalInputTokens: Number(total?.totalInputTokens ?? 0),
|
|
113
|
+
totalOutputTokens: Number(total?.totalOutputTokens ?? 0),
|
|
114
|
+
totalTokens: Number(total?.totalTokens ?? 0),
|
|
115
|
+
},
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
// GET /api/analytics/agents — per-agent metrics
|
|
119
|
+
app.get('/agents', async (c) => {
|
|
120
|
+
const from = c.req.query('from') ?? new Date(Date.now() - 86400_000).toISOString();
|
|
121
|
+
const to = c.req.query('to') ?? new Date().toISOString();
|
|
122
|
+
const rows = db.all(sql `
|
|
123
|
+
SELECT
|
|
124
|
+
s.agent_id as agentId,
|
|
125
|
+
COUNT(DISTINCT s.id) as sessionCount,
|
|
126
|
+
COALESCE(SUM(s.event_count), 0) as totalEvents,
|
|
127
|
+
COALESCE(SUM(s.error_count), 0) as totalErrors,
|
|
128
|
+
COALESCE(SUM(s.total_cost_usd), 0) as totalCostUsd,
|
|
129
|
+
COALESCE(AVG(
|
|
130
|
+
CASE
|
|
131
|
+
WHEN s.ended_at IS NOT NULL
|
|
132
|
+
THEN (julianday(s.ended_at) - julianday(s.started_at)) * 86400000
|
|
133
|
+
ELSE NULL
|
|
134
|
+
END
|
|
135
|
+
), 0) as avgDurationMs
|
|
136
|
+
FROM sessions s
|
|
137
|
+
WHERE s.started_at >= ${from}
|
|
138
|
+
AND s.started_at <= ${to}
|
|
139
|
+
GROUP BY s.agent_id
|
|
140
|
+
ORDER BY sessionCount DESC
|
|
141
|
+
`);
|
|
142
|
+
return c.json({
|
|
143
|
+
agents: rows.map((r) => ({
|
|
144
|
+
agentId: r.agentId,
|
|
145
|
+
sessionCount: Number(r.sessionCount),
|
|
146
|
+
totalEvents: Number(r.totalEvents),
|
|
147
|
+
totalErrors: Number(r.totalErrors),
|
|
148
|
+
errorRate: Number(r.totalEvents) > 0
|
|
149
|
+
? Number(r.totalErrors) / Number(r.totalEvents)
|
|
150
|
+
: 0,
|
|
151
|
+
totalCostUsd: Number(r.totalCostUsd),
|
|
152
|
+
avgDurationMs: Number(r.avgDurationMs),
|
|
153
|
+
})),
|
|
154
|
+
});
|
|
155
|
+
});
|
|
156
|
+
// GET /api/analytics/llm — LLM call analytics
|
|
157
|
+
app.get('/llm', async (c) => {
|
|
158
|
+
const from = c.req.query('from') ?? new Date(Date.now() - 86400_000).toISOString();
|
|
159
|
+
const to = c.req.query('to') ?? new Date().toISOString();
|
|
160
|
+
const granularity = (c.req.query('granularity') ?? 'hour');
|
|
161
|
+
const agentId = c.req.query('agentId');
|
|
162
|
+
const model = c.req.query('model');
|
|
163
|
+
const provider = c.req.query('provider');
|
|
164
|
+
if (!['hour', 'day', 'week'].includes(granularity)) {
|
|
165
|
+
return c.json({ error: 'Invalid granularity. Use: hour, day, week', status: 400 }, 400);
|
|
166
|
+
}
|
|
167
|
+
const formatStr = granularity === 'hour'
|
|
168
|
+
? '%Y-%m-%dT%H:00:00Z'
|
|
169
|
+
: granularity === 'day'
|
|
170
|
+
? '%Y-%m-%dT00:00:00Z'
|
|
171
|
+
: '%Y-%W';
|
|
172
|
+
// Summary totals
|
|
173
|
+
const summary = db.get(sql `
|
|
174
|
+
SELECT
|
|
175
|
+
COUNT(*) as totalCalls,
|
|
176
|
+
COALESCE(SUM(json_extract(payload, '$.costUsd')), 0) as totalCostUsd,
|
|
177
|
+
COALESCE(SUM(json_extract(payload, '$.usage.inputTokens')), 0) as totalInputTokens,
|
|
178
|
+
COALESCE(SUM(json_extract(payload, '$.usage.outputTokens')), 0) as totalOutputTokens,
|
|
179
|
+
COALESCE(AVG(json_extract(payload, '$.latencyMs')), 0) as avgLatencyMs
|
|
180
|
+
FROM events
|
|
181
|
+
WHERE event_type = 'llm_response'
|
|
182
|
+
AND timestamp >= ${from}
|
|
183
|
+
AND timestamp <= ${to}
|
|
184
|
+
${agentId ? sql `AND agent_id = ${agentId}` : sql ``}
|
|
185
|
+
${model ? sql `AND json_extract(payload, '$.model') = ${model}` : sql ``}
|
|
186
|
+
${provider ? sql `AND json_extract(payload, '$.provider') = ${provider}` : sql ``}
|
|
187
|
+
`);
|
|
188
|
+
const totalCalls = Number(summary?.totalCalls ?? 0);
|
|
189
|
+
const totalCostUsd = Number(summary?.totalCostUsd ?? 0);
|
|
190
|
+
// By model breakdown
|
|
191
|
+
const byModel = db.all(sql `
|
|
192
|
+
SELECT
|
|
193
|
+
json_extract(payload, '$.provider') as provider,
|
|
194
|
+
json_extract(payload, '$.model') as model,
|
|
195
|
+
COUNT(*) as calls,
|
|
196
|
+
COALESCE(SUM(json_extract(payload, '$.costUsd')), 0) as costUsd,
|
|
197
|
+
COALESCE(SUM(json_extract(payload, '$.usage.inputTokens')), 0) as inputTokens,
|
|
198
|
+
COALESCE(SUM(json_extract(payload, '$.usage.outputTokens')), 0) as outputTokens,
|
|
199
|
+
COALESCE(AVG(json_extract(payload, '$.latencyMs')), 0) as avgLatencyMs
|
|
200
|
+
FROM events
|
|
201
|
+
WHERE event_type = 'llm_response'
|
|
202
|
+
AND timestamp >= ${from}
|
|
203
|
+
AND timestamp <= ${to}
|
|
204
|
+
${agentId ? sql `AND agent_id = ${agentId}` : sql ``}
|
|
205
|
+
${model ? sql `AND json_extract(payload, '$.model') = ${model}` : sql ``}
|
|
206
|
+
${provider ? sql `AND json_extract(payload, '$.provider') = ${provider}` : sql ``}
|
|
207
|
+
GROUP BY provider, model
|
|
208
|
+
ORDER BY costUsd DESC
|
|
209
|
+
`);
|
|
210
|
+
// By time bucket
|
|
211
|
+
const byTime = db.all(sql `
|
|
212
|
+
SELECT
|
|
213
|
+
strftime(${formatStr}, timestamp) as bucket,
|
|
214
|
+
COUNT(*) as calls,
|
|
215
|
+
COALESCE(SUM(json_extract(payload, '$.costUsd')), 0) as costUsd,
|
|
216
|
+
COALESCE(SUM(json_extract(payload, '$.usage.inputTokens')), 0) as inputTokens,
|
|
217
|
+
COALESCE(SUM(json_extract(payload, '$.usage.outputTokens')), 0) as outputTokens,
|
|
218
|
+
COALESCE(AVG(json_extract(payload, '$.latencyMs')), 0) as avgLatencyMs
|
|
219
|
+
FROM events
|
|
220
|
+
WHERE event_type = 'llm_response'
|
|
221
|
+
AND timestamp >= ${from}
|
|
222
|
+
AND timestamp <= ${to}
|
|
223
|
+
${agentId ? sql `AND agent_id = ${agentId}` : sql ``}
|
|
224
|
+
${model ? sql `AND json_extract(payload, '$.model') = ${model}` : sql ``}
|
|
225
|
+
${provider ? sql `AND json_extract(payload, '$.provider') = ${provider}` : sql ``}
|
|
226
|
+
GROUP BY bucket
|
|
227
|
+
ORDER BY bucket ASC
|
|
228
|
+
`);
|
|
229
|
+
return c.json({
|
|
230
|
+
summary: {
|
|
231
|
+
totalCalls,
|
|
232
|
+
totalCostUsd,
|
|
233
|
+
totalInputTokens: Number(summary?.totalInputTokens ?? 0),
|
|
234
|
+
totalOutputTokens: Number(summary?.totalOutputTokens ?? 0),
|
|
235
|
+
avgLatencyMs: Number(summary?.avgLatencyMs ?? 0),
|
|
236
|
+
avgCostPerCall: totalCalls > 0 ? totalCostUsd / totalCalls : 0,
|
|
237
|
+
},
|
|
238
|
+
byModel: byModel.map((r) => ({
|
|
239
|
+
provider: r.provider,
|
|
240
|
+
model: r.model,
|
|
241
|
+
calls: Number(r.calls),
|
|
242
|
+
costUsd: Number(r.costUsd),
|
|
243
|
+
inputTokens: Number(r.inputTokens),
|
|
244
|
+
outputTokens: Number(r.outputTokens),
|
|
245
|
+
avgLatencyMs: Number(r.avgLatencyMs),
|
|
246
|
+
})),
|
|
247
|
+
byTime: byTime.map((r) => ({
|
|
248
|
+
bucket: r.bucket,
|
|
249
|
+
calls: Number(r.calls),
|
|
250
|
+
costUsd: Number(r.costUsd),
|
|
251
|
+
inputTokens: Number(r.inputTokens),
|
|
252
|
+
outputTokens: Number(r.outputTokens),
|
|
253
|
+
avgLatencyMs: Number(r.avgLatencyMs),
|
|
254
|
+
})),
|
|
255
|
+
});
|
|
256
|
+
});
|
|
257
|
+
// GET /api/analytics/tools — tool usage statistics
|
|
258
|
+
app.get('/tools', async (c) => {
|
|
259
|
+
const from = c.req.query('from') ?? new Date(Date.now() - 86400_000).toISOString();
|
|
260
|
+
const to = c.req.query('to') ?? new Date().toISOString();
|
|
261
|
+
const rows = db.all(sql `
|
|
262
|
+
SELECT
|
|
263
|
+
json_extract(payload, '$.toolName') as toolName,
|
|
264
|
+
COUNT(*) as callCount,
|
|
265
|
+
SUM(CASE WHEN event_type = 'tool_error' THEN 1 ELSE 0 END) as errorCount,
|
|
266
|
+
COALESCE(AVG(
|
|
267
|
+
CASE WHEN event_type IN ('tool_response', 'tool_error')
|
|
268
|
+
THEN json_extract(payload, '$.durationMs')
|
|
269
|
+
ELSE NULL END
|
|
270
|
+
), 0) as avgDurationMs
|
|
271
|
+
FROM events
|
|
272
|
+
WHERE event_type IN ('tool_call', 'tool_response', 'tool_error')
|
|
273
|
+
AND timestamp >= ${from}
|
|
274
|
+
AND timestamp <= ${to}
|
|
275
|
+
AND json_extract(payload, '$.toolName') IS NOT NULL
|
|
276
|
+
GROUP BY toolName
|
|
277
|
+
ORDER BY callCount DESC
|
|
278
|
+
`);
|
|
279
|
+
return c.json({
|
|
280
|
+
tools: rows.map((r) => ({
|
|
281
|
+
toolName: r.toolName,
|
|
282
|
+
callCount: Number(r.callCount),
|
|
283
|
+
errorCount: Number(r.errorCount),
|
|
284
|
+
errorRate: Number(r.callCount) > 0
|
|
285
|
+
? Number(r.errorCount) / Number(r.callCount)
|
|
286
|
+
: 0,
|
|
287
|
+
avgDurationMs: Number(r.avgDurationMs),
|
|
288
|
+
})),
|
|
289
|
+
});
|
|
290
|
+
});
|
|
291
|
+
return app;
|
|
292
|
+
}
|
|
293
|
+
//# sourceMappingURL=analytics.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"analytics.js","sourceRoot":"","sources":["../../src/routes/analytics.ts"],"names":[],"mappings":"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"}
|
package/dist/routes/config.d.ts
CHANGED
|
@@ -11,6 +11,10 @@ import { Hono } from 'hono';
|
|
|
11
11
|
import { z } from 'zod';
|
|
12
12
|
import type { AuthVariables } from '../middleware/auth.js';
|
|
13
13
|
import type { SqliteDb } from '../db/index.js';
|
|
14
|
+
/**
|
|
15
|
+
* Compare a plaintext secret against a stored hash using timing-safe comparison.
|
|
16
|
+
*/
|
|
17
|
+
export declare function verifySecretHash(plaintext: string, storedHash: string): boolean;
|
|
14
18
|
/** Schema for config update request */
|
|
15
19
|
declare const configUpdateSchema: z.ZodObject<{
|
|
16
20
|
retentionDays: z.ZodOptional<z.ZodNumber>;
|
|
@@ -32,6 +36,7 @@ declare const configUpdateSchema: z.ZodObject<{
|
|
|
32
36
|
formBridgeSecret?: string | undefined;
|
|
33
37
|
}>;
|
|
34
38
|
export type ConfigValues = z.infer<typeof configUpdateSchema>;
|
|
39
|
+
export declare function getConfigValue(db: SqliteDb, key: string): string | null;
|
|
35
40
|
export declare function configRoutes(db: SqliteDb): Hono<{
|
|
36
41
|
Variables: AuthVariables;
|
|
37
42
|
}, import("hono/types").BlankSchema, "/">;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/routes/config.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAE5B,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/routes/config.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAE5B,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAC3D,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAW/C;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,SAAS,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAM/E;AAED,uCAAuC;AACvC,QAAA,MAAM,kBAAkB;;;;;;;;;;;;;;;;;;EAMtB,CAAC;AAEH,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAS9D,wBAAgB,cAAc,CAAC,EAAE,EAAE,QAAQ,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAKvE;AAsBD,wBAAgB,YAAY,CAAC,EAAE,EAAE,QAAQ;eACL,aAAa;0CAwDhD"}
|
package/dist/routes/config.js
CHANGED
|
@@ -10,7 +10,26 @@
|
|
|
10
10
|
import { Hono } from 'hono';
|
|
11
11
|
import { sql } from 'drizzle-orm';
|
|
12
12
|
import { z } from 'zod';
|
|
13
|
+
import { createHash, timingSafeEqual } from 'node:crypto';
|
|
13
14
|
import { getConfig } from '../config.js';
|
|
15
|
+
/**
|
|
16
|
+
* Hash a secret with SHA-256 for storage.
|
|
17
|
+
* Used for webhook secrets so plaintext is never persisted.
|
|
18
|
+
*/
|
|
19
|
+
function hashSecret(secret) {
|
|
20
|
+
return createHash('sha256').update(secret).digest('hex');
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Compare a plaintext secret against a stored hash using timing-safe comparison.
|
|
24
|
+
*/
|
|
25
|
+
export function verifySecretHash(plaintext, storedHash) {
|
|
26
|
+
const computedHash = hashSecret(plaintext);
|
|
27
|
+
const a = Buffer.from(computedHash, 'utf8');
|
|
28
|
+
const b = Buffer.from(storedHash, 'utf8');
|
|
29
|
+
if (a.length !== b.length)
|
|
30
|
+
return false;
|
|
31
|
+
return timingSafeEqual(a, b);
|
|
32
|
+
}
|
|
14
33
|
/** Schema for config update request */
|
|
15
34
|
const configUpdateSchema = z.object({
|
|
16
35
|
retentionDays: z.number().int().min(0).max(3650).optional(),
|
|
@@ -25,7 +44,7 @@ const configUpdateSchema = z.object({
|
|
|
25
44
|
function ensureConfigTable(db) {
|
|
26
45
|
db.run(sql `CREATE TABLE IF NOT EXISTS config_kv (key TEXT PRIMARY KEY, value TEXT NOT NULL)`);
|
|
27
46
|
}
|
|
28
|
-
function getConfigValue(db, key) {
|
|
47
|
+
export function getConfigValue(db, key) {
|
|
29
48
|
const row = db.get(sql `SELECT value FROM config_kv WHERE key = ${key}`);
|
|
30
49
|
return row?.value ?? null;
|
|
31
50
|
}
|
|
@@ -48,16 +67,15 @@ function getAllConfig(db) {
|
|
|
48
67
|
export function configRoutes(db) {
|
|
49
68
|
const app = new Hono();
|
|
50
69
|
ensureConfigTable(db);
|
|
51
|
-
// GET /api/config — current config
|
|
70
|
+
// GET /api/config — current config (secrets are never returned)
|
|
52
71
|
app.get('/', (c) => {
|
|
53
72
|
const config = getAllConfig(db);
|
|
54
|
-
// Mask secrets in response
|
|
55
73
|
return c.json({
|
|
56
74
|
retentionDays: config.retentionDays,
|
|
57
75
|
agentGateUrl: config.agentGateUrl,
|
|
58
|
-
|
|
76
|
+
agentGateSecretSet: !!config.agentGateSecret,
|
|
59
77
|
formBridgeUrl: config.formBridgeUrl,
|
|
60
|
-
|
|
78
|
+
formBridgeSecretSet: !!config.formBridgeSecret,
|
|
61
79
|
});
|
|
62
80
|
});
|
|
63
81
|
// PUT /api/config — update config values
|
|
@@ -82,13 +100,15 @@ export function configRoutes(db) {
|
|
|
82
100
|
setConfigValue(db, 'agentGateUrl', updates.agentGateUrl);
|
|
83
101
|
}
|
|
84
102
|
if (updates.agentGateSecret !== undefined) {
|
|
85
|
-
|
|
103
|
+
// Store hash of secret, never plaintext
|
|
104
|
+
setConfigValue(db, 'agentGateSecret', hashSecret(updates.agentGateSecret));
|
|
86
105
|
}
|
|
87
106
|
if (updates.formBridgeUrl !== undefined) {
|
|
88
107
|
setConfigValue(db, 'formBridgeUrl', updates.formBridgeUrl);
|
|
89
108
|
}
|
|
90
109
|
if (updates.formBridgeSecret !== undefined) {
|
|
91
|
-
|
|
110
|
+
// Store hash of secret, never plaintext
|
|
111
|
+
setConfigValue(db, 'formBridgeSecret', hashSecret(updates.formBridgeSecret));
|
|
92
112
|
}
|
|
93
113
|
return c.json({ ok: true });
|
|
94
114
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/routes/config.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAC5B,OAAO,EAAE,GAAG,EAAE,MAAM,aAAa,CAAC;AAClC,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/routes/config.ts"],"names":[],"mappings":"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"}
|
package/dist/routes/events.d.ts
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* GET /api/events/:id — get single event
|
|
7
7
|
*/
|
|
8
8
|
import { Hono } from 'hono';
|
|
9
|
-
import type { IEventStore } from '@
|
|
9
|
+
import type { IEventStore } from '@agentlensai/core';
|
|
10
10
|
import type { AuthVariables } from '../middleware/auth.js';
|
|
11
11
|
export declare function eventsRoutes(store: IEventStore): Hono<{
|
|
12
12
|
Variables: AuthVariables;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"events.d.ts","sourceRoot":"","sources":["../../src/routes/events.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAW5B,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"events.d.ts","sourceRoot":"","sources":["../../src/routes/events.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAW5B,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AACrD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAQ3D,wBAAgB,YAAY,CAAC,KAAK,EAAE,WAAW;eACX,aAAa;0CAuLhD"}
|
package/dist/routes/events.js
CHANGED
|
@@ -8,7 +8,8 @@
|
|
|
8
8
|
import { Hono } from 'hono';
|
|
9
9
|
import { ulid } from 'ulid';
|
|
10
10
|
import { z } from 'zod';
|
|
11
|
-
import { ingestEventSchema, computeEventHash, truncatePayload, DEFAULT_PAGE_SIZE, MAX_PAGE_SIZE, } from '@
|
|
11
|
+
import { ingestEventSchema, computeEventHash, truncatePayload, DEFAULT_PAGE_SIZE, MAX_PAGE_SIZE, } from '@agentlensai/core';
|
|
12
|
+
import { eventBus } from '../lib/event-bus.js';
|
|
12
13
|
/** Schema for the batch ingestion request body */
|
|
13
14
|
const ingestBatchSchema = z.object({
|
|
14
15
|
events: z.array(ingestEventSchema).min(1).max(1000),
|
|
@@ -43,9 +44,8 @@ export function eventsRoutes(store) {
|
|
|
43
44
|
const allProcessed = [];
|
|
44
45
|
// Phase 1: Build all events (validate and compute hashes) without writing
|
|
45
46
|
for (const [sessionId, sessionEvents] of bySession) {
|
|
46
|
-
// Get the last event hash for this session to chain from
|
|
47
|
-
|
|
48
|
-
let prevHash = timeline.length > 0 ? timeline[timeline.length - 1].hash : null;
|
|
47
|
+
// Get the last event hash for this session to chain from (optimized)
|
|
48
|
+
let prevHash = await store.getLastEventHash(sessionId);
|
|
49
49
|
for (const input of sessionEvents) {
|
|
50
50
|
const id = ulid();
|
|
51
51
|
const timestamp = input.timestamp ?? new Date().toISOString();
|
|
@@ -99,6 +99,19 @@ export function eventsRoutes(store) {
|
|
|
99
99
|
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
100
100
|
return c.json({ error: `Batch insert failed: ${message}`, status: 500 }, 500);
|
|
101
101
|
}
|
|
102
|
+
// Emit events to EventBus for SSE fan-out (async, non-blocking)
|
|
103
|
+
const now = new Date().toISOString();
|
|
104
|
+
for (const event of allProcessed) {
|
|
105
|
+
eventBus.emit({ type: 'event_ingested', event, timestamp: now });
|
|
106
|
+
}
|
|
107
|
+
// Emit session updates for affected sessions
|
|
108
|
+
const affectedSessionIds = new Set(allProcessed.map((e) => e.sessionId));
|
|
109
|
+
for (const sessionId of affectedSessionIds) {
|
|
110
|
+
const session = await store.getSession(sessionId);
|
|
111
|
+
if (session) {
|
|
112
|
+
eventBus.emit({ type: 'session_updated', session, timestamp: now });
|
|
113
|
+
}
|
|
114
|
+
}
|
|
102
115
|
return c.json({
|
|
103
116
|
ingested: allProcessed.length,
|
|
104
117
|
events: allProcessed.map((e) => ({ id: e.id, hash: e.hash })),
|