diff --git a/open-sse/config/constants.js b/open-sse/config/constants.js index 99520e5a..70face1d 100644 --- a/open-sse/config/constants.js +++ b/open-sse/config/constants.js @@ -487,6 +487,14 @@ export const CACHE_TTL = { modelAlias: 3600 // 1 hour }; +// Memory management config +export const MEMORY_CONFIG = { + sessionTtlMs: 2 * 60 * 60 * 1000, // 2 hours per session entry + sessionCleanupIntervalMs: 30 * 60 * 1000, // cleanup every 30 minutes + dnsCacheTtlMs: 5 * 60 * 1000, // 5 minutes DNS TTL + proxyDispatchersMaxSize: 20, // max proxy agent instances +}; + // Default max tokens export const DEFAULT_MAX_TOKENS = 64000; diff --git a/open-sse/utils/proxyFetch.js b/open-sse/utils/proxyFetch.js index 87d7398a..5d00309a 100644 --- a/open-sse/utils/proxyFetch.js +++ b/open-sse/utils/proxyFetch.js @@ -1,9 +1,11 @@ +import { MEMORY_CONFIG } from "../config/constants.js"; + const isCloud = typeof caches !== "undefined" && typeof caches === "object"; const originalFetch = globalThis.fetch; const proxyDispatchers = new Map(); -// Constants +// DNS cache: { [hostname]: { ip, expiry } } const DNS_CACHE = {}; const MITM_BYPASS_HOSTS = ["cloudcode-pa.googleapis.com", "daily-cloudcode-pa.googleapis.com", "googleapis.com"]; const MITM_BYPASS_HEADER = "x-request-source"; @@ -22,7 +24,8 @@ function normalizeString(value) { * Resolve real IP using Google DNS (bypass system DNS) */ async function resolveRealIP(hostname) { - if (DNS_CACHE[hostname]) return DNS_CACHE[hostname]; + const cached = DNS_CACHE[hostname]; + if (cached && Date.now() < cached.expiry) return cached.ip; try { const dns = await import("dns"); @@ -31,7 +34,7 @@ async function resolveRealIP(hostname) { resolver.setServers(GOOGLE_DNS_SERVERS); const resolve4 = promisify(resolver.resolve4.bind(resolver)); const addresses = await resolve4(hostname); - DNS_CACHE[hostname] = addresses[0]; + DNS_CACHE[hostname] = { ip: addresses[0], expiry: Date.now() + MEMORY_CONFIG.dnsCacheTtlMs }; return addresses[0]; } catch (error) { console.warn(`[ProxyFetch] DNS resolve failed for ${hostname}:`, error.message); @@ -132,6 +135,10 @@ async function getDispatcher(proxyUrl) { if (!normalized) return null; if (!proxyDispatchers.has(normalized)) { + // Evict oldest entry if max size reached + if (proxyDispatchers.size >= MEMORY_CONFIG.proxyDispatchersMaxSize) { + proxyDispatchers.delete(proxyDispatchers.keys().next().value); + } const { ProxyAgent } = await import("undici"); proxyDispatchers.set(normalized, new ProxyAgent({ uri: normalized })); } diff --git a/open-sse/utils/sessionManager.js b/open-sse/utils/sessionManager.js index 35b3c257..f3fb08cf 100644 --- a/open-sse/utils/sessionManager.js +++ b/open-sse/utils/sessionManager.js @@ -9,11 +9,24 @@ */ import crypto from "crypto"; +import { MEMORY_CONFIG } from "../config/constants.js"; -// Runtime storage for session IDs (per connection/account) -// Key: connectionId (email or identifier), Value: sessionId +// Runtime storage: Key = connectionId, Value = { sessionId, lastUsed } const runtimeSessionStore = new Map(); +// Periodically evict entries that haven't been used within TTL +const cleanupInterval = setInterval(() => { + const now = Date.now(); + for (const [key, entry] of runtimeSessionStore) { + if (now - entry.lastUsed > MEMORY_CONFIG.sessionTtlMs) { + runtimeSessionStore.delete(key); + } + } +}, MEMORY_CONFIG.sessionCleanupIntervalMs); + +// Allow Node.js to exit even if interval is still active +if (cleanupInterval.unref) cleanupInterval.unref(); + /** * Get or create a session ID for the given connection. * @@ -30,22 +43,18 @@ const runtimeSessionStore = new Map(); */ export function deriveSessionId(connectionId) { if (!connectionId) { - // Fallback for requests without a connection identifier return generateBinaryStyleId(); } - // Check if we already have a session ID for this connection in this process run - if (runtimeSessionStore.has(connectionId)) { - return runtimeSessionStore.get(connectionId); + const existing = runtimeSessionStore.get(connectionId); + if (existing) { + existing.lastUsed = Date.now(); + return existing.sessionId; } - // Generate a new ID using the binary's exact logic - const newSessionId = generateBinaryStyleId(); - - // Store it for future requests from this connection - runtimeSessionStore.set(connectionId, newSessionId); - - return newSessionId; + const sessionId = generateBinaryStyleId(); + runtimeSessionStore.set(connectionId, { sessionId, lastUsed: Date.now() }); + return sessionId; } /** diff --git a/src/app/(dashboard)/dashboard/profile/page.js b/src/app/(dashboard)/dashboard/profile/page.js index 4226137f..63d985b2 100644 --- a/src/app/(dashboard)/dashboard/profile/page.js +++ b/src/app/(dashboard)/dashboard/profile/page.js @@ -314,7 +314,7 @@ export default function ProfilePage() { } }; - const observabilityEnabled = settings.observabilityEnabled !== false; + const observabilityEnabled = settings.observabilityEnabled === true; return (