UNPKG

anon-identity

Version:

Decentralized identity framework with DIDs, Verifiable Credentials, and privacy-preserving selective disclosure

853 lines 32.2 kB
"use strict"; /** * Secure Message Router for MCP * * Routes messages between agents and LLM providers with security, monitoring, and reliability */ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.MessageRouter = void 0; const events_1 = require("events"); const types_1 = require("../types"); /** * Secure Message Router */ class MessageRouter extends events_1.EventEmitter { constructor(mcpClient, authManager, auditLogger, rateLimiter, credentialManager, config = { maxRetries: 3, retryDelay: 1000, timeout: 30000, circuitBreaker: { enabled: true, threshold: 5, resetTimeout: 60000 }, loadBalancing: { enabled: true, strategy: 'latency-based' }, encryption: { enabled: true, algorithm: 'aes-256-gcm' } }) { super(); this.mcpClient = mcpClient; this.authManager = authManager; this.auditLogger = auditLogger; this.rateLimiter = rateLimiter; this.credentialManager = credentialManager; this.config = config; this.messageQueue = new Map(); this.activeRequests = new Map(); this.providerHealth = new Map(); this.circuitBreakers = new Map(); this.isShuttingDown = false; this.initializeQueues(); this.loadBalancer = new LoadBalancer(config.loadBalancing.strategy); this.setupHealthMonitoring(); this.startQueueProcessor(); if (config.encryption.enabled) { this.initializeEncryption(); } } /** * Route message to LLM provider */ async routeMessage(request) { const routedMessage = { id: `msg-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`, originalRequest: request, routedTo: '', timestamp: new Date(), attempts: 0, status: 'pending', metrics: { queueTime: 0, routingTime: 0, responseTime: 0, totalTime: 0 } }; this.activeRequests.set(routedMessage.id, routedMessage); try { // Security checks await this.performSecurityChecks(request); // Encrypt sensitive data if enabled if (this.config.encryption.enabled && request.prompt) { request = await this.encryptRequest(request); } // Queue or process immediately based on priority if (request.metadata?.priority === types_1.RequestPriority.CRITICAL) { return await this.processMessageImmediately(routedMessage); } else { return await this.queueMessage(routedMessage); } } catch (error) { routedMessage.status = 'failed'; routedMessage.error = error; await this.auditLogger.logResponse({ id: routedMessage.id, status: 'error', error: error, timestamp: new Date(), provider: '', model: '', usage: { totalTokens: 0, promptTokens: 0, completionTokens: 0, model: '', provider: '' } }, request, Date.now() - routedMessage.timestamp.getTime()); throw error; } finally { this.activeRequests.delete(routedMessage.id); } } /** * Stream message to LLM provider */ async *streamMessage(request) { const routedMessage = { id: `stream-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`, originalRequest: request, routedTo: '', timestamp: new Date(), attempts: 0, status: 'pending', metrics: { queueTime: 0, routingTime: 0, responseTime: 0, totalTime: 0 } }; this.activeRequests.set(routedMessage.id, routedMessage); try { // Security checks await this.performSecurityChecks(request); // Select provider const provider = await this.selectProvider(request); routedMessage.routedTo = provider.id; // Get connection const connection = await this.mcpClient.getConnection(provider.id); if (!connection) { throw new types_1.MCPError({ code: types_1.MCPErrorCode.CONNECTION_ERROR, message: `No connection available for provider ${provider.id}`, timestamp: new Date(), provider: provider.id, retryable: true }); } // Start streaming const startTime = Date.now(); let totalTokens = 0; for await (const chunk of connection.streamRequest(request)) { totalTokens += chunk.tokens || 0; // Decrypt chunk if needed if (this.config.encryption.enabled && chunk.content) { chunk.content = await this.decryptContent(chunk.content); } yield chunk; } // Log streaming completion await this.auditLogger.logResponse({ id: routedMessage.id, status: 'success', timestamp: new Date(), provider: provider.id, model: request.parameters?.model || 'unknown', usage: { totalTokens, promptTokens: 0, completionTokens: totalTokens, model: request.parameters?.model || 'unknown', provider: provider.id }, streaming: true }, request, Date.now() - startTime); // Update provider health this.updateProviderHealth(provider.id, true, Date.now() - startTime); } catch (error) { routedMessage.status = 'failed'; routedMessage.error = error; this.updateProviderHealth(routedMessage.routedTo, false); throw error; } finally { this.activeRequests.delete(routedMessage.id); } } /** * Perform security checks */ async performSecurityChecks(request) { // Check authentication const authResult = await this.authManager.authorize(request.agentDID, 'llm:request', request.type); if (!authResult.authorized) { throw new types_1.MCPError({ code: types_1.MCPErrorCode.FORBIDDEN, message: `Authorization denied: ${authResult.deniedReasons?.join(', ')}`, timestamp: new Date(), retryable: false }); } // Check rate limits const rateLimitResult = await this.rateLimiter.checkRateLimit(request); if (!rateLimitResult.allowed) { await this.auditLogger.logSecurityAlert('rate_limit_exceeded', request.agentDID, { request, reason: rateLimitResult.reason }); throw new types_1.MCPError({ code: types_1.MCPErrorCode.RATE_LIMITED, message: rateLimitResult.reason || 'Rate limit exceeded', timestamp: new Date(), retryable: true, details: { retryAfter: rateLimitResult.retryAfter, resetAt: rateLimitResult.resetAt } }); } // Check for suspicious patterns await this.detectSuspiciousActivity(request); } /** * Detect suspicious activity */ async detectSuspiciousActivity(request) { const suspiciousPatterns = [ /jailbreak/i, /ignore.*instructions/i, /system.*prompt/i, /reveal.*instructions/i ]; const isSuspicious = suspiciousPatterns.some(pattern => pattern.test(request.prompt)); if (isSuspicious) { await this.auditLogger.logSecurityAlert('suspicious_prompt', request.agentDID, { prompt: request.prompt.substring(0, 100), patterns: 'jailbreak_attempt' }); this.emit('security_alert', { type: 'suspicious_prompt', agentDID: request.agentDID, timestamp: new Date() }); } } /** * Select provider based on strategy */ async selectProvider(request) { const availableProviders = await this.getAvailableProviders(request); if (availableProviders.length === 0) { throw new types_1.MCPError({ code: types_1.MCPErrorCode.NO_AVAILABLE_PROVIDERS, message: 'No available providers for request', timestamp: new Date(), retryable: true }); } // Apply load balancing strategy const selected = this.loadBalancer.selectProvider(availableProviders, this.providerHealth, request); // Check circuit breaker if (this.config.circuitBreaker.enabled) { const breaker = this.circuitBreakers.get(selected.id); if (breaker?.open && breaker.resetTime > new Date()) { // Try next best provider const alternative = availableProviders.find(p => p.id !== selected.id); if (alternative) { return alternative; } } } return selected; } /** * Get available providers */ async getAvailableProviders(request) { const providers = await this.mcpClient.getAvailableProviders(); const availableProviders = []; for (const providerId of providers) { // Check if provider supports request type const capabilities = await this.mcpClient.getProviderCapabilities(providerId); if (!this.supportsRequest(capabilities, request)) { continue; } // Check provider health const health = this.providerHealth.get(providerId); if (health?.status === types_1.ConnectionStatus.ERROR) { continue; } // Check credentials const hasCredentials = await this.credentialManager.getCredential(providerId); if (!hasCredentials) { continue; } // Calculate provider score const score = this.calculateProviderScore(providerId, health, request); availableProviders.push({ id: providerId, score }); } // Sort by score return availableProviders.sort((a, b) => b.score - a.score); } /** * Calculate provider score */ calculateProviderScore(providerId, health, request) { let score = 100; // Health metrics if (health) { score *= health.successRate; score -= Math.min(health.averageLatency / 100, 50); // Penalize high latency score -= health.errorCount * 5; } // Request-specific scoring const provider = this.mcpClient.getProvider(providerId); // Provider capability scoring would go here when more detailed capability info is available // Priority boost for preferred providers if (request.metadata?.preferredProvider === providerId) { score *= 1.5; } return Math.max(0, Math.min(100, score)); } /** * Process message immediately */ async processMessageImmediately(routedMessage) { const startTime = Date.now(); for (let attempt = 0; attempt < this.config.maxRetries; attempt++) { routedMessage.attempts = attempt + 1; try { // Select provider const provider = await this.selectProvider(routedMessage.originalRequest); routedMessage.routedTo = provider.id; // Route to provider const response = await this.sendToProvider(provider.id, routedMessage.originalRequest); // Update metrics routedMessage.metrics.responseTime = Date.now() - startTime; routedMessage.metrics.totalTime = Date.now() - routedMessage.timestamp.getTime(); routedMessage.status = 'success'; routedMessage.response = response; // Update provider health this.updateProviderHealth(provider.id, true, routedMessage.metrics.responseTime); // Decrypt response if needed if (this.config.encryption.enabled && response.content) { response.content = await this.decryptContent(response.content); } return response; } catch (error) { const mcpError = error; // Update provider health if (routedMessage.routedTo) { this.updateProviderHealth(routedMessage.routedTo, false); } // Check if retryable if (!mcpError.retryable || attempt === this.config.maxRetries - 1) { routedMessage.status = 'failed'; routedMessage.error = mcpError; throw error; } // Wait before retry await new Promise(resolve => setTimeout(resolve, this.config.retryDelay * Math.pow(2, attempt))); } } throw new types_1.MCPError({ code: types_1.MCPErrorCode.MAX_RETRIES_EXCEEDED, message: 'Maximum retries exceeded', timestamp: new Date(), retryable: false }); } /** * Queue message for processing */ async queueMessage(routedMessage) { return new Promise((resolve, reject) => { const priority = routedMessage.originalRequest.metadata?.priority || types_1.RequestPriority.MEDIUM; const queueEntry = { request: routedMessage.originalRequest, priority, timestamp: new Date(), resolve, reject }; // Set timeout if (this.config.timeout) { queueEntry.timeout = setTimeout(() => { reject(new types_1.MCPError({ code: types_1.MCPErrorCode.TIMEOUT, message: 'Request timeout', timestamp: new Date(), retryable: true })); // Remove from queue const queue = this.messageQueue.get(priority); if (queue) { const index = queue.indexOf(queueEntry); if (index !== -1) { queue.splice(index, 1); } } }, this.config.timeout); } // Add to queue const queue = this.messageQueue.get(priority) || []; queue.push(queueEntry); this.messageQueue.set(priority, queue); // Update metrics routedMessage.metrics.queueTime = Date.now() - routedMessage.timestamp.getTime(); }); } /** * Send request to provider */ async sendToProvider(providerId, request) { const connection = await this.mcpClient.getConnection(providerId); if (!connection) { throw new types_1.MCPError({ code: types_1.MCPErrorCode.CONNECTION_ERROR, message: `No connection available for provider ${providerId}`, timestamp: new Date(), provider: providerId, retryable: true }); } // Log request const auditId = await this.auditLogger.logRequest(request, request.agentDID, request.sessionId); try { const response = await connection.sendRequest(request); // Log response await this.auditLogger.logResponse(response, request, response.metadata?.duration || 0); // Record usage if (response.usage) { await this.rateLimiter.recordUsage(request.agentDID, response.usage, response.provider, response.model); } return response; } catch (error) { // Log error await this.auditLogger.logResponse({ id: auditId, status: 'error', error: error, timestamp: new Date(), provider: providerId, model: request.parameters?.model || 'unknown', usage: { totalTokens: 0, promptTokens: 0, completionTokens: 0, model: '', provider: '' } }, request, 0); throw error; } } /** * Initialize queues */ initializeQueues() { this.messageQueue.set(types_1.RequestPriority.LOW, []); this.messageQueue.set(types_1.RequestPriority.MEDIUM, []); this.messageQueue.set(types_1.RequestPriority.HIGH, []); this.messageQueue.set(types_1.RequestPriority.CRITICAL, []); } /** * Start queue processor */ startQueueProcessor() { setInterval(() => { if (this.isShuttingDown) return; this.processQueues(); }, 100); // Process every 100ms } /** * Process message queues */ async processQueues() { // Process in priority order const priorities = [ types_1.RequestPriority.CRITICAL, types_1.RequestPriority.HIGH, types_1.RequestPriority.MEDIUM, types_1.RequestPriority.LOW ]; for (const priority of priorities) { const queue = this.messageQueue.get(priority); if (!queue || queue.length === 0) continue; // Process first message in queue const queueEntry = queue.shift(); if (!queueEntry) continue; // Clear timeout if (queueEntry.timeout) { clearTimeout(queueEntry.timeout); } // Create routed message const routedMessage = { id: `queued-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`, originalRequest: queueEntry.request, routedTo: '', timestamp: queueEntry.timestamp, attempts: 0, status: 'pending', metrics: { queueTime: Date.now() - queueEntry.timestamp.getTime(), routingTime: 0, responseTime: 0, totalTime: 0 } }; // Process message try { const response = await this.processMessageImmediately(routedMessage); queueEntry.resolve(response); } catch (error) { queueEntry.reject(error); } // Only process one message per cycle to maintain fairness break; } } /** * Update provider health metrics */ updateProviderHealth(providerId, success, latency) { const health = this.providerHealth.get(providerId) || { providerId, status: types_1.ConnectionStatus.CONNECTED, successRate: 1, averageLatency: 0, errorCount: 0, circuitBreakerOpen: false }; // Update success rate (exponential moving average) const alpha = 0.1; // Smoothing factor health.successRate = alpha * (success ? 1 : 0) + (1 - alpha) * health.successRate; // Update latency if (success && latency) { health.averageLatency = alpha * latency + (1 - alpha) * health.averageLatency; health.lastSuccess = new Date(); } // Update error count if (!success) { health.errorCount++; // Check circuit breaker if (this.config.circuitBreaker.enabled && health.errorCount >= this.config.circuitBreaker.threshold) { this.openCircuitBreaker(providerId); health.circuitBreakerOpen = true; } } else { health.errorCount = 0; } // Update status if (health.successRate < 0.5) { health.status = types_1.ConnectionStatus.ERROR; } else if (health.successRate < 0.8) { health.status = types_1.ConnectionStatus.RECONNECTING; } else { health.status = types_1.ConnectionStatus.CONNECTED; } this.providerHealth.set(providerId, health); this.emit('provider_health_updated', health); } /** * Open circuit breaker for provider */ openCircuitBreaker(providerId) { const resetTime = new Date(Date.now() + this.config.circuitBreaker.resetTimeout); this.circuitBreakers.set(providerId, { open: true, resetTime }); this.emit('circuit_breaker_open', { providerId, resetTime }); // Schedule circuit breaker reset setTimeout(() => { this.circuitBreakers.delete(providerId); const health = this.providerHealth.get(providerId); if (health) { health.circuitBreakerOpen = false; health.errorCount = 0; } this.emit('circuit_breaker_reset', { providerId }); }, this.config.circuitBreaker.resetTimeout); } /** * Setup health monitoring */ setupHealthMonitoring() { setInterval(async () => { if (this.isShuttingDown) return; for (const [providerId] of this.providerHealth) { try { const connection = await this.mcpClient.getConnection(providerId); if (connection) { const health = await connection.health(); if (health.status === 'healthy') { this.updateProviderHealth(providerId, true, health.latency); } else { this.updateProviderHealth(providerId, false); } } } catch (error) { this.updateProviderHealth(providerId, false); } } }, 30000); // Check every 30 seconds } /** * Initialize encryption */ async initializeEncryption() { // In production, get key from secure key management const crypto = await Promise.resolve().then(() => __importStar(require('crypto'))); this.encryptionKey = crypto.randomBytes(32); } /** * Encrypt request */ async encryptRequest(request) { if (!this.encryptionKey) return request; const encrypted = { ...request }; if (encrypted.prompt) { encrypted.prompt = await this.encryptContent(encrypted.prompt); } return encrypted; } /** * Encrypt content */ async encryptContent(content) { if (!this.encryptionKey) return content; const crypto = await Promise.resolve().then(() => __importStar(require('crypto'))); const iv = crypto.randomBytes(16); const cipher = crypto.createCipheriv(this.config.encryption.algorithm, this.encryptionKey, iv); let encrypted = cipher.update(content, 'utf8', 'base64'); encrypted += cipher.final('base64'); const tag = cipher.getAuthTag(); return JSON.stringify({ encrypted, iv: iv.toString('base64'), tag: tag.toString('base64') }); } /** * Decrypt content */ async decryptContent(encryptedData) { if (!this.encryptionKey) return encryptedData; try { const { encrypted, iv, tag } = JSON.parse(encryptedData); const crypto = await Promise.resolve().then(() => __importStar(require('crypto'))); const decipher = crypto.createDecipheriv(this.config.encryption.algorithm, this.encryptionKey, Buffer.from(iv, 'base64')); decipher.setAuthTag(Buffer.from(tag, 'base64')); let decrypted = decipher.update(encrypted, 'base64', 'utf8'); decrypted += decipher.final('utf8'); return decrypted; } catch { // If decryption fails, assume content is not encrypted return encryptedData; } } /** * Check if provider supports request */ supportsRequest(capabilities, request) { switch (request.type) { case types_1.LLMRequestType.COMPLETION: return capabilities?.completion === true; case types_1.LLMRequestType.FUNCTION_CALL: return capabilities?.functionCalling === true; case types_1.LLMRequestType.EMBEDDING: return capabilities?.embeddings === true; case types_1.LLMRequestType.MODERATION: return capabilities?.moderation === true; default: return false; } } /** * Get routing statistics */ getStatistics() { const queuedRequests = {}; for (const [priority, queue] of this.messageQueue) { queuedRequests[priority] = queue.length; } const providerHealthArray = Array.from(this.providerHealth.values()); const overallSuccessRate = providerHealthArray.reduce((sum, h) => sum + h.successRate, 0) / (providerHealthArray.length || 1); const overallLatency = providerHealthArray.reduce((sum, h) => sum + h.averageLatency, 0) / (providerHealthArray.length || 1); const circuitBreakerStatus = Array.from(this.circuitBreakers.entries()).map(([providerId, breaker]) => ({ providerId, open: breaker.open, resetTime: breaker.resetTime })); return { activeRequests: this.activeRequests.size, queuedRequests, providerHealth: providerHealthArray, circuitBreakers: circuitBreakerStatus, successRate: overallSuccessRate, averageLatency: overallLatency }; } /** * Route streaming message */ async routeStreamingMessage(request) { // Set streaming flag const streamingRequest = { ...request, streaming: true }; // Route to provider using standard routing logic const response = await this.routeMessage(streamingRequest); // Create async iterable for streaming async function* streamGenerator() { // Mock streaming implementation const content = response.content || ''; const chunks = content.split(' '); for (let i = 0; i < chunks.length; i++) { const chunk = { id: `chunk-${i}`, type: 'chunk', delta: i === 0 ? chunks[i] : ' ' + chunks[i], tokens: 1, timestamp: new Date(), metadata: { chunkIndex: i, isLast: i === chunks.length - 1 } }; yield chunk; // Add small delay to simulate streaming await new Promise(resolve => setTimeout(resolve, 50)); } } const stream = streamGenerator(); stream.providerId = response.provider; stream.model = response.model; return stream; } /** * Shutdown router */ async shutdown() { this.isShuttingDown = true; // Clear all queues for (const [, queue] of this.messageQueue) { for (const entry of queue) { if (entry.timeout) { clearTimeout(entry.timeout); } entry.reject(new types_1.MCPError({ code: types_1.MCPErrorCode.PROVIDER_ERROR, message: 'Router shutting down', timestamp: new Date(), retryable: false })); } } this.messageQueue.clear(); this.activeRequests.clear(); this.providerHealth.clear(); this.circuitBreakers.clear(); this.removeAllListeners(); } } exports.MessageRouter = MessageRouter; /** * Load balancer for provider selection */ class LoadBalancer { constructor(strategy) { this.strategy = strategy; this.roundRobinIndex = 0; } selectProvider(providers, health, request) { switch (this.strategy) { case 'round-robin': return this.roundRobin(providers); case 'least-connections': return this.leastConnections(providers, health); case 'weighted': return this.weighted(providers); case 'latency-based': return this.latencyBased(providers, health); default: return providers[0]; } } roundRobin(providers) { const selected = providers[this.roundRobinIndex % providers.length]; this.roundRobinIndex++; return selected; } leastConnections(providers, health) { // In a real implementation, track active connections per provider return providers[0]; } weighted(providers) { // Select based on scores const totalScore = providers.reduce((sum, p) => sum + p.score, 0); let random = Math.random() * totalScore; for (const provider of providers) { random -= provider.score; if (random <= 0) { return provider; } } return providers[0]; } latencyBased(providers, health) { // Sort by latency return providers.sort((a, b) => { const healthA = health.get(a.id); const healthB = health.get(b.id); const latencyA = healthA?.averageLatency || Infinity; const latencyB = healthB?.averageLatency || Infinity; return latencyA - latencyB; })[0]; } } exports.default = MessageRouter; //# sourceMappingURL=message-router.js.map