UNPKG

anon-identity

Version:

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

605 lines 20.1 kB
"use strict"; /** * Stream Manager for MCP * * Manages streaming responses for real-time LLM interactions */ Object.defineProperty(exports, "__esModule", { value: true }); exports.StreamManager = void 0; const events_1 = require("events"); const types_1 = require("../types"); /** * Stream Manager */ class StreamManager extends events_1.EventEmitter { constructor(messageRouter, authManager, auditLogger, config = { streaming: { enabled: true, chunkSize: 512, flushInterval: 100, maxConcurrentStreams: 10, backpressureThreshold: 1000, compressionEnabled: false }, buffer: { maxSize: 2048, flushInterval: 150, flushOnComplete: true, adaptiveBuffering: true }, realTime: { enableInterruption: true, priorityPreemption: true, multiplexing: true, backpressureHandling: 'buffer', qualityOfService: { targetLatency: 50, maxJitter: 20, adaptiveQuality: true } }, maxConcurrentStreams: 20, sessionTimeout: 300000 // 5 minutes }) { super(); this.messageRouter = messageRouter; this.authManager = authManager; this.auditLogger = auditLogger; this.config = config; this.activeSessions = new Map(); this.sessionBuffers = new Map(); this.sessionTimers = new Map(); this.priorityQueue = new PriorityQueue(); this.realTimeProcessor = new RealTimeProcessor(this.config.realTime); this.setupEventHandlers(); } /** * Start streaming session */ async startStream(request, options = {}) { // Check authorization const authResult = await this.authManager.authorize(request.agentDID, 'stream:create', 'execute'); if (!authResult.authorized) { throw new types_1.MCPError({ code: types_1.MCPErrorCode.FORBIDDEN, message: 'Not authorized to create stream', timestamp: new Date(), retryable: false }); } // Check concurrent stream limit if (this.activeSessions.size >= this.config.maxConcurrentStreams) { if (this.config.realTime.priorityPreemption && options.priority === 'critical') { await this.preemptLowestPriorityStream(); } else { throw new types_1.MCPError({ code: types_1.MCPErrorCode.RATE_LIMITED, message: 'Maximum concurrent streams reached', timestamp: new Date(), retryable: true }); } } // Create session const sessionId = `stream-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`; const session = { id: sessionId, agentDID: request.agentDID, requestId: request.id, providerId: 'auto-select', // Will be updated when provider is selected status: 'active', startedAt: new Date(), totalChunks: 0, totalTokens: 0, metadata: { purpose: request.metadata?.purpose || 'general', priority: options.priority || 'medium', maxDuration: options.maxDuration, bufferSize: options.bufferConfig?.maxSize } }; // Create buffer const bufferConfig = { ...this.config.buffer, ...options.bufferConfig }; const buffer = new StreamBuffer(sessionId, bufferConfig); this.activeSessions.set(sessionId, session); this.sessionBuffers.set(sessionId, buffer); // Add to priority queue this.priorityQueue.enqueue(session, this.getPriorityScore(session)); // Set session timeout if (options.maxDuration || this.config.sessionTimeout) { const timeout = setTimeout(() => { this.timeoutSession(sessionId); }, options.maxDuration || this.config.sessionTimeout); this.sessionTimers.set(sessionId, timeout); } // Start real-time processing this.realTimeProcessor.startSession(session); // Audit stream creation await this.auditLogger.logRequest(request, request.agentDID, sessionId); this.emit('stream_started', session); // Start the actual streaming this.processStreamRequest(session, request, options).catch(error => { this.handleStreamError(sessionId, error); }); return session; } /** * Process streaming request */ async processStreamRequest(session, request, options) { try { // Route request with streaming enabled const streamingRequest = { ...request, streaming: true, metadata: { ...request.metadata, sessionId: session.id, streaming: true } }; // Get stream from message router const stream = await this.messageRouter.routeStreamingMessage(streamingRequest); // Update session with actual provider session.providerId = stream.providerId || 'unknown'; const buffer = this.sessionBuffers.get(session.id); let accumulatedContent = ''; let chunkIndex = 0; // Process stream chunks for await (const chunk of stream) { if (session.status !== 'active') { break; // Session was cancelled or paused } const enhancedChunk = await this.processChunk(session, chunk, chunkIndex++, buffer); accumulatedContent += chunk.delta || ''; session.totalChunks++; session.totalTokens += chunk.tokens || 0; session.lastChunkAt = new Date(); // Call chunk handler if (options.onChunk) { options.onChunk(enhancedChunk); } this.emit('chunk_received', enhancedChunk); // Check for natural breakpoints if (this.isNaturalBreakpoint(accumulatedContent) && buffer.shouldFlush()) { await this.flushBuffer(session.id); } // Apply backpressure if needed if (this.shouldApplyBackpressure(session)) { await this.applyBackpressure(session); } } // Flush final buffer await this.flushBuffer(session.id, true); // Create final response const finalResponse = { id: `response-${session.id}`, content: accumulatedContent, role: types_1.MessageRole.ASSISTANT, model: stream.model || 'unknown', provider: session.providerId, tokens: session.totalTokens, finishReason: 'stop', timestamp: new Date(), metadata: { sessionId: session.id, totalChunks: session.totalChunks, streamDuration: Date.now() - session.startedAt.getTime() } }; // Complete session session.status = 'completed'; session.completedAt = new Date(); // Call completion handler if (options.onComplete) { options.onComplete(finalResponse); } this.emit('stream_completed', { session, response: finalResponse }); // Cleanup await this.cleanupSession(session.id); } catch (error) { await this.handleStreamError(session.id, error); if (options.onError) { options.onError(error); } } } /** * Process individual chunk */ async processChunk(session, chunk, chunkIndex, buffer) { const startTime = Date.now(); // Add to buffer if (chunk.delta) { buffer.add(chunk.delta); } // Calculate latency const latency = Date.now() - (chunk.timestamp?.getTime() || startTime); // Create enhanced chunk const enhancedChunk = { ...chunk, sessionId: session.id, chunkIndex, bufferStatus: { size: buffer.getSize(), flushed: false }, latency, timestamp: new Date() }; // Real-time processing await this.realTimeProcessor.processChunk(session, enhancedChunk); // Quality of service monitoring this.monitorQualityOfService(session, enhancedChunk); return enhancedChunk; } /** * Check if natural breakpoint */ isNaturalBreakpoint(content) { // Look for sentence endings, paragraph breaks, or logical pauses const patterns = [ /\.\s*$/, // Sentence ending /\?\s*$/, // Question ending /!\s*$/, // Exclamation ending /\n\n/, // Paragraph break /:\s*$/, // Colon (list start) /,\s*$/ // Comma pause ]; return patterns.some(pattern => pattern.test(content.slice(-20))); } /** * Flush buffer */ async flushBuffer(sessionId, force = false) { const buffer = this.sessionBuffers.get(sessionId); if (!buffer) return; if (force || buffer.shouldFlush()) { const content = buffer.flush(); if (content) { this.emit('buffer_flushed', { sessionId, content, size: content.length, forced: force }); } } } /** * Check if backpressure should be applied */ shouldApplyBackpressure(session) { const buffer = this.sessionBuffers.get(session.id); if (!buffer) return false; // Check buffer size if (buffer.getSize() > this.config.streaming.backpressureThreshold) { return true; } // Check system load if (this.activeSessions.size > this.config.maxConcurrentStreams * 0.8) { return true; } return false; } /** * Apply backpressure */ async applyBackpressure(session) { switch (this.config.realTime.backpressureHandling) { case 'buffer': // Buffer is already handling this break; case 'throttle': await new Promise(resolve => setTimeout(resolve, 50)); break; case 'drop': // Skip this chunk (already processed) break; } } /** * Monitor quality of service */ monitorQualityOfService(session, chunk) { const qos = this.config.realTime.qualityOfService; // Check latency if (chunk.latency > qos.targetLatency + qos.maxJitter) { this.emit('qos_violation', { sessionId: session.id, metric: 'latency', value: chunk.latency, threshold: qos.targetLatency + qos.maxJitter }); // Apply adaptive quality if enabled if (qos.adaptiveQuality) { this.adaptStreamQuality(session, 'reduce'); } } } /** * Adapt stream quality */ adaptStreamQuality(session, direction) { const buffer = this.sessionBuffers.get(session.id); if (!buffer) return; if (direction === 'reduce') { // Increase buffer size to reduce chunk frequency buffer.adaptBufferSize(1.2); } else { // Decrease buffer size for better responsiveness buffer.adaptBufferSize(0.8); } this.emit('quality_adapted', { sessionId: session.id, direction, newBufferSize: buffer.getMaxSize() }); } /** * Pause stream */ async pauseStream(sessionId) { const session = this.activeSessions.get(sessionId); if (!session) { throw new types_1.MCPError({ code: types_1.MCPErrorCode.INVALID_REQUEST, message: 'Stream session not found', timestamp: new Date(), retryable: false }); } session.status = 'paused'; this.realTimeProcessor.pauseSession(sessionId); this.emit('stream_paused', session); } /** * Resume stream */ async resumeStream(sessionId) { const session = this.activeSessions.get(sessionId); if (!session) { throw new types_1.MCPError({ code: types_1.MCPErrorCode.INVALID_REQUEST, message: 'Stream session not found', timestamp: new Date(), retryable: false }); } session.status = 'active'; this.realTimeProcessor.resumeSession(sessionId); this.emit('stream_resumed', session); } /** * Cancel stream */ async cancelStream(sessionId) { const session = this.activeSessions.get(sessionId); if (!session) return; session.status = 'cancelled'; session.completedAt = new Date(); // Flush any remaining buffer content await this.flushBuffer(sessionId, true); this.emit('stream_cancelled', session); await this.cleanupSession(sessionId); } /** * Handle stream error */ async handleStreamError(sessionId, error) { const session = this.activeSessions.get(sessionId); if (session) { session.status = 'error'; session.completedAt = new Date(); } this.emit('stream_error', { sessionId, error }); await this.cleanupSession(sessionId); } /** * Timeout session */ async timeoutSession(sessionId) { const session = this.activeSessions.get(sessionId); if (session && session.status === 'active') { await this.cancelStream(sessionId); this.emit('stream_timeout', session); } } /** * Preempt lowest priority stream */ async preemptLowestPriorityStream() { const lowestPrioritySession = this.priorityQueue.dequeue(); if (lowestPrioritySession) { await this.cancelStream(lowestPrioritySession.id); this.emit('stream_preempted', lowestPrioritySession); } } /** * Get priority score */ getPriorityScore(session) { const priorityScores = { critical: 4, high: 3, medium: 2, low: 1 }; return priorityScores[session.metadata.priority] || 2; } /** * Cleanup session */ async cleanupSession(sessionId) { this.activeSessions.delete(sessionId); this.sessionBuffers.delete(sessionId); this.realTimeProcessor.endSession(sessionId); const timer = this.sessionTimers.get(sessionId); if (timer) { clearTimeout(timer); this.sessionTimers.delete(sessionId); } } /** * Setup event handlers */ setupEventHandlers() { this.on('stream_error', ({ sessionId, error }) => { console.error(`Stream error in session ${sessionId}:`, error); }); this.on('qos_violation', ({ sessionId, metric, value, threshold }) => { console.warn(`QoS violation in session ${sessionId}: ${metric} = ${value} (threshold: ${threshold})`); }); } /** * Get active sessions */ getActiveSessions() { return Array.from(this.activeSessions.values()); } /** * Get session statistics */ getSessionStatistics(sessionId) { const session = this.activeSessions.get(sessionId); if (!session) return null; const duration = Date.now() - session.startedAt.getTime(); const durationSeconds = duration / 1000; return { duration, chunksPerSecond: durationSeconds > 0 ? session.totalChunks / durationSeconds : 0, tokensPerSecond: durationSeconds > 0 ? session.totalTokens / durationSeconds : 0, averageLatency: 0, // Would track this bufferUtilization: 0 // Would calculate from buffer }; } /** * Shutdown */ shutdown() { // Cancel all active sessions for (const sessionId of this.activeSessions.keys()) { this.cancelStream(sessionId).catch(() => { }); } // Clear timers for (const timer of this.sessionTimers.values()) { clearTimeout(timer); } this.realTimeProcessor.shutdown(); this.removeAllListeners(); } } exports.StreamManager = StreamManager; /** * Stream Buffer */ class StreamBuffer { constructor(sessionId, config) { this.sessionId = sessionId; this.config = config; this.buffer = ''; this.lastFlush = new Date(); } add(content) { this.buffer += content; } shouldFlush() { // Size-based flush if (this.buffer.length >= this.config.maxSize) { return true; } // Time-based flush if (Date.now() - this.lastFlush.getTime() >= this.config.flushInterval) { return true; } // Complete thought flush if (this.config.flushOnComplete && this.isCompleteThought()) { return true; } return false; } flush() { const content = this.buffer; this.buffer = ''; this.lastFlush = new Date(); return content; } getSize() { return this.buffer.length; } getMaxSize() { return this.config.maxSize; } adaptBufferSize(factor) { if (this.config.adaptiveBuffering) { this.config.maxSize = Math.max(256, Math.min(4096, Math.round(this.config.maxSize * factor))); } } isCompleteThought() { // Simple heuristic for complete thoughts return /[.!?]\s*$/.test(this.buffer.trim()); } } /** * Real-Time Processor */ class RealTimeProcessor { constructor(config) { this.config = config; this.activeSessions = new Set(); this.pausedSessions = new Set(); } startSession(session) { this.activeSessions.add(session.id); } pauseSession(sessionId) { this.pausedSessions.add(sessionId); } resumeSession(sessionId) { this.pausedSessions.delete(sessionId); } endSession(sessionId) { this.activeSessions.delete(sessionId); this.pausedSessions.delete(sessionId); } async processChunk(session, chunk) { if (this.pausedSessions.has(session.id)) { return; // Skip processing for paused sessions } // Real-time processing logic would go here // For now, just track the chunk } shutdown() { this.activeSessions.clear(); this.pausedSessions.clear(); } } /** * Priority Queue */ class PriorityQueue { constructor() { this.items = []; } enqueue(item, priority) { this.items.push({ item, priority }); this.items.sort((a, b) => a.priority - b.priority); // Lower priority number = higher priority } dequeue() { const item = this.items.shift(); return item ? item.item : null; } size() { return this.items.length; } } exports.default = StreamManager; //# sourceMappingURL=stream-manager.js.map