| 1 | #nullable enable |
| 2 | |
| 3 | using System.Text.Json; |
| 4 | |
| 5 | namespace CascadeIDE.Services; |
| 6 | |
| 7 | /// <summary> |
| 8 | /// Имена MCP-прокси (<c>ide_*</c>), которые один-в-один дублируются в MAF-агента как именованные <see cref="Microsoft.Extensions.AI.AIFunction"/> |
| 9 | /// Параметры тех же типов что у MCP через JSON-объект (схему даёт MCP-каталог). |
| 10 | /// </summary> |
| 11 | internal static class CascadeIdeMafPromotedTools |
| 12 | { |
| 13 | /// <summary>Точное совпадение с полем <see cref="IdeMcpToolCatalog.BuildTools"/>/<c>Name</c>.</summary> |
| 14 | internal static IEnumerable<string> GetPromotedMcpToolNames(bool includeDebugBuildOnlyExtras) |
| 15 | { |
| 16 | foreach (var n in NamesCore) |
| 17 | yield return n; |
| 18 | |
| 19 | if (includeDebugBuildOnlyExtras) |
| 20 | { |
| 21 | foreach (var n in DebugOnlyExtras) |
| 22 | yield return n; |
| 23 | } |
| 24 | } |
| 25 | |
| 26 | private static readonly string[] NamesCore = |
| 27 | [ |
| 28 | "ide_get_ide_state", |
| 29 | "ide_get_editor_state", |
| 30 | "ide_get_editor_content_range", |
| 31 | "ide_get_solution_info", |
| 32 | "ide_get_solution_files", |
| 33 | "ide_get_build_output", |
| 34 | "ide_build", |
| 35 | "ide_run_tests", |
| 36 | "ide_run_affected_tests", |
| 37 | "ide_open_file", |
| 38 | "ide_load_solution", |
| 39 | "ide_go_to_position", |
| 40 | "ide_select", |
| 41 | "ide_apply_edit", |
| 42 | "ide_set_breakpoint", |
| 43 | "ide_remove_breakpoint", |
| 44 | "ide_get_current_file_diagnostics", |
| 45 | "ide_ping", |
| 46 | "ide_search_workspace_text", |
| 47 | "ide_search_web_public_query", |
| 48 | "ide_fetch_web_public_url", |
| 49 | "ide_read_agent_notes", |
| 50 | "ide_append_agent_notes", |
| 51 | "ide_search_agent_notes", |
| 52 | "ide_get_ui_layout", |
| 53 | "ide_route_context", |
| 54 | "ide_read_hot_context", |
| 55 | ]; |
| 56 | |
| 57 | private static readonly string[] DebugOnlyExtras = |
| 58 | [ |
| 59 | "ide_get_debug_snapshot", |
| 60 | "ide_debug_launch", |
| 61 | "ide_debug_ping", |
| 62 | "ide_list_tools", |
| 63 | ]; |
| 64 | |
| 65 | internal static HashSet<string> BuildLookup(bool includeDebugBuildOnlyExtras) => |
| 66 | new(GetPromotedMcpToolNames(includeDebugBuildOnlyExtras), StringComparer.Ordinal); |
| 67 | |
| 68 | /// <summary>Маппинг как в <see cref="IdeMcpServer"/> (имя тула MCP → command_id).</summary> |
| 69 | internal static bool TryMcpProxyToolToCommandId(string mcpToolName, out string commandId) |
| 70 | { |
| 71 | commandId = ""; |
| 72 | if (string.IsNullOrEmpty(mcpToolName)) |
| 73 | return false; |
| 74 | if (!mcpToolName.StartsWith("ide_", StringComparison.Ordinal)) |
| 75 | return false; |
| 76 | |
| 77 | commandId = mcpToolName["ide_".Length..]; |
| 78 | if (string.Equals(commandId, IdeCommands.Build, StringComparison.Ordinal)) |
| 79 | commandId = IdeCommands.BuildStructured; |
| 80 | return true; |
| 81 | } |
| 82 | |
| 83 | internal static IReadOnlyDictionary<string, JsonElement>? JsonArgsToDict(JsonElement arguments) |
| 84 | { |
| 85 | var kind = arguments.ValueKind; |
| 86 | if (kind is JsonValueKind.Undefined or JsonValueKind.Null) |
| 87 | return null; |
| 88 | if (kind != JsonValueKind.Object) |
| 89 | return null; |
| 90 | var dict = new Dictionary<string, JsonElement>(StringComparer.Ordinal); |
| 91 | foreach (var p in arguments.EnumerateObject()) |
| 92 | dict[p.Name] = p.Value; |
| 93 | return dict; |
| 94 | } |
| 95 | } |
| 96 | |