UNPKG

meld

Version:

Meld: A template language for LLM prompts

317 lines (277 loc) 9.17 kB
/** * @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' }]; } } }