meld
Version:
Meld: A template language for LLM prompts
454 lines (386 loc) • 14.9 kB
text/typescript
/**
* @package
* Test visualization manager for state debugging.
*
* This module provides a simplified interface for test state visualization,
* with support for different verbosity levels based on test configuration.
*/
import type { IStateVisualizationService, VisualizationConfig, ContextVisualizationConfig } from './IStateVisualizationService';
import type { IStateHistoryService } from '../StateHistoryService/IStateHistoryService';
import type { IStateTrackingService } from '../StateTrackingService/IStateTrackingService';
import { StateVisualizationFileOutput, FileOutputConfig } from './FileOutputService';
import { CompactStateVisualization } from './CompactStateVisualization';
import { serviceLogger } from '@core/utils/logger';
/**
* Verbosity levels for test output
*/
export enum TestOutputVerbosity {
/**
* Minimal output - only error information
*/
Minimal = 'minimal',
/**
* Standard output - errors plus basic test state info
*/
Standard = 'standard',
/**
* Verbose output - detailed state information
*/
Verbose = 'verbose',
/**
* Debug output - maximum detail for troubleshooting
*/
Debug = 'debug'
}
/**
* Configuration for test visualization manager
*/
export interface TestVisualizationConfig {
/**
* Verbosity level for console output
*/
verbosity?: TestOutputVerbosity;
/**
* If true, visualizations will be written to files instead of console
*/
outputToFiles?: boolean;
/**
* Base directory for file outputs
*/
outputDir?: string;
/**
* Default visualization format
*/
defaultFormat?: 'mermaid' | 'dot' | 'json';
/**
* If true, system state metrics will be included
*/
includeMetrics?: boolean;
}
/**
* Manages state visualizations for test environments
*/
export class TestVisualizationManager {
private fileOutput: StateVisualizationFileOutput;
private compactVis: CompactStateVisualization;
private verbosity: TestOutputVerbosity;
private outputToFiles: boolean;
private defaultFormat: 'mermaid' | 'dot' | 'json';
private includeMetrics: boolean;
/**
* Create a new test visualization manager
*/
constructor(
private visualizationService: IStateVisualizationService,
private historyService: IStateHistoryService,
private trackingService: IStateTrackingService,
config: TestVisualizationConfig = {}
) {
this.verbosity = this.resolveVerbosity(config.verbosity);
this.outputToFiles = config.outputToFiles ?? false;
this.defaultFormat = config.defaultFormat || 'mermaid';
this.includeMetrics = config.includeMetrics ?? true;
// Initialize file output service if needed
const fileConfig: FileOutputConfig = {
outputDir: config.outputDir || './logs/state-visualization'
};
this.fileOutput = new StateVisualizationFileOutput(fileConfig);
// Initialize compact visualization service
this.compactVis = new CompactStateVisualization(
this.historyService,
this.trackingService,
this.visualizationService
);
serviceLogger.debug('Test visualization manager initialized', {
verbosity: this.verbosity,
outputToFiles: this.outputToFiles,
defaultFormat: this.defaultFormat
});
}
/**
* Visualize a state with appropriate verbosity
* @param stateId - The state ID to visualize
* @param label - Optional label for the visualization
* @returns The visualization result as string or file path
*/
public visualizeState(stateId: string, label?: string): string | null {
try {
// Early return for minimal verbosity
if (this.verbosity === TestOutputVerbosity.Minimal) {
return null;
}
// Handle different verbosity levels
if (this.verbosity === TestOutputVerbosity.Standard) {
return this.handleStandardVerbosity(stateId, label);
} else if (this.verbosity === TestOutputVerbosity.Verbose) {
return this.handleVerboseVerbosity(stateId, label);
} else if (this.verbosity === TestOutputVerbosity.Debug) {
return this.handleDebugVerbosity(stateId, label);
}
return null;
} catch (error) {
serviceLogger.error('Failed to visualize state', { stateId, error });
return `Error visualizing state ${stateId}: ${error instanceof Error ? error.message : 'Unknown error'}`;
}
}
/**
* Visualize multiple states with appropriate verbosity
* @param stateIds - The state IDs to visualize
* @param label - Optional label for the visualization
* @returns The visualization result as string or file path
*/
public visualizeStates(stateIds: string[], label?: string): string | null {
try {
// Early return for minimal verbosity
if (this.verbosity === TestOutputVerbosity.Minimal || stateIds.length === 0) {
return null;
}
if (stateIds.length === 1) {
return this.visualizeState(stateIds[0], label);
}
// For relationship visualizations
const fileName = `state_relationship_${label || 'graph'}`;
if (this.verbosity === TestOutputVerbosity.Standard) {
// Compact summary of states
const summary = stateIds.map(id => this.compactVis.generateCompactStateSummary(id)).join('\n\n');
if (this.outputToFiles) {
return this.fileOutput.writeToFile(summary, fileName, 'text');
}
return summary;
} else {
// Generate relationship graph with appropriate detail level
const config: VisualizationConfig = {
format: this.defaultFormat,
includeMetadata: this.verbosity === TestOutputVerbosity.Debug,
includeTimestamps: this.verbosity === TestOutputVerbosity.Debug,
};
const visualization = this.visualizationService.generateRelationshipGraph(stateIds, config);
if (this.outputToFiles) {
if (this.defaultFormat === 'mermaid') {
return this.fileOutput.writeMermaidHtml(visualization, fileName, label);
} else {
return this.fileOutput.writeToFile(visualization, fileName, this.defaultFormat);
}
}
return visualization;
}
} catch (error) {
serviceLogger.error('Failed to visualize states', { stateIds, error });
return `Error visualizing states: ${error instanceof Error ? error.message : 'Unknown error'}`;
}
}
/**
* Visualize variable resolution across states
* @param variableName - The variable name to trace
* @param rootStateId - Optional root state to limit scope
* @param label - Optional label for the visualization
* @returns The visualization result as string or file path
*/
public visualizeVariableResolution(variableName: string, rootStateId?: string, label?: string): string | null {
try {
// Early return for minimal verbosity
if (this.verbosity === TestOutputVerbosity.Minimal) {
return null;
}
const fileName = `variable_resolution_${variableName}_${label || 'trace'}`;
// Different detail levels based on verbosity
const config: ContextVisualizationConfig = {
format: this.defaultFormat,
includeVars: true,
includeFilePaths: this.verbosity !== TestOutputVerbosity.Standard,
includeTimestamps: this.verbosity === TestOutputVerbosity.Debug,
filterToRelevantVars: this.verbosity !== TestOutputVerbosity.Debug,
};
const visualization = this.visualizationService.visualizeVariablePropagation(
variableName,
rootStateId,
config
);
if (this.outputToFiles) {
if (this.defaultFormat === 'mermaid') {
return this.fileOutput.writeMermaidHtml(visualization, fileName, `Variable: ${variableName}`);
} else {
return this.fileOutput.writeToFile(visualization, fileName, this.defaultFormat);
}
}
return visualization;
} catch (error) {
serviceLogger.error('Failed to visualize variable resolution', { variableName, rootStateId, error });
return `Error visualizing variable resolution: ${error instanceof Error ? error.message : 'Unknown error'}`;
}
}
/**
* Generate state metrics appropriate for the configured verbosity
* @returns The metrics result as string or file path
*/
public generateMetrics(): string | null {
try {
// Skip metrics if not enabled or minimal verbosity
if (!this.includeMetrics || this.verbosity === TestOutputVerbosity.Minimal) {
return null;
}
const fileName = 'state_metrics';
if (this.verbosity === TestOutputVerbosity.Standard) {
// Generate compact metrics summary
const summary = this.compactVis.generateCompactMetricsSummary();
if (this.outputToFiles) {
return this.fileOutput.writeToFile(summary, fileName, 'text');
}
return summary;
} else {
// Generate detailed metrics
const metrics = this.visualizationService.getMetrics();
const detailedMetrics = JSON.stringify(metrics, null, 2);
if (this.outputToFiles) {
return this.fileOutput.writeToFile(detailedMetrics, fileName, 'json');
}
return detailedMetrics;
}
} catch (error) {
serviceLogger.error('Failed to generate metrics', { error });
return `Error generating metrics: ${error instanceof Error ? error.message : 'Unknown error'}`;
}
}
/**
* Set the verbosity level
* @param verbosity - The new verbosity level
*/
public setVerbosity(verbosity: TestOutputVerbosity | string): void {
this.verbosity = this.resolveVerbosity(verbosity);
serviceLogger.debug('Test visualization verbosity updated', { verbosity: this.verbosity });
}
/**
* Set output mode (console vs files)
* @param outputToFiles - Whether to output to files
*/
public setOutputMode(outputToFiles: boolean): void {
this.outputToFiles = outputToFiles;
serviceLogger.debug('Test visualization output mode updated', { outputToFiles });
}
/**
* Clear all visualization files from the output directory
* @returns Success indicator
*/
public clearOutputFiles(): boolean {
return this.fileOutput.clearOutputDirectory();
}
/**
* Handle standard verbosity visualization
* @private
*/
private handleStandardVerbosity(stateId: string, label?: string): string | null {
// Generate compact summary
const summary = this.compactVis.generateCompactStateSummary(stateId);
if (this.outputToFiles) {
const fileName = `state_${stateId}_${label || 'summary'}`;
return this.fileOutput.writeToFile(summary, fileName, 'text');
}
return summary;
}
/**
* Handle verbose verbosity visualization
* @private
*/
private handleVerboseVerbosity(stateId: string, label?: string): string | null {
// Generate both state summary and transformation details
const stateSummary = this.compactVis.generateCompactStateSummary(stateId);
const transformSummary = this.compactVis.generateCompactTransformSummary(stateId);
if (this.outputToFiles) {
const fileName = `state_${stateId}_${label || 'verbose'}`;
const content = `${stateSummary}\n\n${transformSummary}`;
return this.fileOutput.writeToFile(content, fileName, 'text');
}
return `${stateSummary}\n\n${transformSummary}`;
}
/**
* Handle debug verbosity visualization
* @private
*/
private handleDebugVerbosity(stateId: string, label?: string): string | null {
// Generate full hierarchy visualization
const config: VisualizationConfig = {
format: this.defaultFormat,
includeMetadata: true,
includeTimestamps: true,
};
const visualization = this.visualizationService.generateHierarchyView(stateId, config);
// Add transformation diagram if available
const transformations = this.historyService.getTransformationChain(stateId);
let transitionDiagram = '';
if (transformations.length > 0) {
transitionDiagram = this.visualizationService.generateTransitionDiagram(stateId, config);
}
if (this.outputToFiles) {
const fileName = `state_${stateId}_${label || 'debug'}`;
if (this.defaultFormat === 'mermaid') {
// Create combined HTML with both visualizations
const combinedMermaid = `${visualization}\n\n${transitionDiagram}`;
return this.fileOutput.writeMermaidHtml(combinedMermaid, fileName, `State ${stateId} Debug View`);
} else {
// Write separate files
const hierarchyFile = this.fileOutput.writeToFile(
visualization,
`${fileName}_hierarchy`,
this.defaultFormat
);
if (transformations.length > 0) {
this.fileOutput.writeToFile(
transitionDiagram,
`${fileName}_transitions`,
this.defaultFormat
);
}
return hierarchyFile;
}
}
// Return combined visualization for console output
return transformations.length > 0
? `${visualization}\n\n${transitionDiagram}`
: visualization;
}
/**
* Resolve verbosity from string or enum value
* @private
*/
private resolveVerbosity(verbosity?: TestOutputVerbosity | string): TestOutputVerbosity {
if (!verbosity) {
// Check for environment variable
const envVerbosity = process.env.TEST_LOG_LEVEL || process.env.TEST_VISUALIZATION_LEVEL;
if (envVerbosity) {
return this.resolveVerbosityFromString(envVerbosity);
}
// Default to standard
return TestOutputVerbosity.Standard;
}
if (typeof verbosity === 'string') {
return this.resolveVerbosityFromString(verbosity);
}
return verbosity;
}
/**
* Resolve verbosity from string value
* @private
*/
private resolveVerbosityFromString(verbosity: string): TestOutputVerbosity {
switch (verbosity.toLowerCase()) {
case 'minimal':
case 'min':
case 'none':
return TestOutputVerbosity.Minimal;
case 'standard':
case 'normal':
case 'default':
return TestOutputVerbosity.Standard;
case 'verbose':
case 'detailed':
return TestOutputVerbosity.Verbose;
case 'debug':
case 'full':
case 'max':
return TestOutputVerbosity.Debug;
default:
serviceLogger.warn('Unknown verbosity level, defaulting to standard', { requestedVerbosity: verbosity });
return TestOutputVerbosity.Standard;
}
}
}