Forge
csharp6dd9109c
1using System.Diagnostics.CodeAnalysis;
2using CascadeIDE.Contracts;
3using CascadeIDE.Models;
4
5namespace CascadeIDE.Features.IdeMcp.Application;
6
7/// <summary>
8/// Application-level helpers for IDE MCP UI automation actions.
9/// Centralizes common provider guards and normalized defaults.
10/// </summary>
11[ApplicationOrchestrator]
12public static class IdeMcpUiAutomationOrchestrator
13{
14 public static string DefaultJsonObject() => "{}";
15
16 public static string DefaultProviderMissingMessage() => "No provider.";
17
18 public static string NoLayoutProviderMessage() => "No layout provider.";
19
20 public static string AddControlDisabledMessage() => "AddControl disabled.";
21
22 /// <summary>Default window title when MCP passes no title for markdown preview.</summary>
23 public static string DefaultMarkdownPreviewTitle() => "Превью";
24
25 public static string ResolveMarkdownPreviewTitle(string? title) =>
26 title ?? DefaultMarkdownPreviewTitle();
27
28 public static string InvalidMessageIdJson() => """{"ok":false,"error":"invalid_message_id"}""";
29
30 public static string NormalizeJsonInput(string? json) =>
31 string.IsNullOrEmpty(json) ? "{}" : json;
32
33 public static string NormalizeTextInput(string? text) =>
34 text ?? "";
35
36 public static bool ShouldSkipRemoveBreakpoint(string? filePath, int line) =>
37 string.IsNullOrEmpty(filePath) || line < 1;
38
39 /// <summary>После guard — нормализованный путь для <c>BreakpointsFileService</c> (MCP <c>remove_breakpoint</c>).</summary>
40 public static bool TryGetRemoveBreakpointNormalizedPath(
41 string? filePath,
42 int line,
43 [NotNullWhen(true)] out string? normalizedPath)
44 {
45 normalizedPath = null;
46 if (ShouldSkipRemoveBreakpoint(filePath, line))
47 return false;
48 normalizedPath = NormalizeBreakpointFilePath(filePath!);
49 return true;
50 }
51
52 /// <summary>Клик по gutter / toggle — нет строки или нет текущего файла.</summary>
53 public static bool ShouldSkipToggleBreakpointInEditor(int line, string? currentFilePath) =>
54 line < 1 || string.IsNullOrEmpty(currentFilePath);
55
56 public static string NormalizeBreakpointFilePath(string filePath) =>
57 CanonicalFilePath.Normalize(filePath);
58
59 /// <summary>MCP/чат: результат со строкой JSON, только на UI-потоке.</summary>
60 public static Task<string> InvokeStringResultOnUiAsync(IUiScheduler ui, Func<string> onUi) =>
61 ui.InvokeAsync(onUi);
62
63 /// <summary>MCP <c>edit_chat_assistant_message</c>: разбор id и нормализация текста на UI-потоке.</summary>
64 public static Task<string> EditChatAssistantMessageOnUiAsync(
65 IUiScheduler ui,
66 string messageId,
67 string newContent,
68 string? reason,
69 Func<Guid, string, string?, string> editOnUi) =>
70 ui.InvokeAsync(() =>
71 {
72 if (!ChatMessageId.TryParse(messageId, out var id))
73 return InvalidMessageIdJson();
74 return editOnUi(id, NormalizeTextInput(newContent), reason);
75 });
76
77 public static async Task<string> InvokeJsonProviderOrDefaultAsync(
78 IUiScheduler ui,
79 Func<string?>? provider)
80 {
81 if (provider is null)
82 return DefaultJsonObject();
83 return await ui.InvokeAsync(() => provider() ?? DefaultJsonObject());
84 }
85
86 public static async Task<string> InvokeJsonAppearanceProviderAsync(
87 IUiScheduler ui,
88 Func<string?, string>? provider,
89 string? name)
90 {
91 if (provider is null)
92 return DefaultJsonObject();
93 return await ui.InvokeAsync(() => provider(name) ?? DefaultJsonObject());
94 }
95
96 public static async Task<string> InvokeProviderOrMessageAsync(
97 IUiScheduler ui,
98 Func<string?, string>? provider,
99 string? arg,
100 string whenProviderMissing)
101 {
102 if (provider is null)
103 return whenProviderMissing;
104 return await ui.InvokeAsync(() => provider(arg));
105 }
106
107 public static async Task<string> InvokeProviderOrMessageAsync(
108 IUiScheduler ui,
109 Func<string, string, string>? provider,
110 string a,
111 string b,
112 string whenProviderMissing)
113 {
114 if (provider is null)
115 return whenProviderMissing;
116 return await ui.InvokeAsync(() => provider(a, b));
117 }
118
119 public static async Task<string> InvokeProviderOrMessageAsync(
120 IUiScheduler ui,
121 Func<string, string, string?, string?, string>? provider,
122 string a,
123 string b,
124 string? c,
125 string? d,
126 string whenProviderMissing)
127 {
128 if (provider is null)
129 return whenProviderMissing;
130 return await ui.InvokeAsync(() => provider(a, b, c, d));
131 }
132
133 public static async Task<string> InvokeProviderOrMessageAsync(
134 IUiScheduler ui,
135 Func<string, double?, double?, string>? provider,
136 string panel,
137 double? width,
138 double? height,
139 string whenProviderMissing)
140 {
141 if (provider is null)
142 return whenProviderMissing;
143 return await ui.InvokeAsync(() => provider(panel, width, height));
144 }
145
146 /// <summary>MCP <c>send_keys</c>: провайдер <c>(controlName, keys) → json</c>.</summary>
147 public static async Task<string> InvokeSendKeysProviderOrMessageAsync(
148 IUiScheduler ui,
149 Func<string?, string, string>? provider,
150 string? controlName,
151 string keys,
152 string whenProviderMissing)
153 {
154 if (provider is null)
155 return whenProviderMissing;
156 return await ui.InvokeAsync(() => provider(controlName, keys));
157 }
158}
159
View only · write via MCP/CIDE