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