large-models-interface
Version:
A comprehensive, unified interface for all types of AI models - natural language, vision, audio, and video. Supports 51 providers with dynamic model discovery and multi-modal capabilities.
349 lines (291 loc) • 11.1 kB
JavaScript
/**
* @file src/utils/providerEndpointManager.js
* @description Provider endpoint management utilities for centralized model discovery
*/
const fs = require('fs').promises;
const path = require('path');
class ProviderEndpointManager {
constructor() {
this.endpointsFile = path.join(__dirname, '../config/providerModelEndpoints.json');
this.cache = null;
this.lastLoaded = null;
}
/**
* Load provider endpoints configuration
* @returns {Promise<Object>} Provider endpoints configuration
*/
async loadProviderEndpoints() {
try {
// Use cache if loaded within last 5 minutes
if (this.cache && this.lastLoaded && (Date.now() - this.lastLoaded < 5 * 60 * 1000)) {
return this.cache;
}
const data = await fs.readFile(this.endpointsFile, 'utf8');
this.cache = JSON.parse(data);
this.lastLoaded = Date.now();
console.log(`📋 Loaded ${this.cache.metadata.totalProviders} provider endpoints`);
return this.cache;
} catch (error) {
console.error(`❌ Failed to load provider endpoints: ${error.message}`);
throw error;
}
}
/**
* Get model endpoint for a specific provider
* @param {string} providerName - Name of the provider
* @returns {Promise<string|null>} Model endpoint URL or null if not found
*/
async getModelEndpoint(providerName) {
const config = await this.loadProviderEndpoints();
const provider = config.providers[providerName];
if (!provider) {
console.warn(`⚠️ Provider '${providerName}' not found in endpoints configuration`);
return null;
}
if (provider.status !== 'active' && provider.status !== 'local') {
console.warn(`⚠️ Provider '${providerName}' is not active (status: ${provider.status})`);
return null;
}
return provider.modelsEndpoint;
}
/**
* Get provider information
* @param {string} providerName - Name of the provider
* @returns {Promise<Object|null>} Provider information or null if not found
*/
async getProviderInfo(providerName) {
const config = await this.loadProviderEndpoints();
return config.providers[providerName] || null;
}
/**
* Get all providers by category
* @param {string} category - Category name (global, chinese, opensource, etc.)
* @returns {Promise<Array>} Array of provider names in the category
*/
async getProvidersByCategory(category) {
const config = await this.loadProviderEndpoints();
return config.categories[category] || [];
}
/**
* Get providers that support a specific feature
* @param {string} feature - Feature name (chat, vision, audio, etc.)
* @returns {Promise<Array>} Array of provider names that support the feature
*/
async getProvidersByFeature(feature) {
const config = await this.loadProviderEndpoints();
return config.features[feature] || [];
}
/**
* Get all active providers
* @returns {Promise<Array>} Array of active provider names
*/
async getActiveProviders() {
const config = await this.loadProviderEndpoints();
return Object.keys(config.providers).filter(name =>
config.providers[name].status === 'active' ||
config.providers[name].status === 'local'
);
}
/**
* Update provider configuration file with new endpoints
* @param {string} providerName - Name of the provider
* @param {Object} providerConfig - Provider configuration
* @returns {Promise<void>}
*/
async updateProviderEndpoint(providerName, providerConfig) {
const config = await this.loadProviderEndpoints();
config.providers[providerName] = {
...config.providers[providerName],
...providerConfig,
lastUpdated: new Date().toISOString()
};
config.metadata.lastUpdated = new Date().toISOString();
config.metadata.totalProviders = Object.keys(config.providers).length;
await fs.writeFile(this.endpointsFile, JSON.stringify(config, null, 2));
// Clear cache to force reload
this.cache = null;
console.log(`✅ Updated provider configuration for '${providerName}'`);
}
/**
* Add a new provider to the configuration
* @param {string} providerName - Name of the provider
* @param {Object} providerConfig - Complete provider configuration
* @returns {Promise<void>}
*/
async addProvider(providerName, providerConfig) {
const config = await this.loadProviderEndpoints();
if (config.providers[providerName]) {
throw new Error(`Provider '${providerName}' already exists`);
}
config.providers[providerName] = {
...providerConfig,
addedAt: new Date().toISOString()
};
config.metadata.lastUpdated = new Date().toISOString();
config.metadata.totalProviders = Object.keys(config.providers).length;
await fs.writeFile(this.endpointsFile, JSON.stringify(config, null, 2));
// Clear cache to force reload
this.cache = null;
console.log(`✅ Added new provider '${providerName}'`);
}
/**
* Generate provider statistics
* @returns {Promise<Object>} Provider statistics
*/
async getProviderStats() {
const config = await this.loadProviderEndpoints();
const stats = {
total: Object.keys(config.providers).length,
active: 0,
local: 0,
inactive: 0,
byAuthType: {},
byFeature: {},
byCategory: {}
};
// Count by status
Object.values(config.providers).forEach(provider => {
if (provider.status === 'active') stats.active++;
else if (provider.status === 'local') stats.local++;
else stats.inactive++;
// Count by auth type
stats.byAuthType[provider.authType] = (stats.byAuthType[provider.authType] || 0) + 1;
});
// Count by feature
Object.entries(config.features).forEach(([feature, providers]) => {
stats.byFeature[feature] = providers.length;
});
// Count by category
Object.entries(config.categories).forEach(([category, providers]) => {
stats.byCategory[category] = providers.length;
});
return stats;
}
/**
* Validate provider endpoints (check if URLs are accessible)
* @param {Array} providerNames - Optional array of provider names to validate, defaults to all active
* @returns {Promise<Object>} Validation results
*/
async validateProviderEndpoints(providerNames = null) {
const config = await this.loadProviderEndpoints();
const axios = require('axios');
const providersToCheck = providerNames || await this.getActiveProviders();
const results = {
validated: 0,
accessible: 0,
inaccessible: 0,
errors: []
};
console.log(`🔍 Validating ${providersToCheck.length} provider endpoints...`);
for (const providerName of providersToCheck) {
const provider = config.providers[providerName];
if (!provider || !provider.modelsEndpoint) continue;
results.validated++;
try {
// Skip local endpoints
if (provider.status === 'local') {
console.log(`⏭️ Skipping local provider: ${providerName}`);
continue;
}
// Simple HEAD request to check if endpoint is accessible
const response = await axios.head(provider.modelsEndpoint, {
timeout: 5000,
validateStatus: status => status < 500 // Accept client errors but not server errors
});
results.accessible++;
console.log(`✅ ${providerName}: ${response.status}`);
} catch (error) {
results.inaccessible++;
results.errors.push({
provider: providerName,
endpoint: provider.modelsEndpoint,
error: error.message
});
console.log(`❌ ${providerName}: ${error.message}`);
}
}
console.log(`📊 Validation complete: ${results.accessible}/${results.validated} endpoints accessible`);
return results;
}
/**
* Export provider endpoints to different formats
* @param {string} format - Export format: 'json', 'csv', 'yaml'
* @param {string} outputPath - Output file path
* @returns {Promise<void>}
*/
async exportProviderEndpoints(format = 'json', outputPath = null) {
const config = await this.loadProviderEndpoints();
if (!outputPath) {
outputPath = `./provider-endpoints.${format}`;
}
switch (format.toLowerCase()) {
case 'json':
await fs.writeFile(outputPath, JSON.stringify(config, null, 2));
break;
case 'csv':
const csv = this.generateCSV(config);
await fs.writeFile(outputPath, csv);
break;
case 'yaml':
// Simple YAML export (would need yaml library for complex structures)
const yaml = this.generateYAML(config);
await fs.writeFile(outputPath, yaml);
break;
default:
throw new Error(`Unsupported export format: ${format}`);
}
console.log(`📄 Exported provider endpoints to ${outputPath} (${format.toUpperCase()})`);
}
/**
* Generate CSV format from config
* @param {Object} config - Provider configuration
* @returns {string} CSV content
*/
generateCSV(config) {
const headers = ['Provider', 'Name', 'Website', 'ModelsEndpoint', 'AuthType', 'Status', 'Features'];
const rows = [headers.join(',')];
Object.entries(config.providers).forEach(([key, provider]) => {
const features = provider.supportedFeatures ? provider.supportedFeatures.join(';') : '';
const row = [
key,
`"${provider.name}"`,
provider.website,
provider.modelsEndpoint,
provider.authType,
provider.status,
`"${features}"`
];
rows.push(row.join(','));
});
return rows.join('\n');
}
/**
* Generate simple YAML format from config
* @param {Object} config - Provider configuration
* @returns {string} YAML content
*/
generateYAML(config) {
let yaml = `# Provider Model Endpoints Configuration\n`;
yaml += `# Generated: ${new Date().toISOString()}\n\n`;
yaml += `metadata:\n`;
yaml += ` version: "${config.metadata.version}"\n`;
yaml += ` totalProviders: ${config.metadata.totalProviders}\n\n`;
yaml += `providers:\n`;
Object.entries(config.providers).forEach(([key, provider]) => {
yaml += ` ${key}:\n`;
yaml += ` name: "${provider.name}"\n`;
yaml += ` website: "${provider.website}"\n`;
yaml += ` modelsEndpoint: "${provider.modelsEndpoint}"\n`;
yaml += ` authType: "${provider.authType}"\n`;
yaml += ` status: "${provider.status}"\n`;
if (provider.supportedFeatures) {
yaml += ` features: [${provider.supportedFeatures.map(f => `"${f}"`).join(', ')}]\n`;
}
yaml += `\n`;
});
return yaml;
}
}
// Export singleton instance
const providerEndpointManager = new ProviderEndpointManager();
module.exports = providerEndpointManager;