using System; using System.Collections.Generic; using System.Linq; using System.Text.RegularExpressions; namespace io.github.hatayama.uLoopMCP { /// /// Translation result from Translator before formatting /// public class TranslationOutput { public string FriendlyMessage { get; set; } = ""; public string Explanation { get; set; } = ""; public string Example { get; set; } = ""; public List Solutions { get; set; } = new(); public List LearningTips { get; set; } = new(); } /// /// Translate raw errors/exceptions into structured data (no severity weighting) /// public interface IErrorTranslator { TranslationOutput TranslateFromException(Exception exception); TranslationOutput TranslateFromMessage(string errorMessage); } /// /// Format translation into DTO with severity weighting and final output shape /// public interface IErrorFormatter { UserFriendlyErrorDto Format(TranslationOutput translation, string originalError, Exception exception = null); } /// /// Default translator using pattern dictionary and simple fallbacks /// public class DefaultErrorTranslator : IErrorTranslator { private readonly ErrorTranslationDictionary _dictionary = new(); private readonly FriendlyMessageGenerator _messageGenerator = new(); public TranslationOutput TranslateFromException(Exception exception) { if (exception == null) { return new TranslationOutput { FriendlyMessage = "Internal error", Explanation = string.Empty, Example = string.Empty, Solutions = new List(), LearningTips = new List() }; } if (exception is McpSecurityException securityException) { return new TranslationOutput { FriendlyMessage = "Tool blocked by security settings", Explanation = securityException.SecurityReason ?? string.Empty, Solutions = new List { "Open uLoopMCP Security Settings and enable the required permission" } }; } if (exception is TimeoutException) { return new TranslationOutput { FriendlyMessage = "Request timeout", Explanation = exception.Message, Solutions = new List { "Increase timeout or optimize the operation to complete faster" } }; } if (exception is ToolDisabledException disabledException) { return new TranslationOutput { FriendlyMessage = $"Tool '{disabledException.ToolName}' is disabled. To enable it, go to {McpUIConstants.TOOL_SETTINGS_MENU_PATH}", Explanation = "This tool has been disabled in project settings.", Solutions = new List { $"Enable '{disabledException.ToolName}' in {McpUIConstants.TOOL_SETTINGS_MENU_PATH}" } }; } if (exception is ParameterValidationException) { return new TranslationOutput { FriendlyMessage = exception.Message, Explanation = string.Empty, Solutions = new List { "Fix parameter types/values according to the tool schema" } }; } return new TranslationOutput { FriendlyMessage = "Internal error", Explanation = exception.Message }; } public TranslationOutput TranslateFromMessage(string errorMessage) { ErrorPattern pattern = _dictionary.FindPattern(errorMessage); string friendly = pattern?.FriendlyMessage ?? _messageGenerator.TranslateError(errorMessage); string explanation = pattern?.Explanation ?? string.Empty; string example = pattern?.Example ?? string.Empty; List solutions = pattern?.Solutions ?? new List(); List tips = GetLearningTips(errorMessage); return new TranslationOutput { FriendlyMessage = friendly, Explanation = explanation, Example = example, Solutions = solutions, LearningTips = tips }; } private List GetLearningTips(string errorMessage) { List tips = new(); if (errorMessage.Contains("Top-level statements")) { tips.Add("In Dynamic Tool, you can write code directly"); tips.Add("Class and namespace declarations are not required"); } if (errorMessage.Contains("not all code paths return")) { tips.Add("Add a return statement at the end of the code"); tips.Add("Return the execution result as a string"); } if (errorMessage.Contains("ambiguous reference")) { tips.Add("Clearly distinguish between UnityEngine.Object and System.Object"); tips.Add("Using the full name (UnityEngine.Object) is safer"); } return tips; } } /// /// Default formatter applying severity weighting and assembling the DTO /// public class DefaultErrorFormatter : IErrorFormatter { public UserFriendlyErrorDto Format(TranslationOutput translation, string originalError, Exception exception = null) { return new UserFriendlyErrorDto { OriginalError = originalError ?? string.Empty, FriendlyMessage = translation?.FriendlyMessage ?? string.Empty, Explanation = translation?.Explanation ?? string.Empty, Example = translation?.Example ?? string.Empty, SuggestedSolutions = translation?.Solutions ?? new List(), LearningTips = translation?.LearningTips ?? new List(), Severity = DetermineSeverity(originalError, exception) }; } private ErrorSeverity DetermineSeverity(string errorMessage, Exception exception) { if (exception != null) { if (exception is McpSecurityException) { return ErrorSeverity.High; } if (exception is TimeoutException) { return ErrorSeverity.Medium; } if (exception is ToolDisabledException) { return ErrorSeverity.Medium; } if (exception is ParameterValidationException) { return ErrorSeverity.Low; } return ErrorSeverity.High; } string msg = errorMessage ?? string.Empty; if (msg.Contains("Top-level statements") || msg.Contains("not all code paths")) { return ErrorSeverity.High; } if (msg.Contains("ambiguous reference")) { return ErrorSeverity.Medium; } return ErrorSeverity.Low; } } /// /// Related Classes: FriendlyMessageGenerator, ErrorTranslationDictionary /// Now delegates to Translator and Formatter /// public class UserFriendlyErrorConverter { private readonly IErrorTranslator _translator; private readonly IErrorFormatter _formatter; public UserFriendlyErrorConverter() { _translator = new DefaultErrorTranslator(); _formatter = new DefaultErrorFormatter(); } public UserFriendlyErrorConverter(IErrorTranslator translator, IErrorFormatter formatter) { _translator = translator ?? new DefaultErrorTranslator(); _formatter = formatter ?? new DefaultErrorFormatter(); } /// /// Convert an exception to an enhanced, user-friendly error response /// public UserFriendlyErrorDto ProcessException(Exception exception) { if (exception == null) { return new UserFriendlyErrorDto { OriginalError = string.Empty, FriendlyMessage = "Internal error", Explanation = string.Empty, Example = string.Empty, SuggestedSolutions = new List(), LearningTips = new List(), Severity = ErrorSeverity.High }; } TranslationOutput translation = _translator.TranslateFromException(exception); return _formatter.Format(translation, exception.Message, exception); } /// /// Convert execution result errors into a more understandable format /// public UserFriendlyErrorDto ProcessError( ExecutionResult originalResult, string originalCode) { string errorMessage = originalResult.ErrorMessage ?? string.Empty; if (originalResult.Logs?.Any() == true) { string combinedErrors = string.Join(" ", originalResult.Logs); if (!string.IsNullOrEmpty(combinedErrors)) { errorMessage += " " + combinedErrors; } } TranslationOutput translation = _translator.TranslateFromMessage(errorMessage); return _formatter.Format(translation, errorMessage); } } /// /// Error Translation Dictionary - Translating Common Error Patterns into Understandable Messages /// public class ErrorTranslationDictionary { private static readonly Dictionary Patterns = new() { ["Top-level statements must precede"] = new ErrorPattern { PatternRegex = @"Top-level statements must precede", FriendlyMessage = "There is an issue with the code structure", Explanation = "In the Dynamic Tool, class and namespace declarations are not required. Write Unity API processing directly.", Example = @"AVOID: namespace Test { class MyClass { void Method() { ... } } } CORRECT: GameObject cube = GameObject.CreatePrimitive(PrimitiveType.Cube); cube.transform.position = Vector3.zero; return ""Cube creation completed"";", Solutions = new List { "Remove class definition and write only the code inside the method", "Remove namespace declaration", "Keep using statements and extract only the main processing" } }, ["not all code paths return a value"] = new ErrorPattern { PatternRegex = @"not all code paths return", FriendlyMessage = "A return value is required at the end of the code", Explanation = "In the Dynamic Tool, you must return the execution result. Please add a return statement at the end of the processing.", Example = @"AVOID: GameObject cube = GameObject.CreatePrimitive(PrimitiveType.Cube); Debug.Log(""Completed""); CORRECT: GameObject cube = GameObject.CreatePrimitive(PrimitiveType.Cube); Debug.Log(""Completed""); return ""Cube creation completed"";", Solutions = new List { "Add 'return \"Execution completed\";' at the end of the code", "Add return statements to all paths of conditional branching", "Return the execution result as a string" } }, ["Object' is an ambiguous reference"] = new ErrorPattern { PatternRegex = @"'Object' is an ambiguous reference", FriendlyMessage = "Object class reference is ambiguous", Explanation = "UnityEngine.Object and System.Object are mixed. Please specify explicitly.", Example = @"AVOID: Object.DestroyImmediate(obj); CORRECT: UnityEngine.Object.DestroyImmediate(obj);", Solutions = new List { "Explicitly specify UnityEngine.Object", "Use full name description" } } }; public ErrorPattern FindPattern(string errorMessage) { foreach (var kvp in Patterns) { if (Regex.IsMatch(errorMessage, kvp.Value.PatternRegex)) { return kvp.Value; } } return null; } } /// /// User-Friendly Message Generation Feature /// public class FriendlyMessageGenerator { public string TranslateError(string originalError) { // Basic error translation logic if (originalError.Contains("Top-level statements")) { return "There is an issue with the code structure. Class and namespace definitions are not required."; } if (originalError.Contains("not all code paths return")) { return "A return value is required at the end of the code. Please add 'return \"Completed\";'."; } if (originalError.Contains("ambiguous reference")) { return "Class reference is ambiguous. Please explicitly write UnityEngine.Object."; } if (originalError.Contains("Identifier expected")) { return "Syntax error. Please verify the code syntax."; } return originalError; // Fallback } } /// /// Error Pattern Definition /// public class ErrorPattern { public string PatternRegex { get; set; } = ""; public string FriendlyMessage { get; set; } = ""; public string Explanation { get; set; } = ""; public string Example { get; set; } = ""; public List Solutions { get; set; } = new(); } /// /// User-friendly error DTO for API/UI surfaces /// public class UserFriendlyErrorDto { public string OriginalError { get; set; } = ""; public string FriendlyMessage { get; set; } = ""; public string Explanation { get; set; } = ""; public string Example { get; set; } = ""; public List SuggestedSolutions { get; set; } = new(); public List LearningTips { get; set; } = new(); public ErrorSeverity Severity { get; set; } } /// /// Error Severity /// public enum ErrorSeverity { Low, Medium, High, Critical } }