import * as fs from 'fs'; import * as path from 'path'; import { globSync } from 'glob'; /** * Default implementation of the ContextManager interface * Responsible for gathering and managing file context to be sent to the LLM */ export class ContextManager implements IContextManager { private contexts: IFileContext[] = []; /** * Add a single file to the context * @param filePath Path to the file */ public async addFile(filePath: string): Promise { try { const absolutePath = path.resolve(filePath); const content = await fs.promises.readFile(absolutePath, 'utf-8'); const language = this.detectLanguage(absolutePath); this.contexts.push({ path: absolutePath, content, language, metadata: { size: content.length, lastModified: (await fs.promises.stat(absolutePath)).mtime } }); } catch (error: any) { throw new Error(`Failed to add file ${filePath}: ${error.message}`); } } /** * Add multiple files to the context * @param filePaths Array of file paths */ public async addFiles(filePaths: string[]): Promise { await Promise.all(filePaths.map(filePath => this.addFile(filePath))); } /** * Add all files in a directory to the context * @param dirPath Path to the directory * @param options Options for directory traversal */ public async addDirectory(dirPath: string, options?: IDirectoryOptions): Promise { const absoluteDirPath = path.resolve(dirPath); try { // Ensure directory exists await fs.promises.access(absoluteDirPath); // Default options const defaultOptions: IDirectoryOptions = { include: ['**/*.*'], exclude: ['**/node_modules/**', '**/.git/**'], maxDepth: 5, maxFiles: 100 }; const mergedOptions = { ...defaultOptions, ...options }; // Use glob to find files let files: string[] = []; for (const pattern of mergedOptions.include || ['**/*.*']) { const matches = globSync(pattern, { cwd: absoluteDirPath, ignore: mergedOptions.exclude, nodir: true, absolute: true, maxDepth: mergedOptions.maxDepth }); files.push(...matches); } // Remove duplicates and limit to maxFiles files = Array.from(new Set(files)).slice(0, mergedOptions.maxFiles); // Add files to context await this.addFiles(files); } catch (error: any) { throw new Error(`Failed to add directory ${dirPath}: ${error.message}`); } } /** * Get all file contexts * @returns Array of file contexts */ public async getContext(): Promise { return [...this.contexts]; } /** * Clear all contexts */ public clear(): void { this.contexts = []; } /** * Detect the programming language of a file based on its extension * @param filePath Path to the file * @returns Language identifier or undefined */ private detectLanguage(filePath: string): string | undefined { const extension = path.extname(filePath).toLowerCase(); const languageMap: Record = { '.js': 'javascript', '.jsx': 'javascript', '.ts': 'typescript', '.tsx': 'typescript', '.py': 'python', '.rb': 'ruby', '.java': 'java', '.go': 'go', '.php': 'php', '.cs': 'csharp', '.cpp': 'cpp', '.c': 'c', '.h': 'c', '.hpp': 'cpp', '.html': 'html', '.css': 'css', '.scss': 'scss', '.json': 'json', '.md': 'markdown', '.yml': 'yaml', '.yaml': 'yaml', '.sh': 'bash', '.rs': 'rust', '.swift': 'swift', '.kt': 'kotlin', '.sql': 'sql' }; return languageMap[extension]; } /** * Format all file contexts into a single annotated string using <<>> blocks * @returns Formatted string with file metadata and content */ public async contextToString(): Promise { const contexts = await this.getContext(); return contexts.map(ctx => { const attrs = [ `path=\"${ctx.path.replace(/"/g, '\"')}\"`, ctx.language ? `language=\"${ctx.language}\"` : '', ctx.metadata?.size !== undefined ? `size=\"${ctx.metadata.size}\"` : '', ctx.metadata?.lastModified ? `lastModified=\"${ctx.metadata.lastModified}\"` : '' ].filter(Boolean).join(' '); return `<<>>\n${ctx.content}\n<<>>`; }).join('\n'); } } /** * Core interfaces for the Coder library */ /** * Represents a file context to be sent to the LLM */ export interface IFileContext { path: string; content: string; language?: string; metadata?: Record; } /** * Options for directory operations */ export interface IDirectoryOptions { include?: string[]; exclude?: string[]; maxDepth?: number; maxFiles?: number; context?: string[]; } /** * Context Manager interface */ export interface IContextManager { addFile(filePath: string): Promise; addFiles(filePaths: string[]): Promise; addDirectory(dirPath: string, options?: IDirectoryOptions): Promise; getContext(): Promise; clear(): void; } /** * Provider options for configuring LLM providers */ export interface ProviderOptions { apiKey?: string; model?: string; temperature?: number; maxTokens?: number; [key: string]: any; }