@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
241 lines (240 loc) • 8.46 kB
JavaScript
import { nanoid } from "nanoid";
import { NeuroLink } from "../neurolink.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 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;
}
const mcpOutputLimits = buildMcpOutputLimitsFromEnv();
const options = {};
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();