@juspay/neurolink
Version:
Universal AI Development Platform with working MCP integration, multi-provider support, voice (TTS/STT/realtime), and professional CLI. 58+ external MCP servers discoverable, multimodal file processing, RAG pipelines. Build, test, and deploy AI applicatio
188 lines (187 loc) • 8.09 kB
JavaScript
import { logger } from "../utils/logger.js";
// Pure factory pattern with no hardcoded imports
// All providers loaded dynamically via registry to avoid circular dependencies
/**
* True Factory Pattern implementation for AI Providers
* Uses registration-based approach to eliminate switch statements
* and enable dynamic provider registration
*/
export class ProviderFactory {
static providers = new Map();
static initialized = false;
/**
* Register a provider with the factory
*/
static registerProvider(name, constructor, defaultModel, // Optional - provider can read from env
aliases = []) {
const registration = {
constructor,
defaultModel,
aliases,
};
// Register main name
ProviderFactory.providers.set(name.toLowerCase(), registration);
// Register aliases
aliases.forEach((alias) => {
ProviderFactory.providers.set(alias.toLowerCase(), registration);
});
logger.debug(`Registered provider: ${name} with model ${defaultModel || "from-env"}`);
}
/**
* Create a provider instance
* @param providerName - Provider name (optional, uses NEUROLINK_PROVIDER env var or 'vertex' as default)
* @param modelName - Model name (optional, uses provider-specific env var or registry default)
*/
static async createProvider(providerName, modelName, sdk, region, credentials) {
// Note: Providers are registered explicitly by ProviderRegistry to avoid circular dependencies
// Use environment variable or default if not specified
const resolvedProviderName = providerName ||
process.env.NEUROLINK_PROVIDER ||
process.env.AI_PROVIDER ||
"vertex";
const normalizedName = resolvedProviderName.toLowerCase();
const registration = ProviderFactory.providers.get(normalizedName);
if (!registration) {
throw new Error(`Unknown provider: ${resolvedProviderName}. Available providers: ${ProviderFactory.getAvailableProviders().join(", ")}`);
}
// Respect environment variables before falling back to registry default
let model = modelName;
if (!model) {
// Check for provider-specific environment variables
if (resolvedProviderName.toLowerCase().includes("vertex")) {
// Use gemini-2.5-flash as default - latest GA model with best price-performance
model = process.env.VERTEX_MODEL || "gemini-2.5-flash";
}
else if (resolvedProviderName.toLowerCase().includes("bedrock")) {
model = process.env.BEDROCK_MODEL || process.env.BEDROCK_MODEL_ID;
}
// Fallback to registry default if no env var
model = model || registration.defaultModel;
}
// Map registered provider names to NeurolinkCredentials keys.
// Most names match (openai, anthropic, vertex, bedrock, etc.)
// but every kebab-case provider whose canonical credentials key is
// camelCase MUST be mapped here — otherwise per-call credential
// overrides silently get dropped (the factory looks up
// credentials["lm-studio"] which is undefined while the user wrote
// credentials.lmStudio).
const credentialKeyMap = {
"google-ai": "googleAiStudio",
"openai-compatible": "openaiCompatible",
huggingface: "huggingFace",
"lm-studio": "lmStudio",
"nvidia-nim": "nvidiaNim",
};
const credKey = credentialKeyMap[normalizedName] ?? normalizedName;
// Extract provider-scoped credential slice (e.g. credentials.openai for OpenAI)
const scopedCredentials = credentials
? credentials[credKey]
: undefined;
try {
if (typeof registration.constructor !== "function") {
throw new Error(`Invalid constructor for provider ${providerName}: not a function`);
}
let result;
try {
const factoryResult = registration.constructor(model, resolvedProviderName, sdk, region, scopedCredentials);
// Handle both sync and async results
result =
factoryResult instanceof Promise
? await factoryResult
: factoryResult;
}
catch (factoryError) {
if (registration.constructor.prototype &&
registration.constructor.prototype.constructor ===
registration.constructor) {
try {
result = new registration.constructor(model, resolvedProviderName, sdk, region, scopedCredentials);
}
catch (constructorError) {
throw new Error(`Both factory function and constructor failed. Factory error: ${factoryError}. Constructor error: ${constructorError}`, { cause: constructorError });
}
}
else {
throw factoryError;
}
}
return result;
}
catch (error) {
logger.error(`Failed to create provider ${resolvedProviderName}:`, error);
throw new Error(`Failed to create provider ${resolvedProviderName}: ${error}`, { cause: error });
}
}
/**
* Check if a provider is registered
*/
static hasProvider(providerName) {
return ProviderFactory.providers.has(providerName.toLowerCase());
}
/**
* Get list of available providers
*/
static getAvailableProviders() {
return Array.from(ProviderFactory.providers.keys()).filter((name, index, arr) => arr.indexOf(name) === index);
}
/**
* Get provider registration info
*/
static getProviderInfo(providerName) {
return ProviderFactory.providers.get(providerName.toLowerCase());
}
/**
* Normalize provider names using aliases (PHASE 1: Factory Pattern)
*/
static normalizeProviderName(providerName) {
const normalized = providerName.toLowerCase();
// Check direct registration
if (ProviderFactory.providers.has(normalized)) {
return normalized;
}
// Check aliases from all registrations
for (const [name, registration] of ProviderFactory.providers.entries()) {
if (registration.aliases?.includes(normalized)) {
return name;
}
}
return null;
}
/**
* Clear all registrations (mainly for testing)
*/
static clearRegistrations() {
ProviderFactory.providers.clear();
ProviderFactory.initialized = false;
}
/**
* Ensure providers are initialized
*/
static ensureInitialized() {
if (!ProviderFactory.initialized) {
ProviderFactory.initializeDefaultProviders();
ProviderFactory.initialized = true;
}
}
/**
* Initialize default providers
* NOTE: Providers are now registered by ProviderRegistry to avoid circular dependencies
*/
static initializeDefaultProviders() {
logger.debug("BaseProvider factory pattern ready - providers registered by ProviderRegistry");
// No hardcoded registrations - all done dynamically by ProviderRegistry
}
/**
* Create the best available provider for the given name
* Used by NeuroLink SDK for streaming and generation
*/
static async createBestProvider(providerName, modelName, enableMCP, sdk, credentials) {
return await ProviderFactory.createProvider(providerName, modelName, sdk, undefined, credentials);
}
}
/**
* Helper function to create providers with backward compatibility
*/
export async function createAIProvider(providerName, modelName, credentials) {
return await ProviderFactory.createProvider(providerName, modelName, undefined, undefined, credentials);
}