@juspay/neurolink
Version:
Universal AI Development Platform with working MCP integration, multi-provider support, and professional CLI. Built-in tools operational, 58+ external MCP servers discoverable. Connect to filesystem, GitHub, database operations, and more. Build, test, and
361 lines (360 loc) • 14.2 kB
JavaScript
import { z } from "zod";
import { logger } from "../utils/logger.js";
/**
* Model configuration schema for validation
*/
const ModelConfigSchema = z.object({
id: z.string(),
displayName: z.string(),
capabilities: z.array(z.string()),
deprecated: z.boolean(),
pricing: z.object({
input: z.number(),
output: z.number(),
}),
contextWindow: z.number(),
releaseDate: z.string(),
});
const ModelRegistrySchema = z.object({
version: z.string(),
lastUpdated: z.string(),
models: z.record(z.record(ModelConfigSchema)),
aliases: z.record(z.string()).optional(),
defaults: z.record(z.string()).optional(),
});
/**
* Dynamic Model Provider
* Loads and manages model configurations from external sources
*/
export class DynamicModelProvider {
static instance;
modelRegistry = null;
lastFetch = 0;
CACHE_DURATION = 5 * 60 * 1000; // 5 minutes
constructor() { }
static getInstance() {
if (!this.instance) {
this.instance = new DynamicModelProvider();
}
return this.instance;
}
/**
* Initialize the model registry from multiple sources with timeout handling
* Addresses hanging issues when localhost:3001 is not running or GitHub URLs timeout
*/
async initialize() {
const sources = [
{
url: process.env.MODEL_CONFIG_URL || "http://localhost:3001/api/v1/models",
timeout: 3000, // 3s for localhost
name: "local-server",
},
{
url: `https://raw.githubusercontent.com/${process.env.MODEL_CONFIG_GITHUB_REPO || "juspay/neurolink"}/${process.env.MODEL_CONFIG_GITHUB_BRANCH || "release"}/config/models.json`,
timeout: 5000, // 5s for GitHub
name: "github-raw",
},
{
url: "./config/models.json",
timeout: 1000, // 1s for local file
name: "local-file",
},
];
const errors = [];
for (const source of sources) {
try {
logger.debug(`[DynamicModelProvider] Attempting to load from: ${source.url} (timeout: ${source.timeout}ms)`);
const config = await this.loadFromSourceWithTimeout(source.url, source.timeout);
// Validate the configuration
const validatedConfig = ModelRegistrySchema.parse(config);
this.modelRegistry = validatedConfig;
this.lastFetch = Date.now();
logger.info(`[DynamicModelProvider] Successfully loaded model registry from: ${source.name}`, {
source: source.url,
modelCount: this.getTotalModelCount(),
providerCount: Object.keys(validatedConfig.models).length,
loadTime: `<${source.timeout}ms`,
});
return; // Success, stop trying other sources
}
catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
errors.push({ source: source.name, error: errorMessage });
logger.warn(`[DynamicModelProvider] Failed to load from ${source.name} (${source.url}):`, {
error: errorMessage,
timeout: source.timeout,
});
continue;
}
}
// Log all failures for debugging
logger.warn(`[DynamicModelProvider] All model configuration sources failed`, { errors });
throw new Error(`Failed to load model configuration from all source. Attempted: ${errors.map((e) => e.source).join(", ")}`);
}
/**
* Load configuration from a source with timeout handling
* Prevents hanging when local servers are down or network requests timeout
*/
async loadFromSourceWithTimeout(source, timeoutMs) {
if (source.startsWith("http")) {
// Setup timeout and abort controller
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
// Load from URL
const response = await fetch(source, {
headers: {
"User-Agent": "NeuroLink/1.0 (+https://github.com/juspay/neurolink)",
},
});
try {
// Add health check for localhost before attempting full request
if (source.includes("localhost") || source.includes("127.0.0.1")) {
await this.healthCheckLocalhost(source, Math.min(timeoutMs, 1000));
}
const response = await fetch(source, {
headers: {
"User-Agent": "NeuroLink/1.0 (+https://github.com/sachinsharma92/neurolink)",
Accept: "application/json",
"Cache-Control": "no-cache",
},
signal: controller.signal,
});
clearTimeout(timeoutId);
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
const data = await response.json();
return data;
}
catch (error) {
clearTimeout(timeoutId);
if (error instanceof Error && error.name === "AbortError") {
throw new Error(`Request timeout after ${timeoutMs}ms`);
}
throw error;
}
}
else {
// Load from local file with timeout (for very large files)
return new Promise((resolve, reject) => {
const timeoutId = setTimeout(() => {
reject(new Error(`File read timeout after ${timeoutMs}ms`));
}, timeoutMs);
(async () => {
try {
const fs = await import("fs");
const path = await import("path");
const fullPath = path.resolve(source);
// Check if file exists first
if (!fs.existsSync(fullPath)) {
throw new Error(`File not found: ${fullPath}`);
}
const content = fs.readFileSync(fullPath, "utf8");
const data = JSON.parse(content);
clearTimeout(timeoutId);
resolve(data);
}
catch (error) {
clearTimeout(timeoutId);
reject(error);
}
})();
});
}
}
/**
* Quick health check for localhost endpoints
* Prevents hanging on non-responsive local servers
*/
async healthCheckLocalhost(url, timeoutMs) {
const healthUrl = url.replace(/\/api\/.*$/, "/health") || `${url}/health`;
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
try {
const response = await fetch(healthUrl, {
method: "HEAD", // Lightweight request
signal: controller.signal,
});
clearTimeout(timeoutId);
// Don't throw on 404 - the main endpoint might still work
if (response.status >= 500) {
throw new Error(`Server error: ${response.status}`);
}
}
catch (error) {
clearTimeout(timeoutId);
if (error instanceof Error && error.name === "AbortError") {
throw new Error(`Localhost health check timeout - server may not be running`);
}
// For connection refused, throw a more specific error
if (error instanceof Error && error.message.includes("ECONNREFUSED")) {
throw new Error(`Localhost server not running at ${url}`);
}
// For other errors, let the main request handle them
logger.debug(`Health check failed for ${url}, proceeding with main request`, {
error: error instanceof Error ? error.message : String(error),
});
}
}
/**
* Load configuration from a source (URL or file path) - Legacy method for compatibility
* @deprecated Use loadFromSourceWithTimeout instead
*/
async loadFromSource(source) {
return this.loadFromSourceWithTimeout(source, 10000); // 10s default timeout
}
/**
* Get all available models for a provider
*/
getModelsForProvider(provider) {
this.ensureInitialized();
return this.modelRegistry?.models[provider] || {};
}
/**
* Resolve a model by provider and model hint
*/
resolveModel(provider, modelHint) {
this.ensureInitialized();
const providerModels = this.getModelsForProvider(provider);
if (!modelHint) {
// Use default model for provider
const defaultModel = this.modelRegistry?.defaults?.[provider];
return defaultModel ? providerModels[defaultModel] : null;
}
// Check for exact match
if (providerModels[modelHint]) {
return providerModels[modelHint];
}
// Check aliases
const aliasTarget = this.modelRegistry?.aliases?.[modelHint];
if (aliasTarget) {
const [aliasProvider, aliasModel] = aliasTarget.split("/");
return this.resolveModel(aliasProvider, aliasModel);
}
// Fuzzy matching (partial string match)
const fuzzyMatch = Object.keys(providerModels).find((key) => key.toLowerCase().includes(modelHint.toLowerCase()) ||
modelHint.toLowerCase().includes(key.toLowerCase()));
return fuzzyMatch ? providerModels[fuzzyMatch] : null;
}
/**
* Search models by capabilities
*/
searchByCapability(capability, options = {}) {
this.ensureInitialized();
const results = [];
for (const [providerName, models] of Object.entries(this.modelRegistry.models)) {
if (options.provider && providerName !== options.provider) {
continue;
}
for (const [modelName, modelConfig] of Object.entries(models)) {
if (options.excludeDeprecated && modelConfig.deprecated) {
continue;
}
if (options.maxPrice && modelConfig.pricing.input > options.maxPrice) {
continue;
}
if (!modelConfig.capabilities.includes(capability)) {
continue;
}
results.push({
provider: providerName,
model: modelName,
config: modelConfig,
});
}
}
// Sort by price (cheapest first)
return results.sort((a, b) => a.config.pricing.input - b.config.pricing.input);
}
/**
* Get the best model for a specific use case
*/
getBestModelFor(useCase) {
this.ensureInitialized();
switch (useCase) {
case "coding":
return (this.searchByCapability("functionCalling", {
excludeDeprecated: true,
})[0] || null);
case "analysis":
return (this.searchByCapability("analysis", { excludeDeprecated: true })[0] ||
null);
case "vision":
return (this.searchByCapability("vision", { excludeDeprecated: true })[0] ||
null);
case "fastest":
// Return cheapest as proxy for fastest (usually correlates)
return (this.getAllModels()
.filter((m) => !m.config.deprecated)
.sort((a, b) => a.config.pricing.input - b.config.pricing.input)[0] || null);
case "cheapest":
return (this.getAllModels()
.filter((m) => !m.config.deprecated)
.sort((a, b) => a.config.pricing.input - b.config.pricing.input)[0] || null);
default:
return null;
}
}
/**
* Get all models across all providers
*/
getAllModels() {
this.ensureInitialized();
const results = [];
for (const [providerName, models] of Object.entries(this.modelRegistry.models)) {
for (const [modelName, modelConfig] of Object.entries(models)) {
results.push({
provider: providerName,
model: modelName,
config: modelConfig,
});
}
}
return results;
}
/**
* Get total number of models
*/
getTotalModelCount() {
if (!this.modelRegistry) {
return 0;
}
return Object.values(this.modelRegistry.models).reduce((total, providerModels) => total + Object.keys(providerModels).length, 0);
}
/**
* Check if cache needs refresh
*/
needsRefresh() {
return Date.now() - this.lastFetch > this.CACHE_DURATION;
}
/**
* Force refresh the model registry
*/
async refresh() {
this.modelRegistry = null;
await this.initialize();
}
/**
* Ensure the registry is initialized
*/
ensureInitialized() {
if (!this.modelRegistry) {
throw new Error("Model registry not initialized. Call initialize() first.");
}
}
/**
* Get registry metadata
*/
getMetadata() {
if (!this.modelRegistry) {
return null;
}
return {
version: this.modelRegistry.version,
lastUpdated: this.modelRegistry.lastUpdated,
modelCount: this.getTotalModelCount(),
};
}
}
// Export singleton instance
export const dynamicModelProvider = DynamicModelProvider.getInstance();