manifest
Version:
Self-hosted Manifest LLM router with embedded server, SQLite database, and dashboard
197 lines • 9.7 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.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