bytelyst-devops-tools/dashboard/backend/src/modules/hermes-telemetry/routes.ts
2026-06-06 03:34:49 +00:00

60 lines
2.6 KiB
TypeScript

import type { FastifyInstance } from 'fastify';
import { z } from 'zod';
import { getHermesTelemetrySnapshot, getHermesTokenUsageSnapshot } from './repository.js';
import { HermesInstanceIdSchema, HermesTelemetrySnapshotSchema, HermesTokenUsageSnapshotSchema } from './types.js';
import { requireAdmin } from '../../lib/auth.js';
const ParamsSchema = z.object({ instance: HermesInstanceIdSchema });
export async function hermesTelemetryRoutes(fastify: FastifyInstance) {
// GET /api/hermes/telemetry/:instance
// Admin-only: this endpoint shells out to `hermes` CLI in the instance
// owner's environment (`runuser -u uma --` for Bheem) and reads the
// watchdog log + backup repo. Treat it as privileged the same way the
// VM/system endpoints are. See `dashboard/DEPLOYMENT.md` Privilege Surface.
fastify.get('/hermes/telemetry/:instance', {
preHandler: async (req) => requireAdmin(req),
}, async (req, reply) => {
let params: z.infer<typeof ParamsSchema>;
try {
params = ParamsSchema.parse(req.params);
} catch (err) {
return reply.code(400).send({ error: 'Invalid instance', detail: (err as Error).message });
}
try {
const snapshot = await getHermesTelemetrySnapshot(params.instance);
// Validate our own response so a shape regression surfaces here as a
// 500 rather than a corrupt UI state — same approach as hermes-ops.
const validated = HermesTelemetrySnapshotSchema.parse(snapshot);
return reply.send(validated);
} catch (err) {
fastify.log.error(err, 'failed to build hermes telemetry snapshot');
return reply.code(500).send({ error: 'Failed to build hermes telemetry snapshot' });
}
});
// GET /api/hermes/telemetry/:instance/token-usage
// Admin-only: aggregate/session-level token analytics from Hermes state.db.
// The repository deliberately avoids selecting raw message content.
fastify.get('/hermes/telemetry/:instance/token-usage', {
preHandler: async (req) => requireAdmin(req),
}, async (req, reply) => {
let params: z.infer<typeof ParamsSchema>;
try {
params = ParamsSchema.parse(req.params);
} catch (err) {
return reply.code(400).send({ error: 'Invalid instance', detail: (err as Error).message });
}
try {
const snapshot = await getHermesTokenUsageSnapshot(params.instance);
const validated = HermesTokenUsageSnapshotSchema.parse(snapshot);
return reply.send(validated);
} catch (err) {
fastify.log.error(err, 'failed to build hermes token usage snapshot');
return reply.code(500).send({ error: 'Failed to build hermes token usage snapshot' });
}
});
}