UNPKG

@simonecoelhosfo/optimizely-mcp-server

Version:

Optimizely MCP Server for AI assistants with integrated CLI tools

276 lines 8.29 kB
/** * Orchestration State Manager * @description Manages execution state for orchestration templates * @author Optimizely MCP Server * @version 1.0.0 */ import { getLogger } from '../../logging/Logger.js'; import * as JSONPath from 'jsonpath'; export class OrchestrationStateManager { logger = getLogger(); /** * Initialize a new execution state */ async initializeState(executionId, templateId, parameters) { this.logger.info({ executionId, templateId, parameterKeys: Object.keys(parameters) }, 'Initializing orchestration state'); const state = new OrchestrationStateImpl(executionId, templateId, parameters); return state; } /** * Save state snapshot (for debugging/recovery) */ async saveSnapshot(state) { // This could save to a cache or database for recovery this.logger.debug({ executionId: state.execution_id, status: state.status, stepsCompleted: this.countCompletedSteps(state) }, 'Saving state snapshot'); } /** * Restore state from snapshot */ async restoreSnapshot(executionId) { // This would restore from cache or database this.logger.debug({ executionId }, 'Attempting to restore state snapshot'); return null; // Not implemented yet } /** * Count completed steps */ countCompletedSteps(state) { let count = 0; state.steps.forEach(step => { if (step.status === 'completed') count++; }); return count; } } /** * Implementation of OrchestrationState */ class OrchestrationStateImpl { execution_id; template_id; status; parameters; outputs; steps; variables; errors; started_at; completed_at; logger = getLogger(); constructor(executionId, templateId, parameters) { this.execution_id = executionId; this.template_id = templateId; this.status = 'initializing'; this.parameters = { ...parameters }; this.outputs = { created_entities: [] // Initialize created_entities array }; this.steps = new Map(); this.variables = {}; this.errors = []; this.started_at = new Date(); } /** * Get value from state using JSONPath */ get(path) { try { // Build context object for JSONPath const context = this.buildContext(); // Handle simple variable access if (!path.includes('.') && !path.startsWith('$')) { return this.variables[path] || this.parameters[path] || this.outputs[path]; } // Use JSONPath for complex paths const queryPath = path.startsWith('$') ? path : `$.${path}`; const result = JSONPath.query(context, queryPath); // Return single value or array based on result if (result.length === 0) return undefined; if (result.length === 1) return result[0]; return result; } catch (error) { this.logger.warn({ path, error: error instanceof Error ? error.message : String(error) }, 'Failed to get value from state'); return undefined; } } /** * Set value in state using JSONPath */ set(path, value) { try { // Handle simple variable assignment if (!path.includes('.')) { this.variables[path] = value; return; } // Parse path to determine target const parts = path.split('.'); const root = parts[0]; // Determine which object to modify let target; if (root === 'parameters') { target = this.parameters; } else if (root === 'outputs') { target = this.outputs; } else if (root === 'variables') { target = this.variables; } else { // Default to variables target = this.variables; parts.unshift('variables'); } // Set nested value this.setNestedValue(target, parts.slice(1), value); } catch (error) { this.logger.error({ path, error: error instanceof Error ? error.message : String(error) }, 'Failed to set value in state'); throw error; } } /** * Record step start */ recordStepStart(stepId) { this.logger.debug({ stepId }, 'Recording step start'); this.steps.set(stepId, { step_id: stepId, status: 'running', started_at: new Date(), retry_count: 0 }); if (this.status === 'initializing') { this.status = 'running'; } } /** * Record step completion */ recordStepComplete(stepId, result) { this.logger.debug({ stepId, resultType: typeof result }, 'Recording step completion'); const step = this.steps.get(stepId); if (!step) { this.logger.warn({ stepId }, 'Step not found when recording completion'); return; } step.status = 'completed'; step.completed_at = new Date(); step.result = result; // Store step output in variables for reference this.variables[`steps.${stepId}.output`] = result; } /** * Record step error */ recordStepError(stepId, error) { this.logger.debug({ stepId, error: error.message }, 'Recording step error'); const step = this.steps.get(stepId); if (!step) { this.logger.warn({ stepId }, 'Step not found when recording error'); return; } step.status = 'failed'; step.completed_at = new Date(); step.error = { step_id: stepId, message: error.message, timestamp: new Date(), recoverable: false }; // Add to global errors this.errors.push(step.error); // Update execution status if not continuing on error if (this.status === 'running') { this.status = 'failed'; } } /** * Record skipped step */ recordSkippedStep(stepId) { this.logger.debug({ stepId }, 'Recording skipped step'); this.steps.set(stepId, { step_id: stepId, status: 'skipped', started_at: new Date(), completed_at: new Date(), retry_count: 0 }); } /** * Build context object for JSONPath queries */ buildContext() { return { parameters: this.parameters, outputs: this.outputs, variables: this.variables, steps: this.convertStepsToObject(), execution: { id: this.execution_id, status: this.status, started_at: this.started_at, errors: this.errors } }; } /** * Convert steps map to object */ convertStepsToObject() { const obj = {}; this.steps.forEach((step, id) => { obj[id] = { status: step.status, result: step.result, error: step.error, started_at: step.started_at, completed_at: step.completed_at }; }); return obj; } /** * Set nested value in object */ setNestedValue(obj, path, value) { let current = obj; for (let i = 0; i < path.length - 1; i++) { const key = path[i]; if (!(key in current) || typeof current[key] !== 'object') { current[key] = {}; } current = current[key]; } current[path[path.length - 1]] = value; } } //# sourceMappingURL=OrchestrationStateManager.js.map