import * as plugins from '../plugins.js'; import type { ITrimConfig, ContextMode } from './types.js'; /** * Class responsible for trimming file contents to reduce token usage * while preserving important information for context */ export class ContextTrimmer { private config: ITrimConfig; /** * Create a new ContextTrimmer with the given configuration * @param config The trimming configuration */ constructor(config?: ITrimConfig) { this.config = { removeImplementations: true, preserveInterfaces: true, preserveTypeDefs: true, preserveJSDoc: true, maxFunctionLines: 5, removeComments: true, removeBlankLines: true, ...config }; } /** * Trim a file's contents based on the configuration * @param filePath The path to the file * @param content The file's contents * @param mode The context mode to use * @returns The trimmed file contents */ public trimFile(filePath: string, content: string, mode: ContextMode = 'trimmed'): string { // If mode is 'full', return the original content if (mode === 'full') { return content; } // Process based on file type if (filePath.endsWith('.ts') || filePath.endsWith('.tsx')) { return this.trimTypeScriptFile(content); } else if (filePath.endsWith('.md')) { return this.trimMarkdownFile(content); } else if (filePath.endsWith('.json')) { return this.trimJsonFile(content); } // Default to returning the original content for unknown file types return content; } /** * Trim a TypeScript file to reduce token usage * @param content The TypeScript file contents * @returns The trimmed file contents */ private trimTypeScriptFile(content: string): string { let result = content; // Step 1: Preserve JSDoc comments if configured const jsDocComments: string[] = []; if (this.config.preserveJSDoc) { const jsDocRegex = /\/\*\*[\s\S]*?\*\//g; const matches = result.match(jsDocRegex) || []; jsDocComments.push(...matches); } // Step 2: Remove comments if configured if (this.config.removeComments) { // Remove single-line comments result = result.replace(/\/\/.*$/gm, ''); // Remove multi-line comments (except JSDoc if preserveJSDoc is true) if (!this.config.preserveJSDoc) { result = result.replace(/\/\*[\s\S]*?\*\//g, ''); } else { // Only remove non-JSDoc comments result = result.replace(/\/\*(?!\*)[\s\S]*?\*\//g, ''); } } // Step 3: Remove function implementations if configured if (this.config.removeImplementations) { // Match function and method bodies result = result.replace( /(\b(function|constructor|async function)\s+[\w$]*\s*\([^)]*\)\s*{)([\s\S]*?)(})/g, (match, start, funcType, body, end) => { // Keep function signature and opening brace, replace body with comment return `${start} /* implementation removed */ ${end}`; } ); // Match arrow function bodies result = result.replace( /(\([^)]*\)\s*=>\s*{)([\s\S]*?)(})/g, (match, start, body, end) => { return `${start} /* implementation removed */ ${end}`; } ); // Match method declarations result = result.replace( /(^\s*[\w$]*\s*\([^)]*\)\s*{)([\s\S]*?)(})/gm, (match, start, body, end) => { return `${start} /* implementation removed */ ${end}`; } ); // Match class methods result = result.replace( /(\b(public|private|protected|static|async)?\s+[\w$]+\s*\([^)]*\)\s*{)([\s\S]*?)(})/g, (match, start, modifier, body, end) => { return `${start} /* implementation removed */ ${end}`; } ); } else if (this.config.maxFunctionLines && this.config.maxFunctionLines > 0) { // If not removing implementations completely, limit the number of lines // Match function and method bodies result = result.replace( /(\b(function|constructor|async function)\s+[\w$]*\s*\([^)]*\)\s*{)([\s\S]*?)(})/g, (match, start, funcType, body, end) => { return this.limitFunctionBody(start, body, end); } ); // Match arrow function bodies result = result.replace( /(\([^)]*\)\s*=>\s*{)([\s\S]*?)(})/g, (match, start, body, end) => { return this.limitFunctionBody(start, body, end); } ); // Match method declarations result = result.replace( /(^\s*[\w$]*\s*\([^)]*\)\s*{)([\s\S]*?)(})/gm, (match, start, body, end) => { return this.limitFunctionBody(start, body, end); } ); // Match class methods result = result.replace( /(\b(public|private|protected|static|async)?\s+[\w$]+\s*\([^)]*\)\s*{)([\s\S]*?)(})/g, (match, start, modifier, body, end) => { return this.limitFunctionBody(start, body, end); } ); } // Step 4: Remove blank lines if configured if (this.config.removeBlankLines) { result = result.replace(/^\s*[\r\n]/gm, ''); } // Step 5: Restore preserved JSDoc comments if (this.config.preserveJSDoc && jsDocComments.length > 0) { // This is a placeholder; we already preserved JSDoc comments in the regex steps } return result; } /** * Limit a function body to a maximum number of lines * @param start The function signature and opening brace * @param body The function body * @param end The closing brace * @returns The limited function body */ private limitFunctionBody(start: string, body: string, end: string): string { const lines = body.split('\n'); if (lines.length > this.config.maxFunctionLines!) { const limitedBody = lines.slice(0, this.config.maxFunctionLines!).join('\n'); return `${start}${limitedBody}\n // ... (${lines.length - this.config.maxFunctionLines!} lines trimmed)\n${end}`; } return `${start}${body}${end}`; } /** * Trim a Markdown file to reduce token usage * @param content The Markdown file contents * @returns The trimmed file contents */ private trimMarkdownFile(content: string): string { // For markdown files, we generally want to keep most content // but we can remove lengthy code blocks if needed return content; } /** * Trim a JSON file to reduce token usage * @param content The JSON file contents * @returns The trimmed file contents */ private trimJsonFile(content: string): string { try { // Parse the JSON const json = JSON.parse(content); // For package.json, keep only essential information if ('name' in json && 'version' in json && 'dependencies' in json) { const essentialKeys = [ 'name', 'version', 'description', 'author', 'license', 'main', 'types', 'exports', 'type' ]; const trimmedJson: any = {}; essentialKeys.forEach(key => { if (key in json) { trimmedJson[key] = json[key]; } }); // Add dependency information without versions if ('dependencies' in json) { trimmedJson.dependencies = Object.keys(json.dependencies).reduce((acc, dep) => { acc[dep] = '*'; // Replace version with wildcard return acc; }, {} as Record); } // Return the trimmed JSON return JSON.stringify(trimmedJson, null, 2); } // For other JSON files, leave as is return content; } catch (error) { // If there's an error parsing the JSON, return the original content return content; } } /** * Update the trimmer configuration * @param config The new configuration to apply */ public updateConfig(config: ITrimConfig): void { this.config = { ...this.config, ...config }; } }