UNPKG

claude-gpt-collabration

Version:

MCP server for GPT-5 interactive file reading and collaboration with Claude Code

173 lines (172 loc) 7.43 kB
import { callGPT5WithInput, GPT5DebugLogger, GPT5HookManager } from './gpt5.js'; import { SmartFilePreparation } from './smartFilePreparation.js'; import fs from "fs/promises"; import path from "path"; /** * Basic file collaboration using file content injection * This version works with any OpenAI SDK version by including file content in prompts */ export async function collaborateWithFilesBasic(options) { const { root, task, model = "gpt-4-turbo", instructions, max_output_tokens = 800, upload_strategy = 'auto', manual_files, file_patterns, auto_prepare = true, max_files = 10, max_file_size = 50000 // 50KB per file limit for context } = options; const logger = GPT5DebugLogger.getInstance(); const functionName = 'collaborateWithFilesBasic'; const startTime = Date.now(); try { // Execute beforeRequest hooks await GPT5HookManager.executeHooks('beforeRequest', { functionName, input: options, timestamp: new Date().toISOString() }); // Log request await logger.logRequest(functionName, { root, task: task.slice(0, 200) + (task.length > 200 ? '...[truncated]' : ''), fullTaskLength: task.length, model, upload_strategy, manual_files, auto_prepare }); // Step 1: Determine files to include let filesToInclude = []; if (upload_strategy === 'auto' && auto_prepare) { // Smart preparation console.log(`[${functionName}] Auto-preparing files for task...`); const preparator = new SmartFilePreparation(); const suggestedFiles = await preparator.analyzeTaskAndPrepareFiles(task, root, max_files); filesToInclude = suggestedFiles; } else if (upload_strategy === 'manual') { // Manual preparation if (manual_files && manual_files.length > 0) { filesToInclude = manual_files; } else if (file_patterns && file_patterns.length > 0) { // Use glob patterns to find files for (const pattern of file_patterns) { try { const { glob } = await import('glob'); const files = await glob(pattern, { cwd: root, absolute: false, nodir: true }); filesToInclude.push(...files.slice(0, max_files)); } catch (error) { console.warn(`[${functionName}] Pattern failed: ${pattern}`, error); } } } console.log(`[${functionName}] Manually selected ${filesToInclude.length} files`); } // Step 2: Read file contents and build context let fileContext = ''; let includedFiles = []; if (filesToInclude.length > 0) { console.log(`[${functionName}] Reading ${filesToInclude.length} files...`); for (const relativeFile of filesToInclude) { try { const fullPath = path.resolve(root, relativeFile); // Security check if (isPathBlocked(fullPath)) { console.warn(`[${functionName}] Blocked path: ${relativeFile}`); continue; } const stats = await fs.stat(fullPath); // Size check if (stats.size > max_file_size) { console.warn(`[${functionName}] File too large: ${relativeFile} (${stats.size} bytes)`); // Include truncated version const content = await fs.readFile(fullPath, 'utf-8'); const truncated = content.substring(0, max_file_size) + '\n... [truncated]'; fileContext += `\n\n=== File: ${relativeFile} (truncated) ===\n${truncated}\n`; includedFiles.push({ path: relativeFile, size: max_file_size }); continue; } const content = await fs.readFile(fullPath, 'utf-8'); fileContext += `\n\n=== File: ${relativeFile} ===\n${content}\n`; includedFiles.push({ path: relativeFile, size: content.length }); } catch (error) { console.warn(`[${functionName}] Cannot read file: ${relativeFile}`, error); } } } // Step 3: Build the prompt let prompt = `Task: ${task}\n\n`; if (fileContext) { prompt += `I have provided the following project files for analysis:\n`; prompt += includedFiles.map(f => `- ${f.path} (${f.size} chars)`).join('\n'); prompt += `\n\nFile Contents:${fileContext}\n\n`; prompt += `Please analyze these files in the context of the task. `; } else { prompt += `No specific files were provided. Please proceed with the task based on general knowledge. `; prompt += `If you need access to specific files, please let me know which files would be helpful.\n\n`; } prompt += `Instructions: ${instructions || 'Provide a helpful and accurate response based on the available information.'}`; // Step 4: Call GPT-5 console.log(`[${functionName}] Calling GPT-5 with ${fileContext.length} characters of file context`); const response = await callGPT5WithInput({ input: prompt, model, max_output_tokens }); const endTime = Date.now(); // Log response await logger.logResponse(functionName, response, { model, duration: endTime - startTime, includedFiles: includedFiles.length, contextSize: fileContext.length }); // Execute afterResponse hooks await GPT5HookManager.executeHooks('afterResponse', { functionName, input: options, output: response, metadata: { duration: endTime - startTime, model, includedFiles: includedFiles.length }, timestamp: new Date().toISOString() }); return response; } catch (error) { const endTime = Date.now(); // Log error await logger.logError(functionName, error, { input: options, duration: endTime - startTime }); // Execute onError hooks await GPT5HookManager.executeHooks('onError', { functionName, input: options, error, duration: endTime - startTime, timestamp: new Date().toISOString() }); return `❌ 基础文件协作过程中发生错误:${error?.message || String(error)} 任务:"${task}" 请检查配置和网络连接后重试。`; } } /** * Check if path should be blocked for security */ function isPathBlocked(filePath) { const blockedPatterns = [ /\.ssh/, /\.env/, /\.key$/, /\.pem$/, /node_modules/, /\.git/, /password|secret|token/i, /\.lock$/, /\.log$/ ]; return blockedPatterns.some(pattern => pattern.test(filePath)); }