dfa-mcp-server
Version:
DFA-based workflow MCP server for guiding LLM task completion
263 lines โข 10.6 kB
JavaScript
;
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