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 Steps { get; set; } = new List(); public List ToolsUsed { get; set; } = new List(); 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 dictionary = new Dictionary(); ToolProperty obj = new ToolProperty { Type = "string", Description = "save | list | describe | delete" }; int num = 4; List list = new List(num); CollectionsMarshal.SetCount(list, num); Span 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 list2 = new List(num); CollectionsMarshal.SetCount(list2, num); CollectionsMarshal.AsSpan(list2)[0] = "action"; toolParameterSchema.Required = list2; return toolParameterSchema; } } public async Task 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 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 steps = new List(); 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 toolsUsed = new List(); 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 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(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 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(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(json); if (playbookData != null && playbookData.Id > num) { num = playbookData.Id; } } catch { } } return num + 1; } private static async Task 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(await File.ReadAllTextAsync(file, ct)); if (pb != null && pb.Id == id) { return pb; } } catch { } } return null; } }