Files

251 lines
7.7 KiB
C#

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
namespace AxCopilot.Services.Agent;
public class DiffPreviewTool : IAgentTool
{
public string Name => "diff_preview";
public string Description => "Preview file changes before applying them. Shows a unified diff and waits for user approval. If approved, writes the new content to the file. If rejected, no changes are made. The diff output is prefixed with [PREVIEW_PENDING] so the UI can show an approval panel.";
public ToolParameterSchema Parameters
{
get
{
ToolParameterSchema obj = new ToolParameterSchema
{
Properties = new Dictionary<string, ToolProperty>
{
["path"] = new ToolProperty
{
Type = "string",
Description = "File path to modify"
},
["new_content"] = new ToolProperty
{
Type = "string",
Description = "Proposed new content for the file"
},
["description"] = new ToolProperty
{
Type = "string",
Description = "Description of the changes (optional)"
}
}
};
int num = 2;
List<string> list = new List<string>(num);
CollectionsMarshal.SetCount(list, num);
Span<string> span = CollectionsMarshal.AsSpan(list);
span[0] = "path";
span[1] = "new_content";
obj.Required = list;
return obj;
}
}
public async Task<ToolResult> ExecuteAsync(JsonElement args, AgentContext context, CancellationToken ct = default(CancellationToken))
{
string rawPath = args.GetProperty("path").GetString() ?? "";
string newContent = args.GetProperty("new_content").GetString() ?? "";
JsonElement d;
string description = (args.TryGetProperty("description", out d) ? (d.GetString() ?? "") : "");
string path = (Path.IsPathRooted(rawPath) ? rawPath : Path.Combine(context.WorkFolder, rawPath));
if (!context.IsPathAllowed(path))
{
return ToolResult.Fail("경로 접근 차단: " + path);
}
try
{
string originalContent = "";
bool isNewFile = !File.Exists(path);
if (!isNewFile)
{
originalContent = await File.ReadAllTextAsync(path, ct);
}
string[] originalLines = (from l in originalContent.Split('\n')
select l.TrimEnd('\r')).ToArray();
string[] newLines = (from l in newContent.Split('\n')
select l.TrimEnd('\r')).ToArray();
string diff = GenerateUnifiedDiff(originalLines, newLines, path);
StringBuilder sb = new StringBuilder();
sb.AppendLine("[PREVIEW_PENDING]");
StringBuilder stringBuilder;
StringBuilder.AppendInterpolatedStringHandler handler;
if (!string.IsNullOrEmpty(description))
{
stringBuilder = sb;
StringBuilder stringBuilder2 = stringBuilder;
handler = new StringBuilder.AppendInterpolatedStringHandler(7, 1, stringBuilder);
handler.AppendLiteral("변경 설명: ");
handler.AppendFormatted(description);
stringBuilder2.AppendLine(ref handler);
}
stringBuilder = sb;
StringBuilder stringBuilder3 = stringBuilder;
handler = new StringBuilder.AppendInterpolatedStringHandler(4, 1, stringBuilder);
handler.AppendLiteral("파일: ");
handler.AppendFormatted(path);
stringBuilder3.AppendLine(ref handler);
sb.AppendLine(isNewFile ? "상태: 새 파일 생성" : "상태: 기존 파일 수정");
sb.AppendLine();
if (string.IsNullOrEmpty(diff))
{
sb.AppendLine("변경 사항 없음 — 내용이 동일합니다.");
}
else
{
sb.Append(diff);
}
if (!(await context.CheckWritePermissionAsync("diff_preview", path)))
{
return ToolResult.Ok($"사용자가 파일 변경을 거부했습니다.\n\n{sb}");
}
string dir = Path.GetDirectoryName(path);
if (!string.IsNullOrEmpty(dir) && !Directory.Exists(dir))
{
Directory.CreateDirectory(dir);
}
await File.WriteAllTextAsync(path, newContent, ct);
return ToolResult.Ok($"변경 사항이 적용되었습니다: {path}\n\n{sb}", path);
}
catch (Exception ex)
{
return ToolResult.Fail("미리보기 오류: " + ex.Message);
}
}
private static string GenerateUnifiedDiff(string[] original, string[] modified, string filePath)
{
StringBuilder stringBuilder = new StringBuilder();
StringBuilder stringBuilder2 = stringBuilder;
StringBuilder stringBuilder3 = stringBuilder2;
StringBuilder.AppendInterpolatedStringHandler handler = new StringBuilder.AppendInterpolatedStringHandler(9, 1, stringBuilder2);
handler.AppendLiteral("--- ");
handler.AppendFormatted(filePath);
handler.AppendLiteral(" (원본)");
stringBuilder3.AppendLine(ref handler);
stringBuilder2 = stringBuilder;
StringBuilder stringBuilder4 = stringBuilder2;
handler = new StringBuilder.AppendInterpolatedStringHandler(9, 1, stringBuilder2);
handler.AppendLiteral("+++ ");
handler.AppendFormatted(filePath);
handler.AppendLiteral(" (수정)");
stringBuilder4.AppendLine(ref handler);
List<string> list = ComputeLcs(original, modified);
int i = 0;
int j = 0;
int num = 0;
List<(int, int, int, int)> list2 = new List<(int, int, int, int)>();
while (i < original.Length || j < modified.Length)
{
if (num < list.Count && i < original.Length && j < modified.Length && original[i] == list[num] && modified[j] == list[num])
{
i++;
j++;
num++;
continue;
}
int item = i;
int item2 = j;
for (; i < original.Length && (num >= list.Count || original[i] != list[num]); i++)
{
}
for (; j < modified.Length && (num >= list.Count || modified[j] != list[num]); j++)
{
}
list2.Add((item, i, item2, j));
}
if (list2.Count == 0)
{
return "";
}
foreach (var item7 in list2)
{
int item3 = item7.Item1;
int item4 = item7.Item2;
int item5 = item7.Item3;
int item6 = item7.Item4;
stringBuilder2 = stringBuilder;
StringBuilder stringBuilder5 = stringBuilder2;
handler = new StringBuilder.AppendInterpolatedStringHandler(11, 4, stringBuilder2);
handler.AppendLiteral("@@ -");
handler.AppendFormatted(item3 + 1);
handler.AppendLiteral(",");
handler.AppendFormatted(item4 - item3);
handler.AppendLiteral(" +");
handler.AppendFormatted(item5 + 1);
handler.AppendLiteral(",");
handler.AppendFormatted(item6 - item5);
handler.AppendLiteral(" @@");
stringBuilder5.AppendLine(ref handler);
for (int k = item3; k < item4; k++)
{
stringBuilder2 = stringBuilder;
StringBuilder stringBuilder6 = stringBuilder2;
handler = new StringBuilder.AppendInterpolatedStringHandler(1, 1, stringBuilder2);
handler.AppendLiteral("-");
handler.AppendFormatted(original[k]);
stringBuilder6.AppendLine(ref handler);
}
for (int l = item5; l < item6; l++)
{
stringBuilder2 = stringBuilder;
StringBuilder stringBuilder7 = stringBuilder2;
handler = new StringBuilder.AppendInterpolatedStringHandler(1, 1, stringBuilder2);
handler.AppendLiteral("+");
handler.AppendFormatted(modified[l]);
stringBuilder7.AppendLine(ref handler);
}
}
return stringBuilder.ToString();
}
private static List<string> ComputeLcs(string[] a, string[] b)
{
int num = a.Length;
int num2 = b.Length;
if ((long)num * (long)num2 > 10000000)
{
return new List<string>();
}
int[,] array = new int[num + 1, num2 + 1];
for (int num3 = num - 1; num3 >= 0; num3--)
{
for (int num4 = num2 - 1; num4 >= 0; num4--)
{
array[num3, num4] = ((a[num3] == b[num4]) ? (array[num3 + 1, num4 + 1] + 1) : Math.Max(array[num3 + 1, num4], array[num3, num4 + 1]));
}
}
List<string> list = new List<string>();
int num5 = 0;
int num6 = 0;
while (num5 < num && num6 < num2)
{
if (a[num5] == b[num6])
{
list.Add(a[num5]);
num5++;
num6++;
}
else if (array[num5 + 1, num6] >= array[num5, num6 + 1])
{
num5++;
}
else
{
num6++;
}
}
return list;
}
}