using System.Collections.Generic; using System.Text; using System.Text.Json; using System.Threading; using System.Threading.Tasks; using System.Windows; namespace AxCopilot.Services.Agent; public class CodeSearchTool : IAgentTool { private static CodeIndexService? _indexService; private static string _lastWorkFolder = ""; public string Name => "search_codebase"; public string Description => "코드베이스를 시맨틱 검색합니다. 자연어 질문으로 관련 있는 코드를 찾습니다.\n예: '사용자 인증 로직', '데이터베이스 연결 설정', '에러 핸들링 패턴'\n인덱스는 영속 저장되어 변경된 파일만 증분 업데이트합니다."; public ToolParameterSchema Parameters => new ToolParameterSchema { Properties = new Dictionary { ["query"] = new ToolProperty { Type = "string", Description = "검색할 내용 (자연어 또는 키워드)" }, ["max_results"] = new ToolProperty { Type = "integer", Description = "최대 결과 수 (기본 5)" }, ["reindex"] = new ToolProperty { Type = "boolean", Description = "true면 기존 인덱스를 버리고 전체 재인덱싱 (기본 false)" } }, Required = new List { "query" } }; public async Task ExecuteAsync(JsonElement args, AgentContext context, CancellationToken ct = default(CancellationToken)) { if (!((Application.Current as App)?.SettingsService?.Settings.Llm.Code.EnableCodeIndex ?? true)) { return ToolResult.Ok("코드 시맨틱 검색이 비활성 상태입니다. 설정 → AX Agent → 코드에서 활성화하세요."); } JsonElement q; string query = (args.TryGetProperty("query", out q) ? (q.GetString() ?? "") : ""); JsonElement m; int maxResults = (args.TryGetProperty("max_results", out m) ? m.GetInt32() : 5); JsonElement ri; bool reindex = args.TryGetProperty("reindex", out ri) && ri.GetBoolean(); if (string.IsNullOrWhiteSpace(query)) { return ToolResult.Fail("query가 필요합니다."); } if (string.IsNullOrEmpty(context.WorkFolder)) { return ToolResult.Fail("작업 폴더가 설정되어 있지 않습니다."); } if (_indexService == null || _lastWorkFolder != context.WorkFolder || reindex) { _indexService?.Dispose(); _indexService = new CodeIndexService(); _lastWorkFolder = context.WorkFolder; if (!reindex) { _indexService.TryLoadExisting(context.WorkFolder); } } if (!_indexService.IsIndexed || reindex) { await _indexService.IndexAsync(context.WorkFolder, ct); if (!_indexService.IsIndexed) { return ToolResult.Fail("코드 인덱싱에 실패했습니다."); } } List results = _indexService.Search(query, maxResults); if (results.Count == 0) { return ToolResult.Ok("'" + query + "'에 대한 관련 코드를 찾지 못했습니다. 다른 키워드로 검색해보세요."); } StringBuilder sb = new StringBuilder(); StringBuilder stringBuilder = sb; StringBuilder stringBuilder2 = stringBuilder; StringBuilder.AppendInterpolatedStringHandler handler = new StringBuilder.AppendInterpolatedStringHandler(24, 3, stringBuilder); handler.AppendLiteral("'"); handler.AppendFormatted(query); handler.AppendLiteral("' 검색 결과 ("); handler.AppendFormatted(results.Count); handler.AppendLiteral("개, 인덱스 "); handler.AppendFormatted(_indexService.ChunkCount); handler.AppendLiteral("개 청크):\n"); stringBuilder2.AppendLine(ref handler); foreach (SearchResult r in results) { stringBuilder = sb; StringBuilder stringBuilder3 = stringBuilder; handler = new StringBuilder.AppendInterpolatedStringHandler(20, 4, stringBuilder); handler.AppendLiteral("\ud83d\udcc1 "); handler.AppendFormatted(r.FilePath); handler.AppendLiteral(" (line "); handler.AppendFormatted(r.StartLine); handler.AppendLiteral("-"); handler.AppendFormatted(r.EndLine); handler.AppendLiteral(", score "); handler.AppendFormatted(r.Score, "F3"); handler.AppendLiteral(")"); stringBuilder3.AppendLine(ref handler); sb.AppendLine("```"); sb.AppendLine(r.Preview); sb.AppendLine("```"); sb.AppendLine(); } return ToolResult.Ok(sb.ToString()); } public static void InvalidateIndex() { _indexService?.Dispose(); _indexService = null; _lastWorkFolder = ""; } }