feat: add agent API and operational analytics

This commit is contained in:
jjbrunton committed 2026-08-08 22:08:14 +01:00
1 parent 720ea0652a
commit e8b730b592
65 files changed
+2650 -1214

No files matched your search

+52
View File
@@ -0,0 +1,52 @@
import fs from 'node:fs';
import path from 'node:path';
import { describe, expect, it } from 'vitest';
import { API_OPERATIONS, capabilities, createOpenApiDocument } from '../src/web/api-catalog.js';
describe('agent API discovery', () => {
it('publishes every operation under a unique method and path', () => {
const keys = API_OPERATIONS.map((operation) => `${operation.method} ${operation.path}`);
expect(new Set(keys).size).toBe(keys.length);
expect(keys).toContain('GET /api/v1/overview');
expect(keys).toContain('POST /api/v1/control/start');
expect(keys).toContain('POST /api/v1/control/stop');
expect(keys).toContain('POST /api/v1/jobs');
expect(keys).toContain('GET /api/events/stream');
});
it('cannot drift from the Express route modules', () => {
const routesDirectory = path.resolve(process.cwd(), 'src/web/routes');
const implemented = fs.readdirSync(routesDirectory)
.filter((file) => file.endsWith('.ts'))
.flatMap((file) => {
const source = fs.readFileSync(path.join(routesDirectory, file), 'utf8');
return [...source.matchAll(/router\.(get|post|put|delete|patch)\('([^']+)'/g)].map((match) => {
const routePath = match[2] === '/healthz' ? match[2] : `/api${match[2]}`;
return `${match[1].toUpperCase()} ${routePath.replace(/:([A-Za-z0-9_]+)/g, '{$1}')}`;
});
});
const catalogued = API_OPERATIONS.map((item) => `${item.method} ${item.path}`);
expect([...new Set(catalogued)].sort()).toEqual([...new Set(implemented)].sort());
});
it('generates OpenAPI with safety metadata and schemas for versioned operations', () => {
const document = createOpenApiDocument() as { openapi: string; paths: Record<string, Record<string, Record<string, unknown>>> };
expect(document.openapi).toBe('3.1.0');
expect(document.paths['/healthz'].get.security).toEqual([]);
expect(document.paths['/api/v1/overview'].get['x-access']).toBe('read');
expect(document.paths['/api/v1/control/start'].post['x-access']).toBe('control');
expect(document.paths['/api/v1/control/start'].post['x-mutates']).toBe(true);
expect(document.paths['/api/v1/jobs'].post.requestBody).toBeDefined();
expect((document.paths['/api/v1/jobs'].post.responses as Record<string, unknown>)['202']).toBeDefined();
});
it('returns a compact capability catalogue for agents', () => {
const result = capabilities();
expect(result.authentication.agentScopes).toEqual(['read', 'control', 'admin']);
expect(result.operations.find((item) => item.operationId === 'sendReply')).toMatchObject({
access: 'admin',
mutates: true,
});
});
});