UNPKG

dfa-mcp-server

Version:

DFA-based workflow MCP server for guiding LLM task completion

263 lines โ€ข 10.6 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.ErrorFormatter = void 0; class ErrorFormatter { /** * Format a rich error message for invalid transitions */ static formatInvalidActionError(currentState, attemptedAction, validActions, workflowName) { const hasValidActions = validActions.length > 0; let message = `\nโŒ Invalid Action Error\n`; message += `${'โ”€'.repeat(50)}\n`; message += `๐Ÿ“ Current State: '${currentState}'\n`; message += `๐Ÿšซ Attempted Action: '${attemptedAction}'\n`; message += `๐Ÿ“‹ Workflow: '${workflowName}'\n\n`; if (hasValidActions) { message += `โœ… Available Actions from '${currentState}':\n`; validActions.forEach(action => { message += ` โ€ข ${action}\n`; }); // Add "did you mean?" suggestion if there's a close match const suggestion = this.findSimilarAction(attemptedAction, validActions); if (suggestion) { message += `\n๐Ÿ’ก Did you mean '${suggestion}'?\n`; } } else { message += `โš ๏ธ No actions available from state '${currentState}'\n`; message += ` This might be a final state or a dead-end.\n`; } message += `\n๐Ÿ“ Example Usage:\n`; if (hasValidActions) { message += ` workflow.advance({ id: "...", action: "${validActions[0]}", data: { ... } })\n`; } return message; } /** * Format a rich error message for missing required fields */ static formatMissingFieldsError(missingFields, currentContext, targetState, action) { let message = `\nโŒ Missing Required Fields\n`; message += `${'โ”€'.repeat(50)}\n`; message += `๐Ÿ“ Target State: '${targetState}'\n`; message += `๐Ÿ”„ Action: '${action}'\n\n`; message += `๐Ÿšซ Missing Fields:\n`; missingFields.forEach(field => { message += ` โ€ข ${field}\n`; }); message += `\n๐Ÿ“Š Current Context:\n`; const contextKeys = Object.keys(currentContext); if (contextKeys.length > 0) { contextKeys.slice(0, 5).forEach(key => { const value = JSON.stringify(currentContext[key]); message += ` โ€ข ${key}: ${value.length > 50 ? value.substring(0, 50) + '...' : value}\n`; }); if (contextKeys.length > 5) { message += ` ... and ${contextKeys.length - 5} more fields\n`; } } else { message += ` (empty)\n`; } message += `\n๐Ÿ’ก Solution:\n`; message += `Include the missing fields in your transition data:\n\n`; message += `workflow.advance({\n`; message += ` id: "...",\n`; message += ` action: "${action}",\n`; message += ` data: {\n`; missingFields.forEach(field => { message += ` ${field}: ${this.getFieldExample(field)},\n`; }); message += ` // ... other fields\n`; message += ` }\n`; message += `})\n`; return message; } /** * Format judge decision for better readability */ static formatJudgeDecision(decision, attempt) { const approved = decision.approved; const emoji = approved ? 'โœ…' : 'โŒ'; const status = approved ? 'APPROVED' : 'REJECTED'; let message = `\n${emoji} Judge Decision: ${status}\n`; message += `${'โ”€'.repeat(50)}\n`; message += `๐Ÿ“Š Confidence: ${(decision.confidence * 100).toFixed(1)}%\n`; message += `๐Ÿ”„ Transition: ${attempt.fromState} โ†’ [${attempt.action}] โ†’ ${attempt.toState}\n`; message += `\n๐Ÿ“ Reasoning:\n${this.wrapText(decision.reasoning, 60, ' ')}\n`; if (decision.violations && decision.violations.length > 0) { message += `\n๐Ÿšซ Violations:\n`; decision.violations.forEach((violation, i) => { message += ` ${i + 1}. ${violation}\n`; }); } if (decision.suggestions && decision.suggestions.length > 0) { message += `\n๐Ÿ’ก Suggestions:\n`; decision.suggestions.forEach((suggestion, i) => { message += ` ${i + 1}. ${suggestion}\n`; }); } if (!approved && attempt.definition.states[attempt.fromState]) { const validActions = Object.keys(attempt.definition.states[attempt.fromState].transitions || {}); if (validActions.length > 0) { message += `\nโœ… Valid Actions from '${attempt.fromState}':\n`; validActions.forEach(action => { message += ` โ€ข ${action}\n`; }); } } return message; } /** * Format validation error with context comparison */ static formatValidationError(violation, currentValue, expectedValue, fieldPath) { let message = `\nโš ๏ธ Validation Error${fieldPath ? ` at '${fieldPath}'` : ''}\n`; message += `${'โ”€'.repeat(50)}\n`; message += `โŒ Issue: ${violation}\n\n`; if (currentValue !== undefined || expectedValue !== undefined) { message += `๐Ÿ“Š Comparison:\n`; message += ` Current: ${this.formatValue(currentValue)}\n`; message += ` Expected: ${this.formatValue(expectedValue)}\n`; } return message; } /** * Format state transition path for clarity */ static formatTransitionPath(fromState, action, toState, isValid) { const arrow = isValid ? 'โ†’' : 'โคฌ'; const color = isValid ? 'โœ…' : 'โŒ'; return `${color} ${fromState} ${arrow} [${action}] ${arrow} ${toState}`; } /** * Helper: Find similar action name (for "did you mean?" suggestions) */ static findSimilarAction(attempted, valid) { const normalizedAttempt = attempted.toLowerCase(); // Exact match (case-insensitive) const exactMatch = valid.find(v => v.toLowerCase() === normalizedAttempt); if (exactMatch) return exactMatch; // Prefix match const prefixMatch = valid.find(v => v.toLowerCase().startsWith(normalizedAttempt)); if (prefixMatch) return prefixMatch; // Contains match const containsMatch = valid.find(v => v.toLowerCase().includes(normalizedAttempt)); if (containsMatch) return containsMatch; // Simple Levenshtein distance (for small strings) if (attempted.length <= 10) { const distances = valid.map(v => ({ action: v, distance: this.levenshteinDistance(normalizedAttempt, v.toLowerCase()) })); const closest = distances.sort((a, b) => a.distance - b.distance)[0]; if (closest && closest.distance <= 2) { return closest.action; } } return null; } /** * Helper: Simple Levenshtein distance for similarity matching */ static levenshteinDistance(a, b) { const matrix = []; for (let i = 0; i <= b.length; i++) { matrix[i] = [i]; } for (let j = 0; j <= a.length; j++) { matrix[0][j] = j; } for (let i = 1; i <= b.length; i++) { for (let j = 1; j <= a.length; j++) { if (b.charAt(i - 1) === a.charAt(j - 1)) { matrix[i][j] = matrix[i - 1][j - 1]; } else { matrix[i][j] = Math.min(matrix[i - 1][j - 1] + 1, matrix[i][j - 1] + 1, matrix[i - 1][j] + 1); } } } return matrix[b.length][a.length]; } /** * Helper: Get example value for a field name */ static getFieldExample(fieldName) { const lowerField = fieldName.toLowerCase(); // Common field patterns if (lowerField.includes('email')) return '"user@example.com"'; if (lowerField.includes('name')) return '"John Doe"'; if (lowerField.includes('id')) return '"123"'; if (lowerField.includes('date') || lowerField.includes('time')) return 'new Date().toISOString()'; if (lowerField.includes('url')) return '"https://example.com"'; if (lowerField.includes('phone')) return '"+1234567890"'; if (lowerField.includes('amount') || lowerField.includes('price')) return '100.00'; if (lowerField.includes('count') || lowerField.includes('quantity')) return '1'; if (lowerField.includes('comment') || lowerField.includes('description')) return '"Your text here"'; if (lowerField.includes('status')) return '"pending"'; if (lowerField.includes('type')) return '"default"'; if (lowerField.includes('flag') || lowerField.includes('enabled')) return 'true'; // Default return '"<value>"'; } /** * Helper: Format a value for display */ static formatValue(value) { if (value === undefined) return 'undefined'; if (value === null) return 'null'; if (typeof value === 'string') return `"${value}"`; if (typeof value === 'object') { const str = JSON.stringify(value); return str.length > 50 ? str.substring(0, 50) + '...' : str; } return String(value); } /** * Helper: Wrap text to specified width */ static wrapText(text, width, indent = '') { const words = text.split(' '); const lines = []; let currentLine = ''; words.forEach(word => { if ((currentLine + ' ' + word).length > width) { if (currentLine) { lines.push(indent + currentLine); currentLine = word; } else { lines.push(indent + word); } } else { currentLine = currentLine ? currentLine + ' ' + word : word; } }); if (currentLine) { lines.push(indent + currentLine); } return lines.join('\n'); } } exports.ErrorFormatter = ErrorFormatter; //# sourceMappingURL=error-formatter.js.map