UNPKG

@nanocollective/nanocoder

Version:

A local-first CLI coding agent that brings the power of agentic coding tools like Claude Code and Gemini CLI to local models or controlled APIs like OpenRouter

379 lines 12.3 kB
/** * Correlation ID management for tracking requests across components */ import { AsyncLocalStorage } from 'async_hooks'; import { randomBytes } from 'crypto'; /** * Async local storage for correlation context * Provides thread-safe context storage across async operations */ export const correlationStorage = new AsyncLocalStorage(); /** * Correlation Context Monitoring Metrics * Tracks usage, performance, and potential issues for production monitoring */ const correlationMonitoring = { contextsCreated: 0, activeContexts: 0, errors: 0, lastError: null, lastErrorTime: 0, startTime: Date.now(), }; /** * Legacy context has been removed in favor of AsyncLocalStorage-only approach * This eliminates race conditions in concurrent operations * All code should now use withCorrelationContext() or withNewCorrelationContext() */ /** * Get correlation context monitoring metrics * Provides insights into context usage, performance, and health */ export function getCorrelationMonitoring() { return { ...correlationMonitoring }; } /** * Log correlation context monitoring metrics * Useful for periodic logging of system health * @internal */ export function logCorrelationMonitoring(level = 'info') { const metrics = getCorrelationMonitoring(); const uptime = Date.now() - metrics.startTime; const uptimeMinutes = Math.floor(uptime / (1000 * 60)); const errorRate = metrics.contextsCreated > 0 ? ((metrics.errors / metrics.contextsCreated) * 100).toFixed(2) + '%' : '0%'; const message = ` [Correlation Monitoring] - Uptime: ${uptimeMinutes} minutes - Contexts Created: ${metrics.contextsCreated} - Active Contexts: ${metrics.activeContexts} - Errors: ${metrics.errors} - Error Rate: ${errorRate} - Last Error: ${metrics.lastError || 'None'}`; switch (level) { case 'debug': console.debug(message); break; case 'info': console.info(message); break; case 'warn': console.warn(message); break; case 'error': console.error(message); break; } } /** * Reset correlation context monitoring metrics * Useful for testing or periodic reporting */ export function resetCorrelationMonitoring() { correlationMonitoring.contextsCreated = 0; correlationMonitoring.activeContexts = 0; correlationMonitoring.errors = 0; correlationMonitoring.lastError = null; correlationMonitoring.lastErrorTime = 0; correlationMonitoring.startTime = Date.now(); } /** * Perform health check on correlation context system * Verifies that the AsyncLocalStorage context system is functioning properly */ export function checkCorrelationHealth() { try { // Test basic context creation and retrieval const testContext = { id: 'health-check-' + generateShortCorrelationId(), metadata: { healthCheck: true, timestamp: Date.now() }, }; let contextWorking = false; withCorrelationContext(testContext, () => { const current = getCurrentCorrelationContext(); contextWorking = current?.id === testContext.id; }); if (!contextWorking) { return { healthy: false, message: 'Correlation context system failed basic functionality test', metrics: getCorrelationMonitoring(), }; } // Check for excessive errors const errorRate = correlationMonitoring.contextsCreated > 0 ? correlationMonitoring.errors / correlationMonitoring.contextsCreated : 0; if (errorRate > 0.1) { // More than 10% error rate return { healthy: false, message: `High error rate detected: ${(errorRate * 100).toFixed(1)}%`, metrics: getCorrelationMonitoring(), }; } return { healthy: true, message: 'Correlation context system is healthy', metrics: getCorrelationMonitoring(), }; } catch (error) { return { healthy: false, message: `Health check failed: ${error instanceof Error ? error.message : String(error)}`, metrics: getCorrelationMonitoring(), }; } } /** * Generate a new correlation ID */ export function generateCorrelationId() { // Generate 16-byte random hex string (32 characters) return randomBytes(16).toString('hex'); } /** * Generate a short correlation ID (8 characters) for display */ export function generateShortCorrelationId() { // Generate 4-byte random hex string (8 characters) return randomBytes(4).toString('hex'); } /** * Create a new correlation context with specific ID */ export function createCorrelationContextWithId(id, metadata) { return { id, metadata, }; } /** * Create a new correlation context with optional parent ID */ export function createCorrelationContext(parentId, metadata) { return { id: generateCorrelationId(), parentId, metadata, }; } /** * Get the current correlation context * Now uses AsyncLocalStorage exclusively - no legacy fallback */ export function getCurrentCorrelationContext() { const asyncContext = correlationStorage.getStore(); return asyncContext || null; } /** * Run a function with a specific correlation context */ export function withCorrelationContext(context, fn) { try { if (process.env.NANOCODER_CORRELATION_DEBUG === 'true') { console.debug(`[Correlation] Context started: ${context.id}`); } correlationMonitoring.activeContexts++; const result = correlationStorage.run(context, fn); correlationMonitoring.activeContexts--; if (process.env.NANOCODER_CORRELATION_DEBUG === 'true') { console.debug(`[Correlation] Context completed: ${context.id}`); } return result; } catch (error) { correlationMonitoring.errors++; correlationMonitoring.lastError = error instanceof Error ? error.message : String(error); correlationMonitoring.lastErrorTime = Date.now(); correlationMonitoring.activeContexts--; if (process.env.NANOCODER_CORRELATION_DEBUG === 'true') { console.error(`[Correlation] Context error: ${context.id}`, error); // nosemgrep } throw error; } } /** * Run a function with a new correlation context */ export function withNewCorrelationContext(fn, correlationId, metadata) { const context = correlationId ? createCorrelationContextWithId(correlationId, metadata) : createCorrelationContext(undefined, metadata); correlationMonitoring.contextsCreated++; if (process.env.NANOCODER_CORRELATION_DEBUG === 'true') { console.debug(`[Correlation] New context created: ${context.id}`); } try { correlationMonitoring.activeContexts++; const result = correlationStorage.run(context, () => fn(context)); correlationMonitoring.activeContexts--; if (process.env.NANOCODER_CORRELATION_DEBUG === 'true') { console.debug(`[Correlation] New context completed: ${context.id}`); } return result; } catch (error) { correlationMonitoring.errors++; correlationMonitoring.lastError = error instanceof Error ? error.message : String(error); correlationMonitoring.lastErrorTime = Date.now(); correlationMonitoring.activeContexts--; if (process.env.NANOCODER_CORRELATION_DEBUG === 'true') { console.error(`[Correlation] New context error: ${context.id}`, error); // nosemgrep } throw error; } } /** * Get the correlation ID for the current context */ export function getCorrelationId() { const asyncContext = correlationStorage.getStore(); return asyncContext?.id || null; } /** * Check if correlation is enabled */ export function isCorrelationEnabled() { return process.env.NANOCODER_CORRELATION_ENABLED !== 'false'; } /** * Get correlation header for HTTP requests */ export function getCorrelationHeader() { const correlationId = getCorrelationId(); if (!correlationId || !isCorrelationEnabled()) { return {}; } return { 'X-Correlation-ID': correlationId, }; } /** * Extract correlation ID from HTTP headers */ export function extractCorrelationId(headers) { // Try various header names const possibleHeaders = [ 'x-correlation-id', 'x-request-id', 'x-trace-id', 'x-span-id', 'correlation-id', 'request-id', 'trace-id', 'span-id', ]; for (const header of possibleHeaders) { const value = headers[header] || headers[header.toUpperCase()]; if (value && typeof value === 'string') { return value; } } return null; } /** * Create a correlation context from HTTP headers */ export function createCorrelationFromHeaders(headers, metadata) { if (!isCorrelationEnabled()) { return null; } const correlationId = extractCorrelationId(headers); if (correlationId) { return createCorrelationContextWithId(correlationId, metadata); } return createCorrelationContext(undefined, metadata); } /** * Get correlation metadata */ export function getCorrelationMetadata(key) { const asyncContext = correlationStorage.getStore(); if (asyncContext?.metadata) { return key ? asyncContext.metadata[key] : asyncContext.metadata; } return key ? undefined : {}; } /** * Format correlation context for logging */ export function formatCorrelationForLog() { const asyncContext = correlationStorage.getStore(); if (asyncContext && isCorrelationEnabled()) { const result = { correlationId: asyncContext.id, }; if (asyncContext.parentId) { result.parentCorrelationId = asyncContext.parentId; } return result; } return {}; } /** * Correlation middleware for Express-like frameworks */ export function correlationMiddleware() { return (req, res, next) => { // Extract or create correlation ID let correlationId = extractCorrelationId(req.headers || {}); if (!correlationId) { correlationId = generateCorrelationId(); } // Create correlation context with the extracted or generated ID const context = createCorrelationContextWithId(correlationId, { method: req.method, url: req.url, userAgent: req.headers?.['user-agent'], }); // Run the request handler within the correlation context return correlationStorage.run(context, () => { // Add correlation ID to response headers if (res.setHeader) { res.setHeader('X-Correlation-ID', correlationId); } next(); }); }; } /** * Wrap an async function with correlation tracking */ export function withCorrelation(fn, getCorrelationIdFromArgs) { return (async (...args) => { let context = null; // Try to get correlation ID from arguments if provided if (getCorrelationIdFromArgs) { const correlationId = getCorrelationIdFromArgs(...args); if (correlationId) { context = createCorrelationContextWithId(correlationId); } } // If no context from args, try current context or create new if (!context) { const current = getCurrentCorrelationContext(); if (current) { context = createCorrelationContext(current.id); } else if (isCorrelationEnabled()) { context = createCorrelationContext(); } } // Run function within correlation context if (context) { return correlationStorage.run(context, async () => { return await fn(...args); }); } else { return await fn(...args); } }); } //# sourceMappingURL=correlation.js.map