Initial commit to new repository
This commit is contained in:
98
src/AxCopilot/Services/Agent/FileInfoTool.cs
Normal file
98
src/AxCopilot/Services/Agent/FileInfoTool.cs
Normal file
@@ -0,0 +1,98 @@
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace AxCopilot.Services.Agent;
|
||||
|
||||
/// <summary>파일/폴더 메타 정보(크기, 수정일, 줄 수 등) 조회 도구.</summary>
|
||||
public class FileInfoTool : IAgentTool
|
||||
{
|
||||
public string Name => "file_info";
|
||||
public string Description =>
|
||||
"Get file or directory metadata without reading contents. " +
|
||||
"Returns: size, created/modified dates, line count (files), item count (directories), encoding hint.";
|
||||
|
||||
public ToolParameterSchema Parameters => new()
|
||||
{
|
||||
Properties = new()
|
||||
{
|
||||
["path"] = new()
|
||||
{
|
||||
Type = "string",
|
||||
Description = "File or directory path",
|
||||
},
|
||||
},
|
||||
Required = ["path"],
|
||||
};
|
||||
|
||||
public Task<ToolResult> ExecuteAsync(JsonElement args, AgentContext context, CancellationToken ct = default)
|
||||
{
|
||||
var rawPath = args.GetProperty("path").GetString() ?? "";
|
||||
var path = Path.IsPathRooted(rawPath) ? rawPath : Path.Combine(context.WorkFolder, rawPath);
|
||||
|
||||
if (!context.IsPathAllowed(path))
|
||||
return Task.FromResult(ToolResult.Fail($"경로 접근 차단: {path}"));
|
||||
|
||||
try
|
||||
{
|
||||
if (File.Exists(path))
|
||||
{
|
||||
var fi = new FileInfo(path);
|
||||
var sb = new StringBuilder();
|
||||
sb.AppendLine($"Type: File");
|
||||
sb.AppendLine($"Path: {fi.FullName}");
|
||||
sb.AppendLine($"Size: {FormatSize(fi.Length)} ({fi.Length:N0} bytes)");
|
||||
sb.AppendLine($"Extension: {fi.Extension}");
|
||||
sb.AppendLine($"Created: {fi.CreationTime:yyyy-MM-dd HH:mm:ss}");
|
||||
sb.AppendLine($"Modified: {fi.LastWriteTime:yyyy-MM-dd HH:mm:ss}");
|
||||
sb.AppendLine($"ReadOnly: {fi.IsReadOnly}");
|
||||
|
||||
// 텍스트 파일이면 줄 수 카운트 (최대 100만 줄까지)
|
||||
var textExts = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
|
||||
{ ".cs", ".py", ".js", ".ts", ".java", ".cpp", ".c", ".h", ".xml", ".json",
|
||||
".yaml", ".yml", ".md", ".txt", ".csv", ".html", ".htm", ".css", ".sql",
|
||||
".sh", ".bat", ".ps1", ".config", ".ini", ".log", ".xaml" };
|
||||
if (textExts.Contains(fi.Extension) && fi.Length < 50 * 1024 * 1024)
|
||||
{
|
||||
var lineCount = File.ReadLines(path).Take(1_000_000).Count();
|
||||
sb.AppendLine($"Lines: {lineCount:N0}{(lineCount >= 1_000_000 ? "+" : "")}");
|
||||
}
|
||||
|
||||
return Task.FromResult(ToolResult.Ok(sb.ToString()));
|
||||
}
|
||||
|
||||
if (Directory.Exists(path))
|
||||
{
|
||||
var di = new DirectoryInfo(path);
|
||||
var files = di.GetFiles("*", SearchOption.TopDirectoryOnly);
|
||||
var dirs = di.GetDirectories();
|
||||
var totalSize = files.Sum(f => f.Length);
|
||||
|
||||
var sb = new StringBuilder();
|
||||
sb.AppendLine($"Type: Directory");
|
||||
sb.AppendLine($"Path: {di.FullName}");
|
||||
sb.AppendLine($"Files: {files.Length}");
|
||||
sb.AppendLine($"Subdirectories: {dirs.Length}");
|
||||
sb.AppendLine($"Total Size (top-level files): {FormatSize(totalSize)}");
|
||||
sb.AppendLine($"Created: {di.CreationTime:yyyy-MM-dd HH:mm:ss}");
|
||||
sb.AppendLine($"Modified: {di.LastWriteTime:yyyy-MM-dd HH:mm:ss}");
|
||||
|
||||
return Task.FromResult(ToolResult.Ok(sb.ToString()));
|
||||
}
|
||||
|
||||
return Task.FromResult(ToolResult.Fail($"경로를 찾을 수 없습니다: {path}"));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return Task.FromResult(ToolResult.Fail($"정보 조회 오류: {ex.Message}"));
|
||||
}
|
||||
}
|
||||
|
||||
private static string FormatSize(long bytes) => bytes switch
|
||||
{
|
||||
< 1024 => $"{bytes}B",
|
||||
< 1024 * 1024 => $"{bytes / 1024.0:F1}KB",
|
||||
< 1024L * 1024 * 1024 => $"{bytes / (1024.0 * 1024):F1}MB",
|
||||
_ => $"{bytes / (1024.0 * 1024 * 1024):F2}GB",
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user