manifest
Version:
Self-hosted Manifest LLM router with embedded server, SQLite database, and dashboard
376 lines • 14.9 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 ProviderModelFetcherService_1;
Object.defineProperty(exports, "__esModule", { value: true });
exports.ProviderModelFetcherService = exports.PROVIDER_CONFIGS = exports.PROVIDER_BLOCKLIST = exports.PROVIDER_NON_CHAT = exports.UNIVERSAL_NON_CHAT_RE = void 0;
exports.filterNonChatModels = filterNonChatModels;
const common_1 = require("@nestjs/common");
const ollama_1 = require("../common/constants/ollama");
const provider_base_url_1 = require("../routing/provider-base-url");
const qwen_region_1 = require("../routing/qwen-region");
const FETCH_TIMEOUT_MS = 5000;
const DEFAULT_CONTEXT_WINDOW = 128000;
const ANTHROPIC_DEFAULT_CONTEXT = 200000;
const GEMINI_DEFAULT_CONTEXT = 1000000;
const MINIMAX_SUBSCRIPTION_MODELS_URL = 'https://api.minimax.io/anthropic/v1/models?limit=100';
function createModelParser(config) {
return (body, provider) => {
const arr = body?.[config.arrayKey];
if (!Array.isArray(arr))
return [];
return arr
.filter((m) => config.filter(m))
.map((m) => {
const entry = m;
const id = config.getId(entry);
const ctxVal = config.contextWindow ?? DEFAULT_CONTEXT_WINDOW;
return {
id,
displayName: config.getDisplayName(entry, id),
provider,
contextWindow: typeof ctxVal === 'function' ? ctxVal(entry) : ctxVal,
inputPricePerToken: config.inputPricePerToken ?? null,
outputPricePerToken: config.outputPricePerToken ?? null,
capabilityReasoning: false,
capabilityCode: config.capabilityCode ?? false,
qualityScore: config.qualityScore ?? 3,
};
});
};
}
const parseOpenAI = createModelParser({
arrayKey: 'data',
filter: (entry) => typeof entry.id === 'string' && entry.id.length > 0,
getId: (entry) => entry.id,
getDisplayName: (_entry, id) => id,
});
const OPENAI_DATE_SUFFIX_RE = /-\d{4}-\d{2}-\d{2}$/;
const OPENAI_RESPONSES_ONLY_RE = /(?:-codex(?!-mini-latest)|^gpt-5[^/]*-pro(?:-|$)|^gpt-image-|^o1-pro|^o4-mini-deep-research)/i;
function parseOpenAIDeduped(body, provider) {
const filtered = parseOpenAI(body, provider).filter((m) => !OPENAI_RESPONSES_ONLY_RE.test(m.id));
const ids = new Set(filtered.map((m) => m.id));
return filtered.filter((m) => {
if (!OPENAI_DATE_SUFFIX_RE.test(m.id))
return true;
const alias = m.id.replace(OPENAI_DATE_SUFFIX_RE, '');
return !ids.has(alias);
});
}
exports.UNIVERSAL_NON_CHAT_RE = /(?:embed|tts|whisper|dall-e|imagen|cogview|wanx|sambert|paraformer|text-embedding|speech-to|voice-|audio-turbo)/i;
exports.PROVIDER_NON_CHAT = {
openai: /(?:moderation|davinci|babbage|^text-|realtime|-transcribe|^sora|^gpt-3\.5-turbo-instruct|audio|^chatgpt-image|search-api)/i,
'openai-subscription': /(?:moderation|davinci|babbage|^text-|realtime|-transcribe|^sora|audio|^chatgpt-image)/i,
gemini: /(?:^aqs-|nano-banana|^deep-research|computer-use|^lyria|^gemini-2\.0-flash-lite$|flash-lite-preview|robotics)/i,
mistral: /(?:^mistral-ocr|moderation|voxtral-.*-(?:transcribe|realtime)|^labs-|^mistral-vibe-cli)/i,
xai: /(?:imagine|multi-agent)/i,
};
exports.PROVIDER_BLOCKLIST = {
mistral: new Set([
'voxtral-mini-2602',
]),
zai: new Set([
'glm-5.1',
]),
};
function filterNonChatModels(models, configKey) {
const providerFilter = exports.PROVIDER_NON_CHAT[configKey];
const blocklist = exports.PROVIDER_BLOCKLIST[configKey];
return models.filter((m) => {
if (exports.UNIVERSAL_NON_CHAT_RE.test(m.id))
return false;
if (providerFilter && providerFilter.test(m.id))
return false;
if (blocklist && blocklist.has(m.id))
return false;
return true;
});
}
function bearerHeaders(key) {
return { Authorization: `Bearer ${key}` };
}
const parseMistral = createModelParser({
arrayKey: 'data',
filter: (entry) => {
if (typeof entry.id !== 'string' || entry.id.length === 0)
return false;
if (entry.deprecation != null)
return false;
if (entry.capabilities && entry.capabilities.completion_chat === false)
return false;
return true;
},
getId: (entry) => entry.id,
getDisplayName: (_entry, id) => id,
});
const parseAnthropic = createModelParser({
arrayKey: 'data',
filter: (entry) => typeof entry.id === 'string' && entry.type === 'model',
getId: (entry) => entry.id,
getDisplayName: (entry, id) => entry.display_name || id,
contextWindow: ANTHROPIC_DEFAULT_CONTEXT,
});
const GEMINI_VERSION_SUFFIX_RE = /-\d{3}$/;
function parseGemini(body, provider) {
const models = body?.models;
if (!Array.isArray(models))
return [];
const parsed = models
.filter((m) => {
const entry = m;
if (typeof entry.name !== 'string')
return false;
const methods = entry.supportedGenerationMethods;
return Array.isArray(methods) && methods.includes('generateContent');
})
.map((m) => {
const entry = m;
const id = entry.name.replace(/^models\//, '');
return {
id,
displayName: entry.displayName || id,
provider,
contextWindow: entry.inputTokenLimit ?? GEMINI_DEFAULT_CONTEXT,
inputPricePerToken: null,
outputPricePerToken: null,
capabilityReasoning: false,
capabilityCode: false,
qualityScore: 3,
};
});
const ids = new Set(parsed.map((m) => m.id));
return parsed.filter((m) => {
if (!GEMINI_VERSION_SUFFIX_RE.test(m.id))
return true;
const alias = m.id.replace(GEMINI_VERSION_SUFFIX_RE, '');
return !ids.has(alias);
});
}
function parseOpenRouter(body, provider) {
const data = body?.data;
if (!Array.isArray(data))
return [];
return data
.filter((m) => {
const entry = m;
if (typeof entry.id !== 'string')
return false;
const output = entry.architecture?.output_modalities?.map((o) => o.toLowerCase());
if (output && output.length > 0 && !output.every((o) => o === 'text')) {
return false;
}
return true;
})
.map((m) => {
const entry = m;
const prompt = entry.pricing?.prompt ? Number(entry.pricing.prompt) : null;
const completion = entry.pricing?.completion ? Number(entry.pricing.completion) : null;
return {
id: entry.id,
displayName: entry.name || entry.id,
provider,
contextWindow: entry.context_length ?? 128000,
inputPricePerToken: prompt !== null && Number.isFinite(prompt) && prompt >= 0 ? prompt : null,
outputPricePerToken: completion !== null && Number.isFinite(completion) && completion >= 0 ? completion : null,
capabilityReasoning: false,
capabilityCode: false,
qualityScore: 3,
};
});
}
const parseOllama = createModelParser({
arrayKey: 'models',
filter: (entry) => typeof entry.name === 'string',
getId: (entry) => entry.name.replace(/:latest$/, ''),
getDisplayName: (_entry, id) => id,
inputPricePerToken: 0,
outputPricePerToken: 0,
qualityScore: 2,
});
const parseOpenaiSubscription = createModelParser({
arrayKey: 'models',
filter: (entry) => typeof entry.slug === 'string' && entry.visibility === 'list',
getId: (entry) => entry.slug,
getDisplayName: (entry, id) => entry.display_name || id,
contextWindow: (entry) => entry.context_window ?? 200000,
inputPricePerToken: 0,
outputPricePerToken: 0,
capabilityCode: true,
});
const parseCopilot = createModelParser({
arrayKey: 'data',
filter: (entry) => typeof entry.id === 'string' && entry.id.length > 0,
getId: (entry) => `copilot/${entry.id}`,
getDisplayName: (entry) => entry.id,
inputPricePerToken: 0,
outputPricePerToken: 0,
});
exports.PROVIDER_CONFIGS = {
openai: {
endpoint: 'https://api.openai.com/v1/models',
buildHeaders: bearerHeaders,
parse: parseOpenAIDeduped,
},
'openai-subscription': {
endpoint: 'https://chatgpt.com/backend-api/codex/models?client_version=0.99.0',
buildHeaders: (key) => ({
Authorization: `Bearer ${key}`,
'Content-Type': 'application/json',
originator: 'codex_cli_rs',
'user-agent': 'codex_cli_rs/0.0.0 (Unknown 0; unknown) unknown',
}),
parse: parseOpenaiSubscription,
},
deepseek: {
endpoint: 'https://api.deepseek.com/models',
buildHeaders: bearerHeaders,
parse: parseOpenAI,
},
mistral: {
endpoint: 'https://api.mistral.ai/v1/models',
buildHeaders: bearerHeaders,
parse: parseMistral,
},
moonshot: {
endpoint: 'https://api.moonshot.ai/v1/models',
buildHeaders: bearerHeaders,
parse: parseOpenAI,
},
xai: {
endpoint: 'https://api.x.ai/v1/models',
buildHeaders: bearerHeaders,
parse: parseOpenAI,
},
minimax: {
endpoint: 'https://api.minimaxi.chat/v1/models',
buildHeaders: bearerHeaders,
parse: parseOpenAI,
},
'minimax-subscription': {
endpoint: MINIMAX_SUBSCRIPTION_MODELS_URL,
buildHeaders: (key) => ({
Authorization: `Bearer ${key}`,
'anthropic-version': '2023-06-01',
}),
parse: parseAnthropic,
},
qwen: {
endpoint: `${(0, qwen_region_1.getQwenCompatibleBaseUrl)('beijing')}/v1/models`,
buildHeaders: bearerHeaders,
parse: parseOpenAI,
},
zai: {
endpoint: 'https://open.bigmodel.cn/api/paas/v4/models',
buildHeaders: bearerHeaders,
parse: parseOpenAI,
},
anthropic: {
endpoint: 'https://api.anthropic.com/v1/models?limit=100',
buildHeaders: (key, authType) => {
const headers = {
'anthropic-version': '2023-06-01',
};
if (authType === 'subscription') {
headers['Authorization'] = `Bearer ${key}`;
headers['anthropic-beta'] = 'oauth-2025-04-20';
}
else {
headers['x-api-key'] = key;
}
return headers;
},
parse: parseAnthropic,
},
gemini: {
endpoint: (key) => `https://generativelanguage.googleapis.com/v1beta/models?key=${encodeURIComponent(key)}`,
buildHeaders: () => ({}),
parse: parseGemini,
},
openrouter: {
endpoint: 'https://openrouter.ai/api/v1/models',
buildHeaders: () => ({}),
parse: parseOpenRouter,
},
ollama: {
endpoint: `${ollama_1.OLLAMA_HOST}/api/tags`,
buildHeaders: () => ({}),
parse: parseOllama,
},
copilot: {
endpoint: 'https://api.githubcopilot.com/models',
buildHeaders: (key) => ({
Authorization: `Bearer ${key}`,
Accept: 'application/json',
'Editor-Version': 'vscode/1.100.0',
'Editor-Plugin-Version': 'copilot/1.300.0',
'Copilot-Integration-Id': 'vscode-chat',
}),
parse: parseCopilot,
},
};
let ProviderModelFetcherService = ProviderModelFetcherService_1 = class ProviderModelFetcherService {
logger = new common_1.Logger(ProviderModelFetcherService_1.name);
async fetch(providerId, apiKey, authType, endpointOverride) {
let configKey = providerId.toLowerCase();
if (configKey === 'openai' && authType === 'subscription') {
configKey = 'openai-subscription';
}
else if (configKey === 'minimax' && authType === 'subscription') {
configKey = 'minimax-subscription';
}
const config = exports.PROVIDER_CONFIGS[configKey];
if (!config) {
this.logger.warn(`No fetcher config for provider: ${providerId}`);
return [];
}
let url = typeof config.endpoint === 'function' ? config.endpoint(apiKey) : config.endpoint;
if (endpointOverride && configKey === 'minimax-subscription') {
const minimaxBaseUrl = (0, provider_base_url_1.normalizeMinimaxSubscriptionBaseUrl)(endpointOverride);
if (minimaxBaseUrl) {
url = `${minimaxBaseUrl}/v1/models?limit=100`;
}
else {
this.logger.warn('Ignoring invalid MiniMax subscription endpoint override');
}
}
else if (endpointOverride && configKey === 'qwen') {
const qwenBaseUrl = (0, qwen_region_1.normalizeQwenCompatibleBaseUrl)(endpointOverride);
if (qwenBaseUrl) {
url = `${qwenBaseUrl}/v1/models`;
}
else {
this.logger.warn('Ignoring invalid Qwen endpoint override');
}
}
const headers = config.buildHeaders(apiKey, authType);
try {
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);
const res = await fetch(url, {
headers,
signal: controller.signal,
});
clearTimeout(timeout);
if (!res.ok) {
this.logger.warn(`Provider ${providerId} returned ${res.status} from ${url.replace(apiKey, '***')}`);
return [];
}
const body = await res.json();
return filterNonChatModels(config.parse(body, providerId), configKey);
}
catch (err) {
const message = err instanceof Error ? err.message : String(err);
this.logger.warn(`Failed to fetch models from ${providerId}: ${message}`);
return [];
}
}
};
exports.ProviderModelFetcherService = ProviderModelFetcherService;
exports.ProviderModelFetcherService = ProviderModelFetcherService = ProviderModelFetcherService_1 = __decorate([
(0, common_1.Injectable)()
], ProviderModelFetcherService);
//# sourceMappingURL=provider-model-fetcher.service.js.map