feat(gateway): add system.sessionAnalytics usage snapshot RPC
This commit is contained in:
@@ -327,6 +327,65 @@ describe('system.tokenUsage handler', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('system.sessionAnalytics handler', () => {
|
||||
it('returns empty analytics when callback is not provided', async () => {
|
||||
const handlers = createSystemHandlers({
|
||||
startTime: Date.now(),
|
||||
version: '0.1.0',
|
||||
getSessionCount: () => 0,
|
||||
getToolCount: () => 0,
|
||||
getConnectionCount: () => 0,
|
||||
});
|
||||
|
||||
const req: GatewayRequest = { id: 3, method: 'system.sessionAnalytics' };
|
||||
const result = await handlers['system.sessionAnalytics'](req) as GatewayResponse;
|
||||
|
||||
expect(result.id).toBe(3);
|
||||
const r = result.result as {
|
||||
daily: unknown[];
|
||||
topSessions: unknown[];
|
||||
averageMessagesPerSession: number;
|
||||
totalSessions: number;
|
||||
totalMessages: number;
|
||||
};
|
||||
expect(r.daily).toEqual([]);
|
||||
expect(r.topSessions).toEqual([]);
|
||||
expect(r.averageMessagesPerSession).toBe(0);
|
||||
expect(r.totalSessions).toBe(0);
|
||||
expect(r.totalMessages).toBe(0);
|
||||
});
|
||||
|
||||
it('returns analytics from callback', async () => {
|
||||
const getSessionAnalytics = vi.fn(() => ({
|
||||
daily: [{ day: '2026-02-16', sessions: 2, messages: 8 }],
|
||||
topSessions: [{ sessionId: 'telegram:1', messages: 5, lastActivity: 1708080000 }],
|
||||
averageMessagesPerSession: 4,
|
||||
totalSessions: 2,
|
||||
totalMessages: 8,
|
||||
}));
|
||||
|
||||
const handlers = createSystemHandlers({
|
||||
startTime: Date.now(),
|
||||
version: '0.1.0',
|
||||
getSessionCount: () => 2,
|
||||
getToolCount: () => 0,
|
||||
getConnectionCount: () => 1,
|
||||
getSessionAnalytics,
|
||||
});
|
||||
|
||||
const req: GatewayRequest = {
|
||||
id: 4,
|
||||
method: 'system.sessionAnalytics',
|
||||
params: { days: 7, topLimit: 5 },
|
||||
};
|
||||
const result = await handlers['system.sessionAnalytics'](req) as GatewayResponse;
|
||||
|
||||
expect(getSessionAnalytics).toHaveBeenCalledWith({ days: 7, topLimit: 5 });
|
||||
expect(getPath(result.result, 'totalSessions')).toBe(2);
|
||||
expect(getPath(result.result, 'daily')).toEqual([{ day: '2026-02-16', sessions: 2, messages: 8 }]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('session handlers', () => {
|
||||
const mockHistory = [
|
||||
{ role: 'user' as const, content: 'hello' },
|
||||
|
||||
@@ -3,6 +3,7 @@ import { makeResponse, makeError, ErrorCode } from '../protocol.js';
|
||||
import type { MetricsSnapshot, EventEntry, ActiveRequestInfo } from '../metrics.js';
|
||||
import type { ServiceInfo } from './services.js';
|
||||
import type { NodeLocation, NodeStatus, NodePushToken } from './node.js';
|
||||
import type { SessionAnalyticsSnapshot } from '../../session/index.js';
|
||||
|
||||
/** Per-session token usage report returned by system.tokenUsage. */
|
||||
export interface TokenUsageEntry {
|
||||
@@ -64,6 +65,8 @@ export interface SystemHandlerDeps {
|
||||
getTokenUsage?: () => TokenUsageEntry[];
|
||||
/** Optional callback to retrieve aggregated metrics snapshot. */
|
||||
getMetrics?: () => MetricsSnapshot;
|
||||
/** Optional callback to retrieve session analytics. */
|
||||
getSessionAnalytics?: (opts?: { days?: number; topLimit?: number }) => SessionAnalyticsSnapshot;
|
||||
/** Optional callback to retrieve recent events. */
|
||||
getEvents?: (opts?: { level?: string; limit?: number }) => EventEntry[];
|
||||
/** Optional callback to retrieve active requests. */
|
||||
@@ -206,6 +209,24 @@ export function createSystemHandlers(deps: SystemHandlerDeps) {
|
||||
return makeResponse(request.id, deps.getMetrics());
|
||||
},
|
||||
|
||||
'system.sessionAnalytics': async (request: GatewayRequest): Promise<OutboundMessage> => {
|
||||
if (!deps.getSessionAnalytics) {
|
||||
return makeResponse(request.id, {
|
||||
daily: [],
|
||||
topSessions: [],
|
||||
averageMessagesPerSession: 0,
|
||||
totalSessions: 0,
|
||||
totalMessages: 0,
|
||||
});
|
||||
}
|
||||
const params = request.params as { days?: number; topLimit?: number } | undefined;
|
||||
const snapshot = deps.getSessionAnalytics({
|
||||
days: params?.days,
|
||||
topLimit: params?.topLimit,
|
||||
});
|
||||
return makeResponse(request.id, snapshot);
|
||||
},
|
||||
|
||||
'system.events': async (request: GatewayRequest): Promise<OutboundMessage> => {
|
||||
if (!deps.getEvents) {
|
||||
return makeResponse(request.id, { events: [] });
|
||||
|
||||
@@ -190,6 +190,7 @@ export class GatewayServer {
|
||||
getSessionCount: () => this.sessionBridge.listSessions().length,
|
||||
getToolCount: () => this.config.toolRegistry.list().length,
|
||||
getConnectionCount: () => this.sessionBridge.connectionCount,
|
||||
getSessionAnalytics: ({ days, topLimit } = {}) => this.config.sessionManager.getSessionAnalytics({ days, topLimit }),
|
||||
restart: this.config.restart,
|
||||
getChannels: channelRegistry
|
||||
? () => channelRegistry.list().map(a => ({ name: a.name, status: a.status }))
|
||||
|
||||
Reference in New Issue
Block a user