Files
AX-Copilot-Codex/src/AxCopilot/Services/Agent/ToolRegistry.cs
lacvet 946c31e275 AX Agent ?? ?? ??? MCP ?? ??? ???? ??? ??? ??
- MCP ?? ?????? synthetic skill? ???? McpSkillCatalog? ???? ToolRegistry ?? snapshot ?? ??? ???
- managed/user/additional/project/plugin/mcp/legacy ?? source ??, plugin-only ??, source? inline shell trust boundary? SkillService/AppSettings/Settings UI? ???
- SlashCommandCatalog? ChatWindow?? builtin command? skill? ???? ???? ??? ?? ? ????? dedupe?? MCP ???? ? synthetic skill ?? ??? SkillGallery/AgentSettings? ???
- README.md? docs/DEVELOPMENT.md? 2026-04-14 19:13 (KST) ?? ?? ??? ?? ??? ???
- ??: dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\\verify_phase4\\ -p:IntermediateOutputPath=obj\\verify_phase4\\ (?? 0, ?? 0)
- ??: dotnet test src/AxCopilot.Tests/AxCopilot.Tests.csproj -c Release -v minimal --filter "SkillServiceRuntimePolicyTests|SlashCommandCatalogTests|McpSkillCatalogTests" -p:OutputPath=bin\\verify_phase4_tests\\ -p:IntermediateOutputPath=obj\\verify_phase4_tests\\ (?? 17, ?? WorkspaceContextGeneratorTests.cs nullable ?? 1? ??)
2026-04-14 19:15:12 +09:00

266 lines
10 KiB
C#

namespace AxCopilot.Services.Agent;
/// <summary>
/// 사용 가능한 에이전트 도구/스킬을 관리하는 레지스트리.
/// 도구 목록을 LLM function calling에 전달하고, 이름으로 도구를 찾습니다.
/// </summary>
public class ToolRegistry : IDisposable
{
private readonly Dictionary<string, IAgentTool> _tools = new(StringComparer.OrdinalIgnoreCase);
private readonly List<IDisposable> _ownedResources = new();
private readonly Dictionary<string, McpClientService> _mcpClients = new(StringComparer.OrdinalIgnoreCase);
/// <summary>등록된 모든 도구 목록.</summary>
public IReadOnlyCollection<IAgentTool> All => _tools.Values;
/// <summary>도구를 이름으로 찾습니다.</summary>
public IAgentTool? Get(string name) =>
_tools.TryGetValue(AgentToolCatalog.Canonicalize(name), out var tool) ? tool : null;
/// <summary>도구를 등록합니다.</summary>
public void Register(IAgentTool tool) => _tools[tool.Name] = tool;
public IReadOnlyCollection<McpClientService> GetMcpClients() => _mcpClients.Values.ToList().AsReadOnly();
public async Task<int> RegisterMcpToolsAsync(IEnumerable<Models.McpServerEntry>? servers, CancellationToken ct = default)
{
if (servers == null) return 0;
var registered = 0;
foreach (var server in servers)
{
if (server == null || !server.Enabled) continue;
if (!string.Equals(server.Transport, "stdio", StringComparison.OrdinalIgnoreCase))
{
LogService.Warn($"MCP '{server.Name}': unsupported transport '{server.Transport}'.");
continue;
}
var client = new McpClientService(server);
if (!await client.ConnectAsync(ct).ConfigureAwait(false))
{
client.Dispose();
continue;
}
_ownedResources.Add(client);
_mcpClients[server.Name] = client;
foreach (var def in client.Tools)
{
Register(new McpTool(client, def));
registered++;
}
}
McpSkillCatalog.RefreshFromClients(_mcpClients.Values);
return registered;
}
/// <summary>비활성 도구를 제외한 활성 도구 목록을 반환합니다.</summary>
public IReadOnlyCollection<IAgentTool> GetActiveTools(IEnumerable<string>? disabledNames = null)
{
if (disabledNames == null) return All;
var disabled = new HashSet<string>(AgentToolCatalog.CanonicalizeMany(disabledNames), StringComparer.OrdinalIgnoreCase);
if (disabled.Count == 0) return All;
return OrderToolsForExposure(_tools.Values.Where(t => !disabled.Contains(t.Name)))
.ToList()
.AsReadOnly();
}
/// <summary>비활성 도구를 제외하고 현재 탭에 해당하는 도구만 반환합니다.</summary>
public IReadOnlyCollection<IAgentTool> GetActiveToolsForTab(string activeTab, IEnumerable<string>? disabledNames = null)
{
var disabled = disabledNames != null
? new HashSet<string>(AgentToolCatalog.CanonicalizeMany(disabledNames), StringComparer.OrdinalIgnoreCase)
: null;
return OrderToolsForExposure(_tools.Values.Where(t =>
{
if (disabled != null && disabled.Contains(t.Name)) return false;
return IsToolAvailableForTab(t, activeTab);
})).ToList().AsReadOnly();
}
private static IEnumerable<IAgentTool> OrderToolsForExposure(IEnumerable<IAgentTool> tools)
{
return tools
.OrderBy(GetToolExposureBucket)
.ThenBy(t => t.Name, StringComparer.OrdinalIgnoreCase);
}
private static int GetToolExposureBucket(IAgentTool tool)
{
return AgentToolCatalog.GetExposureBucket(tool.Name);
}
/// <summary>도구가 해당 탭에서 사용 가능한지 확인합니다.</summary>
private static bool IsToolAvailableForTab(IAgentTool tool, string activeTab)
{
var category = ResolveTabCategory(tool);
if (string.IsNullOrEmpty(category)) return true; // null = 모든 탭
// 쉼표 구분 복수 탭: "Cowork,Code"
foreach (var part in category.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries))
{
if (string.Equals(part, activeTab, StringComparison.OrdinalIgnoreCase))
return true;
}
return false;
}
/// <summary>
/// 도구별 탭 카테고리 오버라이드.
/// IAgentTool.TabCategory가 null인 도구는 이 맵을 참조합니다.
/// 키: 도구 이름, 값: 허용 탭 (쉼표 구분). 맵에 없으면 = 모든 탭.
/// </summary>
/// <summary>도구의 실질 탭 카테고리를 결정합니다 (IAgentTool.TabCategory → 오버라이드 맵 순).</summary>
private static string? ResolveTabCategory(IAgentTool tool)
{
// 도구 자체에 TabCategory가 명시되어 있으면 우선
if (!string.IsNullOrEmpty(tool.TabCategory))
return tool.TabCategory;
return AgentToolCatalog.GetTabCategory(tool.Name);
}
/// <summary>IDisposable 도구를 모두 해제합니다.</summary>
public void Dispose()
{
foreach (var tool in _tools.Values)
{
if (tool is IDisposable disposable)
disposable.Dispose();
}
foreach (var resource in _ownedResources)
resource.Dispose();
_ownedResources.Clear();
_mcpClients.Clear();
_tools.Clear();
}
/// <summary>기본 도구 + 내장 스킬을 모두 등록한 레지스트리를 생성합니다.</summary>
public static ToolRegistry CreateDefault()
{
var registry = new ToolRegistry();
// 기본 도구 (파일/검색/프로세스)
registry.Register(new FileReadTool());
registry.Register(new FileWriteTool());
registry.Register(new FileEditTool());
registry.Register(new GlobTool());
registry.Register(new GrepTool());
registry.Register(new ProcessTool());
registry.Register(new FolderMapTool());
registry.Register(new DocumentReaderTool());
// 내장 스킬 (문서 생성)
registry.Register(new ExcelSkill());
registry.Register(new DocxSkill());
registry.Register(new CsvSkill());
registry.Register(new MarkdownSkill());
registry.Register(new HtmlSkill());
registry.Register(new ChartSkill());
registry.Register(new BatchSkill());
registry.Register(new PptxSkill());
// 멀티패스 문서 엔진
registry.Register(new DocumentPlannerTool());
registry.Register(new DocumentAssemblerTool());
// 문서 품질 검증 & 포맷 변환
registry.Register(new DocumentReviewTool());
registry.Register(new FormatConvertTool());
// Code 탭: 개발 환경 감지 & 빌드/테스트 & Git
registry.Register(new DevEnvDetectTool());
registry.Register(new BuildRunTool());
registry.Register(new GitTool());
registry.Register(new LspTool());
registry.Register(new SubAgentTool());
registry.Register(new SpawnAgentsTool());
registry.Register(new WaitAgentsTool());
registry.Register(new CodeSearchTool());
registry.Register(new TestLoopTool());
registry.Register(new ToolSearchTool(() => registry.All));
// 코드 리뷰 + 프로젝트 규칙
registry.Register(new CodeReviewTool());
registry.Register(new ProjectRuleTool());
// 스킬 시스템
registry.Register(new SkillManagerTool());
// 에이전트 메모리
registry.Register(new MemoryTool());
// 데이터 처리 + 시스템 유틸리티
registry.Register(new JsonTool());
registry.Register(new RegexTool());
registry.Register(new DiffTool());
registry.Register(new ClipboardTool());
registry.Register(new NotifyTool());
registry.Register(new EnvTool());
registry.Register(new ZipTool());
registry.Register(new HttpTool());
registry.Register(new SqlTool());
registry.Register(new Base64Tool());
registry.Register(new HashTool());
registry.Register(new DateTimeTool());
// 코드 품질
registry.Register(new SnippetRunnerTool());
// 데이터 분석 + 문서 자동화
registry.Register(new DataPivotTool());
registry.Register(new TemplateRenderTool());
registry.Register(new TextSummarizeTool());
// 파일 모니터링 + 이미지 분석
registry.Register(new FileWatchTool());
registry.Register(new ImageAnalyzeTool());
// 파일 관리 + 메타데이터 + 멀티리드
registry.Register(new FileManageTool());
registry.Register(new FileInfoTool());
registry.Register(new MultiReadTool());
// 사용자 질문
registry.Register(new UserAskTool());
// MCP 리소스
registry.Register(new McpListResourcesTool(() => registry.GetMcpClients()));
registry.Register(new McpReadResourceTool(() => registry.GetMcpClients()));
// 외부 열기 + 수학 + XML + 인코딩
registry.Register(new OpenExternalTool());
registry.Register(new MathTool());
registry.Register(new XmlTool());
registry.Register(new EncodingTool());
// 태스크 추적
registry.Register(new TaskTrackerTool());
registry.Register(new TodoWriteTool());
registry.Register(new TaskCreateTool());
registry.Register(new TaskGetTool());
registry.Register(new TaskListTool());
registry.Register(new TaskUpdateTool());
registry.Register(new TaskStopTool());
registry.Register(new TaskOutputTool());
registry.Register(new EnterWorktreeTool());
registry.Register(new ExitWorktreeTool());
registry.Register(new TeamCreateTool());
registry.Register(new TeamDeleteTool());
registry.Register(new CronCreateTool());
registry.Register(new CronDeleteTool());
registry.Register(new CronListTool());
// 워크플로우 도구
registry.Register(new SuggestActionsTool());
registry.Register(new DiffPreviewTool());
registry.Register(new CheckpointTool());
registry.Register(new PlaybookTool());
return registry;
}
}