Files

420 lines
13 KiB
C#

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Text.Encodings.Web;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
namespace AxCopilot.Services.Agent;
public class PlaybookTool : IAgentTool
{
private class PlaybookData
{
public int Id { get; set; }
public string Name { get; set; } = "";
public string Description { get; set; } = "";
public List<string> Steps { get; set; } = new List<string>();
public List<string> ToolsUsed { get; set; } = new List<string>();
public string CreatedAt { get; set; } = "";
}
public string Name => "playbook";
public string Description => "Save, list, describe, or delete execution playbooks. A playbook captures a successful task workflow for reuse.\n- action=\"save\": Save a new playbook (name, description, steps required)\n- action=\"list\": List all saved playbooks\n- action=\"describe\": Show full playbook details (id required)\n- action=\"delete\": Delete a playbook (id required)";
public ToolParameterSchema Parameters
{
get
{
ToolParameterSchema toolParameterSchema = new ToolParameterSchema();
Dictionary<string, ToolProperty> dictionary = new Dictionary<string, ToolProperty>();
ToolProperty obj = new ToolProperty
{
Type = "string",
Description = "save | list | describe | delete"
};
int num = 4;
List<string> list = new List<string>(num);
CollectionsMarshal.SetCount(list, num);
Span<string> span = CollectionsMarshal.AsSpan(list);
span[0] = "save";
span[1] = "list";
span[2] = "describe";
span[3] = "delete";
obj.Enum = list;
dictionary["action"] = obj;
dictionary["name"] = new ToolProperty
{
Type = "string",
Description = "Playbook name (for save)"
};
dictionary["description"] = new ToolProperty
{
Type = "string",
Description = "What this playbook does (for save)"
};
dictionary["steps"] = new ToolProperty
{
Type = "array",
Description = "List of step descriptions (for save)",
Items = new ToolProperty
{
Type = "string",
Description = "Step description"
}
};
dictionary["tools_used"] = new ToolProperty
{
Type = "array",
Description = "List of tool names used in this workflow (for save)",
Items = new ToolProperty
{
Type = "string",
Description = "Tool name"
}
};
dictionary["id"] = new ToolProperty
{
Type = "integer",
Description = "Playbook ID (for describe/delete)"
};
toolParameterSchema.Properties = dictionary;
num = 1;
List<string> list2 = new List<string>(num);
CollectionsMarshal.SetCount(list2, num);
CollectionsMarshal.AsSpan(list2)[0] = "action";
toolParameterSchema.Required = list2;
return toolParameterSchema;
}
}
public async Task<ToolResult> ExecuteAsync(JsonElement args, AgentContext context, CancellationToken ct = default(CancellationToken))
{
if (!args.TryGetProperty("action", out var actionEl))
{
return ToolResult.Fail("action이 필요합니다.");
}
string action = actionEl.GetString() ?? "";
if (string.IsNullOrEmpty(context.WorkFolder))
{
return ToolResult.Fail("작업 폴더가 설정되지 않았습니다.");
}
string playbookDir = Path.Combine(context.WorkFolder, ".ax", "playbooks");
if (1 == 0)
{
}
ToolResult result = action switch
{
"save" => await SavePlaybook(args, playbookDir, ct),
"list" => ListPlaybooks(playbookDir),
"describe" => await DescribePlaybook(args, playbookDir, ct),
"delete" => DeletePlaybook(args, playbookDir),
_ => ToolResult.Fail("알 수 없는 액션: " + action + ". save | list | describe | delete 중 선택하세요."),
};
if (1 == 0)
{
}
return result;
}
private static async Task<ToolResult> SavePlaybook(JsonElement args, string playbookDir, CancellationToken ct)
{
JsonElement n;
string name = (args.TryGetProperty("name", out n) ? (n.GetString() ?? "") : "");
JsonElement d;
string description = (args.TryGetProperty("description", out d) ? (d.GetString() ?? "") : "");
if (string.IsNullOrWhiteSpace(name))
{
return ToolResult.Fail("플레이북 name이 필요합니다.");
}
if (string.IsNullOrWhiteSpace(description))
{
return ToolResult.Fail("플레이북 description이 필요합니다.");
}
List<string> steps = new List<string>();
if (args.TryGetProperty("steps", out var stepsEl) && stepsEl.ValueKind == JsonValueKind.Array)
{
foreach (JsonElement item in stepsEl.EnumerateArray())
{
string s = item.GetString();
if (!string.IsNullOrWhiteSpace(s))
{
steps.Add(s);
}
}
}
if (steps.Count == 0)
{
return ToolResult.Fail("최소 1개 이상의 step이 필요합니다.");
}
List<string> toolsUsed = new List<string>();
if (args.TryGetProperty("tools_used", out var toolsEl) && toolsEl.ValueKind == JsonValueKind.Array)
{
foreach (JsonElement item2 in toolsEl.EnumerateArray())
{
string t = item2.GetString();
if (!string.IsNullOrWhiteSpace(t))
{
toolsUsed.Add(t);
}
}
}
try
{
Directory.CreateDirectory(playbookDir);
int nextId = GetNextId(playbookDir);
string safeName = string.Join("_", name.Split(Path.GetInvalidFileNameChars()));
string fileName = $"{nextId}_{safeName}.json";
PlaybookData playbook = new PlaybookData
{
Id = nextId,
Name = name,
Description = description,
Steps = steps,
ToolsUsed = toolsUsed,
CreatedAt = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")
};
string json = JsonSerializer.Serialize(playbook, new JsonSerializerOptions
{
WriteIndented = true,
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping
});
string filePath = Path.Combine(playbookDir, fileName);
await File.WriteAllTextAsync(filePath, json, ct);
return ToolResult.Ok($"플레이북 저장 완료: [{nextId}] {name}\n단계 수: {steps.Count}, 사용 도구: {toolsUsed.Count}개");
}
catch (Exception ex)
{
return ToolResult.Fail("플레이북 저장 오류: " + ex.Message);
}
}
private static ToolResult ListPlaybooks(string playbookDir)
{
if (!Directory.Exists(playbookDir))
{
return ToolResult.Ok("저장된 플레이북이 없습니다.");
}
List<string> list = (from f in Directory.GetFiles(playbookDir, "*.json")
orderby f
select f).ToList();
if (list.Count == 0)
{
return ToolResult.Ok("저장된 플레이북이 없습니다.");
}
StringBuilder stringBuilder = new StringBuilder();
StringBuilder stringBuilder2 = stringBuilder;
StringBuilder stringBuilder3 = stringBuilder2;
StringBuilder.AppendInterpolatedStringHandler handler = new StringBuilder.AppendInterpolatedStringHandler(7, 1, stringBuilder2);
handler.AppendLiteral("플레이북 ");
handler.AppendFormatted(list.Count);
handler.AppendLiteral("개:");
stringBuilder3.AppendLine(ref handler);
foreach (string item in list)
{
try
{
string json = File.ReadAllText(item);
PlaybookData playbookData = JsonSerializer.Deserialize<PlaybookData>(json);
if (playbookData != null)
{
stringBuilder2 = stringBuilder;
StringBuilder stringBuilder4 = stringBuilder2;
handler = new StringBuilder.AppendInterpolatedStringHandler(15, 5, stringBuilder2);
handler.AppendLiteral(" [");
handler.AppendFormatted(playbookData.Id);
handler.AppendLiteral("] ");
handler.AppendFormatted(playbookData.Name);
handler.AppendLiteral(" — ");
handler.AppendFormatted(playbookData.Description);
handler.AppendLiteral(" (");
handler.AppendFormatted(playbookData.Steps.Count);
handler.AppendLiteral("단계, ");
handler.AppendFormatted(playbookData.CreatedAt);
handler.AppendLiteral(")");
stringBuilder4.AppendLine(ref handler);
}
}
catch
{
stringBuilder2 = stringBuilder;
StringBuilder stringBuilder5 = stringBuilder2;
handler = new StringBuilder.AppendInterpolatedStringHandler(14, 1, stringBuilder2);
handler.AppendLiteral(" [?] ");
handler.AppendFormatted(Path.GetFileName(item));
handler.AppendLiteral(" — 파싱 오류");
stringBuilder5.AppendLine(ref handler);
}
}
return ToolResult.Ok(stringBuilder.ToString());
}
private static async Task<ToolResult> DescribePlaybook(JsonElement args, string playbookDir, CancellationToken ct)
{
if (!args.TryGetProperty("id", out var idEl))
{
return ToolResult.Fail("플레이북 id가 필요합니다.");
}
int parsed;
int id = ((idEl.ValueKind == JsonValueKind.Number) ? idEl.GetInt32() : (int.TryParse(idEl.GetString(), out parsed) ? parsed : (-1)));
PlaybookData playbook = await FindPlaybookById(playbookDir, id, ct);
if (playbook == null)
{
return ToolResult.Fail($"ID {id}의 플레이북을 찾을 수 없습니다.");
}
StringBuilder sb = new StringBuilder();
StringBuilder stringBuilder = sb;
StringBuilder stringBuilder2 = stringBuilder;
StringBuilder.AppendInterpolatedStringHandler handler = new StringBuilder.AppendInterpolatedStringHandler(13, 2, stringBuilder);
handler.AppendLiteral("플레이북: ");
handler.AppendFormatted(playbook.Name);
handler.AppendLiteral(" (ID: ");
handler.AppendFormatted(playbook.Id);
handler.AppendLiteral(")");
stringBuilder2.AppendLine(ref handler);
stringBuilder = sb;
StringBuilder stringBuilder3 = stringBuilder;
handler = new StringBuilder.AppendInterpolatedStringHandler(4, 1, stringBuilder);
handler.AppendLiteral("설명: ");
handler.AppendFormatted(playbook.Description);
stringBuilder3.AppendLine(ref handler);
stringBuilder = sb;
StringBuilder stringBuilder4 = stringBuilder;
handler = new StringBuilder.AppendInterpolatedStringHandler(5, 1, stringBuilder);
handler.AppendLiteral("생성일: ");
handler.AppendFormatted(playbook.CreatedAt);
stringBuilder4.AppendLine(ref handler);
sb.AppendLine();
sb.AppendLine("단계:");
for (int i = 0; i < playbook.Steps.Count; i++)
{
stringBuilder = sb;
StringBuilder stringBuilder5 = stringBuilder;
handler = new StringBuilder.AppendInterpolatedStringHandler(4, 2, stringBuilder);
handler.AppendLiteral(" ");
handler.AppendFormatted(i + 1);
handler.AppendLiteral(". ");
handler.AppendFormatted(playbook.Steps[i]);
stringBuilder5.AppendLine(ref handler);
}
if (playbook.ToolsUsed.Count > 0)
{
sb.AppendLine();
stringBuilder = sb;
StringBuilder stringBuilder6 = stringBuilder;
handler = new StringBuilder.AppendInterpolatedStringHandler(7, 1, stringBuilder);
handler.AppendLiteral("사용 도구: ");
handler.AppendFormatted(string.Join(", ", playbook.ToolsUsed));
stringBuilder6.AppendLine(ref handler);
}
return ToolResult.Ok(sb.ToString());
}
private static ToolResult DeletePlaybook(JsonElement args, string playbookDir)
{
if (!args.TryGetProperty("id", out var value))
{
return ToolResult.Fail("삭제할 플레이북 id가 필요합니다.");
}
int result;
int num = ((value.ValueKind == JsonValueKind.Number) ? value.GetInt32() : (int.TryParse(value.GetString(), out result) ? result : (-1)));
if (!Directory.Exists(playbookDir))
{
return ToolResult.Fail("저장된 플레이북이 없습니다.");
}
string[] files = Directory.GetFiles(playbookDir, $"{num}_*.json");
if (files.Length == 0)
{
string[] files2 = Directory.GetFiles(playbookDir, "*.json");
foreach (string path in files2)
{
try
{
string json = File.ReadAllText(path);
PlaybookData playbookData = JsonSerializer.Deserialize<PlaybookData>(json);
if (playbookData != null && playbookData.Id == num)
{
string name = playbookData.Name;
File.Delete(path);
return ToolResult.Ok($"플레이북 삭제됨: [{num}] {name}");
}
}
catch
{
}
}
return ToolResult.Fail($"ID {num}의 플레이북을 찾을 수 없습니다.");
}
try
{
string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(files[0]);
File.Delete(files[0]);
return ToolResult.Ok("플레이북 삭제됨: " + fileNameWithoutExtension);
}
catch (Exception ex)
{
return ToolResult.Fail("플레이북 삭제 오류: " + ex.Message);
}
}
private static int GetNextId(string playbookDir)
{
if (!Directory.Exists(playbookDir))
{
return 1;
}
int num = 0;
string[] files = Directory.GetFiles(playbookDir, "*.json");
foreach (string path in files)
{
try
{
string json = File.ReadAllText(path);
PlaybookData playbookData = JsonSerializer.Deserialize<PlaybookData>(json);
if (playbookData != null && playbookData.Id > num)
{
num = playbookData.Id;
}
}
catch
{
}
}
return num + 1;
}
private static async Task<PlaybookData?> FindPlaybookById(string playbookDir, int id, CancellationToken ct)
{
if (!Directory.Exists(playbookDir))
{
return null;
}
string[] files = Directory.GetFiles(playbookDir, "*.json");
foreach (string file in files)
{
try
{
PlaybookData pb = JsonSerializer.Deserialize<PlaybookData>(await File.ReadAllTextAsync(file, ct));
if (pb != null && pb.Id == id)
{
return pb;
}
}
catch
{
}
}
return null;
}
}