manifest
Version:
Self-hosted Manifest LLM router with embedded server, SQLite database, and dashboard
237 lines • 12.5 kB
JavaScript
;
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