diff --git a/src/Netclaw.Actors.Tests/Tools/MetaFieldResolutionTests.cs b/src/Netclaw.Actors.Tests/Tools/MetaFieldResolutionTests.cs
index cb4f8e107..137d446c1 100644
--- a/src/Netclaw.Actors.Tests/Tools/MetaFieldResolutionTests.cs
+++ b/src/Netclaw.Actors.Tests/Tools/MetaFieldResolutionTests.cs
@@ -25,7 +25,9 @@ public class MetaFieldResolutionTests
[Theory]
[InlineData("_rationale", "_rationale")]
[InlineData("Rationale", "_rationale")]
+ [InlineData("r_rationale", "_rationale")]
[InlineData("rationale", "_rationale")]
+ [InlineData("rationalle", "_rationale")]
[InlineData("_timeout_seconds", "_timeout_seconds")]
[InlineData("TimeoutSeconds", "_timeout_seconds")]
[InlineData("timeout_seconds", "_timeout_seconds")]
diff --git a/src/Netclaw.Tools.Abstractions/FuzzyMatcher.cs b/src/Netclaw.Tools.Abstractions/FuzzyMatcher.cs
new file mode 100644
index 000000000..414014152
--- /dev/null
+++ b/src/Netclaw.Tools.Abstractions/FuzzyMatcher.cs
@@ -0,0 +1,64 @@
+
+// -----------------------------------------------------------------------
+//
+// Copyright (C) 2026 - 2026 Petabridge, LLC
+//
+// -----------------------------------------------------------------------
+using System.Linq;
+
+namespace Netclaw.Tools
+{
+ public static class FuzzyMatcher
+ {
+ public static string? FindBestMatch(string input, System.Collections.Generic.IEnumerable candidates, double threshold = 0.75)
+ {
+ if (string.IsNullOrWhiteSpace(input)) return null;
+ var normalisedInput = Normalise(input);
+ if (normalisedInput.Length == 0) return null;
+
+ string? bestMatch = null;
+ double bestScore = 0;
+
+ foreach (var candidate in candidates)
+ {
+ var normalisedCandidate = Normalise(candidate);
+ if (normalisedCandidate.Length == 0) continue;
+ double score = Similarity(normalisedInput, normalisedCandidate);
+ if (normalisedCandidate.Contains(normalisedInput, StringComparison.Ordinal)) score = System.Math.Max(score, 0.85);
+ if (score > bestScore)
+ {
+ bestScore = score;
+ bestMatch = candidate;
+ }
+ }
+
+ return bestScore >= threshold ? bestMatch : null;
+ }
+
+ private static string Normalise(string value) =>
+ new string(value.Where(char.IsLetterOrDigit).Select(char.ToLowerInvariant).ToArray());
+
+ private static double Similarity(string a, string b)
+ {
+ int distance = LevenshteinDistance(a, b);
+ return 1.0 - (double)distance / System.Math.Max(a.Length, b.Length);
+ }
+
+ private static int LevenshteinDistance(string a, string b)
+ {
+ var previous = System.Linq.Enumerable.Range(0, b.Length + 1).ToArray();
+ var current = new int[b.Length + 1];
+ for (int i = 1; i <= a.Length; i++)
+ {
+ current[0] = i;
+ for (int j = 1; j <= b.Length; j++)
+ {
+ int cost = a[i - 1] == b[j - 1] ? 0 : 1;
+ current[j] = System.Math.Min(System.Math.Min(current[j - 1] + 1, previous[j] + 1), previous[j - 1] + cost);
+ }
+ (previous, current) = (current, previous);
+ }
+ return previous[b.Length];
+ }
+ }
+}
diff --git a/src/Netclaw.Tools.Abstractions/ToolArgumentHelper.cs b/src/Netclaw.Tools.Abstractions/ToolArgumentHelper.cs
index 4595fa999..f488855c1 100644
--- a/src/Netclaw.Tools.Abstractions/ToolArgumentHelper.cs
+++ b/src/Netclaw.Tools.Abstractions/ToolArgumentHelper.cs
@@ -112,11 +112,7 @@ private static Dictionary BuildNormalizedMetaFields()
{
var map = new Dictionary(StringComparer.OrdinalIgnoreCase);
foreach (var canonical in ToolCallMeta.MetaFieldNames)
- map[NormalizeKey(canonical)] = canonical;
-
- map["timeout"] = "_timeout_seconds";
- map["rational"] = "_rationale";
- map["rationalle"] = "_rationale";
+ map[NormalizeKey(canonical)] = canonical;
return map;
}
@@ -132,7 +128,15 @@ private static Dictionary BuildNormalizedMetaFields()
public static string? ResolveMetaField(string? key)
=> string.IsNullOrWhiteSpace(key)
? null
- : NormalizedMetaFields.TryGetValue(NormalizeKey(key), out var canonical) ? canonical : null;
+ : NormalizedMetaFields.TryGetValue(NormalizeKey(key), out var canonical)
+ ? canonical
+ : FuzzyMatchUnmatched(key);
+
+ private static string? FuzzyMatchUnmatched(string key)
+ {
+ const double threshold = 0.75;
+ return FuzzyMatcher.FindBestMatch(key, ToolCallMeta.MetaFieldNames, threshold);
+ }
public static string? GetString(IDictionary? arguments, string key)
{