meld
Version:
Meld: A template language for LLM prompts
317 lines (277 loc) • 9.17 kB
text/typescript
/**
* @package
* Implementation of state debugging service.
*/
import type { IStateDebuggerService, DebugSessionConfig, DebugSessionResult, StateDiagnostic } from './IStateDebuggerService.js';
import type { IStateVisualizationService } from '../StateVisualizationService/IStateVisualizationService.js';
import type { IStateHistoryService } from '../StateHistoryService/IStateHistoryService.js';
import type { IStateTrackingService, StateMetadata } from '../StateTrackingService/IStateTrackingService.js';
import { v4 as uuidv4 } from 'uuid';
/**
* Implements debugging capabilities by integrating state tracking,
* history, and visualization services.
*/
export class StateDebuggerService implements IStateDebuggerService {
private sessions: Map<string, {
config: DebugSessionConfig;
startTime: number;
diagnostics: StateDiagnostic[];
snapshots: Map<string, any>;
metrics: Record<string, number>;
}> = new Map();
private analyzers: Array<(stateId: string) => Promise<StateDiagnostic[]>> = [];
constructor(
private visualizationService: IStateVisualizationService,
private historyService: IStateHistoryService,
private trackingService: IStateTrackingService
) {}
public startSession(config: DebugSessionConfig): string {
const sessionId = uuidv4();
this.sessions.set(sessionId, {
config,
startTime: Date.now(),
diagnostics: [],
snapshots: new Map(),
metrics: {},
});
return sessionId;
}
public async endSession(sessionId: string): Promise<DebugSessionResult> {
const session = this.sessions.get(sessionId);
if (!session) {
throw new Error(`No debug session found with ID: ${sessionId}`);
}
const result: DebugSessionResult = {
sessionId,
startTime: session.startTime,
endTime: Date.now(),
diagnostics: session.diagnostics,
snapshots: session.snapshots,
metrics: session.metrics,
};
if (session.config.visualization) {
result.visualization = this.visualizationService.exportStateGraph(
session.config.visualization
);
}
return result;
}
public async analyzeState(stateId: string): Promise<StateDiagnostic[]> {
const diagnostics: StateDiagnostic[] = [];
try {
// Run all registered analyzers
for (const analyzer of this.analyzers) {
try {
const results = await analyzer(stateId);
if (results) {
diagnostics.push(...results);
}
} catch (error) {
diagnostics.push({
stateId,
timestamp: Date.now(),
type: 'error',
message: `Custom analyzer failed: ${error instanceof Error ? error.message : 'Unknown error'}`
});
}
}
// Add basic state analysis
const [metadata, history] = await Promise.all([
this.trackingService.getStateMetadata(stateId),
this.historyService.getStateHistory(stateId)
]);
if (!metadata || !history) {
diagnostics.push({
stateId,
timestamp: Date.now(),
type: 'error',
message: 'Failed to retrieve state metadata or history'
});
return diagnostics;
}
// Check for common issues
if ((metadata.childStates?.length ?? 0) > 10) {
diagnostics.push({
stateId,
timestamp: Date.now(),
type: 'warning',
message: 'High number of transformations may indicate complexity issues',
context: { metadata }
});
}
if ((metadata.childStates?.length ?? 0) > 20) {
diagnostics.push({
stateId,
timestamp: Date.now(),
type: 'warning',
message: 'Large number of child states may impact performance',
context: { metadata }
});
}
return diagnostics;
} catch (error) {
return [{
stateId,
timestamp: Date.now(),
type: 'error',
message: error instanceof Error ? error.message : 'Unknown error during analysis'
}];
}
}
public async traceOperation<T>(
stateId: string,
operation: () => Promise<T>
): Promise<{ result: T; diagnostics: StateDiagnostic[] }> {
try {
const startSnapshot = await this.getStateSnapshot(stateId, 'full');
const startTime = Date.now();
const result = await operation();
const endSnapshot = await this.getStateSnapshot(stateId, 'full');
// Compare snapshots for changes
const diagnostics = await this.analyzeStateChanges(
stateId,
startSnapshot,
endSnapshot,
startTime
);
return { result, diagnostics };
} catch (error) {
let metadata;
try {
metadata = await this.trackingService.getStateMetadata(stateId);
} catch {
// Ignore metadata fetch errors in error handling
}
const diagnostics: StateDiagnostic[] = [{
stateId,
timestamp: Date.now(),
type: 'error',
message: error instanceof Error ? error.message : 'Unknown error',
context: { metadata }
}];
throw { error, diagnostics };
}
}
public async getStateSnapshot(stateId: string, format: 'full' | 'summary'): Promise<any> {
const metadata = await this.trackingService.getStateMetadata(stateId);
const history = await this.historyService.getStateHistory(stateId);
if (!metadata || !history) {
throw new Error(`Failed to retrieve state data for ID: ${stateId}`);
}
if (format === 'summary') {
return {
id: stateId,
type: metadata.source,
childCount: (metadata.childStates || []).length,
transformationCount: history.transformations.length,
lastModified: metadata.lastModified || metadata.createdAt
};
}
const children = await Promise.all(
(metadata.childStates || []).map(async (id: string) => {
try {
return await this.getStateSnapshot(id, 'summary');
} catch {
return {
id,
type: 'unknown',
childCount: 0,
transformationCount: 0,
lastModified: Date.now()
};
}
})
);
return {
metadata,
history,
children
};
}
public async generateDebugReport(sessionId: string): Promise<string> {
const session = this.sessions.get(sessionId);
if (!session) {
throw new Error(`No debug session found with ID: ${sessionId}`);
}
const lines: string[] = [
`Debug Session Report (${sessionId})`,
`Duration: ${(Date.now() - session.startTime) / 1000}s`,
'',
'Diagnostics:',
...session.diagnostics.map(d =>
`[${d.type.toUpperCase()}] ${d.message}`
),
'',
'Metrics:',
...Object.entries(session.metrics).map(([k, v]) =>
`${k}: ${v}`
),
'',
'Snapshots:',
...Array.from(session.snapshots.entries()).map(([k, v]) =>
`${k}: ${JSON.stringify(v, null, 2)}`
)
];
return lines.join('\n');
}
public registerAnalyzer(
analyzer: (stateId: string) => Promise<StateDiagnostic[]>
): void {
this.analyzers.push(analyzer);
}
public clearSession(sessionId: string): void {
this.sessions.delete(sessionId);
}
private async analyzeStateChanges(
stateId: string,
before: any,
after: any,
startTime: number
): Promise<StateDiagnostic[]> {
const diagnostics: StateDiagnostic[] = [];
try {
const metadata = await this.trackingService.getStateMetadata(stateId);
if (!metadata) {
return [{
stateId,
timestamp: Date.now(),
type: 'error',
message: 'Failed to retrieve state metadata for change analysis'
}];
}
// Analyze structural changes
const beforeChildCount = before?.metadata?.childStates?.length || 0;
const afterChildCount = after?.metadata?.childStates?.length || 0;
if (beforeChildCount !== afterChildCount) {
diagnostics.push({
stateId,
timestamp: Date.now(),
type: 'info',
message: `Child state count changed from ${beforeChildCount} to ${afterChildCount}`,
context: { metadata }
});
}
// Analyze transformation changes
const beforeTransformCount = before?.history?.transformations?.length || 0;
const afterTransformCount = after?.history?.transformations?.length || 0;
const newTransformations = afterTransformCount - beforeTransformCount;
if (newTransformations > 0) {
diagnostics.push({
stateId,
timestamp: Date.now(),
type: 'info',
message: `${newTransformations} new transformations applied`,
context: { metadata }
});
}
return diagnostics;
} catch (error) {
return [{
stateId,
timestamp: Date.now(),
type: 'error',
message: error instanceof Error ? error.message : 'Unknown error during change analysis'
}];
}
}
}