@agentlensai/server 0.6.0 → 0.7.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 (36) hide show
  1. package/dist/db/benchmark-store.d.ts +74 -0
  2. package/dist/db/benchmark-store.d.ts.map +1 -0
  3. package/dist/db/benchmark-store.js +268 -0
  4. package/dist/db/benchmark-store.js.map +1 -0
  5. package/dist/db/migrate.d.ts.map +1 -1
  6. package/dist/db/migrate.js +48 -0
  7. package/dist/db/migrate.js.map +1 -1
  8. package/dist/index.d.ts +1 -0
  9. package/dist/index.d.ts.map +1 -1
  10. package/dist/index.js +12 -0
  11. package/dist/index.js.map +1 -1
  12. package/dist/lib/benchmark/engine.d.ts +24 -0
  13. package/dist/lib/benchmark/engine.d.ts.map +1 -0
  14. package/dist/lib/benchmark/engine.js +159 -0
  15. package/dist/lib/benchmark/engine.js.map +1 -0
  16. package/dist/lib/benchmark/metric-aggregator.d.ts +38 -0
  17. package/dist/lib/benchmark/metric-aggregator.d.ts.map +1 -0
  18. package/dist/lib/benchmark/metric-aggregator.js +159 -0
  19. package/dist/lib/benchmark/metric-aggregator.js.map +1 -0
  20. package/dist/lib/benchmark/statistical.d.ts +51 -0
  21. package/dist/lib/benchmark/statistical.d.ts.map +1 -0
  22. package/dist/lib/benchmark/statistical.js +381 -0
  23. package/dist/lib/benchmark/statistical.js.map +1 -0
  24. package/dist/lib/replay/builder.d.ts +28 -0
  25. package/dist/lib/replay/builder.d.ts.map +1 -0
  26. package/dist/lib/replay/builder.js +482 -0
  27. package/dist/lib/replay/builder.js.map +1 -0
  28. package/dist/routes/benchmarks.d.ts +18 -0
  29. package/dist/routes/benchmarks.d.ts.map +1 -0
  30. package/dist/routes/benchmarks.js +312 -0
  31. package/dist/routes/benchmarks.js.map +1 -0
  32. package/dist/routes/replay.d.ts +28 -0
  33. package/dist/routes/replay.d.ts.map +1 -0
  34. package/dist/routes/replay.js +140 -0
  35. package/dist/routes/replay.js.map +1 -0
  36. package/package.json +2 -2
@@ -0,0 +1,312 @@
1
+ /**
2
+ * Benchmark REST Endpoints (Story 3.5)
3
+ *
4
+ * POST /api/benchmarks — Create benchmark
5
+ * GET /api/benchmarks — List benchmarks
6
+ * GET /api/benchmarks/:id — Get benchmark detail
7
+ * PUT /api/benchmarks/:id/status — Transition status
8
+ * GET /api/benchmarks/:id/results — Get comparison results
9
+ * DELETE /api/benchmarks/:id — Delete draft/cancelled
10
+ */
11
+ import { Hono } from 'hono';
12
+ import { BENCHMARK_METRICS } from '@agentlensai/core';
13
+ import { getTenantStore } from './tenant-helper.js';
14
+ import { BenchmarkStore, } from '../db/benchmark-store.js';
15
+ import { BenchmarkEngine } from '../lib/benchmark/engine.js';
16
+ // ─── Validation Helpers ────────────────────────────────────
17
+ const VALID_METRICS = new Set(BENCHMARK_METRICS);
18
+ const VALID_STATUSES = new Set(['draft', 'running', 'completed', 'cancelled']);
19
+ function validateCreateInput(body) {
20
+ if (!body || typeof body !== 'object') {
21
+ return { error: 'Request body is required' };
22
+ }
23
+ // Name required
24
+ if (!body.name || typeof body.name !== 'string' || body.name.trim() === '') {
25
+ return { error: 'name is required and must be a non-empty string' };
26
+ }
27
+ // Variants: 2-10 with name+tag
28
+ if (!Array.isArray(body.variants)) {
29
+ return { error: 'variants must be an array' };
30
+ }
31
+ if (body.variants.length < 2 || body.variants.length > 10) {
32
+ return { error: 'Must have between 2 and 10 variants' };
33
+ }
34
+ for (let i = 0; i < body.variants.length; i++) {
35
+ const v = body.variants[i];
36
+ if (!v || typeof v !== 'object') {
37
+ return { error: `variants[${i}] must be an object` };
38
+ }
39
+ if (!v.name || typeof v.name !== 'string' || v.name.trim() === '') {
40
+ return { error: `variants[${i}].name is required` };
41
+ }
42
+ if (!v.tag || typeof v.tag !== 'string' || v.tag.trim() === '') {
43
+ return { error: `variants[${i}].tag is required` };
44
+ }
45
+ }
46
+ // Metrics: optional array, but if provided must be valid
47
+ let metrics = BENCHMARK_METRICS.filter(m => m !== 'health_score'); // default: all supported
48
+ if (body.metrics !== undefined) {
49
+ if (!Array.isArray(body.metrics) || body.metrics.length === 0) {
50
+ return { error: 'metrics must be a non-empty array' };
51
+ }
52
+ for (const m of body.metrics) {
53
+ if (!VALID_METRICS.has(m)) {
54
+ return { error: `Invalid metric: ${m}` };
55
+ }
56
+ if (m === 'health_score') {
57
+ return { error: `Metric "health_score" is not yet supported for benchmarks. It requires pre-computed health snapshots.` };
58
+ }
59
+ }
60
+ metrics = body.metrics;
61
+ }
62
+ // minSessionsPerVariant: optional, must be ≥ 1
63
+ let minSessions;
64
+ if (body.minSessionsPerVariant !== undefined) {
65
+ const val = Number(body.minSessionsPerVariant);
66
+ if (!Number.isInteger(val) || val < 1) {
67
+ return { error: 'minSessionsPerVariant must be an integer ≥ 1' };
68
+ }
69
+ minSessions = val;
70
+ }
71
+ const input = {
72
+ name: body.name.trim(),
73
+ description: body.description ?? undefined,
74
+ agentId: body.agentId ?? undefined,
75
+ metrics,
76
+ minSessionsPerVariant: minSessions,
77
+ timeRange: body.timeRange,
78
+ variants: body.variants.map((v) => ({
79
+ name: v.name.trim(),
80
+ description: v.description ?? undefined,
81
+ tag: v.tag.trim(),
82
+ agentId: v.agentId ?? undefined,
83
+ })),
84
+ };
85
+ return { input };
86
+ }
87
+ // ─── Route Factory ─────────────────────────────────────────
88
+ export function benchmarkRoutes(store, db) {
89
+ const app = new Hono();
90
+ const engine = new BenchmarkEngine();
91
+ function getBenchmarkStore(c) {
92
+ if (!db)
93
+ return null;
94
+ return new BenchmarkStore(db);
95
+ }
96
+ function getTenantId(c) {
97
+ const apiKey = c.get('apiKey');
98
+ return apiKey?.tenantId ?? 'default';
99
+ }
100
+ // ─── POST / — Create benchmark ─────────────────────────
101
+ app.post('/', async (c) => {
102
+ const benchmarkStore = getBenchmarkStore(c);
103
+ if (!benchmarkStore) {
104
+ return c.json({ error: 'Database not available', status: 500 }, 500);
105
+ }
106
+ const tenantId = getTenantId(c);
107
+ const body = await c.req.json().catch(() => null);
108
+ const { error, input } = validateCreateInput(body);
109
+ if (error || !input) {
110
+ return c.json({ error, status: 400 }, 400);
111
+ }
112
+ try {
113
+ const benchmark = benchmarkStore.create(tenantId, input);
114
+ return c.json(benchmark, 201);
115
+ }
116
+ catch (err) {
117
+ const message = err instanceof Error ? err.message : 'Failed to create benchmark';
118
+ return c.json({ error: message, status: 400 }, 400);
119
+ }
120
+ });
121
+ // ─── GET / — List benchmarks ────────────────────────────
122
+ app.get('/', async (c) => {
123
+ const benchmarkStore = getBenchmarkStore(c);
124
+ if (!benchmarkStore) {
125
+ return c.json({ error: 'Database not available', status: 500 }, 500);
126
+ }
127
+ const tenantId = getTenantId(c);
128
+ // Parse query params
129
+ const statusRaw = c.req.query('status');
130
+ const agentId = c.req.query('agentId') || undefined;
131
+ const limitStr = c.req.query('limit');
132
+ const offsetStr = c.req.query('offset');
133
+ // Validate status
134
+ let status;
135
+ if (statusRaw) {
136
+ if (!VALID_STATUSES.has(statusRaw)) {
137
+ return c.json({ error: `Invalid status: ${statusRaw}`, status: 400 }, 400);
138
+ }
139
+ status = statusRaw;
140
+ }
141
+ // Validate limit (1-100, default 20)
142
+ let limit = 20;
143
+ if (limitStr !== undefined && limitStr !== '') {
144
+ const val = parseInt(limitStr, 10);
145
+ if (isNaN(val) || val < 1 || val > 100) {
146
+ return c.json({ error: 'limit must be an integer between 1 and 100', status: 400 }, 400);
147
+ }
148
+ limit = val;
149
+ }
150
+ // Validate offset
151
+ let offset = 0;
152
+ if (offsetStr !== undefined && offsetStr !== '') {
153
+ const val = parseInt(offsetStr, 10);
154
+ if (isNaN(val) || val < 0) {
155
+ return c.json({ error: 'offset must be a non-negative integer', status: 400 }, 400);
156
+ }
157
+ offset = val;
158
+ }
159
+ const filters = { status, agentId, limit, offset };
160
+ const { benchmarks, total } = benchmarkStore.list(tenantId, filters);
161
+ return c.json({
162
+ benchmarks,
163
+ total,
164
+ hasMore: offset + benchmarks.length < total,
165
+ });
166
+ });
167
+ // ─── GET /:id — Get benchmark detail ────────────────────
168
+ app.get('/:id', async (c) => {
169
+ const benchmarkStore = getBenchmarkStore(c);
170
+ if (!benchmarkStore) {
171
+ return c.json({ error: 'Database not available', status: 500 }, 500);
172
+ }
173
+ const tenantId = getTenantId(c);
174
+ const id = c.req.param('id');
175
+ const benchmark = benchmarkStore.getById(tenantId, id);
176
+ if (!benchmark) {
177
+ return c.json({ error: 'Benchmark not found', status: 404 }, 404);
178
+ }
179
+ // Enrich variants with session counts
180
+ const tenantStore = getTenantStore(store, c);
181
+ const variantsWithCounts = await Promise.all(benchmark.variants.map(async (v) => {
182
+ try {
183
+ const { total } = await tenantStore.querySessions({
184
+ tenantId: v.tenantId,
185
+ agentId: v.agentId,
186
+ tags: [v.tag],
187
+ from: benchmark.timeRange?.from,
188
+ to: benchmark.timeRange?.to,
189
+ limit: 1, // Minimal fetch — we only need the count
190
+ });
191
+ return { ...v, sessionCount: total };
192
+ }
193
+ catch {
194
+ return { ...v, sessionCount: 0 };
195
+ }
196
+ }));
197
+ return c.json({
198
+ ...benchmark,
199
+ variants: variantsWithCounts,
200
+ });
201
+ });
202
+ // ─── PUT /:id/status — Transition status ────────────────
203
+ app.put('/:id/status', async (c) => {
204
+ const benchmarkStore = getBenchmarkStore(c);
205
+ if (!benchmarkStore) {
206
+ return c.json({ error: 'Database not available', status: 500 }, 500);
207
+ }
208
+ const tenantId = getTenantId(c);
209
+ const id = c.req.param('id');
210
+ const body = await c.req.json().catch(() => null);
211
+ if (!body || !body.status) {
212
+ return c.json({ error: 'status is required', status: 400 }, 400);
213
+ }
214
+ if (!VALID_STATUSES.has(body.status)) {
215
+ return c.json({ error: `Invalid status: ${body.status}`, status: 400 }, 400);
216
+ }
217
+ const newStatus = body.status;
218
+ // Get current benchmark
219
+ const current = benchmarkStore.getById(tenantId, id);
220
+ if (!current) {
221
+ return c.json({ error: 'Benchmark not found', status: 404 }, 404);
222
+ }
223
+ // When transitioning to "running": validate ≥1 session per variant
224
+ if (newStatus === 'running') {
225
+ const tenantStore = getTenantStore(store, c);
226
+ for (const v of current.variants) {
227
+ const { sessions } = await tenantStore.querySessions({
228
+ tenantId: v.tenantId,
229
+ agentId: v.agentId,
230
+ tags: [v.tag],
231
+ from: current.timeRange?.from,
232
+ to: current.timeRange?.to,
233
+ limit: 1,
234
+ });
235
+ if (sessions.length === 0) {
236
+ return c.json({
237
+ error: `Variant "${v.name}" has no sessions. Each variant must have at least 1 session to start.`,
238
+ status: 409,
239
+ }, 409);
240
+ }
241
+ }
242
+ }
243
+ try {
244
+ const updated = benchmarkStore.updateStatus(tenantId, id, newStatus);
245
+ // When transitioning to "completed": compute and cache results
246
+ if (newStatus === 'completed') {
247
+ const tenantStore = getTenantStore(store, c);
248
+ await engine.computeResults(updated, tenantStore, benchmarkStore);
249
+ }
250
+ return c.json(updated);
251
+ }
252
+ catch (err) {
253
+ const message = err instanceof Error ? err.message : 'Failed to update status';
254
+ if (message.includes('Invalid status transition')) {
255
+ return c.json({ error: message, status: 409 }, 409);
256
+ }
257
+ return c.json({ error: message, status: 400 }, 400);
258
+ }
259
+ });
260
+ // ─── GET /:id/results — Get comparison results ──────────
261
+ app.get('/:id/results', async (c) => {
262
+ const benchmarkStore = getBenchmarkStore(c);
263
+ if (!benchmarkStore) {
264
+ return c.json({ error: 'Database not available', status: 500 }, 500);
265
+ }
266
+ const tenantId = getTenantId(c);
267
+ const id = c.req.param('id');
268
+ const benchmark = benchmarkStore.getById(tenantId, id);
269
+ if (!benchmark) {
270
+ return c.json({ error: 'Benchmark not found', status: 404 }, 404);
271
+ }
272
+ // Draft benchmarks can't have results
273
+ if (benchmark.status === 'draft') {
274
+ return c.json({ error: 'Cannot get results for a draft benchmark. Start it first.', status: 400 }, 400);
275
+ }
276
+ const tenantStore = getTenantStore(store, c);
277
+ const results = await engine.computeResults(benchmark, tenantStore, benchmarkStore);
278
+ // Strip distributions unless requested
279
+ const includeDistributions = c.req.query('includeDistributions') === 'true';
280
+ if (!includeDistributions) {
281
+ for (const v of results.variants) {
282
+ for (const key of Object.keys(v.metrics)) {
283
+ const stats = v.metrics[key];
284
+ if (stats) {
285
+ delete stats.values;
286
+ }
287
+ }
288
+ }
289
+ }
290
+ return c.json(results);
291
+ });
292
+ // ─── DELETE /:id — Delete benchmark ─────────────────────
293
+ app.delete('/:id', async (c) => {
294
+ const benchmarkStore = getBenchmarkStore(c);
295
+ if (!benchmarkStore) {
296
+ return c.json({ error: 'Database not available', status: 500 }, 500);
297
+ }
298
+ const tenantId = getTenantId(c);
299
+ const id = c.req.param('id');
300
+ const benchmark = benchmarkStore.getById(tenantId, id);
301
+ if (!benchmark) {
302
+ return c.json({ error: 'Benchmark not found', status: 404 }, 404);
303
+ }
304
+ if (benchmark.status === 'running' || benchmark.status === 'completed') {
305
+ return c.json({ error: `Cannot delete a ${benchmark.status} benchmark`, status: 409 }, 409);
306
+ }
307
+ benchmarkStore.delete(tenantId, id);
308
+ return c.body(null, 204);
309
+ });
310
+ return app;
311
+ }
312
+ //# sourceMappingURL=benchmarks.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"benchmarks.js","sourceRoot":"","sources":["../../src/routes/benchmarks.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Replay REST Endpoint (Stories 2.2, 2.3)
3
+ *
4
+ * GET /api/sessions/:id/replay — Returns ReplayState for session replay UI.
5
+ *
6
+ * Supports pagination, event type filtering, and server-side LRU caching.
7
+ */
8
+ import { Hono } from 'hono';
9
+ import type { IEventStore, ReplayState } from '@agentlensai/core';
10
+ import type { AuthVariables } from '../middleware/auth.js';
11
+ interface CacheEntry {
12
+ state: ReplayState;
13
+ createdAt: number;
14
+ }
15
+ /** Simple Map-based LRU cache with TTL. Exported for testing. */
16
+ export declare const replayCache: Map<string, CacheEntry>;
17
+ /**
18
+ * Register the replay route directly on the provided Hono app.
19
+ * Path: GET /api/sessions/:id/replay
20
+ *
21
+ * Uses `registerReplayRoutes(app, store)` pattern (like health routes)
22
+ * since the path nests under /api/sessions which already has auth middleware.
23
+ */
24
+ export declare function registerReplayRoutes(app: Hono<{
25
+ Variables: AuthVariables;
26
+ }>, store: IEventStore): void;
27
+ export {};
28
+ //# sourceMappingURL=replay.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"replay.d.ts","sourceRoot":"","sources":["../../src/routes/replay.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAC5B,OAAO,KAAK,EAAE,WAAW,EAAa,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAE7E,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAU3D,UAAU,UAAU;IAClB,KAAK,EAAE,WAAW,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,iEAAiE;AACjE,eAAO,MAAM,WAAW,yBAAgC,CAAC;AAmDzD;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAClC,GAAG,EAAE,IAAI,CAAC;IAAE,SAAS,EAAE,aAAa,CAAA;CAAE,CAAC,EACvC,KAAK,EAAE,WAAW,GACjB,IAAI,CAgGN"}
@@ -0,0 +1,140 @@
1
+ /**
2
+ * Replay REST Endpoint (Stories 2.2, 2.3)
3
+ *
4
+ * GET /api/sessions/:id/replay — Returns ReplayState for session replay UI.
5
+ *
6
+ * Supports pagination, event type filtering, and server-side LRU caching.
7
+ */
8
+ import { Hono } from 'hono';
9
+ import { EVENT_TYPES } from '@agentlensai/core';
10
+ import { getTenantStore } from './tenant-helper.js';
11
+ import { ReplayBuilder } from '../lib/replay/builder.js';
12
+ // ─── LRU Cache (Story 2.3) ────────────────────────────────
13
+ const MAX_CACHE_SIZE = 100;
14
+ const CACHE_TTL_MS = 10 * 60 * 1000; // 10 minutes
15
+ const LLM_HISTORY_CAP = 50;
16
+ /** Simple Map-based LRU cache with TTL. Exported for testing. */
17
+ export const replayCache = new Map();
18
+ function cacheKey(tenantId, sessionId) {
19
+ return `${tenantId}:${sessionId}`;
20
+ }
21
+ function getCached(tenantId, sessionId) {
22
+ const key = cacheKey(tenantId, sessionId);
23
+ const entry = replayCache.get(key);
24
+ if (!entry)
25
+ return null;
26
+ // TTL check
27
+ if (Date.now() - entry.createdAt > CACHE_TTL_MS) {
28
+ replayCache.delete(key);
29
+ return null;
30
+ }
31
+ // Move to end (most-recently-used)
32
+ replayCache.delete(key);
33
+ replayCache.set(key, entry);
34
+ return entry.state;
35
+ }
36
+ function putCache(tenantId, sessionId, state) {
37
+ const key = cacheKey(tenantId, sessionId);
38
+ // Evict if at capacity (remove oldest = first entry)
39
+ if (replayCache.size >= MAX_CACHE_SIZE && !replayCache.has(key)) {
40
+ const firstKey = replayCache.keys().next().value;
41
+ if (firstKey !== undefined) {
42
+ replayCache.delete(firstKey);
43
+ }
44
+ }
45
+ replayCache.set(key, { state, createdAt: Date.now() });
46
+ }
47
+ /**
48
+ * Cap LLM history in context to the last N entries (memory guard).
49
+ */
50
+ function capLlmHistory(state) {
51
+ for (const step of state.steps) {
52
+ if (step.context.llmHistory.length > LLM_HISTORY_CAP) {
53
+ step.context.llmHistory = step.context.llmHistory.slice(-LLM_HISTORY_CAP);
54
+ }
55
+ }
56
+ return state;
57
+ }
58
+ // ─── Route Registration ────────────────────────────────────
59
+ /**
60
+ * Register the replay route directly on the provided Hono app.
61
+ * Path: GET /api/sessions/:id/replay
62
+ *
63
+ * Uses `registerReplayRoutes(app, store)` pattern (like health routes)
64
+ * since the path nests under /api/sessions which already has auth middleware.
65
+ */
66
+ export function registerReplayRoutes(app, store) {
67
+ app.get('/api/sessions/:id/replay', async (c) => {
68
+ const tenantStore = getTenantStore(store, c);
69
+ const sessionId = c.req.param('id');
70
+ // ── Parse & validate query params ──
71
+ // offset
72
+ const offsetStr = c.req.query('offset');
73
+ let offset = 0;
74
+ if (offsetStr !== undefined && offsetStr !== '') {
75
+ const parsed = parseInt(offsetStr, 10);
76
+ if (isNaN(parsed) || parsed < 0) {
77
+ return c.json({ error: 'Invalid offset: must be a non-negative integer', status: 400 }, 400);
78
+ }
79
+ offset = parsed;
80
+ }
81
+ // limit
82
+ const limitStr = c.req.query('limit');
83
+ let limit = 1000;
84
+ if (limitStr !== undefined && limitStr !== '') {
85
+ const parsed = parseInt(limitStr, 10);
86
+ if (isNaN(parsed) || parsed < 1 || parsed > 5000) {
87
+ return c.json({ error: 'Invalid limit: must be an integer between 1 and 5000', status: 400 }, 400);
88
+ }
89
+ limit = parsed;
90
+ }
91
+ // eventTypes
92
+ const eventTypesStr = c.req.query('eventTypes');
93
+ let eventTypes;
94
+ if (eventTypesStr !== undefined && eventTypesStr !== '') {
95
+ const types = eventTypesStr.split(',').map((s) => s.trim());
96
+ const validTypes = new Set(EVENT_TYPES);
97
+ for (const t of types) {
98
+ if (!validTypes.has(t)) {
99
+ return c.json({ error: `Invalid event type: ${t}`, status: 400 }, 400);
100
+ }
101
+ }
102
+ eventTypes = types;
103
+ }
104
+ // includeContext
105
+ const includeContextStr = c.req.query('includeContext');
106
+ let includeContext = true;
107
+ if (includeContextStr !== undefined && includeContextStr !== '') {
108
+ if (includeContextStr === 'false' || includeContextStr === '0') {
109
+ includeContext = false;
110
+ }
111
+ else if (includeContextStr !== 'true' && includeContextStr !== '1') {
112
+ return c.json({ error: 'Invalid includeContext: must be true or false', status: 400 }, 400);
113
+ }
114
+ }
115
+ try {
116
+ const builder = new ReplayBuilder(tenantStore);
117
+ const state = await builder.build(sessionId, {
118
+ offset,
119
+ limit,
120
+ eventTypes,
121
+ includeContext,
122
+ });
123
+ if (!state) {
124
+ return c.json({ error: 'Session not found', status: 404 }, 404);
125
+ }
126
+ // Apply memory guard: cap LLM history
127
+ capLlmHistory(state);
128
+ // Cache the state (LLM history has been capped for memory efficiency)
129
+ const apiKeyInfo = c.get('apiKey');
130
+ const tenantId = apiKeyInfo?.tenantId ?? 'default';
131
+ putCache(tenantId, sessionId, state);
132
+ return c.json(state);
133
+ }
134
+ catch (error) {
135
+ const message = error instanceof Error ? error.message : 'Internal error';
136
+ return c.json({ error: message, status: 500 }, 500);
137
+ }
138
+ });
139
+ }
140
+ //# sourceMappingURL=replay.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"replay.js","sourceRoot":"","sources":["../../src/routes/replay.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agentlensai/server",
3
- "version": "0.6.0",
3
+ "version": "0.7.0",
4
4
  "description": "AgentLens API server — event ingestion, querying, dashboard, and alerting for AI agents",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -45,7 +45,7 @@
45
45
  "dev": "tsx watch src/index.ts"
46
46
  },
47
47
  "dependencies": {
48
- "@agentlensai/core": "workspace:*",
48
+ "@agentlensai/core": "^0.7.0",
49
49
  "drizzle-orm": "^0.44.2",
50
50
  "better-sqlite3": "^11.9.1",
51
51
  "hono": "^4.7.10",