UNPKG

@juspay/neurolink

Version:

The pipe layer of an AI nervous system: one interface connecting provider neurons to your application, across three inference types — generate, stream and decide. `decide` returns typed, calibrated judgements from a non-generative model (~400ms, ~$0.00002

252 lines (251 loc) • 9.06 kB
import { nanoid } from "nanoid"; import { NeuroLink } from "../neurolink.js"; import { buildObservabilityConfigFromEnv } from "../utils/observabilityHelpers.js"; /** * Build mcp.outputLimits config from environment variables. * Reads NEUROLINK_MCP_OUTPUT_STRATEGY and NEUROLINK_MCP_MAX_OUTPUT_BYTES. * Returns undefined when neither variable is set (no overhead). */ function buildMcpOutputLimitsFromEnv() { const strategyRaw = process.env.NEUROLINK_MCP_OUTPUT_STRATEGY; const maxBytesRaw = process.env.NEUROLINK_MCP_MAX_OUTPUT_BYTES; const warnBytesRaw = process.env.NEUROLINK_MCP_WARN_OUTPUT_BYTES; if (!strategyRaw && !maxBytesRaw) { return undefined; } const strategy = strategyRaw === "inline" || strategyRaw === "externalize" ? strategyRaw : "externalize"; // safe default when only maxBytes is set const maxBytes = maxBytesRaw ? parseInt(maxBytesRaw, 10) : undefined; const warnBytes = warnBytesRaw ? parseInt(warnBytesRaw, 10) : undefined; return { strategy, ...(maxBytes !== undefined && Number.isFinite(maxBytes) && maxBytes >= 0 ? { maxBytes } : {}), ...(warnBytes !== undefined && Number.isFinite(warnBytes) && warnBytes >= 0 ? { warnBytes } : {}), }; } export class GlobalSessionManager { static instance; loopSession = null; /** Optional tool-routing config set by CLI handlers before SDK construction. */ _toolRoutingConfig = undefined; /** Optional classifier-router config set by CLI handlers before SDK construction. */ _classifierRouterConfig = undefined; /** Optional skills config set by CLI handlers before SDK construction. */ _skillsConfig = undefined; _fileToolRoots = undefined; static getInstance() { if (!GlobalSessionManager.instance) { GlobalSessionManager.instance = new GlobalSessionManager(); } return GlobalSessionManager.instance; } setLoopSession(config) { const sessionId = `NL_${nanoid()}`; const neurolinkOptions = {}; if (config?.enabled) { neurolinkOptions.conversationMemory = { enabled: true, maxSessions: config.maxSessions, maxTurnsPerSession: config.maxTurnsPerSession, }; } // Add observability config from environment variables (CLI usage) const observabilityConfig = buildObservabilityConfigFromEnv(); if (observabilityConfig) { neurolinkOptions.observability = observabilityConfig; } // Add MCP output limits from environment variables (CLI usage) const mcpOutputLimits = buildMcpOutputLimitsFromEnv(); if (mcpOutputLimits) { neurolinkOptions.mcp = { ...neurolinkOptions.mcp, outputLimits: mcpOutputLimits, }; } this.loopSession = { neurolinkInstance: new NeuroLink(neurolinkOptions), sessionId, isActive: true, conversationMemoryConfig: config, sessionVariables: {}, }; return sessionId; } /** * Restore a loop session with an existing sessionId and NeuroLink instance * Used for conversation restoration */ restoreLoopSession(sessionId, neurolinkInstance, config, sessionVariables) { this.loopSession = { neurolinkInstance, sessionId, isActive: true, conversationMemoryConfig: config, sessionVariables: sessionVariables || {}, }; } /** * Update session variables during restoration */ restoreSessionVariables(variables) { const session = this.getLoopSession(); if (session) { session.sessionVariables = { ...session.sessionVariables, ...variables }; } } /** * Check if a session is currently active */ hasActiveSession() { return this.loopSession?.isActive ?? false; } /** * Get current session metadata for restoration purposes */ getSessionMetadata() { const session = this.getLoopSession(); return { sessionId: session?.sessionId, conversationMemoryConfig: session?.conversationMemoryConfig, sessionVariables: session?.sessionVariables || {}, isActive: session?.isActive ?? false, }; } /** * Update the sessionId of the current session (used during restoration) */ updateSessionId(newSessionId) { const session = this.getLoopSession(); if (session) { session.sessionId = newSessionId; } } getLoopSession() { return this.loopSession?.isActive ? this.loopSession : null; } clearLoopSession() { if (this.loopSession) { this.loopSession.isActive = false; this.loopSession = null; } } /** * Store a tool-routing config to be injected at SDK construction time. * Call this BEFORE `getOrCreateNeuroLink()` inside a command handler. * When a loop session is already active the config is ignored (the instance * already exists). */ setToolRoutingConfig(config) { if (this.hasActiveSession()) { return; } this._toolRoutingConfig = config; } /** * Store a classifier-router config to be injected at SDK construction time. * Call this BEFORE `getOrCreateNeuroLink()` inside a command handler. * When a loop session is already active the config is ignored (the instance * already exists). */ setClassifierRouterConfig(config) { if (this.hasActiveSession()) { return; } this._classifierRouterConfig = config; } /** * Store a skills config to be injected at SDK construction time. * Call this BEFORE `getOrCreateNeuroLink()` inside a command handler. * When a loop session is already active the config is ignored (the instance * already exists). */ setSkillsConfig(config) { if (this.hasActiveSession()) { return; } this._skillsConfig = config; } /** * Store file-tool roots (`--tool-root`) to be injected at SDK construction * time as `tools.fileRoots`. Call this BEFORE `getOrCreateNeuroLink()`. * When a loop session is already active the roots are ignored (the * instance already exists). */ setFileToolRoots(roots) { if (this.hasActiveSession() || !roots?.length) { return; } this._fileToolRoots = roots; } getOrCreateNeuroLink() { const session = this.getLoopSession(); if (session) { return session.neurolinkInstance; } // Create new NeuroLink with observability config from environment (CLI usage) const observabilityConfig = buildObservabilityConfigFromEnv(); const mcpOutputLimits = buildMcpOutputLimitsFromEnv(); const options = {}; if (observabilityConfig) { options.observability = observabilityConfig; } if (mcpOutputLimits) { options.mcp = { outputLimits: mcpOutputLimits }; } if (this._toolRoutingConfig) { options.toolRouting = this._toolRoutingConfig; this._toolRoutingConfig = undefined; } if (this._classifierRouterConfig) { options.classifierRouter = this._classifierRouterConfig; this._classifierRouterConfig = undefined; } if (this._skillsConfig) { options.skills = this._skillsConfig; this._skillsConfig = undefined; } if (this._fileToolRoots) { options.tools = { ...options.tools, fileRoots: this._fileToolRoots }; this._fileToolRoots = undefined; } return new NeuroLink(Object.keys(options).length ? options : undefined); } getCurrentSessionId() { return this.getLoopSession()?.sessionId; } // Session variable management setSessionVariable(key, value) { const session = this.getLoopSession(); if (session) { session.sessionVariables[key] = value; } } getSessionVariable(key) { const session = this.getLoopSession(); return session?.sessionVariables[key]; } getSessionVariables() { const session = this.getLoopSession(); return session?.sessionVariables || {}; } unsetSessionVariable(key) { const session = this.getLoopSession(); if (session && key in session.sessionVariables) { delete session.sessionVariables[key]; return true; } return false; } clearSessionVariables() { const session = this.getLoopSession(); if (session) { session.sessionVariables = {}; } } } export const globalSession = GlobalSessionManager.getInstance();