anon-identity
Version:
Decentralized identity framework with DIDs, Verifiable Credentials, and privacy-preserving selective disclosure
853 lines • 32.2 kB
JavaScript
"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