UNPKG

manifest

Version:

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

305 lines 15.1 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); } }; var ProviderService_1; Object.defineProperty(exports, "__esModule", { value: true }); exports.ProviderService = void 0; const common_1 = require("@nestjs/common"); const typeorm_1 = require("@nestjs/typeorm"); const typeorm_2 = require("typeorm"); const user_provider_entity_1 = require("../../entities/user-provider.entity"); const tier_assignment_entity_1 = require("../../entities/tier-assignment.entity"); const model_pricing_cache_service_1 = require("../../model-prices/model-pricing-cache.service"); const tier_auto_assign_service_1 = require("./tier-auto-assign.service"); const routing_cache_service_1 = require("./routing-cache.service"); const crypto_1 = require("crypto"); const crypto_util_1 = require("../../common/utils/crypto.util"); const subscription_support_1 = require("../../common/utils/subscription-support"); const manifest_shared_1 = require("manifest-shared"); const qwen_region_1 = require("../qwen-region"); let ProviderService = ProviderService_1 = class ProviderService { providerRepo; tierRepo; autoAssign; pricingCache; routingCache; logger = new common_1.Logger(ProviderService_1.name); constructor(providerRepo, tierRepo, autoAssign, pricingCache, routingCache) { this.providerRepo = providerRepo; this.tierRepo = tierRepo; this.autoAssign = autoAssign; this.pricingCache = pricingCache; this.routingCache = routingCache; } async recalculateTiers(agentId) { await this.autoAssign.recalculate(agentId); this.routingCache.invalidateAgent(agentId); } async getProviders(agentId) { const cached = this.routingCache.getProviders(agentId); if (cached) return cached; await this.cleanupUnsupportedSubscriptionProviders(agentId); const providers = (await this.providerRepo.find({ where: { agent_id: agentId } })).filter(subscription_support_1.isManifestUsableProvider); this.routingCache.setProviders(agentId, providers); return providers; } async upsertProvider(agentId, userId, provider, apiKey, authType, region) { const effectiveAuthType = authType ?? 'api_key'; const existing = await this.providerRepo.findOne({ where: { agent_id: agentId, provider, auth_type: effectiveAuthType }, }); const resolvedRegion = await this.resolveProviderRegion(provider, effectiveAuthType, region, apiKey, existing); const apiKeyEncrypted = apiKey ? (0, crypto_util_1.encrypt)(apiKey, (0, crypto_util_1.getEncryptionSecret)()) : null; const keyPrefix = apiKey ? apiKey.substring(0, 8) : null; if (existing) { if (apiKeyEncrypted !== null) { existing.api_key_encrypted = apiKeyEncrypted; existing.key_prefix = keyPrefix; } existing.region = resolvedRegion; existing.is_active = true; existing.updated_at = new Date().toISOString(); await this.providerRepo.save(existing); await this.afterProviderInsert(agentId); return { provider: existing, isNew: false }; } const record = Object.assign(new user_provider_entity_1.UserProvider(), { id: (0, crypto_1.randomUUID)(), user_id: userId, agent_id: agentId, provider, auth_type: effectiveAuthType, api_key_encrypted: apiKeyEncrypted, key_prefix: keyPrefix, region: resolvedRegion, is_active: true, connected_at: new Date().toISOString(), updated_at: new Date().toISOString(), }); await this.providerRepo.insert(record); await this.afterProviderInsert(agentId); return { provider: record, isNew: true }; } async resolveProviderRegion(provider, authType, requestedRegion, apiKey, existing) { const lower = provider.toLowerCase(); const isQwenProvider = lower === 'qwen' || lower === 'alibaba'; if (!isQwenProvider || authType !== 'api_key') return null; if (requestedRegion === undefined) { if (apiKey) { return this.detectQwenRegionOrThrow(apiKey); } return (0, qwen_region_1.isQwenResolvedRegion)(existing?.region) ? existing.region : null; } if (!(0, qwen_region_1.isQwenRegion)(requestedRegion)) { throw new common_1.BadRequestException('Qwen region must be one of: auto, singapore, us, beijing'); } if (requestedRegion !== 'auto') return requestedRegion; const keyToProbe = await this.getQwenDetectionKey(apiKey, existing); if (!keyToProbe) { return (0, qwen_region_1.isQwenResolvedRegion)(existing?.region) ? existing.region : null; } return this.detectQwenRegionOrThrow(keyToProbe); } async getQwenDetectionKey(apiKey, existing) { if (apiKey) return apiKey; if (!existing?.api_key_encrypted) return null; try { return (0, crypto_util_1.decrypt)(existing.api_key_encrypted, (0, crypto_util_1.getEncryptionSecret)()); } catch { this.logger.warn('Failed to decrypt API key while auto-detecting Alibaba region'); return null; } } async detectQwenRegionOrThrow(apiKey) { const detected = await (0, qwen_region_1.detectQwenRegion)(apiKey); if (detected) return detected; throw new common_1.BadRequestException('Could not auto-detect Alibaba region from this API key. Verify the key and try again.'); } async registerSubscriptionProvider(agentId, userId, provider) { if (!(0, subscription_support_1.isSupportedSubscriptionProvider)(provider)) { this.logger.debug(`Ignoring unsupported subscription provider registration for ${provider}`); return { isNew: false }; } const existing = await this.providerRepo.findOne({ where: { agent_id: agentId, provider, auth_type: 'subscription' }, }); if (existing) return { isNew: false }; const hasApiKey = await this.providerRepo.findOne({ where: { agent_id: agentId, provider, auth_type: 'api_key', is_active: true }, }); if (hasApiKey) return { isNew: false }; const record = Object.assign(new user_provider_entity_1.UserProvider(), { id: (0, crypto_1.randomUUID)(), user_id: userId, agent_id: agentId, provider, auth_type: 'subscription', api_key_encrypted: null, key_prefix: null, is_active: true, connected_at: new Date().toISOString(), updated_at: new Date().toISOString(), }); await this.providerRepo.insert(record); await this.afterProviderInsert(agentId); return { isNew: true }; } async afterProviderInsert(agentId) { await this.autoAssign.recalculate(agentId); this.routingCache.invalidateAgent(agentId); } async removeProvider(agentId, provider, authType) { const where = { agent_id: agentId, provider }; if (authType) where.auth_type = authType; const existing = await this.providerRepo.findOne({ where }); if (!existing) throw new common_1.NotFoundException('Provider not found'); existing.is_active = false; existing.updated_at = new Date().toISOString(); await this.providerRepo.save(existing); const otherActive = await this.providerRepo.find({ where: { agent_id: agentId, provider, is_active: true }, }); if (otherActive.some((record) => (0, subscription_support_1.isManifestUsableProvider)(record))) { this.routingCache.invalidateAgent(agentId); return { notifications: [] }; } const { invalidated } = await this.clearTierAssignmentsForProviders(agentId, [provider]); await this.autoAssign.recalculate(agentId); this.routingCache.invalidateAgent(agentId); const notifications = []; if (invalidated.length > 0) { const tierNames = invalidated.map((i) => i.tier); const updatedTiers = await this.tierRepo.find({ where: { agent_id: agentId, tier: (0, typeorm_2.In)(tierNames) }, }); const tierMap = new Map(updatedTiers.map((t) => [t.tier, t])); for (const { tier, modelName } of invalidated) { const updated = tierMap.get(tier); const newModel = updated?.auto_assigned_model ?? null; const tierLabel = manifest_shared_1.TIER_LABELS[tier] ?? tier; const suffix = newModel ? `${tierLabel} is back to automatic mode (${newModel}).` : `${tierLabel} is back to automatic mode.`; notifications.push(`${modelName} is no longer available. ${suffix}`); } } return { notifications }; } async deactivateAllProviders(agentId) { await this.providerRepo.update({ agent_id: agentId }, { is_active: false, updated_at: new Date().toISOString() }); await this.tierRepo.update({ agent_id: agentId }, { override_model: null, override_provider: null, override_auth_type: null, fallback_models: null, updated_at: new Date().toISOString(), }); await this.autoAssign.recalculate(agentId); this.routingCache.invalidateAgent(agentId); } async cleanupUnsupportedSubscriptionProviders(agentId) { const activeProviders = await this.providerRepo.find({ where: { agent_id: agentId, is_active: true }, }); const unsupported = activeProviders.filter((record) => record.auth_type === 'subscription' && !(0, subscription_support_1.isManifestUsableProvider)(record)); if (unsupported.length === 0) return; const now = new Date().toISOString(); for (const record of unsupported) { record.is_active = false; record.updated_at = now; } await this.providerRepo.save(unsupported); const unsupportedIds = new Set(unsupported.map((record) => record.id)); const remainingActive = activeProviders.filter((record) => !unsupportedIds.has(record.id)); const usableProviders = remainingActive.filter(subscription_support_1.isManifestUsableProvider); const removedProviders = Array.from(new Set(unsupported .map((record) => record.provider) .filter((provider) => !usableProviders.some((record) => record.provider.toLowerCase() === provider.toLowerCase())))); if (removedProviders.length > 0) { const { hadTierAssignments } = await this.clearTierAssignmentsForProviders(agentId, removedProviders); if (hadTierAssignments) { await this.autoAssign.recalculate(agentId); } } this.routingCache.invalidateAgent(agentId); } async clearTierAssignmentsForProviders(agentId, providers) { if (providers.length === 0) return { invalidated: [], hadTierAssignments: false }; const providerNames = new Set(providers.map((provider) => provider.toLowerCase())); const overrides = await this.tierRepo.find({ where: { agent_id: agentId, override_model: (0, typeorm_2.Not)((0, typeorm_2.IsNull)()) }, }); const invalidated = []; const tiersToSave = []; for (const tier of overrides) { const overrideProvider = tier.override_provider?.toLowerCase(); const pricingProvider = this.pricingCache .getByModel(tier.override_model) ?.provider.toLowerCase(); if ((overrideProvider && providerNames.has(overrideProvider)) || (pricingProvider && providerNames.has(pricingProvider))) { invalidated.push({ tier: tier.tier, modelName: tier.override_model }); tier.override_model = null; tier.override_provider = null; tier.override_auth_type = null; tier.updated_at = new Date().toISOString(); tiersToSave.push(tier); } } const allTiers = await this.tierRepo.find({ where: { agent_id: agentId } }); const hadTierAssignments = allTiers.length > 0; const savedIds = new Set(tiersToSave.map((tier) => tier.id)); for (const tier of allTiers) { if (!tier.fallback_models || tier.fallback_models.length === 0) continue; const filtered = tier.fallback_models.filter((model) => { const pricing = this.pricingCache.getByModel(model); return !pricing || !providerNames.has(pricing.provider.toLowerCase()); }); if (filtered.length !== tier.fallback_models.length) { tier.fallback_models = filtered.length > 0 ? filtered : null; tier.updated_at = new Date().toISOString(); if (!savedIds.has(tier.id)) tiersToSave.push(tier); } } if (tiersToSave.length > 0) await this.tierRepo.save(tiersToSave); return { invalidated, hadTierAssignments }; } }; exports.ProviderService = ProviderService; exports.ProviderService = ProviderService = ProviderService_1 = __decorate([ (0, common_1.Injectable)(), __param(0, (0, typeorm_1.InjectRepository)(user_provider_entity_1.UserProvider)), __param(1, (0, typeorm_1.InjectRepository)(tier_assignment_entity_1.TierAssignment)), __metadata("design:paramtypes", [typeorm_2.Repository, typeorm_2.Repository, tier_auto_assign_service_1.TierAutoAssignService, model_pricing_cache_service_1.ModelPricingCacheService, routing_cache_service_1.RoutingCacheService]) ], ProviderService); //# sourceMappingURL=provider.service.js.map