UNPKG

manifest

Version:

Self-hosted Manifest LLM router with embedded server, SQLite database, and dashboard

237 lines 12.5 kB
"use strict"; var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; return c > 3 && r && Object.defineProperty(target, key, r), r; }; var __metadata = (this && this.__metadata) || function (k, v) { if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); }; var __param = (this && this.__param) || function (paramIndex, decorator) { return function (target, key) { decorator(target, key, paramIndex); } }; Object.defineProperty(exports, "__esModule", { value: true }); exports.TimeseriesQueriesService = void 0; const common_1 = require("@nestjs/common"); const typeorm_1 = require("@nestjs/typeorm"); const typeorm_2 = require("typeorm"); const agent_message_entity_1 = require("../../entities/agent-message.entity"); const agent_entity_1 = require("../../entities/agent.entity"); const range_util_1 = require("../../common/utils/range.util"); const query_helpers_1 = require("./query-helpers"); const tenant_cache_service_1 = require("../../common/services/tenant-cache.service"); const sql_dialect_1 = require("../../common/utils/sql-dialect"); let TimeseriesQueriesService = class TimeseriesQueriesService { turnRepo; agentRepo; dataSource; tenantCache; dialect; constructor(turnRepo, agentRepo, dataSource, tenantCache) { this.turnRepo = turnRepo; this.agentRepo = agentRepo; this.dataSource = dataSource; this.tenantCache = tenantCache; this.dialect = (0, sql_dialect_1.detectDialect)(this.dataSource.options.type); } async getTimeseries(range, userId, hourly, tenantId, agentName) { const interval = (0, range_util_1.rangeToInterval)(range); const cutoff = (0, sql_dialect_1.computeCutoff)(interval); const bucketExpr = hourly ? (0, sql_dialect_1.sqlHourBucket)('at.timestamp', this.dialect) : (0, sql_dialect_1.sqlDateBucket)('at.timestamp', this.dialect); const bucketAlias = hourly ? 'hour' : 'date'; const qb = this.turnRepo .createQueryBuilder('at') .select(bucketExpr, bucketAlias) .addSelect('COALESCE(SUM(at.input_tokens), 0)', 'input_tokens') .addSelect('COALESCE(SUM(at.output_tokens), 0)', 'output_tokens') .addSelect(`COALESCE(SUM(${(0, sql_dialect_1.sqlSanitizeCost)('at.cost_usd')}), 0)`, 'cost') .addSelect('COUNT(*)', 'count') .where('at.timestamp >= :cutoff', { cutoff }); (0, query_helpers_1.addTenantFilter)(qb, userId, agentName, tenantId); const rows = await qb.groupBy(bucketAlias).orderBy(bucketAlias, 'ASC').getRawMany(); const tokenUsage = []; const costUsage = []; const messageUsage = []; for (const r of rows) { const parsed = this.parseBucketRow(r, bucketAlias); const bucketKey = { hour: parsed.hour, date: parsed.date }; tokenUsage.push({ ...bucketKey, input_tokens: parsed.input_tokens, output_tokens: parsed.output_tokens, }); costUsage.push({ ...bucketKey, cost: parsed.cost }); messageUsage.push({ ...bucketKey, count: parsed.count }); } return { tokenUsage, costUsage, messageUsage }; } async getActiveSkills(range, userId, agentName, tenantId) { const resolved = tenantId ?? (await this.tenantCache.resolve(userId)) ?? undefined; const interval = (0, range_util_1.rangeToInterval)(range); const cutoff = (0, sql_dialect_1.computeCutoff)(interval); const qb = this.turnRepo .createQueryBuilder('at') .select('at.skill_name', 'name') .addSelect('MIN(at.agent_name)', 'agent_name') .addSelect('COUNT(*)', 'run_count') .addSelect('MAX(at.timestamp)', 'last_active_at') .where('at.timestamp >= :cutoff', { cutoff }) .andWhere('at.skill_name IS NOT NULL'); (0, query_helpers_1.addTenantFilter)(qb, userId, agentName, resolved); const rows = await qb.groupBy('at.skill_name').orderBy('run_count', 'DESC').getRawMany(); return rows.map((r) => ({ name: String(r['name']), agent_name: r['agent_name'] ? String(r['agent_name']) : null, run_count: Number(r['run_count'] ?? 0), last_active_at: String(r['last_active_at']), status: 'active', })); } async getRecentActivity(range, userId, limit = 5, agentName, tenantId) { const resolved = tenantId ?? (await this.tenantCache.resolve(userId)) ?? undefined; const interval = (0, range_util_1.rangeToInterval)(range); const cutoff = (0, sql_dialect_1.computeCutoff)(interval); const costExpr = (0, sql_dialect_1.sqlCastFloat)((0, sql_dialect_1.sqlSanitizeCost)('at.cost_usd'), this.dialect); const qb = this.turnRepo .createQueryBuilder('at') .select('at.id', 'id') .addSelect('at.timestamp', 'timestamp') .addSelect('at.agent_name', 'agent_name') .addSelect('at.model', 'model') .addSelect('at.model', 'display_name') .addSelect('at.input_tokens', 'input_tokens') .addSelect('at.output_tokens', 'output_tokens') .addSelect('at.status', 'status') .addSelect('at.input_tokens + at.output_tokens', 'total_tokens') .addSelect(costExpr, 'cost') .addSelect('at.routing_tier', 'routing_tier') .addSelect('at.routing_reason', 'routing_reason') .addSelect('at.error_message', 'error_message') .addSelect('at.auth_type', 'auth_type') .addSelect('at.fallback_from_model', 'fallback_from_model') .addSelect('at.fallback_index', 'fallback_index') .where('at.timestamp >= :cutoff', { cutoff }); (0, query_helpers_1.addTenantFilter)(qb, userId, agentName, resolved); return qb.orderBy('at.timestamp', 'DESC').limit(limit).getRawMany(); } async getCostByModel(range, userId, agentName, tenantId) { const resolved = tenantId ?? (await this.tenantCache.resolve(userId)) ?? undefined; const interval = (0, range_util_1.rangeToInterval)(range); const cutoff = (0, sql_dialect_1.computeCutoff)(interval); const qb = this.turnRepo .createQueryBuilder('at') .select("COALESCE(at.model, 'unknown')", 'model') .addSelect('at.model', 'display_name') .addSelect('SUM(at.input_tokens + at.output_tokens)', 'tokens') .addSelect(`COALESCE(SUM(${(0, sql_dialect_1.sqlSanitizeCost)('at.cost_usd')}), 0)`, 'estimated_cost') .addSelect('at.auth_type', 'auth_type') .where('at.timestamp >= :cutoff', { cutoff }) .andWhere('at.model IS NOT NULL') .andWhere("at.model != ''"); (0, query_helpers_1.addTenantFilter)(qb, userId, agentName, resolved); const rows = await qb .groupBy('at.model') .addGroupBy('at.auth_type') .orderBy('tokens', 'DESC') .getRawMany(); const totalTokens = rows.reduce((sum, r) => sum + Number(r['tokens'] ?? 0), 0); return rows.map((r) => ({ model: String(r['model']), display_name: r['display_name'] ? String(r['display_name']) : String(r['model']), tokens: Number(r['tokens'] ?? 0), share_pct: totalTokens === 0 ? 0 : Math.round((Number(r['tokens'] ?? 0) / totalTokens) * 1000) / 10, estimated_cost: Number(r['estimated_cost'] ?? 0), auth_type: r['auth_type'] ? String(r['auth_type']) : null, })); } async getAgentList(userId, tenantId) { const resolved = tenantId ?? (await this.tenantCache.resolve(userId)) ?? undefined; const agentQb = this.agentRepo.createQueryBuilder('a'); if (resolved) { agentQb.where('a.tenant_id = :tenantId', { tenantId: resolved }); } else { agentQb.leftJoin('a.tenant', 't').where('t.name = :userId', { userId }); } const statsCutoff = (0, sql_dialect_1.computeCutoff)('30 days'); const costExpr = (0, sql_dialect_1.sqlCastFloat)((0, sql_dialect_1.sqlSanitizeCost)('at.cost_usd'), this.dialect); const statsQb = this.turnRepo .createQueryBuilder('at') .select('at.agent_name', 'agent_name') .addSelect('COUNT(*)', 'message_count') .addSelect('MAX(at.timestamp)', 'last_active') .addSelect(`COALESCE(SUM(${costExpr}), 0)`, 'total_cost') .addSelect('COALESCE(SUM(at.input_tokens + at.output_tokens), 0)', 'total_tokens') .where('at.agent_name IS NOT NULL') .andWhere('at.timestamp >= :statsCutoff', { statsCutoff }); (0, query_helpers_1.addTenantFilter)(statsQb, userId, undefined, resolved); const sparkCutoff = (0, sql_dialect_1.computeCutoff)('7 days'); const dateExpr = (0, sql_dialect_1.sqlDateBucket)('at.timestamp', this.dialect); const sparkQb = this.turnRepo .createQueryBuilder('at') .select('at.agent_name', 'agent_name') .addSelect(dateExpr, 'date') .addSelect('COALESCE(SUM(at.input_tokens + at.output_tokens), 0)', 'tokens') .where('at.timestamp >= :sparkCutoff', { sparkCutoff }) .andWhere('at.agent_name IS NOT NULL'); (0, query_helpers_1.addTenantFilter)(sparkQb, userId, undefined, resolved); const [agents, statsRows, sparkRows] = await Promise.all([ agentQb.andWhere('a.is_active = true').orderBy('a.created_at', 'DESC').getMany(), statsQb.groupBy('at.agent_name').orderBy('last_active', 'DESC').getRawMany(), sparkQb .groupBy('at.agent_name') .addGroupBy('date') .orderBy('at.agent_name', 'ASC') .addOrderBy('date', 'ASC') .getRawMany(), ]); const statsMap = new Map(); for (const r of statsRows) { statsMap.set(String(r['agent_name']), r); } const sparkMap = new Map(); for (const r of sparkRows) { const name = String(r['agent_name']); if (!sparkMap.has(name)) sparkMap.set(name, []); sparkMap.get(name).push(Number(r['tokens'] ?? 0)); } return agents.map((a) => { const name = a.name; const stats = statsMap.get(name); return { agent_name: name, display_name: a.display_name ?? name, message_count: Number(stats?.['message_count'] ?? 0), last_active: String(stats?.['last_active'] ?? a.created_at ?? ''), total_cost: Number(stats?.['total_cost'] ?? 0), total_tokens: Number(stats?.['total_tokens'] ?? 0), sparkline: sparkMap.get(name) ?? [], }; }); } parseBucketRow(r, bucketAlias) { const row = { input_tokens: Number(r['input_tokens'] ?? 0), output_tokens: Number(r['output_tokens'] ?? 0), cost: Number(r['cost'] ?? 0), count: Number(r['count'] ?? 0), }; row[bucketAlias] = String(r[bucketAlias]); return row; } }; exports.TimeseriesQueriesService = TimeseriesQueriesService; exports.TimeseriesQueriesService = TimeseriesQueriesService = __decorate([ (0, common_1.Injectable)(), __param(0, (0, typeorm_1.InjectRepository)(agent_message_entity_1.AgentMessage)), __param(1, (0, typeorm_1.InjectRepository)(agent_entity_1.Agent)), __metadata("design:paramtypes", [typeorm_2.Repository, typeorm_2.Repository, typeorm_2.DataSource, tenant_cache_service_1.TenantCacheService]) ], TimeseriesQueriesService); //# sourceMappingURL=timeseries-queries.service.js.map