Skip to main content
Glama
custom-prompt.ts18.7 kB
/** * Custom Prompt Executor - Modern v4.3 Universal Template * * Universal fallback function that allows Claude to communicate any task directly to local LLM * Acts as the Swiss Army knife for tasks that don't match other specialized functions * * Created using the modern universal template architecture */ import { BasePlugin } from '../../plugins/base-plugin.js'; import { IPromptPlugin } from '../shared/types.js'; import { ThreeStagePromptManager } from '../../core/ThreeStagePromptManager.js'; import { PromptStages } from '../../types/prompt-stages.js'; import { withSecurity } from '../../security/integration-helpers.js'; import { readFileContent } from '../shared/helpers.js'; import { ModelSetup, ResponseProcessor, ParameterValidator, ErrorHandler, MultiFileAnalysis, TokenCalculator } from '../../utils/plugin-utilities.js'; import { getAnalysisCache } from '../../cache/index.js'; export class CustomPromptExecutor extends BasePlugin implements IPromptPlugin { name = 'custom_prompt'; category = 'custom' as const; description = 'Universal fallback executor for any custom prompt with optional file context. Uses dynamic token allocation based on your loaded model - can handle everything from quick tasks to comprehensive multi-file analysis. The Swiss Army knife when no other specialized function matches your needs.'; // Universal parameter set - supports both single and multi-file scenarios parameters = { // Single-file parameters code: { type: 'string' as const, description: 'The code to analyze (for single-file analysis)', required: false }, filePath: { type: 'string' as const, description: 'Path to single file to analyze', required: false }, // Multi-file parameters projectPath: { type: 'string' as const, description: 'Path to project root (for multi-file analysis)', required: false }, files: { type: 'array' as const, description: 'Array of specific file paths to include as context', required: false, items: { type: 'string' as const } }, maxDepth: { type: 'number' as const, description: 'Maximum directory depth for multi-file discovery (1-5)', required: false, default: 3 }, // Custom prompt specific parameters prompt: { type: 'string' as const, description: 'The custom prompt/task to send to local LLM', required: true }, working_directory: { type: 'string' as const, description: 'Working directory context (defaults to current working directory)', required: false }, context: { type: 'object' as const, description: 'Optional structured context object for the task', required: false, properties: { task_type: { type: 'string' as const }, requirements: { type: 'array' as const }, constraints: { type: 'array' as const }, output_format: { type: 'string' as const } } }, // Universal parameters language: { type: 'string' as const, description: 'Programming language (if applicable)', required: false, default: 'text' }, analysisDepth: { type: 'string' as const, description: 'Level of analysis detail', enum: ['basic', 'detailed', 'comprehensive'], default: 'detailed', required: false }, analysisType: { type: 'string' as const, description: 'Type of analysis to perform', enum: ['general', 'technical', 'creative', 'analytical'], default: 'general', required: false } }; private analysisCache = getAnalysisCache(); private multiFileAnalysis = new MultiFileAnalysis(); constructor() { super(); // Cache and analysis utilities are initialized above } async execute(params: any, llmClient: any) { return await withSecurity(this, params, llmClient, async (secureParams) => { try { // 1. Auto-detect analysis mode based on parameters const analysisMode = this.detectAnalysisMode(secureParams); // 2. Validate parameters based on detected mode this.validateParameters(secureParams, analysisMode); // 3. Setup model const { model, contextLength } = await ModelSetup.getReadyModel(llmClient); // 4. Route to appropriate analysis method if (analysisMode === 'single-file') { return await this.executeSingleFileAnalysis(secureParams, model, contextLength); } else { return await this.executeMultiFileAnalysis(secureParams, model, contextLength); } } catch (error: any) { return ErrorHandler.createExecutionError('custom_prompt', error); } }); } /** * Auto-detect whether this is single-file or multi-file analysis */ private detectAnalysisMode(params: any): 'single-file' | 'multi-file' { // Multi-file indicators if (params.projectPath || (params.files && params.files.length > 1)) { return 'multi-file'; } // Single-file indicators or no file context (just prompt) if (params.code || params.filePath || (params.files && params.files.length === 1) || (!params.projectPath && !params.files)) { return 'single-file'; } // Default to single-file for simple prompts return 'single-file'; } /** * Validate parameters based on detected analysis mode */ private validateParameters(params: any, mode: 'single-file' | 'multi-file'): void { // Always require the prompt if (!params.prompt || typeof params.prompt !== 'string') { throw new Error('Prompt is required and must be a string'); } if (mode === 'single-file') { // Single-file mode can work with just a prompt, or with file context if (params.filePath) { ParameterValidator.validateCodeOrFile(params); } } else { // Multi-file mode requires either projectPath or files array if (!params.projectPath && !params.files) { throw new Error('Multi-file mode requires either projectPath or files array'); } if (params.projectPath) { ParameterValidator.validateProjectPath(params); } ParameterValidator.validateDepth(params); } // Universal validations ParameterValidator.validateEnum(params, 'analysisType', ['general', 'technical', 'creative', 'analytical']); ParameterValidator.validateEnum(params, 'analysisDepth', ['basic', 'detailed', 'comprehensive']); } /** * Execute single-file analysis */ private async executeSingleFileAnalysis(params: any, model: any, contextLength: number) { // Process single file input let codeToAnalyze = params.code || ''; if (params.filePath) { codeToAnalyze = await readFileContent(params.filePath); } else if (params.files && params.files.length === 1) { codeToAnalyze = await readFileContent(params.files[0]); } // Generate prompt stages for single file const promptStages = this.getSingleFilePromptStages({ ...params, code: codeToAnalyze }); // Execute with appropriate method const promptManager = new ThreeStagePromptManager(); const needsChunking = TokenCalculator.needsChunking(promptStages, contextLength); if (needsChunking) { const chunkSize = TokenCalculator.calculateOptimalChunkSize(promptStages, contextLength); const dataChunks = promptManager.chunkDataPayload(promptStages.dataPayload, chunkSize); const conversation = promptManager.createChunkedConversation(promptStages, dataChunks); const messages = [ conversation.systemMessage, ...conversation.dataMessages, conversation.analysisMessage ]; return await ResponseProcessor.executeChunked( messages, model, contextLength, 'custom_prompt', 'single' ); } else { // Use proper dynamic token calculation for custom prompts const maxTokens = TokenCalculator.calculateForDirect( promptStages, contextLength, { minTokens: 2000 // Minimum for code generation } ); const fullPrompt = `${promptStages.systemAndContext} ${promptStages.dataPayload} ${promptStages.outputInstructions}`; const response = await model.complete(fullPrompt, { maxTokens, temperature: 0.7 }); return { success: true, timestamp: new Date().toISOString(), modelUsed: model.identifier || 'unknown', executionTimeMs: 0, data: response?.content || response?.text || response || 'No response content' }; } } /** * Execute multi-file analysis */ private async executeMultiFileAnalysis(params: any, model: any, contextLength: number) { // Discover files let filesToAnalyze: string[] = params.files || await this.discoverRelevantFiles( params.projectPath, params.maxDepth, params.analysisType ); // Perform multi-file analysis with caching const analysisResult = await this.performMultiFileAnalysis( filesToAnalyze, params, model, contextLength ); // Generate prompt stages for multi-file const promptStages = this.getMultiFilePromptStages({ ...params, analysisResult, fileCount: filesToAnalyze.length }); // Always use chunking for multi-file const promptManager = new ThreeStagePromptManager(); const chunkSize = TokenCalculator.calculateOptimalChunkSize(promptStages, contextLength); const dataChunks = promptManager.chunkDataPayload(promptStages.dataPayload, chunkSize); const conversation = promptManager.createChunkedConversation(promptStages, dataChunks); const messages = [ conversation.systemMessage, ...conversation.dataMessages, conversation.analysisMessage ]; return await ResponseProcessor.executeChunked( messages, model, contextLength, 'custom_prompt', 'multifile' ); } /** * Single-file prompt stages - optimized for universal custom tasks */ private getSingleFilePromptStages(params: any): PromptStages { const { prompt, code, language, analysisDepth, analysisType, context, working_directory } = params; let systemAndContext = `You are an expert AI assistant specializing in ${analysisDepth} ${analysisType} tasks. **Your Role**: Universal problem-solver and task executor with full access to dynamic token allocation **Analysis Context**: - Task Type: ${analysisType} - Analysis Depth: ${analysisDepth} - Language: ${language} - Mode: Single File/Context Analysis - Token Allocation: Dynamic (optimized for your current model's context window) **Your Mission**: Execute the custom task with precision, creativity, and actionable results. You have access to the full context window of the loaded model, so don't hesitate to be comprehensive when the task requires it. You are the Swiss Army knife - adaptable, reliable, and comprehensive.`; if (working_directory) { systemAndContext += `\n- Working Directory: ${working_directory}`; } if (context) { systemAndContext += `\n\n**Task Context**:`; if (context.task_type) systemAndContext += `\n- Task Type: ${context.task_type}`; if (context.requirements) systemAndContext += `\n- Requirements: ${Array.isArray(context.requirements) ? context.requirements.join(', ') : context.requirements}`; if (context.constraints) systemAndContext += `\n- Constraints: ${Array.isArray(context.constraints) ? context.constraints.join(', ') : context.constraints}`; if (context.output_format) systemAndContext += `\n- Output Format: ${context.output_format}`; } let dataPayload = ''; if (code && code.trim()) { dataPayload = `**File Content for Analysis:** \`\`\`${language} ${code} \`\`\``; } else { dataPayload = `**Context**: Ready to execute custom task without specific file context.`; } const outputInstructions = `**CUSTOM TASK TO EXECUTE:** ${prompt} **Your Response Should:** - Directly address the task requirements - Be actionable and practical - Match the requested output format (if specified) - Demonstrate deep understanding and expertise - Provide clear, implementable solutions Execute this task with excellence and precision. You are Claude's trusted specialist for custom operations.`; return { systemAndContext, dataPayload, outputInstructions }; } /** * Multi-file prompt stages - optimized for complex custom tasks across multiple files */ private getMultiFilePromptStages(params: any): PromptStages { const { prompt, analysisResult, analysisType, analysisDepth, fileCount, context, working_directory } = params; let systemAndContext = `You are an expert multi-file AI analyst specializing in ${analysisDepth} ${analysisType} tasks. **Your Role**: Advanced multi-file task executor and architectural consultant with full dynamic token access **Analysis Context**: - Task Type: ${analysisType} - Analysis Depth: ${analysisDepth} - Files Processed: ${fileCount} - Mode: Multi-File Custom Analysis - Token Allocation: Dynamic (full model context window available) **Your Mission**: Execute complex custom tasks across multiple files with comprehensive understanding. You have access to the full context window, so provide detailed, thorough analysis when appropriate. You excel at seeing patterns, relationships, and providing strategic insights across entire codebases and project structures.`; if (working_directory) { systemAndContext += `\n- Working Directory: ${working_directory}`; } if (context) { systemAndContext += `\n\n**Task Context**:`; if (context.task_type) systemAndContext += `\n- Task Type: ${context.task_type}`; if (context.requirements) systemAndContext += `\n- Requirements: ${Array.isArray(context.requirements) ? context.requirements.join(', ') : context.requirements}`; if (context.constraints) systemAndContext += `\n- Constraints: ${Array.isArray(context.constraints) ? context.constraints.join(', ') : context.constraints}`; if (context.output_format) systemAndContext += `\n- Output Format: ${context.output_format}`; } const dataPayload = `**Multi-File Analysis Results:** ${JSON.stringify(analysisResult, null, 2)} **Files Analyzed**: ${fileCount} files processed across the project structure.`; const outputInstructions = `**CUSTOM MULTI-FILE TASK TO EXECUTE:** ${prompt} **Your Multi-File Response Should:** - Synthesize insights across all ${fileCount} analyzed files - Identify cross-file patterns, dependencies, and relationships - Provide architectural and strategic recommendations - Address the task with comprehensive project-wide understanding - Deliver actionable results that consider the entire codebase context - Match any specified output format requirements Execute this complex multi-file task with expert-level analysis and strategic thinking. You are Claude's advanced specialist for comprehensive project-wide operations.`; return { systemAndContext, dataPayload, outputInstructions }; } /** * Backwards compatibility method */ getPromptStages(params: any): PromptStages { const mode = this.detectAnalysisMode(params); if (mode === 'single-file') { return this.getSingleFilePromptStages(params); } else { return this.getMultiFilePromptStages(params); } } // Multi-file helper methods private async discoverRelevantFiles( projectPath: string, maxDepth: number, analysisType: string ): Promise<string[]> { const extensions = this.getFileExtensions(analysisType); return await this.multiFileAnalysis.discoverFiles(projectPath, extensions, maxDepth); } private async performMultiFileAnalysis( files: string[], params: any, model: any, contextLength: number ): Promise<any> { const cacheKey = this.analysisCache.generateKey( 'custom_prompt', params, files ); const cached = await this.analysisCache.get(cacheKey); if (cached) return cached; const fileAnalysisResults = await this.multiFileAnalysis.analyzeBatch( files, (file: string) => this.analyzeIndividualFile(file, params, model), contextLength ); // Aggregate results into proper analysis result format const aggregatedResult = { summary: `Multi-file custom task analysis of ${files.length} files`, findings: fileAnalysisResults, data: { fileCount: files.length, totalSize: fileAnalysisResults.reduce((sum: number, result: any) => sum + (result.size || 0), 0), customTaskContext: { prompt: params.prompt, analysisType: params.analysisType, context: params.context } } }; await this.analysisCache.cacheAnalysis(cacheKey, aggregatedResult, { modelUsed: model.identifier || 'unknown', executionTime: Date.now() - Date.now(), // TODO: Track actual execution time timestamp: new Date().toISOString() }); return aggregatedResult; } private async analyzeIndividualFile(file: string, params: any, model: any): Promise<any> { const content = await import('fs/promises').then(fs => fs.readFile(file, 'utf-8')); return { filePath: file, size: content.length, lines: content.split('\n').length, extension: file.split('.').pop() || '', customContext: { taskType: params.analysisType, prompt: params.prompt } }; } private getFileExtensions(analysisType: string): string[] { const extensionMap: Record<string, string[]> = { 'general': ['.js', '.ts', '.jsx', '.tsx', '.php', '.py', '.java', '.md', '.txt', '.json', '.xml', '.css', '.html'], 'technical': ['.js', '.ts', '.jsx', '.tsx', '.php', '.py', '.java', '.cs', '.cpp', '.h', '.go', '.rs', '.rb'], 'creative': ['.md', '.txt', '.html', '.css', '.json', '.xml', '.yml', '.yaml'], 'analytical': ['.js', '.ts', '.jsx', '.tsx', '.php', '.py', '.java', '.json', '.csv', '.sql', '.md'] }; return extensionMap[analysisType] || extensionMap.general; } private generateCacheKey(files: string[], params: any): string { const fileHash = files.join('|'); const paramHash = JSON.stringify(params); return `${fileHash}_${paramHash}`.substring(0, 64); } } export default CustomPromptExecutor;

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/houtini-ai/lm'

If you have feedback or need assistance with the MCP directory API, please join our Discord server