UNPKG

crewai-ts

Version:

TypeScript port of crewAI for agent-based workflows

237 lines (236 loc) 7.98 kB
/** * FlowStateManager * * Efficiently manages flow state with optimized memory usage, change tracking, * and performance monitoring. */ import { FlowState } from '../FlowState.js'; /** * FlowStateManager handles efficient state management for flows * with optimized memory usage and performance tracking. */ export class FlowStateManager { // Current state and state history currentState; stateHistory = new Map(); stateHistoryTimestamps = []; // Optimization flags options; persistence; // Performance tracking performanceMetrics = { updateCount: 0, totalUpdateTimeMs: 0, averageUpdateTimeMs: 0, maxUpdateTimeMs: 0, lastUpdateTimeMs: 0, totalMemoryChangeMb: 0 }; // Change tracking changePending = false; persistenceTimeout; constructor(initialState, options = {}) { // Set default options this.options = { persistOnChange: true, persistenceDebounceMs: 1000, useStructuralSharing: true, maxStateHistorySize: 10, enableGarbageCollection: true, trackPerformance: true, warningThresholdMs: 50, ...options }; // Set initial state this.currentState = this.cloneState(initialState); // Add initial state to history this.addStateToHistory(this.currentState); // Set persistence handler this.persistence = options.persistence; } /** * Get the current state */ getState() { return this.currentState; } /** * Update the state using an updater function * Uses optimized structural sharing when enabled */ updateState(updater) { // Track performance if enabled const startTime = this.options.trackPerformance ? performance.now() : 0; try { // Create a new state for immutability let newState; if (this.options.useStructuralSharing) { // With structural sharing, we only clone at the top level // and let the updater modify it directly newState = this.shallowCloneState(this.currentState); } else { // Without structural sharing, we do a deep clone newState = this.cloneState(this.currentState); } // Apply updater function to new state updater(newState); // Update current state this.currentState = newState; // Add to history this.addStateToHistory(newState); // Persist state if configured this.handleStateChange(); return newState; } finally { // Update performance metrics if (this.options.trackPerformance) { const endTime = performance.now(); const updateTimeMs = endTime - startTime; this.updatePerformanceMetrics(updateTimeMs); // Log warning if update takes too long if (updateTimeMs > this.options.warningThresholdMs) { console.warn(`Flow state update took ${updateTimeMs.toFixed(2)}ms, which exceeds the warning threshold of ${this.options.warningThresholdMs}ms`); } } } } /** * Get performance metrics for state operations */ getPerformanceMetrics() { return { ...this.performanceMetrics }; } /** * Get the state history */ getStateHistory() { return Array.from(this.stateHistory.values()); } /** * Restore state to a specific point in history */ restoreState(timestamp) { const historicalState = this.stateHistory.get(timestamp); if (historicalState) { this.currentState = this.cloneState(historicalState); this.handleStateChange(); return true; } return false; } /** * Clear all state history to free memory */ clearHistory() { const currentTimestamp = Date.now(); this.stateHistory.clear(); this.stateHistoryTimestamps = []; // Keep current state in history this.stateHistory.set(currentTimestamp, this.currentState); this.stateHistoryTimestamps.push(currentTimestamp); } /** * Save the current state to persistence layer immediately */ async persistStateNow() { if (!this.persistence) return; try { await this.persistence.saveState(this.currentState); this.changePending = false; } catch (error) { console.error('Error persisting flow state:', error); } } /** * Handle state change with debounced persistence */ handleStateChange() { if (!this.persistence || !this.options.persistOnChange) return; // Mark change as pending this.changePending = true; // Debounce persistence to avoid too many writes if (this.persistenceTimeout) { clearTimeout(this.persistenceTimeout); } this.persistenceTimeout = setTimeout(() => { if (this.changePending) { this.persistStateNow().catch(err => { console.error('Failed to persist state:', err); }); } }, this.options.persistenceDebounceMs); } /** * Add a state to the history with efficient memory management */ addStateToHistory(state) { const timestamp = Date.now(); // Add to history this.stateHistory.set(timestamp, state); this.stateHistoryTimestamps.push(timestamp); // Enforce history size limit this.pruneStateHistory(); } /** * Remove oldest states from history to stay within size limit */ pruneStateHistory() { // Keep history size within limit while (this.stateHistoryTimestamps.length > this.options.maxStateHistorySize) { const oldestTimestamp = this.stateHistoryTimestamps.shift(); if (oldestTimestamp) { this.stateHistory.delete(oldestTimestamp); } } // Trigger garbage collection if configured if (this.options.enableGarbageCollection && this.stateHistoryTimestamps.length % 5 === 0 && global.gc) { // Only works if Node.js is started with --expose-gc flag try { global.gc(); } catch (e) { // Ignore errors if gc is not available } } } /** * Update performance metrics with new data */ updatePerformanceMetrics(updateTimeMs) { this.performanceMetrics.updateCount++; this.performanceMetrics.totalUpdateTimeMs += updateTimeMs; this.performanceMetrics.lastUpdateTimeMs = updateTimeMs; // Update max time if this was the longest update if (updateTimeMs > this.performanceMetrics.maxUpdateTimeMs) { this.performanceMetrics.maxUpdateTimeMs = updateTimeMs; } // Recalculate average this.performanceMetrics.averageUpdateTimeMs = this.performanceMetrics.totalUpdateTimeMs / this.performanceMetrics.updateCount; } /** * Create a shallow clone of a state object for structural sharing */ shallowCloneState(state) { // Create a new state instance const newState = new FlowState({ id: state.id }); // Copy all properties Object.assign(newState, state); return newState; } /** * Create a deep clone of a state object */ cloneState(state) { // For deep cloning, we use JSON serialization // This is not the most efficient but ensures complete isolation return JSON.parse(JSON.stringify(state)); } }