UNPKG

manifest

Version:

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

197 lines 9.7 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.MessagesQueryService = 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 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"); const provider_inference_1 = require("../../common/utils/provider-inference"); const ttl_cache_1 = require("../../common/utils/ttl-cache"); const MODELS_CACHE_TTL_MS = 60_000; const COUNT_CACHE_TTL_MS = 30_000; const MAX_CACHE_ENTRIES = 5_000; let MessagesQueryService = class MessagesQueryService { turnRepo; dataSource; tenantCache; dialect; modelsCache = new ttl_cache_1.TtlCache({ maxSize: MAX_CACHE_ENTRIES, ttlMs: MODELS_CACHE_TTL_MS, }); countCache = new ttl_cache_1.TtlCache({ maxSize: MAX_CACHE_ENTRIES, ttlMs: COUNT_CACHE_TTL_MS, }); constructor(turnRepo, dataSource, tenantCache) { this.turnRepo = turnRepo; this.dataSource = dataSource; this.tenantCache = tenantCache; this.dialect = (0, sql_dialect_1.detectDialect)(this.dataSource.options.type); } async getMessages(params) { const tenantId = (await this.tenantCache.resolve(params.userId)) ?? undefined; const cutoff = params.range ? (0, sql_dialect_1.computeCutoff)((0, range_util_1.rangeToInterval)(params.range)) : undefined; const baseQb = this.turnRepo.createQueryBuilder('at'); if (cutoff) { baseQb.where('at.timestamp >= :cutoff', { cutoff }); } (0, query_helpers_1.addTenantFilter)(baseQb, params.userId, undefined, tenantId); if (params.service_type) baseQb.andWhere('at.service_type = :serviceType', { serviceType: params.service_type }); if (params.cost_min !== undefined) baseQb.andWhere('at.cost_usd >= :costMin', { costMin: params.cost_min }); if (params.cost_max !== undefined) baseQb.andWhere('at.cost_usd <= :costMax', { costMax: params.cost_max }); if (params.agent_name) baseQb.andWhere('at.agent_name = :filterAgent', { filterAgent: params.agent_name }); if (params.provider) { const allModels = await this.getDistinctModels(params.userId, params.range, tenantId, params.agent_name); const matching = allModels.filter((m) => (0, provider_inference_1.inferProviderFromModel)(m) === params.provider); if (matching.length === 0) { const providers = this.deriveProviders(allModels); return { items: [], next_cursor: null, total_count: 0, providers }; } baseQb.andWhere('at.model IN (:...providerModels)', { providerModels: matching }); } const countCacheKey = this.buildCountCacheKey(params); const countQb = baseQb.clone().select('COUNT(*)', 'total'); const costExpr = (0, sql_dialect_1.sqlCastFloat)((0, sql_dialect_1.sqlSanitizeCost)('at.cost_usd'), this.dialect); const dataQb = baseQb .clone() .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.description', 'description') .addSelect('at.service_type', 'service_type') .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.cache_read_tokens', 'cache_read_tokens') .addSelect('at.cache_creation_tokens', 'cache_creation_tokens') .addSelect('at.duration_ms', 'duration_ms') .addSelect('at.error_message', 'error_message') .addSelect('at.error_http_status', 'error_http_status') .addSelect('at.auth_type', 'auth_type') .addSelect('at.fallback_from_model', 'fallback_from_model') .addSelect('at.fallback_index', 'fallback_index'); if (params.cursor) { const sepIdx = params.cursor.indexOf('|'); if (sepIdx !== -1) { const cursorTs = params.cursor.substring(0, sepIdx); const cursorId = params.cursor.substring(sepIdx + 1); dataQb.andWhere(new typeorm_2.Brackets((sub) => { sub.where('at.timestamp < :cursorTs', { cursorTs }).orWhere(new typeorm_2.Brackets((inner) => { inner .where('at.timestamp = :cursorTs2', { cursorTs2: cursorTs }) .andWhere('at.id < :cursorId', { cursorId }); })); })); } } const cachedCount = params.cursor ? this.countCache.get(countCacheKey) : undefined; const countHit = cachedCount !== undefined; const [countResult, rows, allModels] = await Promise.all([ countHit ? null : countQb.getRawOne(), dataQb .orderBy('at.timestamp', 'DESC') .addOrderBy('at.id', 'DESC') .limit(params.limit + 1) .getRawMany(), this.getDistinctModels(params.userId, params.range, tenantId, params.agent_name), ]); let totalCount; if (countHit) { totalCount = cachedCount; } else { totalCount = Number(countResult?.total ?? 0); this.countCache.set(countCacheKey, totalCount); } const hasMore = rows.length > params.limit; const items = rows.slice(0, params.limit); const lastItem = items[items.length - 1]; const ts = lastItem?.['timestamp']; const tsStr = ts instanceof Date ? (0, query_helpers_1.formatTimestamp)(ts) : String(ts ?? ''); const lastId = lastItem?.['id']; const nextCursor = hasMore && lastItem ? `${tsStr}|${String(lastId)}` : null; const providers = this.deriveProviders(allModels); return { items, next_cursor: nextCursor, total_count: totalCount, providers, }; } deriveProviders(models) { const seen = new Set(); for (const m of models) { const p = (0, provider_inference_1.inferProviderFromModel)(m); if (p) seen.add(p); } return [...seen].sort(); } async getDistinctModels(userId, range, tenantId, agentName) { const cacheKey = `${userId}:${agentName ?? ''}:${range ?? 'all'}`; const cached = this.modelsCache.get(cacheKey); if (cached) return cached; const cutoff = range ? (0, sql_dialect_1.computeCutoff)((0, range_util_1.rangeToInterval)(range)) : undefined; const modelsQb = this.turnRepo .createQueryBuilder('at') .select('DISTINCT at.model', 'model') .where('at.model IS NOT NULL') .andWhere("at.model != ''"); if (cutoff) { modelsQb.andWhere('at.timestamp >= :cutoff', { cutoff }); } (0, query_helpers_1.addTenantFilter)(modelsQb, userId, agentName, tenantId); const modelsResult = await modelsQb.orderBy('at.model', 'ASC').getRawMany(); const models = modelsResult.map((r) => String(r['model'])); this.modelsCache.set(cacheKey, models); return models; } buildCountCacheKey(params) { return [ params.userId, params.range ?? '', params.provider ?? '', params.service_type ?? '', params.agent_name ?? '', params.cost_min ?? '', params.cost_max ?? '', ].join(':'); } }; exports.MessagesQueryService = MessagesQueryService; exports.MessagesQueryService = MessagesQueryService = __decorate([ (0, common_1.Injectable)(), __param(0, (0, typeorm_1.InjectRepository)(agent_message_entity_1.AgentMessage)), __metadata("design:paramtypes", [typeorm_2.Repository, typeorm_2.DataSource, tenant_cache_service_1.TenantCacheService]) ], MessagesQueryService); //# sourceMappingURL=messages-query.service.js.map