UNPKG

@juspay/neurolink

Version:

Universal AI Development Platform with working MCP integration, multi-provider support, voice (TTS/STT/realtime), and professional CLI. 58+ external MCP servers discoverable, multimodal file processing, RAG pipelines. Build, test, and deploy AI applicatio

263 lines 9.22 kB
/** * Loop Mode Utilities * Utilities specific to CLI loop mode session management and restoration */ import chalk from "chalk"; import fs from "fs/promises"; import path from "path"; import os from "os"; import { globalSession } from "../session/globalSessionState.js"; import { logger } from "./logger.js"; /** * Verify that conversation context is accessible and properly loaded * Uses the global session to access the NeuroLink instance */ export async function verifyConversationContext(sessionId) { const session = globalSession.getLoopSession(); if (!session) { logger.warn("No active session found for conversation verification"); return; } try { const messages = await session.neurolinkInstance.getConversationHistory(sessionId); logger.debug(`Successfully loaded ${messages.length} messages from conversation ${sessionId}`); if (messages.length === 0) { logger.warn(`No messages found for session ${sessionId} after restoration`); logger.warn("The conversation exists in Redis but may not be accessible through the API"); } else { logger.info(`Conversation context restored successfully: ${messages.length} messages loaded`); } } catch (error) { logger.warn("Could not access conversation history after restoration:", error); logger.warn("The conversation may still be accessible when generating new responses"); } } /** * Get conversation context for display (first few and last few messages) * Uses the global session to access the NeuroLink instance */ export async function getConversationPreview(sessionId, previewCount = 2) { const session = globalSession.getLoopSession(); if (!session) { logger.debug("No active session found for conversation preview"); return []; } try { const allMessages = await session.neurolinkInstance.getConversationHistory(sessionId); if (allMessages.length <= previewCount * 2) { return allMessages; } const firstMessages = allMessages.slice(0, previewCount); const lastMessages = allMessages.slice(-previewCount); return [...firstMessages, ...lastMessages]; } catch (error) { logger.debug("Failed to get conversation preview:", error); return []; } } /** * Generate a title from content by truncating to appropriate length */ export function generateConversationTitle(content) { const truncated = content.slice(0, LOOP_DISPLAY_LIMITS.TITLE_LENGTH); return truncated.length < content.length ? `${truncated}...` : truncated; } /** * Truncate text content to specified length with ellipsis */ export function truncateText(content, maxLength) { if (content.length <= maxLength) { return content; } return `${content.slice(0, maxLength)}...`; } /** * Format timestamp as human-readable relative time * Uses Intl.RelativeTimeFormat for natural language output */ export function formatTimeAgo(timestamp) { const now = Date.now(); const date = new Date(timestamp); if (isNaN(date.getTime())) { logger.warn(`Invalid timestamp provided: ${timestamp}`); return "Unknown time"; } const diffSeconds = Math.floor((date.getTime() - now) / 1000); for (const { unit, seconds } of TIME_UNITS) { if (Math.abs(diffSeconds) >= seconds || unit === "minute") { return rtf.format(Math.round(diffSeconds / seconds), unit); } } return rtf.format(0, "second"); } /** * Get appropriate icon for content based on regex patterns */ export function getContentIcon(content) { const match = CONTENT_ICON_PATTERNS.find((pattern) => pattern.pattern.test(content)); return match?.icon ?? "📝"; } /** * Display session restoration status message */ export function displaySessionMessage(result) { if (result.success) { logger.always(chalk.green(`✅ Resumed conversation: ${result.sessionId.slice(0, LOOP_DISPLAY_LIMITS.SESSION_ID_DISPLAY)}...`)); logger.always(chalk.gray(` ${result.messageCount} messages | Last activity: ${result.lastActivity ? new Date(result.lastActivity).toLocaleString() : "Unknown"}`)); } else { logger.always(chalk.red(`❌ Failed to restore conversation: ${result.error}`)); logger.always(chalk.gray(" Starting new conversation instead...")); } } /** * Load command history from the global history file */ export async function loadCommandHistory() { try { const content = await fs.readFile(HISTORY_FILE, "utf8"); return content.split("\n").filter((line) => line.trim()); } catch { return []; } } /** * Save a command to the global history file */ export async function saveCommandToHistory(command) { try { const sensitivePattern = /\b(api[-_]?key|token|password|secret|authorization)\b/i; if (sensitivePattern.test(command)) { return; } await fs.writeFile(HISTORY_FILE, command + "\n", { flag: "a", mode: 0o600, }); await fs.chmod(HISTORY_FILE, 0o600); } catch (error) { logger.warn("Warning: Could not save command to history:", error); } } /** * Display conversation preview with formatted messages */ export function displayConversationPreview(preview, maxPreview = 2) { if (preview.length === 0) { return; } logger.always(chalk.gray("\n--- Conversation Preview ---")); preview.slice(0, maxPreview).forEach((msg) => { const role = msg.role === "user" ? chalk.cyan("You") : chalk.green("AI"); const content = msg.content.length > 100 ? msg.content.slice(0, 100) + "..." : msg.content; logger.always(`${role}: ${content}`); }); if (preview.length > maxPreview) { logger.always(chalk.gray("... (conversation continues)")); } logger.always(chalk.gray("--- End Preview ---\n")); } /** * Parse a string value to its appropriate type (string, number, or boolean) * Useful for parsing user input from CLI commands */ export function parseValue(value) { // Try to parse as number if (!isNaN(Number(value))) { return Number(value); } // Try to parse as boolean if (value.toLowerCase() === "true") { return true; } if (value.toLowerCase() === "false") { return false; } // Return as string return value; } /** * Restore session variables from conversation metadata * Extracts and sets session variables stored in conversation metadata */ export async function restoreSessionVariables(conversationData) { try { // Check if conversation has stored session variables const metadata = conversationData.metadata; if (metadata && metadata.sessionVariables) { logger.debug("Restoring session variables from conversation metadata"); const sessionVariables = metadata.sessionVariables; for (const [key, value] of Object.entries(sessionVariables)) { if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") { globalSession.setSessionVariable(key, value); logger.debug(`Restored session variable: ${key} = ${value}`); } } } else { logger.debug("No session variables found in conversation metadata"); } } catch (error) { logger.warn("Failed to restore session variables:", error); // Don't fail the restoration for this } } // ============================================================================ // CONSTANTS // ============================================================================ export const HISTORY_FILE = path.join(os.homedir(), ".neurolink_history"); export const LOOP_CACHE_CONFIG = { TTL_MS: 5 * 60 * 1000, }; export const LOOP_DISPLAY_LIMITS = { MAX_CONVERSATIONS: 20, CONTENT_LENGTH: 50, TITLE_LENGTH: 40, SESSION_ID_DISPLAY: 12, SESSION_ID_SHORT: 8, PAGE_SIZE: 15, }; const TIME_UNITS = [ { unit: "year", seconds: 31536000 }, { unit: "month", seconds: 2592000 }, { unit: "week", seconds: 604800 }, { unit: "day", seconds: 86400 }, { unit: "hour", seconds: 3600 }, { unit: "minute", seconds: 60 }, ]; const rtf = new Intl.RelativeTimeFormat("en", { numeric: "auto" }); const CONTENT_ICON_PATTERNS = [ { icon: "💻", pattern: /\b(code|debug|programming|function|class|bug|script|syntax|compile|error)\b/i, }, { icon: "💡", pattern: /\b(explain|what|how|why|understand|clarify|learn|teach)\b/i, }, { icon: "📊", pattern: /\b(analyz[e|ing]|data|report|metrics?|statistics?|chart|graph|visualiz[e|ation])\b/i, }, { icon: "✍️", pattern: /\b(write|create|generat[e|ing]|compose|draft|build|author)\b/i, }, { icon: "🤖", pattern: /\b(help|assist|support|guide|tutorial|show me)\b/i, }, ]; //# sourceMappingURL=loopUtils.js.map