UNPKG

langterm

Version:

Secure CLI tool that translates natural language to shell commands using local AI models via Ollama, with project memory system, reusable command templates (hooks), MCP (Model Context Protocol) support, and dangerous command detection

172 lines (150 loc) 6.1 kB
/** * Intent Classification System - MCP-First Approach * * This system prioritizes MCP tools when available and falls back to * terminal commands only when no suitable MCP tools are found. */ /** * Intent types that help determine how to handle user requests */ export const INTENT_TYPES = { MCP_TOOL_DIRECT: 'mcp_tool_direct', // Direct MCP tool execution TERMINAL_COMMAND: 'terminal_command', // Traditional terminal command HYBRID: 'hybrid', // Both MCP tool + terminal command AMBIGUOUS: 'ambiguous' // Needs clarification }; /** * Get human-readable description for an intent type */ export function getIntentDescription(intent) { switch (intent) { case INTENT_TYPES.MCP_TOOL_DIRECT: return 'MCP Tool Direct Execution'; case INTENT_TYPES.TERMINAL_COMMAND: return 'Terminal Command Generation'; case INTENT_TYPES.HYBRID: return 'Hybrid Approach'; case INTENT_TYPES.AMBIGUOUS: return 'Ambiguous - needs clarification'; default: return 'Unknown intent'; } } /** * Analyzes user input to determine the most appropriate intent * MCP-first approach: Always try to match with MCP tools first * * @param {string} userInput - Natural language input from user * @param {Array} availableTools - Available MCP tools * @returns {object} Intent analysis result */ export function analyzeIntent(userInput, availableTools = []) { const input = userInput.toLowerCase().trim(); // First, check if we have any MCP tools available if (availableTools.length > 0) { // Find relevant MCP tools based on the input const relevantTools = findRelevantTools(input, availableTools); if (relevantTools.length > 0) { // We found matching MCP tools - prefer using them return { intent: INTENT_TYPES.MCP_TOOL_DIRECT, confidence: calculateToolRelevance(relevantTools), scores: { [INTENT_TYPES.MCP_TOOL_DIRECT]: calculateToolRelevance(relevantTools), [INTENT_TYPES.TERMINAL_COMMAND]: 0.2, // Low score since we have MCP tools [INTENT_TYPES.HYBRID]: 0.1 }, reasoning: `Found ${relevantTools.length} relevant MCP tool(s) that can handle this request`, suggestedTools: relevantTools, isAmbiguous: false }; } } // No MCP tools available or no matching tools - fall back to terminal command return { intent: INTENT_TYPES.TERMINAL_COMMAND, confidence: 0.8, // High confidence for terminal fallback scores: { [INTENT_TYPES.MCP_TOOL_DIRECT]: 0, [INTENT_TYPES.TERMINAL_COMMAND]: 0.8, [INTENT_TYPES.HYBRID]: 0.1 }, reasoning: availableTools.length === 0 ? 'No MCP tools available, using terminal command' : 'No matching MCP tools found, falling back to terminal command', suggestedTools: [], isAmbiguous: false }; } /** * Find MCP tools relevant to the user's input * Uses intelligent matching based on tool names and descriptions */ export function findRelevantTools(input, availableTools) { if (!availableTools || availableTools.length === 0) { return []; } const inputWords = input.toLowerCase().split(/\s+/); return availableTools .map(tool => { let score = 0; const toolName = (tool.name || '').toLowerCase(); const toolDesc = (tool.description || '').toLowerCase(); // Check if tool name matches any part of the input if (inputWords.some(word => toolName.includes(word) || word.includes(toolName))) { score += 0.5; } // Check if input contains tool name if (input.includes(toolName)) { score += 0.5; } // Check description relevance const descWords = toolDesc.split(/\s+/); const matchingWords = inputWords.filter(word => descWords.some(descWord => descWord.includes(word) || word.includes(descWord) ) ); score += (matchingWords.length / inputWords.length) * 0.3; // Special cases for common operations // These are not hardcoded patterns but intelligent matches if (tool.name.includes('list') && input.includes('list')) score += 0.2; if (tool.name.includes('read') && (input.includes('read') || input.includes('show') || input.includes('display'))) score += 0.2; if (tool.name.includes('write') && (input.includes('write') || input.includes('save') || input.includes('create'))) score += 0.2; if (tool.name.includes('search') && (input.includes('search') || input.includes('find') || input.includes('grep'))) score += 0.2; return { ...tool, relevanceScore: score }; }) .filter(tool => tool.relevanceScore > 0.1) // Only include tools with some relevance .sort((a, b) => b.relevanceScore - a.relevanceScore) .slice(0, 5); // Return top 5 most relevant tools } /** * Calculate overall relevance score for a set of tools */ function calculateToolRelevance(tools) { if (!tools || tools.length === 0) return 0; // Use the highest scoring tool's relevance const maxScore = Math.max(...tools.map(t => t.relevanceScore || 0)); // Boost confidence if multiple tools are relevant const boost = Math.min(tools.length * 0.1, 0.3); return Math.min(maxScore + boost, 1.0); } /** * Generate reasoning explanation for the intent decision * Simplified to focus on MCP-first approach */ export function generateReasoning(input, scores, availableTools) { const hasTools = availableTools && availableTools.length > 0; if (!hasTools) { return 'No MCP tools available, defaulting to terminal command generation'; } const mcpScore = scores[INTENT_TYPES.MCP_TOOL_DIRECT] || 0; const terminalScore = scores[INTENT_TYPES.TERMINAL_COMMAND] || 0; if (mcpScore > terminalScore) { return 'MCP tools can handle this request directly'; } else if (terminalScore > mcpScore) { return 'No suitable MCP tools found for this request, using terminal command'; } else { return 'Request requires clarification'; } }