UNPKG

@vfarcic/dot-ai

Version:

Universal Kubernetes application deployment agent with CLI and MCP interfaces

583 lines (582 loc) 27.8 kB
"use strict"; /** * Organizational Data Management Tool * * Unified MCP tool for managing organizational knowledge: deployment patterns, * governance policies, AI memory, and other institutional data. * * Currently implements: patterns * Future: policies, memory, config */ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.ORGANIZATIONAL_DATA_TOOL_INPUT_SCHEMA = exports.ORGANIZATIONAL_DATA_TOOL_DESCRIPTION = exports.ORGANIZATIONAL_DATA_TOOL_NAME = void 0; exports.handleOrganizationalDataTool = handleOrganizationalDataTool; const zod_1 = require("zod"); const error_handling_1 = require("../core/error-handling"); // Import only what we need - other imports removed as they're no longer used with Vector DB const pattern_creation_session_1 = require("../core/pattern-creation-session"); const index_1 = require("../core/index"); const session_utils_1 = require("../core/session-utils"); const fs = __importStar(require("fs")); const path = __importStar(require("path")); // Tool metadata for MCP registration exports.ORGANIZATIONAL_DATA_TOOL_NAME = 'manageOrgData'; exports.ORGANIZATIONAL_DATA_TOOL_DESCRIPTION = 'Unified tool for managing cluster data: organizational patterns (available now), resource capabilities (PRD #48), and resource dependencies (PRD #49). For patterns: supports step-by-step creation workflow. For capabilities/dependencies: returns implementation status. Use dataType parameter to specify what to manage: "pattern" for organizational patterns, "capabilities" for resource capabilities, "dependencies" for resource dependencies.'; // Extensible schema - supports patterns, capabilities, and dependencies exports.ORGANIZATIONAL_DATA_TOOL_INPUT_SCHEMA = { dataType: zod_1.z.enum(['pattern', 'capabilities', 'dependencies']).describe('Type of cluster data to manage: pattern (organizational patterns), capabilities (resource capabilities), dependencies (resource dependencies)'), operation: zod_1.z.enum(['create', 'list', 'get', 'delete', 'scan', 'analyze']).describe('Operation to perform on the cluster data'), // Workflow fields for step-by-step pattern creation sessionId: zod_1.z.string().optional().describe('Pattern creation session ID (for continuing multi-step workflow)'), response: zod_1.z.string().optional().describe('User response to previous workflow step question'), // Generic fields for get/delete operations id: zod_1.z.string().optional().describe('Data item ID (required for get/delete operations)'), // Generic fields for list operations limit: zod_1.z.number().optional().describe('Maximum number of items to return (default: 10)'), // Resource-specific fields (for capabilities and dependencies) resource: zod_1.z.object({ kind: zod_1.z.string(), group: zod_1.z.string(), apiVersion: zod_1.z.string() }).optional().describe('Kubernetes resource reference (for capabilities/dependencies operations)') }; /** * Get Vector DB-based pattern service with optional embedding support */ async function getPatternService() { const vectorDB = new index_1.VectorDBService(); const embeddingService = new index_1.EmbeddingService(); // Optional - gracefully handles missing API keys const patternService = new index_1.PatternVectorService(vectorDB, embeddingService); // Always ensure proper collection initialization try { await patternService.initialize(); } catch (error) { // If initialization fails, try to provide helpful error context const errorMessage = error instanceof Error ? error.message : String(error); throw new Error(`Vector DB collection initialization failed: ${errorMessage}. This may be due to dimension mismatch or collection configuration issues.`); } return patternService; } /** * Validate Vector DB connection and return helpful error if unavailable */ async function validateVectorDBConnection(patternService, logger, requestId) { const isHealthy = await patternService.healthCheck(); if (!isHealthy) { logger.warn('Vector DB connection not available', { requestId }); return { success: false, error: { message: 'Vector DB connection required for pattern management', details: 'Pattern management requires a Qdrant Vector Database connection to store and search organizational patterns.', setup: { selfHosted: { docker: 'docker run -d -p 6333:6333 --name qdrant qdrant/qdrant', environment: 'export QDRANT_URL=http://localhost:6333' }, saas: { signup: 'Sign up at https://cloud.qdrant.io', environment: [ 'export QDRANT_URL=https://your-cluster.aws.cloud.qdrant.io:6333', 'export QDRANT_API_KEY=your-api-key-from-dashboard' ] }, docs: 'See documentation for detailed setup instructions' }, currentConfig: { QDRANT_URL: process.env.QDRANT_URL || 'not set (defaults to http://localhost:6333)', QDRANT_API_KEY: process.env.QDRANT_API_KEY ? 'set' : 'not set (optional)' } } }; } return { success: true }; } /** * Validate embedding service configuration and fail if unavailable */ async function validateEmbeddingService(logger, requestId) { const { EmbeddingService } = await Promise.resolve().then(() => __importStar(require('../core/embedding-service'))); const embeddingService = new EmbeddingService(); const status = embeddingService.getStatus(); if (!status.available) { logger.warn('Embedding service required but not available', { requestId, reason: status.reason }); return { success: false, error: { message: 'OpenAI API key required for pattern management', details: 'Pattern management requires OpenAI embeddings for semantic search and storage. The system cannot proceed without proper configuration.', reason: status.reason, setup: { required: 'export OPENAI_API_KEY=your-openai-api-key', optional: [ 'export OPENAI_MODEL=text-embedding-3-small (default)', 'export OPENAI_DIMENSIONS=1536 (default)' ], docs: 'Get API key from https://platform.openai.com/api-keys' }, currentConfig: { OPENAI_API_KEY: process.env.OPENAI_API_KEY ? 'set' : 'not set', QDRANT_URL: process.env.QDRANT_URL || 'http://localhost:6333', status: 'embedding service unavailable' } } }; } logger.info('Embedding service available', { requestId, provider: status.provider, model: status.model, dimensions: status.dimensions }); return { success: true }; } /** * Handle pattern operations with workflow support */ async function handlePatternOperation(operation, args, logger, requestId) { // Get pattern service and validate Vector DB connection const patternService = await getPatternService(); const connectionCheck = await validateVectorDBConnection(patternService, logger, requestId); if (!connectionCheck.success) { return { success: false, operation, dataType: 'pattern', error: connectionCheck.error, message: 'Vector DB connection required for pattern management' }; } // Validate embedding service and fail if unavailable const embeddingCheck = await validateEmbeddingService(logger, requestId); if (!embeddingCheck.success) { return { success: false, operation, dataType: 'pattern', error: embeddingCheck.error, message: 'OpenAI API key required for pattern management' }; } const sessionManager = new pattern_creation_session_1.PatternCreationSessionManager(); switch (operation) { case 'create': { let workflowStep; if (args.sessionId) { // Continue existing session logger.info('Continuing pattern creation workflow', { requestId, sessionId: args.sessionId }); if (args.response) { // Process user response and move to next step workflowStep = sessionManager.processResponse(args.sessionId, args.response, args); } else { // Just get current step without processing response workflowStep = sessionManager.getNextStep(args.sessionId, args); } if (!workflowStep) { throw error_handling_1.ErrorHandler.createError(error_handling_1.ErrorCategory.VALIDATION, error_handling_1.ErrorSeverity.HIGH, `Session not found or workflow failed`, { operation: 'pattern_workflow_continue', component: 'OrganizationalDataTool', requestId, input: { sessionId: args.sessionId } }); } } else { // Start new workflow session logger.info('Starting new pattern creation workflow', { requestId }); const session = sessionManager.createSession(args); workflowStep = sessionManager.getNextStep(session.sessionId, args); if (!workflowStep) { throw error_handling_1.ErrorHandler.createError(error_handling_1.ErrorCategory.OPERATION, error_handling_1.ErrorSeverity.HIGH, `Failed to start pattern creation workflow`, { operation: 'pattern_workflow_start', component: 'OrganizationalDataTool', requestId }); } } // Always check if workflow is complete and store pattern in Vector DB let storageInfo = {}; logger.info('Checking workflow completion', { requestId, step: workflowStep.step, hasPattern: !!workflowStep.data?.pattern, patternId: workflowStep.data?.pattern?.id }); if (workflowStep.step === 'complete' && workflowStep.data?.pattern) { try { await patternService.storePattern(workflowStep.data.pattern); const vectorDBConfig = new index_1.VectorDBService().getConfig(); storageInfo = { stored: true, vectorDbUrl: vectorDBConfig.url, collectionName: vectorDBConfig.collectionName, patternId: workflowStep.data.pattern.id }; logger.info('Pattern stored in Vector DB successfully', { requestId, patternId: workflowStep.data.pattern.id, vectorDbUrl: vectorDBConfig.url }); // Clean up session file after successful Vector DB storage try { const sessionDir = (0, session_utils_1.getAndValidateSessionDirectory)(args, false); const sessionFile = path.join(sessionDir, 'pattern-sessions', `${workflowStep.sessionId}.json`); if (fs.existsSync(sessionFile)) { fs.unlinkSync(sessionFile); logger.info('Session file cleaned up after successful pattern storage', { requestId, sessionId: workflowStep.sessionId, sessionFile }); } } catch (cleanupError) { // Log cleanup failure but don't fail the operation logger.warn('Failed to cleanup session file after pattern storage', { requestId, sessionId: workflowStep.sessionId, error: cleanupError instanceof Error ? cleanupError.message : String(cleanupError) }); } } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); const vectorDBConfig = new index_1.VectorDBService().getConfig(); storageInfo = { stored: false, error: errorMessage, vectorDbUrl: vectorDBConfig.url, collectionName: vectorDBConfig.collectionName, patternId: workflowStep.data.pattern.id }; logger.error('Failed to store pattern in Vector DB', error, { requestId, patternId: workflowStep.data.pattern.id, error: errorMessage }); } } // For completed patterns, storage failure means creation failure const isComplete = workflowStep.step === 'complete'; const storageSucceeded = storageInfo.stored === true; const operationSucceeded = !isComplete || storageSucceeded; return { success: operationSucceeded, operation: 'create', dataType: 'pattern', workflow: workflowStep, storage: storageInfo, message: isComplete ? (storageSucceeded ? 'Pattern created successfully' : `Pattern creation failed: ${storageInfo.error}`) : 'Workflow step ready' }; } case 'list': { const limit = args.limit || 10; const patterns = await patternService.getAllPatterns(); const totalCount = await patternService.getPatternsCount(); const searchMode = patternService.getSearchMode(); // Apply limit client-side (Vector DB returns all, we slice) const limitedPatterns = patterns.slice(0, limit); logger.info('Patterns listed successfully', { requestId, returnedCount: limitedPatterns.length, totalCount, limit, searchMode: searchMode.semantic ? 'semantic+keyword' : 'keyword-only' }); return { success: true, operation: 'list', dataType: 'pattern', data: { patterns: limitedPatterns.map(p => ({ id: p.id, description: p.description.substring(0, 100) + (p.description.length > 100 ? '...' : ''), triggersCount: p.triggers.length, resourcesCount: p.suggestedResources.length, createdAt: p.createdAt, createdBy: p.createdBy })), totalCount, returnedCount: limitedPatterns.length, limit, searchCapabilities: { semantic: searchMode.semantic, provider: searchMode.provider, mode: searchMode.semantic ? 'semantic+keyword hybrid search' : 'keyword-only search', note: searchMode.reason || (searchMode.semantic ? 'Full semantic search enabled' : undefined) } }, message: `Found ${limitedPatterns.length} of ${totalCount} total patterns. Search mode: ${searchMode.semantic ? 'semantic+keyword' : 'keyword-only'}` }; } case 'get': { if (!args.id) { throw error_handling_1.ErrorHandler.createError(error_handling_1.ErrorCategory.VALIDATION, error_handling_1.ErrorSeverity.HIGH, 'Pattern ID is required for get operation', { operation: 'pattern_get_validation', component: 'OrganizationalDataTool', requestId, input: args }); } const pattern = await patternService.getPattern(args.id); if (!pattern) { throw error_handling_1.ErrorHandler.createError(error_handling_1.ErrorCategory.VALIDATION, error_handling_1.ErrorSeverity.MEDIUM, `Pattern not found with ID: ${args.id}`, { operation: 'pattern_get', component: 'OrganizationalDataTool', requestId, input: { id: args.id } }); } logger.info('Pattern retrieved successfully', { requestId, patternId: pattern.id, description: pattern.description.substring(0, 50) + (pattern.description.length > 50 ? '...' : '') }); return { success: true, operation: 'get', dataType: 'pattern', data: pattern, message: `Retrieved pattern: ${pattern.description.substring(0, 50)}${pattern.description.length > 50 ? '...' : ''}` }; } case 'delete': { if (!args.id) { throw error_handling_1.ErrorHandler.createError(error_handling_1.ErrorCategory.VALIDATION, error_handling_1.ErrorSeverity.HIGH, 'Pattern ID is required for delete operation', { operation: 'pattern_delete_validation', component: 'OrganizationalDataTool', requestId, input: args }); } // Get pattern info before deletion for logging const pattern = await patternService.getPattern(args.id); if (!pattern) { throw error_handling_1.ErrorHandler.createError(error_handling_1.ErrorCategory.VALIDATION, error_handling_1.ErrorSeverity.MEDIUM, `Pattern not found with ID: ${args.id}`, { operation: 'pattern_delete', component: 'OrganizationalDataTool', requestId, input: { id: args.id } }); } await patternService.deletePattern(args.id); logger.info('Pattern deleted successfully', { requestId, patternId: args.id, description: pattern.description.substring(0, 50) + (pattern.description.length > 50 ? '...' : '') }); return { success: true, operation: 'delete', dataType: 'pattern', data: { id: args.id }, message: `Pattern deleted successfully: ${args.id}` }; } default: throw error_handling_1.ErrorHandler.createError(error_handling_1.ErrorCategory.VALIDATION, error_handling_1.ErrorSeverity.HIGH, `Unsupported pattern operation: ${operation}`, { operation: 'pattern_operation_validation', component: 'OrganizationalDataTool', requestId, input: { operation, supportedOperations: ['create', 'list', 'get', 'delete'] } }); } } /** * Handle capabilities operations (placeholder for PRD #48) */ async function handleCapabilitiesOperation(operation, args, logger, requestId) { logger.info('Capabilities operation requested', { requestId, operation }); return { success: false, operation, dataType: 'capabilities', error: { message: 'Resource capabilities management not yet implemented', details: 'This feature is planned for PRD #48 - Resource Capabilities Discovery & Integration', status: 'coming-soon', implementationPlan: { prd: 'PRD #48', description: 'Kubernetes resource capability discovery and semantic matching', expectedFeatures: [ 'Cluster resource scanning', 'Capability inference from schemas', 'Semantic resource matching', 'Vector DB storage and search' ] } }, message: 'Capabilities management will be available after PRD #48 implementation' }; } /** * Handle dependencies operations (placeholder for PRD #49) */ async function handleDependenciesOperation(operation, args, logger, requestId) { logger.info('Dependencies operation requested', { requestId, operation }); return { success: false, operation, dataType: 'dependencies', error: { message: 'Resource dependencies management not yet implemented', details: 'This feature is planned for PRD #49 - Resource Dependencies Discovery & Integration', status: 'coming-soon', implementationPlan: { prd: 'PRD #49', description: 'Resource dependency discovery and complete solution assembly', expectedFeatures: [ 'Dependency relationship discovery', 'Complete solution assembly', 'Deployment order optimization', 'Vector DB storage and search' ] } }, message: 'Dependencies management will be available after PRD #49 implementation' }; } /** * Main tool handler - routes to appropriate data type handler */ async function handleOrganizationalDataTool(args, _dotAI, logger, requestId) { try { logger.info('Processing organizational-data tool request', { requestId, dataType: args.dataType, operation: args.operation }); // Validate required parameters if (!args.dataType) { throw error_handling_1.ErrorHandler.createError(error_handling_1.ErrorCategory.VALIDATION, error_handling_1.ErrorSeverity.HIGH, 'dataType parameter is required', { operation: 'organizational_data_validation', component: 'OrganizationalDataTool', requestId, input: args }); } if (!args.operation) { throw error_handling_1.ErrorHandler.createError(error_handling_1.ErrorCategory.VALIDATION, error_handling_1.ErrorSeverity.HIGH, 'operation parameter is required', { operation: 'organizational_data_validation', component: 'OrganizationalDataTool', requestId, input: args }); } // Route to appropriate handler based on data type let result; switch (args.dataType) { case 'pattern': result = await handlePatternOperation(args.operation, args, logger, requestId); break; case 'capabilities': result = await handleCapabilitiesOperation(args.operation, args, logger, requestId); break; case 'dependencies': result = await handleDependenciesOperation(args.operation, args, logger, requestId); break; default: throw error_handling_1.ErrorHandler.createError(error_handling_1.ErrorCategory.VALIDATION, error_handling_1.ErrorSeverity.HIGH, `Unsupported data type: ${args.dataType}. Currently supported: pattern, capabilities, dependencies`, { operation: 'data_type_validation', component: 'OrganizationalDataTool', requestId, input: { dataType: args.dataType, supportedTypes: ['pattern', 'capabilities', 'dependencies'] } }); } logger.info('Organizational-data tool request completed successfully', { requestId, dataType: args.dataType, operation: args.operation, success: result.success }); // Return consistent MCP response format return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] }; } catch (error) { logger.error('Organizational-data tool request failed', error); // Handle errors consistently if (error instanceof Error && 'category' in error) { // Already an AppError, format for MCP response const appError = error; return { content: [{ type: 'text', text: JSON.stringify({ success: false, error: { message: appError.message, category: appError.category, severity: appError.severity, code: appError.code }, timestamp: new Date().toISOString() }, null, 2) }] }; } // Generic error handling const errorMessage = error instanceof Error ? error.message : String(error); return { content: [{ type: 'text', text: JSON.stringify({ success: false, error: { message: errorMessage, category: 'OPERATION', severity: 'HIGH' }, timestamp: new Date().toISOString() }, null, 2) }] }; } }