@agentlensai/server 0.13.0 → 0.14.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/app.d.ts +27 -0
- package/dist/app.d.ts.map +1 -0
- package/dist/app.js +178 -0
- package/dist/app.js.map +1 -0
- package/dist/config.d.ts +2 -6
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +13 -10
- package/dist/config.js.map +1 -1
- package/dist/health.d.ts +22 -0
- package/dist/health.d.ts.map +1 -0
- package/dist/health.js +34 -0
- package/dist/health.js.map +1 -0
- package/dist/index.d.ts +17 -31
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +28 -502
- package/dist/index.js.map +1 -1
- package/dist/lib/api-schema.d.ts +126 -0
- package/dist/lib/api-schema.d.ts.map +1 -0
- package/dist/lib/api-schema.js +69 -0
- package/dist/lib/api-schema.js.map +1 -0
- package/dist/lib/api-version.d.ts +21 -0
- package/dist/lib/api-version.d.ts.map +1 -0
- package/dist/lib/api-version.js +36 -0
- package/dist/lib/api-version.js.map +1 -0
- package/dist/lib/lore-client.d.ts +64 -112
- package/dist/lib/lore-client.d.ts.map +1 -1
- package/dist/lib/lore-client.js +120 -155
- package/dist/lib/lore-client.js.map +1 -1
- package/dist/routes/agents.d.ts.map +1 -1
- package/dist/routes/agents.js +73 -0
- package/dist/routes/agents.js.map +1 -1
- package/dist/routes/alerts.d.ts.map +1 -1
- package/dist/routes/alerts.js +13 -36
- package/dist/routes/alerts.js.map +1 -1
- package/dist/routes/analytics.d.ts.map +1 -1
- package/dist/routes/analytics.js +93 -0
- package/dist/routes/analytics.js.map +1 -1
- package/dist/routes/api-version.d.ts +9 -0
- package/dist/routes/api-version.d.ts.map +1 -0
- package/dist/routes/api-version.js +19 -0
- package/dist/routes/api-version.js.map +1 -0
- package/dist/routes/audit-verify.d.ts +3 -2
- package/dist/routes/audit-verify.d.ts.map +1 -1
- package/dist/routes/audit-verify.js +91 -27
- package/dist/routes/audit-verify.js.map +1 -1
- package/dist/routes/cost-budgets.d.ts.map +1 -1
- package/dist/routes/cost-budgets.js +19 -36
- package/dist/routes/cost-budgets.js.map +1 -1
- package/dist/routes/guardrails.d.ts.map +1 -1
- package/dist/routes/guardrails.js +121 -37
- package/dist/routes/guardrails.js.map +1 -1
- package/dist/routes/helpers.d.ts +27 -0
- package/dist/routes/helpers.d.ts.map +1 -0
- package/dist/routes/helpers.js +46 -0
- package/dist/routes/helpers.js.map +1 -0
- package/dist/routes/lore-proxy.d.ts +8 -6
- package/dist/routes/lore-proxy.d.ts.map +1 -1
- package/dist/routes/lore-proxy.js +39 -193
- package/dist/routes/lore-proxy.js.map +1 -1
- package/dist/routes/mcp-policies.d.ts +40 -0
- package/dist/routes/mcp-policies.d.ts.map +1 -0
- package/dist/routes/mcp-policies.js +200 -0
- package/dist/routes/mcp-policies.js.map +1 -0
- package/dist/routes/optimization-advisor.d.ts +13 -0
- package/dist/routes/optimization-advisor.d.ts.map +1 -0
- package/dist/routes/optimization-advisor.js +42 -0
- package/dist/routes/optimization-advisor.js.map +1 -0
- package/dist/routes/recall.d.ts.map +1 -1
- package/dist/routes/recall.js +7 -3
- package/dist/routes/recall.js.map +1 -1
- package/dist/routes/registration.d.ts +27 -0
- package/dist/routes/registration.d.ts.map +1 -0
- package/dist/routes/registration.js +311 -0
- package/dist/routes/registration.js.map +1 -0
- package/dist/routes/replay.d.ts.map +1 -1
- package/dist/routes/replay.js +51 -0
- package/dist/routes/replay.js.map +1 -1
- package/dist/services/optimization-advisor.d.ts +37 -0
- package/dist/services/optimization-advisor.d.ts.map +1 -0
- package/dist/services/optimization-advisor.js +239 -0
- package/dist/services/optimization-advisor.js.map +1 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,92 +1,32 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @agentlensai/server — Hono HTTP API server and event storage
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* Thin entry point: re-exports createApp from app.ts and startServer below.
|
|
5
|
+
* The heavy lifting lives in:
|
|
6
|
+
* - app.ts — Hono app creation, middleware setup
|
|
7
|
+
* - routes/registration.ts — route mounting
|
|
8
|
+
* - health.ts — inline health check endpoint
|
|
7
9
|
*/
|
|
8
|
-
import { Hono } from 'hono';
|
|
9
|
-
import { OpenAPIHono } from '@hono/zod-openapi';
|
|
10
|
-
import { apiReference } from '@scalar/hono-api-reference';
|
|
11
|
-
import { cors } from 'hono/cors';
|
|
12
|
-
import { logger } from 'hono/logger';
|
|
13
10
|
import { serve } from '@hono/node-server';
|
|
14
|
-
import { serveStatic } from '@hono/node-server/serve-static';
|
|
15
|
-
import { readFileSync, existsSync } from 'node:fs';
|
|
16
|
-
import { resolve, dirname } from 'node:path';
|
|
17
|
-
import { fileURLToPath } from 'node:url';
|
|
18
|
-
import { BearerAuthScheme } from './schemas/common.js';
|
|
19
11
|
import { getConfig, validateConfig } from './config.js';
|
|
20
|
-
import { authMiddleware } from './middleware/auth.js';
|
|
21
|
-
import { unifiedAuthMiddleware } from './middleware/unified-auth.js';
|
|
22
|
-
import { requireCategory, requireMethodCategory, requireCategoryByMethod } from './middleware/rbac.js';
|
|
23
|
-
import { otlpAuthRequired as otlpAuthRequiredError, otlpInvalidToken } from './middleware/auth-errors.js';
|
|
24
|
-
import { securityHeadersMiddleware } from './middleware/security-headers.js';
|
|
25
|
-
import { sanitizeErrorMessage, getErrorStatus } from './lib/error-sanitizer.js';
|
|
26
|
-
import { buildCorsOptions } from './middleware/cors-config.js';
|
|
27
|
-
import { apiKeysRoutes } from './routes/api-keys.js';
|
|
28
|
-
import { eventsRoutes } from './routes/events.js';
|
|
29
|
-
import { sessionsRoutes } from './routes/sessions.js';
|
|
30
|
-
import { agentsRoutes } from './routes/agents.js';
|
|
31
|
-
import { statsRoutes } from './routes/stats.js';
|
|
32
|
-
import { configRoutes } from './routes/config.js';
|
|
33
|
-
import { alertsRoutes } from './routes/alerts.js';
|
|
34
|
-
import { notificationRoutes } from './routes/notifications.js';
|
|
35
|
-
import { NotificationChannelRepository } from './db/repositories/notification-channel-repository.js';
|
|
36
|
-
import { NotificationRouter } from './lib/notifications/router.js';
|
|
37
|
-
import { ingestRoutes } from './routes/ingest.js';
|
|
38
|
-
import { analyticsRoutes } from './routes/analytics.js';
|
|
39
|
-
import { streamRoutes } from './routes/stream.js';
|
|
40
|
-
import { reflectRoutes } from './routes/reflect.js';
|
|
41
|
-
import { recallRoutes } from './routes/recall.js';
|
|
42
|
-
import { contextRoutes } from './routes/context.js';
|
|
43
|
-
import { optimizeRoutes } from './routes/optimize.js';
|
|
44
|
-
import { healthRoutes } from './routes/health.js';
|
|
45
|
-
import { diagnoseRoutes } from './routes/diagnose.js';
|
|
46
|
-
import { registerReplayRoutes } from './routes/replay.js';
|
|
47
|
-
import { benchmarkRoutes } from './routes/benchmarks.js';
|
|
48
|
-
import { promptRoutes } from './routes/prompts.js';
|
|
49
|
-
import { guardrailRoutes } from './routes/guardrails.js';
|
|
50
|
-
import { evalRoutes } from './routes/eval.js';
|
|
51
|
-
import { capabilityRoutes } from './routes/capabilities.js';
|
|
52
|
-
import { capabilityTopRoutes } from './routes/capabilities-top.js';
|
|
53
|
-
import { discoveryRoutes } from './routes/discovery.js';
|
|
54
|
-
import { delegationRoutes } from './routes/delegation.js';
|
|
55
|
-
import { delegationTopRoutes } from './routes/delegations-top.js';
|
|
56
|
-
import { trustRoutes } from './routes/trust.js';
|
|
57
|
-
import { LocalPoolTransport } from './services/delegation-service.js';
|
|
58
|
-
import { loreProxyRoutes, loreCommunityProxyRoutes } from './routes/lore-proxy.js';
|
|
59
|
-
import { createLoreAdapter } from './lib/lore-client.js';
|
|
60
|
-
import { meshProxyRoutes } from './routes/mesh-proxy.js';
|
|
61
|
-
import { RemoteMeshAdapter } from './lib/mesh-client.js';
|
|
62
|
-
import { otlpRoutes } from './routes/otlp.js';
|
|
63
|
-
import { authRoutes } from './routes/auth.js';
|
|
64
|
-
import { authRateLimit, apiRateLimit } from './middleware/rate-limit.js';
|
|
65
|
-
import { apiBodyLimit } from './middleware/body-limit.js';
|
|
66
|
-
import { auditRoutes } from './routes/audit.js';
|
|
67
|
-
import { cloudOrgRoutes } from './cloud/routes/index.js';
|
|
68
|
-
import { auditVerifyRoutes } from './routes/audit-verify.js';
|
|
69
|
-
import { complianceRoutes } from './routes/compliance.js';
|
|
70
|
-
import { createAuditLogger, cleanupAuditLogs } from './lib/audit.js';
|
|
71
|
-
import { auditMiddleware } from './middleware/audit.js';
|
|
72
|
-
import { GuardrailEngine } from './lib/guardrails/engine.js';
|
|
73
|
-
import { GuardrailStore } from './db/guardrail-store.js';
|
|
74
|
-
import { ContentGuardrailEngine } from './lib/guardrails/content-engine.js';
|
|
75
|
-
import { setAgentStore, setNotificationRouter } from './lib/guardrails/actions.js';
|
|
76
|
-
import { BudgetEngine } from './lib/budget-engine.js';
|
|
77
|
-
import { CostAnomalyDetector } from './lib/cost-anomaly-detector.js';
|
|
78
|
-
import { costBudgetRoutes } from './routes/cost-budgets.js';
|
|
79
12
|
import { createDb } from './db/index.js';
|
|
80
13
|
import { runMigrations } from './db/migrate.js';
|
|
81
14
|
import { SqliteEventStore } from './db/sqlite-store.js';
|
|
82
15
|
import { AlertEngine } from './lib/alert-engine.js';
|
|
83
|
-
import { eventBus } from './lib/event-bus.js';
|
|
84
16
|
import { EmbeddingWorker } from './lib/embeddings/worker.js';
|
|
85
17
|
import { EmbeddingStore } from './db/embedding-store.js';
|
|
86
|
-
import {
|
|
18
|
+
import { NotificationChannelRepository } from './db/repositories/notification-channel-repository.js';
|
|
19
|
+
import { NotificationRouter } from './lib/notifications/router.js';
|
|
20
|
+
import { GuardrailEngine } from './lib/guardrails/engine.js';
|
|
21
|
+
import { setAgentStore, setNotificationRouter } from './lib/guardrails/actions.js';
|
|
22
|
+
import { BudgetEngine } from './lib/budget-engine.js';
|
|
23
|
+
import { CostAnomalyDetector } from './lib/cost-anomaly-detector.js';
|
|
24
|
+
import { cleanupAuditLogs } from './lib/audit.js';
|
|
87
25
|
import { createLogger } from './lib/logger.js';
|
|
26
|
+
// Re-export createApp from the new module
|
|
27
|
+
export { createApp } from './app.js';
|
|
88
28
|
const log = createLogger('Server');
|
|
89
|
-
// Re-
|
|
29
|
+
// ─── Re-exports (preserve public API) ──────────────────────
|
|
90
30
|
export { getConfig, validateConfig } from './config.js';
|
|
91
31
|
export { authMiddleware, hashApiKey } from './middleware/auth.js';
|
|
92
32
|
export { buildCorsOptions } from './middleware/cors-config.js';
|
|
@@ -122,431 +62,24 @@ export { apiBodyLimit } from './middleware/body-limit.js';
|
|
|
122
62
|
export { auditMiddleware } from './middleware/audit.js';
|
|
123
63
|
export { healthRoutes, registerHealthRoutes } from './routes/health.js';
|
|
124
64
|
export { ContextRetriever } from './lib/context/retrieval.js';
|
|
125
|
-
export { loreProxyRoutes
|
|
126
|
-
export { createLoreAdapter,
|
|
65
|
+
export { loreProxyRoutes } from './routes/lore-proxy.js';
|
|
66
|
+
export { createLoreAdapter, LoreReadAdapter, LoreError } from './lib/lore-client.js';
|
|
127
67
|
export { meshProxyRoutes } from './routes/mesh-proxy.js';
|
|
128
68
|
export { RemoteMeshAdapter, MeshError } from './lib/mesh-client.js';
|
|
129
69
|
export { otlpRoutes } from './routes/otlp.js';
|
|
130
70
|
export { guardrailRoutes } from './routes/guardrails.js';
|
|
71
|
+
export { mcpPolicyRoutes } from './routes/mcp-policies.js';
|
|
131
72
|
export { GuardrailEngine } from './lib/guardrails/engine.js';
|
|
132
73
|
export { GuardrailStore } from './db/guardrail-store.js';
|
|
133
74
|
export { BudgetEngine } from './lib/budget-engine.js';
|
|
134
75
|
export { CostAnomalyDetector } from './lib/cost-anomaly-detector.js';
|
|
135
76
|
export { CostBudgetStore } from './db/cost-budget-store.js';
|
|
136
77
|
export { costBudgetRoutes } from './routes/cost-budgets.js';
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
*
|
|
143
|
-
* Returns relative path suitable for serveStatic root, or null if not found.
|
|
144
|
-
*/
|
|
145
|
-
function getDashboardRoot() {
|
|
146
|
-
const candidates = [
|
|
147
|
-
// When running from packages/server/dist/ or packages/server/src/
|
|
148
|
-
resolve(dirname(fileURLToPath(import.meta.url)), '../../dashboard/dist'),
|
|
149
|
-
// Fallback: env var override
|
|
150
|
-
process.env['DASHBOARD_PATH'] ?? '',
|
|
151
|
-
].filter(Boolean);
|
|
152
|
-
for (const candidate of candidates) {
|
|
153
|
-
if (existsSync(resolve(candidate, 'index.html'))) {
|
|
154
|
-
return candidate;
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
return null;
|
|
158
|
-
}
|
|
159
|
-
/**
|
|
160
|
-
* Read the dashboard index.html for SPA fallback (non-API routes).
|
|
161
|
-
* Cached after first read.
|
|
162
|
-
*/
|
|
163
|
-
let cachedIndexHtml;
|
|
164
|
-
function getDashboardIndexHtml() {
|
|
165
|
-
if (cachedIndexHtml !== undefined)
|
|
166
|
-
return cachedIndexHtml;
|
|
167
|
-
const root = getDashboardRoot();
|
|
168
|
-
if (!root) {
|
|
169
|
-
cachedIndexHtml = null;
|
|
170
|
-
return null;
|
|
171
|
-
}
|
|
172
|
-
const indexPath = resolve(root, 'index.html');
|
|
173
|
-
try {
|
|
174
|
-
cachedIndexHtml = readFileSync(indexPath, 'utf-8');
|
|
175
|
-
return cachedIndexHtml;
|
|
176
|
-
}
|
|
177
|
-
catch {
|
|
178
|
-
cachedIndexHtml = null;
|
|
179
|
-
return null;
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
/**
|
|
183
|
-
* Create a configured Hono app with all routes and middleware.
|
|
184
|
-
*
|
|
185
|
-
* @param store - IEventStore implementation for data access
|
|
186
|
-
* @param config - Optional partial config override (defaults from env)
|
|
187
|
-
*/
|
|
188
|
-
export async function createApp(store, config) {
|
|
189
|
-
const resolvedConfig = { ...getConfig(), ...config };
|
|
190
|
-
const app = new OpenAPIHono({
|
|
191
|
-
defaultHook: (result, c) => {
|
|
192
|
-
if (!result.success) {
|
|
193
|
-
return c.json({
|
|
194
|
-
error: 'Validation failed',
|
|
195
|
-
status: 400,
|
|
196
|
-
details: result.error.issues.map((i) => ({
|
|
197
|
-
path: i.path.map(String).join('.'),
|
|
198
|
-
message: i.message,
|
|
199
|
-
})),
|
|
200
|
-
}, 400);
|
|
201
|
-
}
|
|
202
|
-
},
|
|
203
|
-
});
|
|
204
|
-
// Register Bearer auth security scheme for OpenAPI [F13-S1]
|
|
205
|
-
app.openAPIRegistry.registerComponent('securitySchemes', 'Bearer', BearerAuthScheme);
|
|
206
|
-
// ─── Security headers (position 1 — must be first) ────
|
|
207
|
-
app.use('*', securityHeadersMiddleware());
|
|
208
|
-
// ─── Global error handler ──────────────────────────────
|
|
209
|
-
app.onError((err, c) => {
|
|
210
|
-
const status = getErrorStatus(err);
|
|
211
|
-
if (status >= 500) {
|
|
212
|
-
log.error('Unhandled error', { error: err instanceof Error ? err.message : String(err) });
|
|
213
|
-
}
|
|
214
|
-
const message = sanitizeErrorMessage(err);
|
|
215
|
-
return c.json({ error: message, status }, status);
|
|
216
|
-
});
|
|
217
|
-
// ─── 404 handler — API routes return JSON, others get SPA fallback ──
|
|
218
|
-
app.notFound((c) => {
|
|
219
|
-
const path = new URL(c.req.url).pathname;
|
|
220
|
-
if (path.startsWith('/api/')) {
|
|
221
|
-
return c.json({ error: 'Not found', status: 404 }, 404);
|
|
222
|
-
}
|
|
223
|
-
// Static asset requests (paths with file extensions) should 404,
|
|
224
|
-
// not fall through to SPA index.html
|
|
225
|
-
if (/\.\w{1,10}$/.test(path)) {
|
|
226
|
-
return c.json({ error: 'Not found', status: 404 }, 404);
|
|
227
|
-
}
|
|
228
|
-
// SPA fallback: serve index.html for client-side routing
|
|
229
|
-
const indexHtml = getDashboardIndexHtml();
|
|
230
|
-
if (indexHtml) {
|
|
231
|
-
return c.html(indexHtml);
|
|
232
|
-
}
|
|
233
|
-
return c.json({ error: 'Not found', status: 404 }, 404);
|
|
234
|
-
});
|
|
235
|
-
// ─── Middleware on /api/* ──────────────────────────────
|
|
236
|
-
app.use('/api/*', cors(buildCorsOptions({
|
|
237
|
-
corsOrigins: resolvedConfig.corsOrigins ?? resolvedConfig.corsOrigin,
|
|
238
|
-
nodeEnv: process.env['NODE_ENV'],
|
|
239
|
-
})));
|
|
240
|
-
app.use('/api/*', logger());
|
|
241
|
-
// ─── SH-3: Body size limit (1MB default) ────────────────
|
|
242
|
-
app.use('/api/*', apiBodyLimit);
|
|
243
|
-
// ─── Rate limiting: API endpoints ──────────────────────
|
|
244
|
-
app.use('/api/*', apiRateLimit);
|
|
245
|
-
// ─── Health check (no auth) ────────────────────────────
|
|
246
|
-
app.get('/api/health', async (c) => {
|
|
247
|
-
const result = { status: 'ok', version: '0.1.0' };
|
|
248
|
-
// DB health check — works for both SQLite and Postgres
|
|
249
|
-
if (config?.pgSql) {
|
|
250
|
-
const { postgresHealthCheck } = await import('./db/index.js');
|
|
251
|
-
result.db = await postgresHealthCheck(config.pgSql);
|
|
252
|
-
}
|
|
253
|
-
else if (config?.db) {
|
|
254
|
-
// SQLite health check
|
|
255
|
-
const start = performance.now();
|
|
256
|
-
try {
|
|
257
|
-
config.db.run((await import('drizzle-orm')).sql `SELECT 1`);
|
|
258
|
-
result.db = { ok: true, latencyMs: Math.round(performance.now() - start) };
|
|
259
|
-
}
|
|
260
|
-
catch {
|
|
261
|
-
result.db = { ok: false, latencyMs: Math.round(performance.now() - start) };
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
|
-
return c.json(result);
|
|
265
|
-
});
|
|
266
|
-
// ─── Feature flags (no auth — dashboard needs before login) ──
|
|
267
|
-
app.get('/api/config/features', (c) => {
|
|
268
|
-
return c.json({ lore: resolvedConfig.loreEnabled, mesh: resolvedConfig.meshEnabled });
|
|
269
|
-
});
|
|
270
|
-
// ─── SSE stream (authenticates via Bearer header or ?token= query param) ──
|
|
271
|
-
// Mounted before auth middleware — handles its own auth internally for EventSource compat.
|
|
272
|
-
app.route('/api/stream', streamRoutes(config?.apiKeyLookup, resolvedConfig.authDisabled));
|
|
273
|
-
// ─── Webhook ingest (no API key auth — uses HMAC signature verification) ──
|
|
274
|
-
app.route('/api/events/ingest', ingestRoutes(store, {
|
|
275
|
-
agentgateWebhookSecret: process.env['AGENTGATE_WEBHOOK_SECRET'],
|
|
276
|
-
formbridgeWebhookSecret: process.env['FORMBRIDGE_WEBHOOK_SECRET'],
|
|
277
|
-
}));
|
|
278
|
-
// ─── Rate limiting: auth endpoints ─────────────────────
|
|
279
|
-
app.use('/auth/*', authRateLimit);
|
|
280
|
-
// ─── OIDC Auth routes (no API key auth — handles own auth) ──
|
|
281
|
-
{
|
|
282
|
-
const authDb = config?.db;
|
|
283
|
-
if (authDb) {
|
|
284
|
-
const { loadOidcConfig } = await import('agentkit-auth');
|
|
285
|
-
const oidcConfig = loadOidcConfig();
|
|
286
|
-
if (oidcConfig) {
|
|
287
|
-
const jwtSecret = process.env['JWT_SECRET'];
|
|
288
|
-
if (!jwtSecret && process.env['NODE_ENV'] === 'production') {
|
|
289
|
-
throw new Error('JWT_SECRET must be set in production. Refusing to start with default secret.');
|
|
290
|
-
}
|
|
291
|
-
if (!jwtSecret) {
|
|
292
|
-
log.warn('JWT_SECRET not set — using insecure default. Do NOT use in production.');
|
|
293
|
-
}
|
|
294
|
-
app.route('/auth', authRoutes(authDb, {
|
|
295
|
-
oidcConfig,
|
|
296
|
-
authConfig: {
|
|
297
|
-
oidc: null,
|
|
298
|
-
jwt: {
|
|
299
|
-
secret: jwtSecret ?? 'dev-secret-change-me',
|
|
300
|
-
accessTokenTtlSeconds: Number(process.env['JWT_ACCESS_TTL'] ?? 900),
|
|
301
|
-
refreshTokenTtlSeconds: Number(process.env['JWT_REFRESH_TTL'] ?? 604800),
|
|
302
|
-
},
|
|
303
|
-
authDisabled: resolvedConfig.authDisabled,
|
|
304
|
-
},
|
|
305
|
-
}));
|
|
306
|
-
}
|
|
307
|
-
}
|
|
308
|
-
}
|
|
309
|
-
// ─── Fallback auth endpoints when auth is disabled ─────
|
|
310
|
-
if (resolvedConfig.authDisabled) {
|
|
311
|
-
app.get('/auth/me', (c) => c.json({ authMode: 'api-key-only' }, 200));
|
|
312
|
-
}
|
|
313
|
-
// ─── Auth middleware on protected routes [F2-S3] ───────
|
|
314
|
-
// Fail-closed: single catch-all for /api/* with public routes registered above.
|
|
315
|
-
const db = config?.db;
|
|
316
|
-
if (!db && !resolvedConfig.authDisabled) {
|
|
317
|
-
throw new Error('createApp() requires a `db` option when auth is enabled. ' +
|
|
318
|
-
'Either provide a database or set authDisabled: true.');
|
|
319
|
-
}
|
|
320
|
-
{
|
|
321
|
-
const authLookup = config?.apiKeyLookup ?? db ?? null;
|
|
322
|
-
const authConfig = {
|
|
323
|
-
authDisabled: resolvedConfig.authDisabled,
|
|
324
|
-
jwtSecret: process.env['JWT_SECRET'],
|
|
325
|
-
};
|
|
326
|
-
// ── Unified auth catch-all (replaces 40+ individual app.use calls) ──
|
|
327
|
-
app.use('/api/*', unifiedAuthMiddleware(authLookup, authConfig));
|
|
328
|
-
// ── RBAC enforcement per architecture §3.3 ──────────
|
|
329
|
-
// Manage-level routes (owner, admin only)
|
|
330
|
-
const manageGuard = requireCategory('manage');
|
|
331
|
-
app.use('/api/keys/*', manageGuard);
|
|
332
|
-
app.use('/api/keys', manageGuard);
|
|
333
|
-
app.use('/api/audit/*', manageGuard);
|
|
334
|
-
app.use('/api/audit', manageGuard);
|
|
335
|
-
app.use('/api/compliance/*', manageGuard);
|
|
336
|
-
app.use('/api/compliance', manageGuard);
|
|
337
|
-
const configGuard = requireCategoryByMethod({ GET: 'read', PUT: 'manage', PATCH: 'manage' });
|
|
338
|
-
app.use('/api/config/*', configGuard);
|
|
339
|
-
app.use('/api/config', configGuard);
|
|
340
|
-
const guardrailGuard = requireCategoryByMethod({ GET: 'read', POST: 'manage', PUT: 'manage', DELETE: 'manage' });
|
|
341
|
-
app.use('/api/guardrails/*', guardrailGuard);
|
|
342
|
-
app.use('/api/guardrails', guardrailGuard);
|
|
343
|
-
// Default safety net: GET = read (all roles), mutations = write (member+)
|
|
344
|
-
app.use('/api/*', requireMethodCategory());
|
|
345
|
-
// ── Audit middleware (after auth — has access to auth context) ──
|
|
346
|
-
if (db) {
|
|
347
|
-
const auditLogger = createAuditLogger(db);
|
|
348
|
-
app.use('/api/*', auditMiddleware(auditLogger));
|
|
349
|
-
}
|
|
350
|
-
}
|
|
351
|
-
// ─── Routes ────────────────────────────────────────────
|
|
352
|
-
if (db) {
|
|
353
|
-
app.route('/api/keys', apiKeysRoutes(db));
|
|
354
|
-
}
|
|
355
|
-
app.route('/api/events', eventsRoutes(store, {
|
|
356
|
-
embeddingWorker: config?.embeddingWorker ?? null,
|
|
357
|
-
sessionSummaryStore: db ? new SessionSummaryStore(db) : null,
|
|
358
|
-
}));
|
|
359
|
-
// Replay route registered directly on main app BEFORE sessions sub-app
|
|
360
|
-
// (otherwise the sessions sub-app catches /api/sessions/* first)
|
|
361
|
-
registerReplayRoutes(app, store);
|
|
362
|
-
app.route('/api/sessions', sessionsRoutes(store));
|
|
363
|
-
// Health routes [F13-S2] — factory pattern, mounted at /api
|
|
364
|
-
app.route('/api', healthRoutes(store, db));
|
|
365
|
-
if (db) {
|
|
366
|
-
const { app: discApp } = discoveryRoutes(db);
|
|
367
|
-
app.route('/api/agents', discApp);
|
|
368
|
-
app.route('/api/agents', capabilityRoutes(store, db));
|
|
369
|
-
const poolTransport = new LocalPoolTransport();
|
|
370
|
-
const { app: delApp } = delegationRoutes(db, poolTransport);
|
|
371
|
-
app.route('/api/agents', delApp);
|
|
372
|
-
const { app: trustApp } = trustRoutes(db);
|
|
373
|
-
app.route('/api/agents', trustApp);
|
|
374
|
-
}
|
|
375
|
-
app.route('/api/agents', agentsRoutes(store));
|
|
376
|
-
app.route('/api/stats', statsRoutes(store));
|
|
377
|
-
if (db) {
|
|
378
|
-
app.route('/api/config', configRoutes(db));
|
|
379
|
-
app.route('/api/analytics', analyticsRoutes(store, db, config?.pgDb));
|
|
380
|
-
}
|
|
381
|
-
app.route('/api/alerts', alertsRoutes(store));
|
|
382
|
-
// Feature 12: Notification channels
|
|
383
|
-
const notifRepo = db ? new NotificationChannelRepository(db) : null;
|
|
384
|
-
const notifRouter = notifRepo ? new NotificationRouter(notifRepo) : null;
|
|
385
|
-
if (notifRepo && notifRouter) {
|
|
386
|
-
app.route('/api/notifications', notificationRoutes(notifRepo, notifRouter));
|
|
387
|
-
}
|
|
388
|
-
let loreAdapter = null;
|
|
389
|
-
if (resolvedConfig.loreEnabled) {
|
|
390
|
-
try {
|
|
391
|
-
loreAdapter = createLoreAdapter(resolvedConfig);
|
|
392
|
-
}
|
|
393
|
-
catch (err) {
|
|
394
|
-
log.warn(`Lore adapter init failed: ${err instanceof Error ? err.message : err}`);
|
|
395
|
-
}
|
|
396
|
-
}
|
|
397
|
-
if (loreAdapter) {
|
|
398
|
-
app.route('/api/lessons', loreProxyRoutes(loreAdapter));
|
|
399
|
-
}
|
|
400
|
-
// ─── AI Diagnostics (Feature 18) ───────────────────────
|
|
401
|
-
app.route('/api', diagnoseRoutes(store));
|
|
402
|
-
// ─── Reflect / Pattern Analysis ────────────────────────
|
|
403
|
-
app.route('/api/reflect', reflectRoutes(store));
|
|
404
|
-
// ─── Optimize / Cost Recommendations ──────────────────
|
|
405
|
-
app.route('/api/optimize', optimizeRoutes(store));
|
|
406
|
-
// ─── Benchmarks / A/B Testing ─────────────────────────
|
|
407
|
-
if (db) {
|
|
408
|
-
app.route('/api/benchmarks', benchmarkRoutes(store, db));
|
|
409
|
-
app.route('/api/prompts', promptRoutes(db));
|
|
410
|
-
app.route('/api/eval', evalRoutes(db));
|
|
411
|
-
}
|
|
412
|
-
// ─── Guardrails / Proactive Guardrails ────────────────
|
|
413
|
-
if (db) {
|
|
414
|
-
const gStore = new GuardrailStore(db);
|
|
415
|
-
const contentEngine = new ContentGuardrailEngine(gStore);
|
|
416
|
-
app.route('/api/guardrails', guardrailRoutes(gStore, contentEngine));
|
|
417
|
-
}
|
|
418
|
-
// ─── Cost Budgets (Feature 5) ─────────────────────────
|
|
419
|
-
if (db) {
|
|
420
|
-
const cBudgetEngine = new BudgetEngine(store, db);
|
|
421
|
-
const budgetStore = cBudgetEngine.getStore();
|
|
422
|
-
app.route('/api/cost-budgets', costBudgetRoutes(budgetStore, store, cBudgetEngine));
|
|
423
|
-
}
|
|
424
|
-
// ─── Recall / Semantic Search ─────────────────────────
|
|
425
|
-
{
|
|
426
|
-
const embeddingService = config?.embeddingService ?? null;
|
|
427
|
-
const embeddingStore = db ? new EmbeddingStore(db) : null;
|
|
428
|
-
app.route('/api/recall', recallRoutes({ embeddingService, embeddingStore, eventStore: store }));
|
|
429
|
-
// ─── Context / Cross-Session Retrieval ──────────────
|
|
430
|
-
if (db) {
|
|
431
|
-
app.route('/api/context', contextRoutes(store, {
|
|
432
|
-
db,
|
|
433
|
-
embeddingService,
|
|
434
|
-
embeddingStore,
|
|
435
|
-
}));
|
|
436
|
-
}
|
|
437
|
-
}
|
|
438
|
-
// ─── Top-level Capabilities & Delegations (dashboard-facing) ──
|
|
439
|
-
if (db) {
|
|
440
|
-
app.route('/api/capabilities', capabilityTopRoutes(store, db));
|
|
441
|
-
app.route('/api/delegations', delegationTopRoutes(db));
|
|
442
|
-
// Discovery top-level (for /api/discovery?taskType=...)
|
|
443
|
-
const { app: discTopApp } = discoveryRoutes(db);
|
|
444
|
-
app.route('/api/discovery', discTopApp);
|
|
445
|
-
}
|
|
446
|
-
// ─── Audit Log (SH-2) ──────────────────────────────────
|
|
447
|
-
if (db) {
|
|
448
|
-
app.route('/api/audit', auditRoutes(db));
|
|
449
|
-
app.route('/api/audit/verify', auditVerifyRoutes(db, resolvedConfig.auditSigningKey));
|
|
450
|
-
app.route('/api/compliance', complianceRoutes(db, resolvedConfig.auditSigningKey, {
|
|
451
|
-
retentionDays: resolvedConfig.retentionDays,
|
|
452
|
-
}));
|
|
453
|
-
}
|
|
454
|
-
// ─── Cloud org routes with org access validation [F6-fix] ──
|
|
455
|
-
if (config?.pgSql) {
|
|
456
|
-
const cloudDb = {
|
|
457
|
-
async query(sql, params) {
|
|
458
|
-
const result = await config.pgSql.unsafe(sql, params);
|
|
459
|
-
return { rows: Array.from(result) };
|
|
460
|
-
},
|
|
461
|
-
};
|
|
462
|
-
app.route('/api/cloud/orgs', cloudOrgRoutes({ db: cloudDb }));
|
|
463
|
-
}
|
|
464
|
-
// ─── Community Sharing (Stories 4.1–4.3) ────────────────
|
|
465
|
-
// Auth is handled by the unified catch-all above.
|
|
466
|
-
if (loreAdapter) {
|
|
467
|
-
app.route('/api/community', loreCommunityProxyRoutes(loreAdapter));
|
|
468
|
-
}
|
|
469
|
-
// ─── Mesh Proxy (agentkit-mesh) ─────────────────────────
|
|
470
|
-
// Auth is handled by the unified catch-all above.
|
|
471
|
-
if (resolvedConfig.meshEnabled && resolvedConfig.meshUrl) {
|
|
472
|
-
const meshAdapter = new RemoteMeshAdapter(resolvedConfig.meshUrl);
|
|
473
|
-
app.route('/api/mesh', meshProxyRoutes(meshAdapter));
|
|
474
|
-
}
|
|
475
|
-
// ─── OTLP HTTP Receiver [F2-S5] ─────────────────────────
|
|
476
|
-
// Default: no auth (standard OTel convention). Opt-in via env vars.
|
|
477
|
-
if (resolvedConfig.otlpAuthRequired) {
|
|
478
|
-
// Full unified auth on OTLP endpoints
|
|
479
|
-
const authLookup = config?.apiKeyLookup ?? db ?? null;
|
|
480
|
-
app.use('/v1/*', unifiedAuthMiddleware(authLookup, {
|
|
481
|
-
authDisabled: resolvedConfig.authDisabled,
|
|
482
|
-
jwtSecret: process.env['JWT_SECRET'],
|
|
483
|
-
}));
|
|
484
|
-
}
|
|
485
|
-
else if (resolvedConfig.otlpAuthToken) {
|
|
486
|
-
// Simple bearer token check
|
|
487
|
-
const { createMiddleware } = await import('hono/factory');
|
|
488
|
-
app.use('/v1/*', createMiddleware(async (c, next) => {
|
|
489
|
-
const authHeader = c.req.header('Authorization');
|
|
490
|
-
if (!authHeader?.startsWith('Bearer ')) {
|
|
491
|
-
return otlpAuthRequiredError(c);
|
|
492
|
-
}
|
|
493
|
-
const token = authHeader.slice(7);
|
|
494
|
-
if (token !== resolvedConfig.otlpAuthToken) {
|
|
495
|
-
return otlpInvalidToken(c);
|
|
496
|
-
}
|
|
497
|
-
return next();
|
|
498
|
-
}));
|
|
499
|
-
}
|
|
500
|
-
app.route('/v1', otlpRoutes(store, resolvedConfig));
|
|
501
|
-
// ─── Server Info (Feature 10, Story 10.1) ─────────────
|
|
502
|
-
{
|
|
503
|
-
const features = [
|
|
504
|
-
'sessions', 'agents', 'alerts', 'analytics', 'stats',
|
|
505
|
-
'recall', 'reflect', 'optimize', 'context', 'health',
|
|
506
|
-
'replay', 'benchmarks', 'guardrails', 'discovery', 'delegation',
|
|
507
|
-
'cost-budgets', 'trust', 'lessons',
|
|
508
|
-
];
|
|
509
|
-
const { serverInfoRoutes } = await import('./routes/server-info.js');
|
|
510
|
-
app.route('/api/server-info', serverInfoRoutes(features));
|
|
511
|
-
}
|
|
512
|
-
// ─── OpenAPI Spec & Documentation [F13-S1] ────────────
|
|
513
|
-
app.doc('/api/openapi.json', {
|
|
514
|
-
openapi: '3.1.0',
|
|
515
|
-
info: {
|
|
516
|
-
title: 'AgentLens API',
|
|
517
|
-
version: '0.12.1',
|
|
518
|
-
description: 'Observability, governance, and orchestration for AI agents.',
|
|
519
|
-
license: { name: 'MIT' },
|
|
520
|
-
},
|
|
521
|
-
servers: [
|
|
522
|
-
{ url: 'http://localhost:3000', description: 'Local development' },
|
|
523
|
-
],
|
|
524
|
-
security: [{ Bearer: [] }],
|
|
525
|
-
tags: [
|
|
526
|
-
{ name: 'Sessions', description: 'Agent session lifecycle and queries' },
|
|
527
|
-
{ name: 'Events', description: 'Event ingestion and retrieval' },
|
|
528
|
-
{ name: 'Agents', description: 'Agent management and health' },
|
|
529
|
-
{ name: 'Auth', description: 'Authentication and API keys' },
|
|
530
|
-
{ name: 'Analytics', description: 'Metrics, costs, and statistics' },
|
|
531
|
-
{ name: 'Alerts', description: 'Alert rules and history' },
|
|
532
|
-
{ name: 'Intelligence', description: 'Reflect, recall, context, optimize' },
|
|
533
|
-
{ name: 'Trust & Governance', description: 'Trust scores, guardrails, cost budgets' },
|
|
534
|
-
{ name: 'Multi-Agent', description: 'Discovery, delegation, capabilities, mesh' },
|
|
535
|
-
{ name: 'Observability', description: 'Health, benchmarks, audit' },
|
|
536
|
-
{ name: 'Platform', description: 'Config, OTLP, streaming, webhooks' },
|
|
537
|
-
],
|
|
538
|
-
});
|
|
539
|
-
app.get('/api/docs', apiReference({
|
|
540
|
-
url: '/api/openapi.json',
|
|
541
|
-
theme: 'kepler',
|
|
542
|
-
}));
|
|
543
|
-
// ─── Dashboard SPA static assets ──────────────────────
|
|
544
|
-
const dashboardRoot = getDashboardRoot();
|
|
545
|
-
if (dashboardRoot) {
|
|
546
|
-
app.use('/*', serveStatic({ root: dashboardRoot }));
|
|
547
|
-
}
|
|
548
|
-
return app;
|
|
549
|
-
}
|
|
78
|
+
export { apiVersionMiddleware, CURRENT_API_VERSION, SUPPORTED_API_VERSIONS } from './lib/api-version.js';
|
|
79
|
+
export { apiVersionRoutes } from './routes/api-version.js';
|
|
80
|
+
export { optimizationAdvisorRoutes } from './routes/optimization-advisor.js';
|
|
81
|
+
export { getOptimizationSuggestions, getOptimizationSummary } from './services/optimization-advisor.js';
|
|
82
|
+
// ─── startServer ─────────────────────────────────────────────
|
|
550
83
|
/**
|
|
551
84
|
* Start the server as a standalone process.
|
|
552
85
|
* Creates the database, runs migrations, and starts listening.
|
|
@@ -558,26 +91,23 @@ export async function startServer() {
|
|
|
558
91
|
const config = getConfig();
|
|
559
92
|
validateConfig(config);
|
|
560
93
|
// Create and initialize database
|
|
561
|
-
// For Postgres, we need the raw sql client for shutdown & health checks
|
|
562
94
|
let pgSql;
|
|
563
95
|
let pgDb;
|
|
564
96
|
let store;
|
|
565
97
|
let db;
|
|
566
|
-
// SQLite is always created for auxiliary features
|
|
567
|
-
// Even when PG is the primary event/embedding store
|
|
98
|
+
// SQLite is always created for auxiliary features
|
|
568
99
|
db = createDb({ databasePath: config.dbPath });
|
|
569
100
|
runMigrations(db);
|
|
570
101
|
if (config.storageBackend === 'postgres') {
|
|
571
102
|
const { createPostgresConnection, verifyPostgresConnection } = await import('./db/connection.postgres.js');
|
|
572
103
|
const conn = createPostgresConnection();
|
|
573
|
-
await verifyPostgresConnection(conn.sql);
|
|
104
|
+
await verifyPostgresConnection(conn.sql);
|
|
574
105
|
pgSql = conn.sql;
|
|
575
106
|
pgDb = conn.db;
|
|
576
107
|
const { runPostgresMigrations } = await import('./db/migrate.postgres.js');
|
|
577
108
|
await runPostgresMigrations(pgDb);
|
|
578
109
|
const { PostgresEventStore } = await import('./db/postgres-store.js');
|
|
579
110
|
store = new PostgresEventStore(pgDb);
|
|
580
|
-
// Warn about silent SQLite → PG switch for existing Docker Compose users
|
|
581
111
|
log.warn('STORAGE_BACKEND=postgres is now active. Previous SQLite data at ' +
|
|
582
112
|
`${config.dbPath} is not automatically migrated.`);
|
|
583
113
|
log.info('Database: PostgreSQL');
|
|
@@ -615,9 +145,9 @@ export async function startServer() {
|
|
|
615
145
|
}
|
|
616
146
|
}
|
|
617
147
|
// Create app with db reference for auth
|
|
618
|
-
// Create API key lookup for auth (uses SQLite for auxiliary features in both modes)
|
|
619
148
|
const { SqliteApiKeyLookup } = await import('./db/api-key-lookup.js');
|
|
620
149
|
const apiKeyLookup = new SqliteApiKeyLookup(db);
|
|
150
|
+
const { createApp } = await import('./app.js');
|
|
621
151
|
const app = await createApp(store, { ...config, db, apiKeyLookup, embeddingService, embeddingWorker, pgSql, pgDb });
|
|
622
152
|
// Start listening
|
|
623
153
|
log.info(`AgentLens server starting on port ${config.port}`);
|
|
@@ -638,13 +168,12 @@ export async function startServer() {
|
|
|
638
168
|
}
|
|
639
169
|
}
|
|
640
170
|
}
|
|
641
|
-
// Start alert evaluation engine
|
|
171
|
+
// Start alert evaluation engine
|
|
642
172
|
const notifRepoForEngine = db ? new NotificationChannelRepository(db) : null;
|
|
643
173
|
const notifRouterForEngine = notifRepoForEngine ? new NotificationRouter(notifRepoForEngine) : null;
|
|
644
174
|
const alertEngine = new AlertEngine(store, { notificationRouter: notifRouterForEngine ?? undefined });
|
|
645
175
|
alertEngine.start();
|
|
646
176
|
// Start guardrail evaluation engine (v0.8.0)
|
|
647
|
-
// Wire the agent store so pause_agent/downgrade_model actions can UPDATE the agents table (B1)
|
|
648
177
|
setAgentStore(store);
|
|
649
178
|
if (notifRouterForEngine)
|
|
650
179
|
setNotificationRouter(notifRouterForEngine);
|
|
@@ -657,7 +186,7 @@ export async function startServer() {
|
|
|
657
186
|
const anomalyDetector = new CostAnomalyDetector(store, budgetEngine.getStore());
|
|
658
187
|
anomalyDetector.start();
|
|
659
188
|
log.info('Cost budgets & anomaly detection: enabled');
|
|
660
|
-
// M-11 FIX: Graceful shutdown
|
|
189
|
+
// M-11 FIX: Graceful shutdown
|
|
661
190
|
let httpServer;
|
|
662
191
|
let shuttingDown = false;
|
|
663
192
|
const shutdown = async () => {
|
|
@@ -665,16 +194,13 @@ export async function startServer() {
|
|
|
665
194
|
return;
|
|
666
195
|
shuttingDown = true;
|
|
667
196
|
log.info('Shutting down...');
|
|
668
|
-
// 1. Stop accepting new requests
|
|
669
197
|
if (httpServer) {
|
|
670
198
|
httpServer.close(() => log.info('HTTP server closed'));
|
|
671
199
|
}
|
|
672
|
-
// 2. Stop engines and workers
|
|
673
200
|
alertEngine.stop();
|
|
674
201
|
guardrailEngine.stop();
|
|
675
202
|
if (embeddingWorker)
|
|
676
203
|
embeddingWorker.stop();
|
|
677
|
-
// 3. Drain PG pool (5s timeout)
|
|
678
204
|
if (pgSql) {
|
|
679
205
|
try {
|
|
680
206
|
log.info('Draining PostgreSQL connection pool...');
|