UNPKG

vibe-coder-mcp

Version:

Production-ready MCP server with complete agent integration, multi-transport support, and comprehensive development automation tools for AI-assisted workflows.

507 lines (506 loc) 19.5 kB
import axios, { AxiosError, AxiosHeaders } from 'axios'; import { vi } from 'vitest'; import * as dotenv from 'dotenv'; dotenv.config(); const openRouterBaseUrl = process.env.OPENROUTER_BASE_URL || 'https://openrouter.ai/api/v1'; function detectOperationType(requestData) { const messages = requestData?.messages || []; const systemMessage = messages.find((m) => m.role === 'system')?.content || ''; const userMessage = messages.find((m) => m.role === 'user')?.content || ''; const cacheKey = `${systemMessage}|${userMessage}`; if (operationTypeCache.has(cacheKey)) { return operationTypeCache.get(cacheKey); } let operationType = 'intent_recognition'; if (systemMessage.includes('natural language processing system') || systemMessage.includes('recognizing user intents') || systemMessage.includes('intent recognition') || userMessage.includes('recognize intent') || userMessage.includes('intent recognition')) { operationType = 'intent_recognition'; } else if (systemMessage.includes('atomic task detection') || systemMessage.includes('atomic task analyzer') || systemMessage.includes('determine if a given task is atomic') || systemMessage.includes('RDD (Recursive Decomposition and Decision-making)') || userMessage.includes('isAtomic') || userMessage.includes('atomic task analysis')) { operationType = 'atomic_detection'; } else if (systemMessage.includes('task decomposition specialist') || systemMessage.includes('break down complex tasks') || systemMessage.includes('decomposing complex tasks') || systemMessage.includes('decomposition') || systemMessage.includes('split') || userMessage.includes('decompose') || userMessage.includes('break down')) { operationType = 'task_decomposition'; } operationTypeCache.set(cacheKey, operationType); return operationType; } function formatResponseForOperation(content, operationType) { if (typeof content === 'string') { return content; } if (typeof content === 'object' && Object.keys(content).length > 0) { switch (operationType) { case 'intent_recognition': if (content.intent && typeof content.confidence === 'number') { return JSON.stringify(content); } break; case 'atomic_detection': if (typeof content.isAtomic === 'boolean') { return JSON.stringify(content); } break; case 'task_decomposition': if (content.tasks || content.subTasks) { return JSON.stringify(content); } break; } } if (prebuiltResponses.has(operationType)) { const prebuilt = prebuiltResponses.get(operationType); if (typeof content === 'object' && Object.keys(content).length > 0) { return JSON.stringify({ ...prebuilt, ...content }); } return JSON.stringify(prebuilt); } return JSON.stringify(content || {}); } const mockResponseQueues = new Map(); let currentTestId = null; const operationTypeCache = new Map(); const prebuiltResponses = new Map(); prebuiltResponses.set('intent_recognition', { intent: 'create_task', confidence: 0.85, parameters: { task_title: 'implement user authentication', type: 'development' }, context: { temporal: 'immediate', urgency: 'normal' }, alternatives: [] }); prebuiltResponses.set('task_decomposition', { tasks: [{ title: 'Default Task', description: 'Default decomposed task', estimatedHours: 0.1, acceptanceCriteria: ['Task should be completed'], priority: 'medium' }] }); prebuiltResponses.set('atomic_detection', { isAtomic: true, confidence: 0.98, reasoning: 'Task is atomic and focused', estimatedHours: 0.1, complexityFactors: [], recommendations: [] }); export function setTestId(testId) { currentTestId = testId; if (!mockResponseQueues.has(testId)) { mockResponseQueues.set(testId, []); } } export function queueMockResponses(responses) { if (!currentTestId) { throw new Error('Test ID must be set before queueing mock responses. Call setTestId() first.'); } mockResponseQueues.set(currentTestId, [...responses]); if (responses.length > 0) { mockOpenRouterResponse(responses[0]); } } export function clearMockQueue() { if (currentTestId) { mockResponseQueues.set(currentTestId, []); } } export function clearAllMockQueues() { mockResponseQueues.clear(); currentTestId = null; } export function clearPerformanceCaches() { operationTypeCache.clear(); } export function getPerformanceStats() { return { cacheSize: operationTypeCache.size, }; } export const MockTemplates = { intentRecognition: (intent = 'create_task', confidence = 0.85) => ({ responseContent: { intent, confidence, parameters: { task_title: 'test task', type: 'development' }, context: { temporal: 'immediate', urgency: 'normal' }, alternatives: [] }, model: /google\/gemini-2\.5-flash-preview/, operationType: 'intent_recognition' }), atomicDetection: (isAtomic = true, confidence = 0.9) => ({ responseContent: { isAtomic, confidence, reasoning: isAtomic ? 'Task is atomic and focused' : 'Task can be decomposed further', estimatedHours: isAtomic ? 0.1 : 2.0, complexityFactors: isAtomic ? [] : ['Multiple components', 'Complex logic'], recommendations: [] }, model: /google\/gemini-2\.5-flash-preview/, operationType: 'atomic_detection' }), taskDecomposition: (subtaskCount = 3) => ({ responseContent: { subtasks: Array(subtaskCount).fill(null).map((_, i) => ({ id: `subtask-${i + 1}`, title: `Subtask ${i + 1}`, description: `Description for subtask ${i + 1}`, estimatedHours: 0.1, priority: 'medium', acceptanceCriteria: [`Criteria ${i + 1}`], tags: ['test'] })), reasoning: 'Task decomposed into atomic subtasks', confidence: 0.9 }, model: /google\/gemini-2\.5-flash-preview/, operationType: 'task_decomposition' }), error: (errorMessage = 'Mock API Error') => ({ shouldError: true, errorMessage, statusCode: 500, model: /google\/gemini-2\.5-flash-preview/ }) }; export class MockQueueBuilder { queue = []; addIntentRecognitions(count, intent = 'create_task') { for (let i = 0; i < count; i++) { this.queue.push(MockTemplates.intentRecognition(intent, 0.8 + (i * 0.02))); } return this; } addAtomicDetections(count, isAtomic = true) { for (let i = 0; i < count; i++) { this.queue.push(MockTemplates.atomicDetection(isAtomic, 0.9 + (i * 0.01))); } return this; } addTaskDecompositions(count, subtaskCount = 3) { for (let i = 0; i < count; i++) { this.queue.push(MockTemplates.taskDecomposition(subtaskCount)); } return this; } addErrors(count, errorMessage) { for (let i = 0; i < count; i++) { this.queue.push(MockTemplates.error(errorMessage)); } return this; } build() { return [...this.queue]; } queueResponses() { queueMockResponses(this.build()); } clear() { this.queue = []; return this; } } export const performFormatAwareLlmCallWithCentralizedConfig = vi.fn().mockImplementation(async (prompt, systemPrompt, logicalTaskName, expectedFormat = 'text', expectedSchema, _temperature = 0.1) => { const detectedOperationType = detectOperationType({ messages: [ { role: 'system', content: systemPrompt }, { role: 'user', content: prompt } ] }); if (currentTestId && mockResponseQueues.has(currentTestId)) { const testQueue = mockResponseQueues.get(currentTestId); if (testQueue.length > 0) { const mockOptions = testQueue.shift(); const responseContent = mockOptions.responseContent || {}; return formatResponseForOperation(responseContent, detectedOperationType); } } let mockResponse; switch (detectedOperationType) { case 'intent_recognition': mockResponse = { intent: 'create_task', confidence: 0.85, parameters: { task_title: 'mock task', type: 'development' }, context: { temporal: 'immediate', urgency: 'normal' }, alternatives: [] }; break; case 'atomic_detection': mockResponse = { isAtomic: true, confidence: 0.95, reasoning: 'Mock atomic analysis for centralized config', estimatedHours: 0.5, complexityFactors: [], recommendations: [] }; break; case 'task_decomposition': mockResponse = { tasks: [{ title: 'Mock decomposed task', description: 'Mock task from centralized config', type: 'development', priority: 'medium', estimatedHours: 1, acceptanceCriteria: ['Mock acceptance criterion'], tags: ['mock', 'centralized'] }] }; break; default: mockResponse = { result: 'Mock LLM response from centralized config', confidence: 0.9, reasoning: 'Mock reasoning for centralized config test' }; } switch (expectedFormat) { case 'json': return JSON.stringify(mockResponse); case 'yaml': return Object.entries(mockResponse) .map(([key, value]) => `${key}: ${typeof value === 'object' ? JSON.stringify(value) : value}`) .join('\n'); case 'markdown': return `# Mock Response\n\n${JSON.stringify(mockResponse, null, 2)}`; case 'text': default: return typeof mockResponse === 'string' ? mockResponse : JSON.stringify(mockResponse); } }); export class PerformanceTestUtils { static createRobustQueue(primaryResponses, fallbackCount = 20) { const fallbackResponses = Array(fallbackCount).fill(null).map(() => MockTemplates.atomicDetection(true, 0.95)); return [...primaryResponses, ...fallbackResponses]; } static setupEnhancedMocks(testId, responses) { setTestId(testId); queueMockResponses(this.createRobustQueue(responses)); } static createConcurrentMocks(operationType, count) { const builder = new MockQueueBuilder(); switch (operationType) { case 'intent_recognition': builder.addIntentRecognitions(count); break; case 'atomic_detection': builder.addAtomicDetections(count); break; case 'task_decomposition': builder.addTaskDecompositions(count); break; default: builder .addIntentRecognitions(Math.ceil(count / 3)) .addAtomicDetections(Math.ceil(count / 3)) .addTaskDecompositions(Math.floor(count / 3)); } return builder.build(); } static async measureMockPerformance(testName, testFn) { const startTime = Date.now(); const startStats = getPerformanceStats(); try { const result = await testFn(); const endTime = Date.now(); const endStats = getPerformanceStats(); const mockPerformance = { duration: endTime - startTime, cacheStats: { start: startStats, end: endStats, growth: endStats.cacheSize - startStats.cacheSize } }; if (mockPerformance.duration > 2000) { console.warn(`⚠️ Test "${testName}" took ${mockPerformance.duration}ms - consider optimizing mocks`); } return { ...result, mockPerformance }; } catch (error) { const endTime = Date.now(); const endStats = getPerformanceStats(); const mockPerformance = { duration: endTime - startTime, cacheStats: { start: startStats, end: endStats, growth: endStats.cacheSize - startStats.cacheSize } }; console.error(`❌ Test "${testName}" failed after ${mockPerformance.duration}ms`); throw error; } } } function createOperationAwareFallback(operation, originalOptions) { const fallbackOptions = { ...originalOptions }; switch (operation) { case 'intent_recognition': fallbackOptions.responseContent = { intent: 'create_task', confidence: 0.85, parameters: { task_title: 'fallback task', type: 'development' }, context: { temporal: 'immediate', urgency: 'normal' }, alternatives: [] }; break; case 'atomic_detection': fallbackOptions.responseContent = { isAtomic: true, confidence: 0.95, reasoning: 'Fallback atomic detection - task is considered atomic', estimatedHours: 0.08, complexityFactors: [], recommendations: [] }; break; case 'task_decomposition': fallbackOptions.responseContent = { tasks: [ { title: 'Fallback Task', description: 'Fallback task created when queue exhausted', estimatedHours: 0.08, acceptanceCriteria: ['Task should be completed'], priority: 'medium', tags: ['fallback'] } ] }; break; default: fallbackOptions.responseContent = { intent: 'create_task', confidence: 0.75, parameters: {}, context: {}, alternatives: [] }; } return fallbackOptions; } export function mockOpenRouterResponse(options) { const { model, matchUrl = `${openRouterBaseUrl}/chat/completions` } = options; const axiosPostSpy = vi.spyOn(axios, 'post'); axiosPostSpy.mockImplementation(async (url, data, config) => { const urlMatches = (typeof matchUrl === 'string' && url === matchUrl) || (matchUrl instanceof RegExp && matchUrl.test(url)); if (!urlMatches) { console.error(`Unexpected axios.post call to URL: ${url}. Mock configured for ${matchUrl}`); throw new Error(`Unexpected axios.post call to URL: ${url}. Mock configured for ${matchUrl}`); } const requestModel = data?.model; let modelMatches = true; if (model && requestModel) { modelMatches = (typeof model === 'string' && requestModel === model) || (model instanceof RegExp && model.test(requestModel)); } if (model && !modelMatches) { console.error(`Unexpected axios.post call for model: ${requestModel}. Mock configured for ${model}`); throw new Error(`Unexpected axios.post call for model: ${requestModel}. Mock configured for ${model}`); } let detectedOperationType; if (options.operationType && options.operationType !== 'auto') { detectedOperationType = options.operationType; } else { detectedOperationType = detectOperationType(data); } let currentOptions = options; if (currentTestId && mockResponseQueues.has(currentTestId)) { const testQueue = mockResponseQueues.get(currentTestId); if (testQueue.length > 0) { currentOptions = testQueue.shift(); } else { currentOptions = createOperationAwareFallback(detectedOperationType, options); } } if (currentOptions.shouldError) { const currentErrorMessage = currentOptions.errorMessage || 'Mock API Error'; const currentStatusCode = currentOptions.statusCode || 500; const errorConfig = config || {}; const internalErrorConfig = { ...errorConfig, headers: new AxiosHeaders(), }; const error = new AxiosError(currentErrorMessage, currentStatusCode.toString(), internalErrorConfig, data, { data: { error: { message: currentErrorMessage, type: 'mock_error' } }, status: currentStatusCode, statusText: 'Mock Error', headers: {}, config: internalErrorConfig, }); return Promise.reject(error); } if (currentOptions.operationType && currentOptions.operationType !== 'auto') { detectedOperationType = currentOptions.operationType; } const messageContent = currentOptions.responseContent ? formatResponseForOperation(currentOptions.responseContent, detectedOperationType) : formatResponseForOperation({}, detectedOperationType); const currentStatusCode = currentOptions.statusCode || 200; const mockResponseData = { id: `chatcmpl-mock-${Date.now()}`, object: 'chat.completion', created: Math.floor(Date.now() / 1000), model: requestModel || 'mock-model', choices: [ { index: 0, message: { role: 'assistant', content: messageContent, }, finish_reason: 'stop', }, ], usage: { prompt_tokens: 50, completion_tokens: 50, total_tokens: 100, }, }; return Promise.resolve({ data: mockResponseData, status: currentStatusCode, statusText: 'OK', headers: { 'content-type': 'application/json' }, config: { ...(config || {}), headers: new AxiosHeaders(), }, }); }); }