@simonecoelhosfo/optimizely-mcp-server
Version:
Optimizely MCP Server for AI assistants with integrated CLI tools
276 lines • 8.29 kB
JavaScript
/**
* 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