route-claudecode
Version:
Advanced routing and transformation system for Claude Code outputs to multiple AI providers
299 lines • 11.9 kB
JavaScript
;
/**
* Simple Session Manager
* Provides basic session tracking without concurrency control
*
* Key Features:
* 1. Session metadata tracking
* 2. Request ID generation
* 3. Basic statistics
* 4. No queuing or concurrency control
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.SimpleSessionManager = void 0;
exports.getSimpleSessionManager = getSimpleSessionManager;
const logger_1 = require("@/utils/logger");
const events_1 = require("events");
/**
* Simple session manager without concurrency control
* Provides basic session tracking and request ID generation
*/
class SimpleSessionManager extends events_1.EventEmitter {
port;
activeRequests = new Map();
sessionCounters = new Map();
completedRequests = 0;
failedRequests = 0;
outOfOrderResponses = 0;
conversationHistory = new Map();
constructor(port) {
super();
this.port = port;
// Cleanup expired sessions every 10 minutes
setInterval(() => this.cleanupExpiredSessions(), 10 * 60 * 1000);
logger_1.logger.info('SimpleSessionManager initialized', {
port: this.port,
concurrencyControl: false,
queueManagement: false
}, 'session-manager');
}
/**
* Generate a simple request ID without queuing
* Format: sessionId:conversationId:timestamp:random
*/
generateRequestId(sessionId, conversationId, isStreaming = false) {
const sessionKey = `${sessionId}:${conversationId}`;
// Get next counter for this session
const counter = this.getNextCounter(sessionKey);
const timestamp = Date.now();
const random = Math.random().toString(36).substr(2, 4);
const requestId = `${sessionKey}:${counter}:${timestamp}:${random}`;
// Create session request record
const sessionRequest = {
requestId,
sessionId,
conversationId,
timestamp: new Date(),
isStreaming,
status: 'active',
sequenceNumber: counter,
startTime: performance.now()
};
this.activeRequests.set(requestId, sessionRequest);
logger_1.logger.debug('Generated simple request ID', {
requestId,
sessionId,
conversationId,
counter,
isStreaming
}, requestId, 'session-manager');
this.emit('requestGenerated', sessionRequest);
return requestId;
}
/**
* Mark request as completed
*/
completeRequest(requestId, finishReason) {
const request = this.activeRequests.get(requestId);
if (!request) {
logger_1.logger.warn('Attempted to complete unknown request', {
requestId,
finishReason
}, requestId, 'session-manager');
return;
}
request.status = 'completed';
request.endTime = performance.now();
this.activeRequests.delete(requestId);
this.completedRequests++;
// Check for out-of-order completion
this.checkRequestOrder(request);
// Add to conversation history
const conversationKey = `${request.sessionId}:${request.conversationId}`;
if (!this.conversationHistory.has(conversationKey)) {
this.conversationHistory.set(conversationKey, []);
}
this.conversationHistory.get(conversationKey).push(request);
logger_1.logger.debug('Request completed', {
requestId,
sessionId: request.sessionId,
conversationId: request.conversationId,
sequenceNumber: request.sequenceNumber,
processingTime: request.endTime - request.startTime,
finishReason,
totalCompleted: this.completedRequests
}, requestId, 'session-manager');
this.emit('requestCompleted', {
requestId,
finishReason,
sessionRequest: request
});
}
/**
* Mark request as failed
*/
failRequest(requestId, error) {
const request = this.activeRequests.get(requestId);
if (!request) {
logger_1.logger.warn('Attempted to fail unknown request', {
requestId,
error: error instanceof Error ? error.message : String(error)
}, requestId, 'session-manager');
return;
}
request.status = 'failed';
this.activeRequests.delete(requestId);
this.failedRequests++;
logger_1.logger.debug('Request failed', {
requestId,
sessionId: request.sessionId,
conversationId: request.conversationId,
error: error instanceof Error ? error.message : String(error),
totalFailed: this.failedRequests
}, requestId, 'session-manager');
this.emit('requestFailed', {
requestId,
error,
sessionRequest: request
});
}
/**
* Check if a request is active
*/
isRequestActive(requestId) {
return this.activeRequests.has(requestId);
}
/**
* Get session statistics
*/
getSessionStats() {
const totalSessions = this.sessionCounters.size;
const activeRequests = this.activeRequests.size;
return {
totalSessions,
activeRequests,
completedRequests: this.completedRequests,
failedRequests: this.failedRequests,
outOfOrderResponses: this.outOfOrderResponses
};
}
/**
* Get active requests for a specific session
*/
getActiveRequests(sessionId, conversationId) {
const requests = Array.from(this.activeRequests.values());
if (conversationId) {
return requests.filter(req => req.sessionId === sessionId && req.conversationId === conversationId);
}
return requests.filter(req => req.sessionId === sessionId);
}
/**
* Get next counter for a session
*/
getNextCounter(sessionKey) {
const current = this.sessionCounters.get(sessionKey) || 0;
const next = current + 1;
this.sessionCounters.set(sessionKey, next);
return next;
}
/**
* Check if request completed out of order and log warning
*/
checkRequestOrder(completedRequest) {
const conversationKey = `${completedRequest.sessionId}:${completedRequest.conversationId}`;
const history = this.conversationHistory.get(conversationKey) || [];
// Check if there are any earlier requests that are still active
const earlierActiveRequests = Array.from(this.activeRequests.values())
.filter(req => `${req.sessionId}:${req.conversationId}` === conversationKey &&
req.sequenceNumber < completedRequest.sequenceNumber);
if (earlierActiveRequests.length > 0) {
this.outOfOrderResponses++;
// 🚨 Critical Warning: Out-of-order response detected
console.warn(`🚨 OUT-OF-ORDER RESPONSE DETECTED:`);
console.warn(` Completed Request: ${completedRequest.requestId} (seq: ${completedRequest.sequenceNumber})`);
console.warn(` Earlier Active Requests: ${earlierActiveRequests.length}`);
earlierActiveRequests.forEach(req => {
console.warn(` - ${req.requestId} (seq: ${req.sequenceNumber})`);
});
console.warn(` Conversation: ${conversationKey}`);
console.warn(` Processing Time: ${Math.round(completedRequest.endTime - completedRequest.startTime)}ms`);
logger_1.logger.warn('Out-of-order response detected', {
completedRequestId: completedRequest.requestId,
completedSequence: completedRequest.sequenceNumber,
earlierActiveRequests: earlierActiveRequests.map(req => ({
requestId: req.requestId,
sequenceNumber: req.sequenceNumber,
waitingTime: performance.now() - req.startTime
})),
conversationKey,
processingTime: completedRequest.endTime - completedRequest.startTime,
totalOutOfOrder: this.outOfOrderResponses
}, completedRequest.requestId, 'order-detection');
this.emit('outOfOrderResponse', {
completedRequest,
earlierActiveRequests,
conversationKey
});
}
}
/**
* Clean up expired sessions
*/
cleanupExpiredSessions() {
const now = new Date();
const expiredThreshold = 2 * 60 * 60 * 1000; // 2 hours
const expiredRequests = [];
// Find expired active requests
for (const [requestId, request] of this.activeRequests.entries()) {
const age = now.getTime() - request.timestamp.getTime();
if (age > expiredThreshold) {
expiredRequests.push(requestId);
}
}
// Clean up expired requests
for (const requestId of expiredRequests) {
const request = this.activeRequests.get(requestId);
if (request) {
this.activeRequests.delete(requestId);
this.failedRequests++;
logger_1.logger.warn('Cleaned up expired request', {
requestId,
sessionId: request.sessionId,
conversationId: request.conversationId,
age: now.getTime() - request.timestamp.getTime()
}, requestId, 'session-manager');
}
}
// Clean up old session counters (keep only active sessions)
const activeSessions = new Set();
for (const request of this.activeRequests.values()) {
activeSessions.add(`${request.sessionId}:${request.conversationId}`);
}
const expiredSessions = [];
for (const sessionKey of this.sessionCounters.keys()) {
if (!activeSessions.has(sessionKey)) {
expiredSessions.push(sessionKey);
}
}
for (const sessionKey of expiredSessions) {
this.sessionCounters.delete(sessionKey);
}
// Clean up old conversation history
const expiredHistoryKeys = [];
for (const [conversationKey, history] of this.conversationHistory.entries()) {
if (history.length > 0) {
const oldestRequest = history[0];
const age = now.getTime() - oldestRequest.timestamp.getTime();
if (age > expiredThreshold) {
expiredHistoryKeys.push(conversationKey);
}
}
}
for (const conversationKey of expiredHistoryKeys) {
this.conversationHistory.delete(conversationKey);
}
if (expiredRequests.length > 0 || expiredSessions.length > 0 || expiredHistoryKeys.length > 0) {
logger_1.logger.info('Cleaned up expired session data', {
expiredRequests: expiredRequests.length,
expiredSessions: expiredSessions.length,
expiredHistory: expiredHistoryKeys.length,
activeRequests: this.activeRequests.size,
activeSessions: activeSessions.size,
conversationHistories: this.conversationHistory.size
}, 'session-manager');
}
}
}
exports.SimpleSessionManager = SimpleSessionManager;
// Global simple session manager instances per port
const sessionManagers = new Map();
/**
* Get or create simple session manager for a specific port
*/
function getSimpleSessionManager(port) {
if (!sessionManagers.has(port)) {
sessionManagers.set(port, new SimpleSessionManager(port));
}
return sessionManagers.get(port);
}
//# sourceMappingURL=simple-session-manager.js.map