Files

221 lines
7.1 KiB
C#

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
namespace AxCopilot.Services.Agent;
public class FolderMapTool : IAgentTool
{
private static readonly HashSet<string> IgnoredDirs = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
{
"bin", "obj", "node_modules", ".git", ".vs", ".idea", ".vscode", "__pycache__", ".mypy_cache", ".pytest_cache",
"dist", "build", "packages", ".nuget", "TestResults", "coverage", ".next", "target", ".gradle", ".cargo"
};
private const int MaxEntries = 500;
public string Name => "folder_map";
public string Description => "Generate a directory tree map of the work folder or a specified subfolder. Shows folders and files in a tree structure. Use this to understand the project layout before reading or editing files.";
public ToolParameterSchema Parameters => new ToolParameterSchema
{
Properties = new Dictionary<string, ToolProperty>
{
["path"] = new ToolProperty
{
Type = "string",
Description = "Subdirectory to map. Optional, defaults to work folder root."
},
["depth"] = new ToolProperty
{
Type = "integer",
Description = "Maximum depth to traverse (1-10). Default: 3."
},
["include_files"] = new ToolProperty
{
Type = "boolean",
Description = "Whether to include files. Default: true."
},
["pattern"] = new ToolProperty
{
Type = "string",
Description = "File extension filter (e.g. '.cs', '.py'). Optional, shows all files if omitted."
}
},
Required = new List<string>()
};
public Task<ToolResult> ExecuteAsync(JsonElement args, AgentContext context, CancellationToken ct)
{
JsonElement value;
string text = (args.TryGetProperty("path", out value) ? (value.GetString() ?? "") : "");
int num = 3;
if (args.TryGetProperty("depth", out var value2))
{
int result;
if (value2.ValueKind == JsonValueKind.Number)
{
num = value2.GetInt32();
}
else if (value2.ValueKind == JsonValueKind.String && int.TryParse(value2.GetString(), out result))
{
num = result;
}
}
string s = num.ToString();
bool includeFiles = true;
if (args.TryGetProperty("include_files", out var value3))
{
includeFiles = ((value3.ValueKind != JsonValueKind.True && value3.ValueKind != JsonValueKind.False) ? (!string.Equals(value3.GetString(), "false", StringComparison.OrdinalIgnoreCase)) : value3.GetBoolean());
}
JsonElement value4;
string extFilter = (args.TryGetProperty("pattern", out value4) ? (value4.GetString() ?? "") : "");
if (!int.TryParse(s, out var result2) || result2 < 1)
{
result2 = 3;
}
result2 = Math.Min(result2, 10);
string text2 = (string.IsNullOrEmpty(text) ? context.WorkFolder : FileReadTool.ResolvePath(text, context.WorkFolder));
if (string.IsNullOrEmpty(text2) || !Directory.Exists(text2))
{
return Task.FromResult(ToolResult.Fail("디렉토리가 존재하지 않습니다: " + text2));
}
if (!context.IsPathAllowed(text2))
{
return Task.FromResult(ToolResult.Fail("경로 접근 차단: " + text2));
}
try
{
StringBuilder stringBuilder = new StringBuilder();
string value5 = Path.GetFileName(text2);
if (string.IsNullOrEmpty(value5))
{
value5 = text2;
}
StringBuilder stringBuilder2 = stringBuilder;
StringBuilder stringBuilder3 = stringBuilder2;
StringBuilder.AppendInterpolatedStringHandler handler = new StringBuilder.AppendInterpolatedStringHandler(1, 1, stringBuilder2);
handler.AppendFormatted(value5);
handler.AppendLiteral("/");
stringBuilder3.AppendLine(ref handler);
int entryCount = 0;
BuildTree(stringBuilder, text2, "", 0, result2, includeFiles, extFilter, context, ref entryCount);
if (entryCount >= 500)
{
stringBuilder2 = stringBuilder;
StringBuilder stringBuilder4 = stringBuilder2;
handler = new StringBuilder.AppendInterpolatedStringHandler(42, 1, stringBuilder2);
handler.AppendLiteral("\n... (");
handler.AppendFormatted(500);
handler.AppendLiteral("개 항목 제한 도달, depth 또는 pattern을 조정하세요)");
stringBuilder4.AppendLine(ref handler);
}
string value6 = $"폴더 맵 생성 완료 ({entryCount}개 항목, 깊이 {result2})";
return Task.FromResult(ToolResult.Ok($"{value6}\n\n{stringBuilder}"));
}
catch (Exception ex)
{
return Task.FromResult(ToolResult.Fail("폴더 맵 생성 실패: " + ex.Message));
}
}
private static void BuildTree(StringBuilder sb, string dir, string prefix, int currentDepth, int maxDepth, bool includeFiles, string extFilter, AgentContext context, ref int entryCount)
{
if (currentDepth >= maxDepth || entryCount >= 500)
{
return;
}
List<DirectoryInfo> list;
try
{
list = (from d in new DirectoryInfo(dir).GetDirectories()
where !d.Attributes.HasFlag(FileAttributes.Hidden) && !IgnoredDirs.Contains(d.Name)
orderby d.Name
select d).ToList();
}
catch
{
return;
}
List<FileInfo> list2 = new List<FileInfo>();
if (includeFiles)
{
try
{
list2 = (from f in new DirectoryInfo(dir).GetFiles()
where !f.Attributes.HasFlag(FileAttributes.Hidden) && (string.IsNullOrEmpty(extFilter) || f.Extension.Equals(extFilter, StringComparison.OrdinalIgnoreCase))
orderby f.Name
select f).ToList();
}
catch
{
}
}
int num = list.Count + list2.Count;
int num2 = 0;
foreach (DirectoryInfo item in list)
{
if (entryCount >= 500)
{
break;
}
num2++;
bool flag = num2 == num;
string value = (flag ? "└── " : "├── ");
string text = (flag ? " " : "│ ");
StringBuilder stringBuilder = sb;
StringBuilder stringBuilder2 = stringBuilder;
StringBuilder.AppendInterpolatedStringHandler handler = new StringBuilder.AppendInterpolatedStringHandler(1, 3, stringBuilder);
handler.AppendFormatted(prefix);
handler.AppendFormatted(value);
handler.AppendFormatted(item.Name);
handler.AppendLiteral("/");
stringBuilder2.AppendLine(ref handler);
entryCount++;
if (context.IsPathAllowed(item.FullName))
{
BuildTree(sb, item.FullName, prefix + text, currentDepth + 1, maxDepth, includeFiles, extFilter, context, ref entryCount);
}
}
foreach (FileInfo item2 in list2)
{
if (entryCount >= 500)
{
break;
}
num2++;
string value2 = ((num2 == num) ? "└── " : "├── ");
string value3 = FormatSize(item2.Length);
StringBuilder stringBuilder = sb;
StringBuilder stringBuilder3 = stringBuilder;
StringBuilder.AppendInterpolatedStringHandler handler = new StringBuilder.AppendInterpolatedStringHandler(4, 4, stringBuilder);
handler.AppendFormatted(prefix);
handler.AppendFormatted(value2);
handler.AppendFormatted(item2.Name);
handler.AppendLiteral(" (");
handler.AppendFormatted(value3);
handler.AppendLiteral(")");
stringBuilder3.AppendLine(ref handler);
entryCount++;
}
}
private static string FormatSize(long bytes)
{
if (1 == 0)
{
}
string result = ((bytes < 1024) ? $"{bytes} B" : ((bytes >= 1048576) ? $"{(double)bytes / 1048576.0:F1} MB" : $"{(double)bytes / 1024.0:F1} KB"));
if (1 == 0)
{
}
return result;
}
}