using System.ComponentModel; using System.Text; namespace MAF1.Tools; /// /// 给 LLM 调用的本地读文件工具。Description 属性会进工具 schema,模型据此决定何时调用。 /// 路径解析会试当前目录、exe 目录、以及 MAF1_CONTENT_ROOT(插件进程由宿主注入)。 /// public static class FileTools { [Description("Read the full UTF-8 text of a local file. Always call this before judging city names.")] public static string ReadTextFile([Description("Absolute or relative path of the file to read.")] string path) { if (string.IsNullOrWhiteSpace(path)) { return "未提供文件路径。"; } string? resolved = Resolve(path.Trim().Trim('"')); if (resolved is null) { return $"找不到文件:{path}"; } FileInfo info = new(resolved); if (info.Length > 64 * 1024) { return $"文件过大({info.Length} 字节),请换一个不超过 64KB 的文本文件。"; } return File.ReadAllText(resolved, Encoding.UTF8); } /// 相对路径时依次试 cwd、exe 目录、内容根、再往上两级父目录(适配 bin/Debug/netX)。 private static string? Resolve(string path) { if (Path.IsPathRooted(path) && File.Exists(path)) { return Path.GetFullPath(path); } string fromCwd = Path.GetFullPath(path); if (File.Exists(fromCwd)) { return fromCwd; } foreach (string root in SearchRoots()) { string candidate = Path.GetFullPath(Path.Combine(root, path)); if (File.Exists(candidate)) { return candidate; } } return null; } private static IEnumerable SearchRoots() { yield return AppContext.BaseDirectory; string? contentRoot = Environment.GetEnvironmentVariable("MAF1_CONTENT_ROOT"); if (!string.IsNullOrWhiteSpace(contentRoot)) { yield return contentRoot; } DirectoryInfo? parent = Directory.GetParent(AppContext.BaseDirectory.TrimEnd(Path.DirectorySeparatorChar)); if (parent is not null) { yield return parent.FullName; if (parent.Parent is not null) { yield return parent.Parent.FullName; } } } }