@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
590 lines (589 loc) • 21.1 kB
JavaScript
/**
* Factory options processing utilities
*
* Processes factory configuration and ensures it flows through to AI providers
*/
import { logger } from "./logger.js";
import { isNonNullObject } from "./typeUtils.js";
// Removed crypto import - using faster string-based hash instead
/**
* LRU Cache for factory processing results
* Addresses GitHub Copilot review comment about adding caching for factory processing results
*/
class FactoryProcessingCache {
cache = new Map();
// Object identity cache to avoid recomputing cache keys for same object
// Using WeakMap to prevent memory leaks - entries are auto-collected when objects are GC'd
objectKeyCache = new WeakMap();
maxSize;
stats = {
hits: 0,
misses: 0,
evictions: 0,
totalRequests: 0,
keysCacheHits: 0, // New stat for object identity cache hits
};
constructor() {
// Configurable cache size via environment variable with bounds checking
const envCacheSize = process.env.NEUROLINK_FACTORY_CACHE_SIZE || "100";
const parsedSize = parseInt(envCacheSize, 10);
// Add bounds checking: min 1, max 10000 to prevent memory issues
if (isNaN(parsedSize) || parsedSize < 1) {
this.maxSize = 1;
logger.warn(`Invalid cache size '${envCacheSize}', using minimum value: 1`);
}
else if (parsedSize > 10000) {
this.maxSize = 10000;
logger.warn(`Cache size '${parsedSize}' exceeds maximum, using maximum value: 10000`);
}
else {
this.maxSize = parsedSize;
}
logger.debug(`FactoryProcessingCache initialized with max size: ${this.maxSize}`);
}
/**
* Generate cache key from options using fast non-cryptographic hash
* Optimized for large options objects by extracting only key fields
* Uses numeric hash combination to avoid string concatenation in hot paths
* Implements object identity cache to avoid recomputation for same objects
*/
generateCacheKey(options) {
// Use object identity cache if possible for performance
if (isNonNullObject(options)) {
const cachedKey = this.objectKeyCache.get(options);
if (cachedKey) {
this.stats.keysCacheHits++;
return cachedKey;
}
}
try {
// Extract key field hashes directly without string operations
const factoryConfigHash = this.extractKeyFieldsHash(options.factoryConfig);
const contextHash = this.extractKeyFieldsHash(options.context);
// Combine hashes numerically instead of string concatenation
const combinedHash = this.combineHashes(factoryConfigHash, contextHash);
const key = combinedHash.toString(16);
// Cache the computed key for future use (WeakMap auto-cleans on GC)
if (isNonNullObject(options)) {
this.objectKeyCache.set(options, key);
}
return key;
}
catch (error) {
logger.warn("Failed to generate cache key, using deterministic fallback", { error });
return "fallback_" + this.stableStringify(options);
}
}
/**
* Create deterministic string representation for fallback cache keys
* Ensures identical options always produce the same cache key
*/
stableStringify(obj) {
try {
if (obj === null || obj === undefined) {
return String(obj);
}
if (typeof obj !== "object") {
return String(obj);
}
// For objects, create a stable representation by sorting keys
const record = obj;
const sortedKeys = Object.keys(record).sort();
const pairs = sortedKeys.map((key) => `${key}:${this.stableStringify(record[key])}`);
return `{${pairs.join(",")}}`;
}
catch {
// Ultimate fallback - use object type
return `[${typeof obj}]`;
}
}
/**
* Extract key field hash from objects without string concatenation
* Uses numeric hash combination for maximum performance in hot paths
*/
extractKeyFieldsHash(obj) {
if (!obj || typeof obj !== "object") {
return this.hashValue(obj);
}
const record = obj;
let hash = 0;
// Extract only the most identifying fields - ordered by importance
const importantFields = [
"domainType",
"enhancementType",
"domainConfig",
"id",
"type",
"name",
];
// Use numeric hash combination instead of string operations
for (let i = 0; i < importantFields.length; i++) {
const field = importantFields[i];
const value = record[field];
if (value !== undefined) {
// Combine field name hash and value hash numerically
const fieldHash = this.hashString(field);
const valueHash = this.hashValue(value);
hash = this.combineHashes(hash, this.combineHashes(fieldHash, valueHash));
}
}
// If no important fields found, use object structure hash
if (hash === 0) {
hash = Object.keys(record).length;
}
return hash;
}
/**
* Extract key identifying fields from objects for cache key generation
* Optimized to avoid expensive operations on large objects
* @deprecated Use extractKeyFieldsHash for better performance
*/
extractKeyFields(obj) {
if (!obj || typeof obj !== "object") {
return String(obj || "");
}
const record = obj;
// Pre-allocate array with known maximum size for better performance
const keyFields = [];
keyFields.length = 0; // Reset but keep allocated memory
// Extract only the most identifying fields - ordered by importance for early exit
const importantFields = [
"domainType",
"enhancementType",
"domainConfig",
"id",
"type",
"name",
];
// Use direct property access instead of 'in' operator for better performance
for (let i = 0; i < importantFields.length; i++) {
const field = importantFields[i];
const value = record[field];
if (value !== undefined) {
// Avoid template literals in hot path - use direct concatenation
keyFields.push(field + ":" + String(value));
}
}
// If no important fields found, use a more efficient fallback
if (keyFields.length === 0) {
// Cache the keys.length to avoid repeated Object.keys calls
keyFields.push("keys:" + Object.keys(record).length.toString());
}
return keyFields.join(",");
}
/**
* Fast non-cryptographic string hash function
* Much faster than MD5 for cache key generation
*/
fastStringHash(str) {
let hash = 0;
if (str.length === 0) {
return "0";
}
for (let i = 0; i < str.length; i++) {
const char = str.charCodeAt(i);
hash = (hash << 5) - hash + char;
hash = hash & hash; // Convert to 32bit integer
}
// Convert to unsigned 32-bit integer hex string to avoid hash collisions
return (hash >>> 0).toString(16);
}
/**
* Fast numeric hash function for strings
* Returns numeric hash instead of string for performance
*/
hashString(str) {
let hash = 0;
if (str.length === 0) {
return 0;
}
for (let i = 0; i < str.length; i++) {
const char = str.charCodeAt(i);
hash = (hash << 5) - hash + char;
hash = hash & hash; // Convert to 32bit integer
}
return hash >>> 0;
}
/**
* Hash random value to a numeric hash
* Optimized for different value types
*/
hashValue(value) {
if (value === null) {
return 0;
}
if (value === undefined) {
return 1;
}
const type = typeof value;
switch (type) {
case "string":
return this.hashString(value);
case "number":
return (Math.floor(value) >>> 0) % 2147483647;
case "boolean":
return value ? 1 : 0;
case "object":
// For objects, use a simple structural hash
try {
return this.hashString(JSON.stringify(value));
}
catch {
return this.hashString(String(value));
}
default:
return this.hashString(String(value));
}
}
/**
* Combine two numeric hashes efficiently
* Uses bitwise operations for maximum performance
*/
combineHashes(hash1, hash2) {
// Use a variation of hash combination that minimizes collisions
return ((hash1 << 5) + hash1 + hash2) & 0x7fffffff;
}
/**
* Get cached result if available
*/
get(options) {
this.stats.totalRequests++;
const key = this.generateCacheKey(options);
const cached = this.cache.get(key);
if (cached) {
// Update access info for LRU
cached.accessCount++;
cached.timestamp = Date.now();
// Move to end (most recently used)
this.cache.delete(key);
this.cache.set(key, cached);
this.stats.hits++;
logger.debug("Factory processing cache hit", {
key: key.substring(0, 8),
});
return cached.result;
}
this.stats.misses++;
return null;
}
/**
* Store result in cache
*/
set(options, result) {
try {
const key = this.generateCacheKey(options);
// Evict oldest entry if at capacity
if (this.cache.size >= this.maxSize) {
const oldestKey = this.cache.keys().next().value;
if (oldestKey !== undefined) {
this.cache.delete(oldestKey);
this.stats.evictions++;
}
}
this.cache.set(key, {
result: { ...result }, // Deep copy to prevent mutations
timestamp: Date.now(),
accessCount: 1,
});
logger.debug("Factory processing result cached", {
key: key.substring(0, 8),
cacheSize: this.cache.size,
});
}
catch (error) {
logger.warn("Failed to cache factory processing result", { error });
}
}
/**
* Clear cache (useful for testing or memory management)
*/
clear() {
const size = this.cache.size;
this.cache.clear();
logger.debug(`Factory processing cache cleared (${size} entries removed)`);
}
/**
* Get cache statistics
*/
getStats() {
const hitRate = this.stats.totalRequests > 0
? Math.round((this.stats.hits / this.stats.totalRequests) * 100 * 100) /
100
: 0;
return {
...this.stats,
size: this.cache.size,
hitRate,
};
}
/**
* Remove entries older than specified age (in milliseconds)
*/
evictOld(maxAge = 5 * 60 * 1000) {
// Default: 5 minutes
const now = Date.now();
let evicted = 0;
for (const [key, entry] of this.cache.entries()) {
if (now - entry.timestamp > maxAge) {
this.cache.delete(key);
evicted++;
}
}
if (evicted > 0) {
logger.debug(`Evicted ${evicted} old cache entries`);
this.stats.evictions += evicted;
}
return evicted;
}
}
// Global cache instance
const factoryProcessingCache = new FactoryProcessingCache();
/**
* Validates if a value conforms to JsonValue type
* JsonValue = string | number | boolean | null | JsonValue[] | { [key: string]: JsonValue }
*/
function isJsonValue(value) {
if (value === null ||
typeof value === "string" ||
typeof value === "number" ||
typeof value === "boolean") {
return true;
}
if (Array.isArray(value)) {
return value.every((item) => isJsonValue(item));
}
if (isNonNullObject(value)) {
const obj = value;
return Object.values(obj).every((val) => isJsonValue(val));
}
return false;
}
/**
* Safely converts unknown context values to JsonValue-compliant Record
* Filters out non-JsonValue compliant values and logs warnings
*/
function validateAndConvertContext(context) {
if (!context || typeof context !== "object" || Array.isArray(context)) {
logger.warn("Context must be a plain object, ignoring invalid context");
return {};
}
const validatedContext = {};
const obj = context;
for (const [key, value] of Object.entries(obj)) {
if (isJsonValue(value)) {
validatedContext[key] = value;
}
else {
logger.warn(`Context value for key "${key}" is not JsonValue compliant, excluding from context`);
}
}
return validatedContext;
}
/**
* Internal factory processing function (for caching)
*/
function processFactoryOptionsInternal(options) {
const functionTag = "processFactoryOptionsInternal";
try {
const factoryConfig = options.factoryConfig;
if (!factoryConfig) {
return { hasFactoryConfig: false };
}
logger.debug(`[${functionTag}] Processing factory configuration`, {
domainType: factoryConfig.domainType,
enhancementType: factoryConfig.enhancementType,
validateDomainData: factoryConfig.validateDomainData,
});
// Extract domain configuration
const domainType = factoryConfig.domainType;
const domainConfig = factoryConfig.domainConfig;
const enhancementType = factoryConfig.enhancementType;
// Create processed context that includes domain information with validation
const processedContext = {
...validateAndConvertContext(options.context),
};
// Add domain information to context if available
if (domainType) {
processedContext.domainType = domainType;
}
if (domainConfig) {
if (isJsonValue(domainConfig)) {
processedContext.domainConfig = domainConfig;
}
else {
logger.warn("Domain config is not JsonValue compliant, excluding from context");
}
}
if (enhancementType) {
processedContext.enhancementType = enhancementType;
}
// Add factory metadata
processedContext.factoryEnhanced = true;
processedContext.factoryProcessedAt = Date.now();
logger.debug(`[${functionTag}] Factory configuration processed successfully`, {
domainType,
enhancementType,
contextKeys: Object.keys(processedContext),
});
return {
hasFactoryConfig: true,
domainType,
domainConfig,
enhancementType,
processedContext,
};
}
catch (error) {
logger.warn(`[${functionTag}] Failed to process factory configuration`, {
error: error instanceof Error ? error.message : String(error),
});
return { hasFactoryConfig: false };
}
}
/**
* Process factory configuration from enhanced options (with caching)
* Extracts and validates factory config for provider integration
*/
export function processFactoryOptions(options) {
// Try to get result from cache first
const cachedResult = factoryProcessingCache.get(options);
if (cachedResult) {
return cachedResult;
}
// Process and cache the result
const result = processFactoryOptionsInternal(options);
factoryProcessingCache.set(options, result);
return result;
}
/**
* Enhance TextGenerationOptions with factory configuration
* Converts enhanced GenerateOptions/StreamOptions to internal format
*/
export function enhanceTextGenerationOptions(baseOptions, factoryResult) {
if (!factoryResult.hasFactoryConfig) {
return baseOptions;
}
// Validate and merge contexts to ensure JsonValue compliance
const validatedBaseContext = validateAndConvertContext(baseOptions.context);
const factoryProcessedContext = factoryResult.processedContext || {};
const enhanced = {
...baseOptions,
// Merge contexts with proper validation to prevent runtime type errors
context: {
...validatedBaseContext,
...factoryProcessedContext,
},
// Ensure evaluation is enabled when using factory patterns
enableEvaluation: baseOptions.enableEvaluation ?? true,
// Use domain type for evaluation if available
evaluationDomain: factoryResult.domainType || baseOptions.evaluationDomain,
};
logger.debug("Enhanced TextGenerationOptions with factory configuration", {
domainType: factoryResult.domainType,
enhancementType: factoryResult.enhancementType,
hasProcessedContext: !!factoryResult.processedContext,
});
return enhanced;
}
/**
* Check if options require factory processing
* Quick check to determine if factory enhancement is needed
*/
export function requiresFactoryProcessing(options) {
return !!options?.factoryConfig;
}
/**
* Extract streaming configuration for factory processing
* Handles streaming-specific factory enhancements
*/
export function processStreamingFactoryOptions(options) {
const streamingConfig = options.streaming;
if (!streamingConfig) {
return { hasStreamingConfig: false };
}
logger.debug("Processing streaming factory configuration", {
enabled: streamingConfig.enabled,
chunkSize: streamingConfig.chunkSize,
enableProgress: streamingConfig.enableProgress,
});
return {
hasStreamingConfig: true,
streamingEnabled: streamingConfig.enabled,
enhancedConfig: streamingConfig,
};
}
/**
* Convert enhanced StreamOptions back to clean StreamOptions
* Strips factory configuration while preserving enhanced context
*/
export function createCleanStreamOptions(enhancedOptions) {
const { factoryConfig, ...cleanOptions } = enhancedOptions;
// Return clean options without factoryConfig
return cleanOptions;
}
/**
* Validate factory configuration
* Ensures factory config is valid before processing
*/
export function validateFactoryConfig(factoryConfig) {
const errors = [];
if (!factoryConfig) {
return { isValid: true, errors: [] }; // No config is valid
}
// Validate domain type if present
if (factoryConfig.domainType !== undefined) {
if (typeof factoryConfig.domainType !== "string") {
errors.push("domainType must be a string");
}
else if (factoryConfig.domainType.length === 0) {
// Empty string is allowed (will be converted to "generic")
logger.debug("Empty domainType will be converted to 'generic'");
}
}
// Validate domain config if present
if (factoryConfig.domainConfig !== undefined) {
if (typeof factoryConfig.domainConfig !== "object" ||
factoryConfig.domainConfig === null) {
errors.push("domainConfig must be an object");
}
}
// Validate enhancement type if present
if (factoryConfig.enhancementType !== undefined) {
const validTypes = [
"domain-configuration",
"streaming-optimization",
"mcp-integration",
"legacy-migration",
"context-conversion",
];
if (!validTypes.includes(factoryConfig.enhancementType)) {
errors.push(`enhancementType must be one of: ${validTypes.join(", ")}`);
}
}
const isValid = errors.length === 0;
if (!isValid) {
logger.warn("Factory configuration validation failed", { errors });
}
return { isValid, errors };
}
/**
* Get factory processing cache statistics
* Useful for monitoring cache performance and debugging
*/
export function getFactoryProcessingCacheStats() {
return factoryProcessingCache.getStats();
}
/**
* Clear factory processing cache
* Useful for testing or memory management
*/
export function clearFactoryProcessingCache() {
factoryProcessingCache.clear();
}
/**
* Evict old entries from factory processing cache
* Useful for periodic cleanup
*/
export function evictOldFactoryProcessingCache(maxAge) {
return factoryProcessingCache.evictOld(maxAge);
}