Skip to main content
Glama
operate-analysis.ts10.2 kB
import { GenericSessionManager } from '../core/generic-session-manager'; import { KUBECTL_INVESTIGATION_TOOLS, executeKubectlTools } from '../core/kubectl-tools'; import { createAIProvider } from '../core/ai-provider-factory'; import { Logger } from '../core/error-handling'; import { loadPrompt } from '../core/shared-prompt-loader'; import { EmbeddedContext, OperateSessionData, OperateSession, embedContext, formatPatterns, formatPolicies, formatCapabilities } from './operate'; /** * Analyzes user intent and generates operational proposal using AI tool loop * * @param intent - User's operational intent (e.g., "update my-api to v2.0") * @param logger - Logger instance * @param sessionManager - Session manager instance * @param sessionId - Optional session ID for refinement * @param interaction_id - Optional interaction ID for eval datasets * @returns Operation output with proposed changes */ export async function analyzeIntent( intent: string, logger: Logger, sessionManager: GenericSessionManager<OperateSessionData>, sessionId?: string, interaction_id?: string ): Promise<any> { logger.info('Starting operate analysis', { intent, sessionId }); // 1. Embed context (patterns, policies, capabilities) const context = await embedContext(intent, logger); // 2. Load prompts (static system + dynamic user message) const systemPrompt = loadSystemPrompt(); const userMessage = buildUserMessage(intent, context); // 3. Execute AI tool loop with kubectl tools const aiResult = await executeToolLoop(systemPrompt, userMessage, logger, interaction_id); // 4. Parse AI response into structured format const proposedChanges = parseAIResponse(aiResult, logger); // 5. Create and save session const session = await saveAnalysisSession( intent, context, proposedChanges, sessionManager, sessionId, interaction_id, logger ); logger.info('Operate analysis complete', { sessionId: session.sessionId }); // 6. Return formatted output for user return { status: 'awaiting_user_approval', sessionId: session.sessionId, analysis: { summary: proposedChanges.analysis, currentState: proposedChanges.currentState, proposedChanges: session.data.proposedChanges, commands: session.data.commands, dryRunValidation: session.data.dryRunValidation, patternsApplied: session.data.patternsApplied, capabilitiesUsed: session.data.capabilitiesUsed, policiesChecked: session.data.policiesChecked, risks: session.data.risks, validationIntent: session.data.validationIntent }, message: 'Operational proposal generated successfully. Review changes and execute with operate(sessionId, executeChoice=1).', nextAction: `Review the proposed changes and call operate({ sessionId: "${session.sessionId}", executeChoice: 1 }) to execute.` }; } /** * Loads static system prompt from prompts/operate-system.md * This prompt is cacheable across all operate calls */ function loadSystemPrompt(): string { return loadPrompt('operate-system'); } /** * Builds dynamic user message with intent and embedded context * Uses template from prompts/operate-user.md and formatting functions from operate.ts */ function buildUserMessage(intent: string, context: EmbeddedContext): string { // Format context sections using shared formatting functions const patternsText = formatPatterns(context.patterns); const policiesText = formatPolicies(context.policies); const capabilitiesText = formatCapabilities(context.capabilities); // Use loadPrompt with Handlebars template variables return loadPrompt('operate-user', { intent, patterns: patternsText, policies: policiesText, capabilities: capabilitiesText }); } /** * Executes AI tool loop with kubectl investigation tools * AI autonomously inspects cluster and validates changes with dry-run * * @param systemPrompt - Static instructions (cacheable) * @param userMessage - Dynamic content with intent and context * @param logger - Logger instance * @param interaction_id - Optional interaction ID for eval datasets * @returns AI's final response * @throws Error if AI fails to converge within 30 iterations */ async function executeToolLoop( systemPrompt: string, userMessage: string, logger: Logger, interaction_id?: string ): Promise<string> { logger.debug('Starting AI tool loop for operate analysis'); const aiProvider = createAIProvider(); const result = await aiProvider.toolLoop({ systemPrompt, userMessage, tools: KUBECTL_INVESTIGATION_TOOLS, toolExecutor: executeKubectlTools, maxIterations: 30, operation: 'operate-analysis', evaluationContext: { user_intent: userMessage.substring(0, 200) // First 200 chars as context }, interaction_id }); logger.debug('AI tool loop completed', { iterations: result.iterations, toolCallsExecuted: result.toolCallsExecuted.length, responseLength: result.finalMessage.length }); return result.finalMessage; } /** * Parses AI response into structured ProposedChanges format * Enforces strict JSON parsing with validation * * @param response - AI's final response * @param logger - Logger instance * @returns Parsed proposed changes * @throws Error if response is not valid JSON or missing required fields */ function parseAIResponse(response: string, logger: Logger): any { logger.debug('Parsing AI response'); // Extract JSON from code block const jsonMatch = response.match(/```json\n([\s\S]+?)\n```/); if (!jsonMatch) { const truncatedResponse = response.substring(0, 500); logger.error(`AI response missing JSON code block. Response: ${truncatedResponse}`); throw new Error( 'AI did not return structured JSON response. Expected ```json code block with proposal.' ); } try { const parsed = JSON.parse(jsonMatch[1]); // Validate required fields if (!parsed.analysis || typeof parsed.analysis !== 'string') { throw new Error('AI response missing required "analysis" field (string)'); } if (!parsed.commands || !Array.isArray(parsed.commands)) { throw new Error('AI response missing required "commands" array'); } if (parsed.commands.length === 0) { throw new Error('AI response has empty "commands" array - no operations proposed'); } if (!parsed.dryRunValidation || typeof parsed.dryRunValidation !== 'object') { throw new Error('AI response missing required "dryRunValidation" object'); } // Trust AI's claim but log for audit trail logger.info('AI dry-run validation status', { validation: parsed.dryRunValidation, status: parsed.dryRunValidation.status }); // Ensure proposedChanges structure exists if (!parsed.proposedChanges) { parsed.proposedChanges = { create: [], update: [], delete: [] }; } // Validate proposedChanges structure const changes = parsed.proposedChanges; if (!Array.isArray(changes.create)) changes.create = []; if (!Array.isArray(changes.update)) changes.update = []; if (!Array.isArray(changes.delete)) changes.delete = []; // Ensure metadata arrays exist if (!Array.isArray(parsed.patternsApplied)) parsed.patternsApplied = []; if (!Array.isArray(parsed.capabilitiesUsed)) parsed.capabilitiesUsed = []; if (!Array.isArray(parsed.policiesChecked)) parsed.policiesChecked = []; // Ensure risks object exists if (!parsed.risks) { parsed.risks = { level: 'low', description: 'No specific risks identified' }; } // Ensure validationIntent exists if (!parsed.validationIntent || typeof parsed.validationIntent !== 'string') { parsed.validationIntent = 'Validate that the operation completed successfully'; } logger.debug('AI response parsed successfully', { commandCount: parsed.commands.length, createCount: changes.create.length, updateCount: changes.update.length, deleteCount: changes.delete.length }); return parsed; } catch (error) { const errorMsg = error instanceof Error ? error.message : String(error); logger.error(`Failed to parse AI response: ${errorMsg}`); throw new Error(`Invalid AI response format: ${errorMsg}`); } } /** * Saves analysis session to disk using GenericSessionManager * * @param intent - User's operational intent * @param context - Embedded context * @param proposedChanges - Parsed AI proposal * @param sessionManager - Session manager instance * @param sessionId - Optional existing session ID for updates * @param interaction_id - Optional interaction ID for eval datasets * @param logger - Logger instance * @returns Saved session */ async function saveAnalysisSession( intent: string, context: EmbeddedContext, proposedChanges: any, sessionManager: GenericSessionManager<OperateSessionData>, sessionId: string | undefined, interaction_id: string | undefined, logger: Logger ): Promise<OperateSession> { const sessionData: OperateSessionData = { intent, interaction_id, context, proposedChanges: proposedChanges.proposedChanges, commands: proposedChanges.commands, dryRunValidation: proposedChanges.dryRunValidation, patternsApplied: proposedChanges.patternsApplied, capabilitiesUsed: proposedChanges.capabilitiesUsed, policiesChecked: proposedChanges.policiesChecked, risks: proposedChanges.risks, validationIntent: proposedChanges.validationIntent, status: 'analysis_complete' }; if (sessionId) { // Update existing session (refinement case) logger.debug('Updating existing operate session', { sessionId }); await sessionManager.replaceSession(sessionId, sessionData); const session = sessionManager.getSession(sessionId); if (!session) { throw new Error(`Failed to retrieve session ${sessionId} after update`); } return session; } else { // Create new session logger.debug('Creating new operate session'); const session = await sessionManager.createSession(sessionData); logger.info('Operate session created', { sessionId: session.sessionId }); return session; } }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/vfarcic/dot-ai'

If you have feedback or need assistance with the MCP directory API, please join our Discord server