microsoft/TypeAgent

Public

mirrored fromhttps://github.com/microsoft/TypeAgentAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
cleanup/test-consolelogs

Branches

Tags

  • No tags available.
0Branches0Tags
Go to file
Add file
Code

Clone

HTTPS

Download ZIP

dotnet/agentLauncher/src/Program.cs

417lines · modecode

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4using System.Diagnostics;
5using System.Text;
6
7namespace AgentLauncher;
8
9class Program
10{
11 private static readonly string LogFilePath = Path.Combine(
12 Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
13 "AgentLauncher",
14 "agent.log");
15
16 private static readonly Stopwatch _processStopwatch = Stopwatch.StartNew();
17
18 [STAThread]
19 static int Main(string[] args)
20 {
21 try
22 {
23 LogTiming("PROCESS_START", "Process started");
24 Log("TypeAgent Launcher starting...");
25 Log($"Arguments: {string.Join(" ", args)}");
26
27 LogTiming("ARGS_PARSED", "Arguments parsed");
28
29 if (args.Length > 0)
30 {
31 var firstArg = args[0];
32
33 // COM server registration removed - using URI protocol activation instead
34 if (firstArg.StartsWith("typeagent-launcher:", StringComparison.OrdinalIgnoreCase))
35 {
36 LogTiming("PROTOCOL_DETECTED", "Protocol activation detected");
37 Log($"Protocol activation received: {firstArg}");
38 var result = HandleProtocolActivation(firstArg).GetAwaiter().GetResult();
39 LogTiming("PROCESS_COMPLETE", "Process complete");
40 return result;
41 }
42 else if (firstArg == "--test" || firstArg == "-t")
43 {
44 var prompt = args.Length > 1 ? args[1] : "Hello, agent!";
45 return TestAgent(prompt).GetAwaiter().GetResult();
46 }
47 else if (firstArg == "--register" || firstArg == "-r")
48 {
49 return RegisterWithODR();
50 }
51 else if (firstArg == "--help" || firstArg == "-h")
52 {
53 PrintHelp();
54 return 0;
55 }
56 }
57
58 Log("No recognized arguments, exiting silently");
59 return 0;
60 }
61 catch (Exception ex)
62 {
63 Log($"ERROR: {ex}");
64 Console.Error.WriteLine($"Error: {ex.Message}");
65 return 1;
66 }
67 }
68
69 private static async Task<int> HandleProtocolActivation(string uriString)
70 {
71 try
72 {
73 LogTiming("URI_PARSE_START", "Starting URI parse");
74 var uri = new Uri(uriString);
75 Log($"Parsing URI: {uri}");
76
77 var queryParams = ParseQueryString(uri.Query);
78 LogTiming("URI_PARSE_COMPLETE", "URI parsed");
79
80 var agentName = queryParams.GetValueOrDefault("agentName");
81 var prompt = queryParams.GetValueOrDefault("prompt");
82
83 if (string.IsNullOrWhiteSpace(agentName) || string.IsNullOrWhiteSpace(prompt))
84 {
85 var error = $"Missing required parameters. agentName: {agentName ?? "(null)"}, prompt: {prompt ?? "(null)"}";
86 Log($"ERROR: {error}");
87 Console.Error.WriteLine(error);
88 return 1;
89 }
90
91 Log($"Processing protocol activation - Agent: {agentName}, Prompt: {prompt}");
92 LogTiming("NODE_CALL_START", "Starting Node.js execution");
93
94 var result = await ProcessWithNodeAsync(agentName, prompt, null);
95
96 LogTiming("NODE_CALL_COMPLETE", "Node.js execution complete");
97 Log($"Protocol activation completed successfully");
98 Log($"Result: {result?.Substring(0, Math.Min(100, result?.Length ?? 0))}...");
99
100 return 0;
101 }
102 catch (Exception ex)
103 {
104 Log($"ERROR: Protocol activation failed: {ex}");
105 Console.Error.WriteLine($"Protocol activation error: {ex.Message}");
106 return 1;
107 }
108 }
109
110 private static Dictionary<string, string> ParseQueryString(string query)
111 {
112 var result = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
113
114 if (string.IsNullOrWhiteSpace(query))
115 return result;
116
117 query = query.TrimStart('?');
118
119 var pairs = query.Split('&');
120 foreach (var pair in pairs)
121 {
122 var parts = pair.Split('=', 2);
123 if (parts.Length == 2)
124 {
125 var key = Uri.UnescapeDataString(parts[0]);
126 var value = Uri.UnescapeDataString(parts[1]);
127 result[key] = value;
128 }
129 }
130
131 return result;
132 }
133
134
135 private static async Task<int> TestAgent(string prompt)
136 {
137 Console.WriteLine($"Testing agent with prompt: {prompt}");
138 var settings = AgentSettings.Instance;
139 Console.WriteLine($"Script path: {settings.GetResolvedScriptPath()}");
140 Console.WriteLine();
141
142 try
143 {
144 var result = await ProcessWithNodeAsync("TestAgent", prompt, null);
145 Console.WriteLine("Result:");
146 Console.WriteLine(result);
147 return 0;
148 }
149 catch (Exception ex)
150 {
151 Console.Error.WriteLine($"Test failed: {ex.Message}");
152 Log($"Test failed: {ex}");
153 return 1;
154 }
155 }
156
157 private static int RegisterWithODR()
158 {
159 try
160 {
161 var manifestPath = Path.Combine(AppContext.BaseDirectory, "Assets", "agent-definition.json");
162
163 if (!File.Exists(manifestPath))
164 {
165 Console.Error.WriteLine($"ERROR: agent-definition.json not found at: {manifestPath}");
166 Log($"ERROR: agent-definition.json not found at: {manifestPath}");
167 return 1;
168 }
169
170 Console.WriteLine($"Registering agent with ODR...");
171 Console.WriteLine($"Manifest path: {manifestPath}");
172 Log($"Registering agent with ODR using manifest: {manifestPath}");
173
174 var processInfo = new ProcessStartInfo
175 {
176 FileName = "odr",
177 Arguments = $"app-agents add \"{manifestPath}\"",
178 CreateNoWindow = false,
179 UseShellExecute = false,
180 RedirectStandardOutput = true,
181 RedirectStandardError = true
182 };
183
184 using var process = Process.Start(processInfo);
185 if (process == null)
186 {
187 Console.Error.WriteLine("ERROR: Failed to start ODR process");
188 Log("ERROR: Failed to start ODR process");
189 return 1;
190 }
191
192 var output = process.StandardOutput.ReadToEnd();
193 var error = process.StandardError.ReadToEnd();
194 process.WaitForExit();
195
196 if (!string.IsNullOrWhiteSpace(output))
197 {
198 Console.WriteLine(output);
199 Log($"ODR output: {output}");
200 }
201
202 if (!string.IsNullOrWhiteSpace(error))
203 {
204 Console.Error.WriteLine(error);
205 Log($"ODR error: {error}");
206 }
207
208 if (process.ExitCode == 0)
209 {
210 Console.WriteLine("Agent registered successfully!");
211 Log("Agent registered successfully with ODR");
212 return 0;
213 }
214 else
215 {
216 Console.Error.WriteLine($"ODR registration failed with exit code: {process.ExitCode}");
217 Log($"ODR registration failed with exit code: {process.ExitCode}");
218 return process.ExitCode;
219 }
220 }
221 catch (Exception ex)
222 {
223 Console.Error.WriteLine($"ERROR: {ex.Message}");
224 Log($"ERROR during ODR registration: {ex}");
225 return 1;
226 }
227 }
228
229 private static void PrintHelp()
230 {
231 Console.WriteLine("TypeAgent Launcher");
232 Console.WriteLine();
233 Console.WriteLine("Usage:");
234 Console.WriteLine(" AgentLauncher.exe [options]");
235 Console.WriteLine();
236 Console.WriteLine("Options:");
237 Console.WriteLine(" --test, -t [prompt] Test agent with a prompt");
238 Console.WriteLine(" --register, -r Register agent with On-Device Registry");
239 Console.WriteLine(" --help, -h Show this help");
240 Console.WriteLine();
241 Console.WriteLine("Environment Variables:");
242 Console.WriteLine(" TYPEAGENT_NODE_PATH Path to Node.js executable");
243 Console.WriteLine(" TYPEAGENT_TIMEOUT Timeout in milliseconds (default: 60000)");
244 Console.WriteLine(" TYPEAGENT_VERBOSE Enable verbose logging (true/false)");
245 Console.WriteLine(" TYPEAGENT_WORKDIR Working directory for Node.js");
246 Console.WriteLine();
247 Console.WriteLine("Examples:");
248 Console.WriteLine(" AgentLauncher.exe --test \"Hello, world!\"");
249 Console.WriteLine(" AgentLauncher.exe --register");
250 Console.WriteLine();
251 Console.WriteLine(" SET TYPEAGENT_VERBOSE=true");
252 Console.WriteLine(" SET TYPEAGENT_TIMEOUT=120000");
253 Console.WriteLine(" AgentLauncher.exe --test \"Long running test\"");
254 }
255
256 public static async Task<string> ProcessWithNodeAsync(
257 string agentName,
258 string prompt,
259 string? filePath)
260 {
261 LogTiming("SETTINGS_LOAD_START", "Loading settings");
262 var settings = AgentSettings.Instance;
263 var scriptPath = settings.GetResolvedScriptPath();
264 var nodePath = settings.GetResolvedNodePath();
265 LogTiming("SETTINGS_LOAD_COMPLETE", "Settings loaded");
266
267 Log($"Processing request - Agent: {agentName}, Prompt: {prompt?.Substring(0, Math.Min(50, prompt?.Length ?? 0))}...");
268 Log($"Script: {scriptPath}");
269 Log($"Node: {nodePath}");
270
271 if (!File.Exists(scriptPath))
272 {
273 throw new FileNotFoundException(
274 $"Script not found: {scriptPath}. " +
275 $"The bundled script should be included in the MSIX package.");
276 }
277
278 var uri = BuildUriString(agentName, prompt, filePath);
279 Log($"URI: {uri}");
280
281 LogTiming("PROCESS_SETUP_START", "Setting up Node.js process");
282 var processInfo = new ProcessStartInfo
283 {
284 FileName = nodePath,
285 Arguments = $"\"{scriptPath}\" \"{uri}\"",
286 CreateNoWindow = true,
287 UseShellExecute = false,
288 RedirectStandardOutput = true,
289 RedirectStandardError = true,
290 RedirectStandardInput = false,
291 StandardOutputEncoding = Encoding.UTF8,
292 StandardErrorEncoding = Encoding.UTF8
293 };
294
295 if (settings.Environment != null)
296 {
297 foreach (var (key, value) in settings.Environment)
298 {
299 processInfo.Environment[key] = value;
300 }
301 }
302
303 var workingDir = settings.GetResolvedWorkingDirectory();
304 if (!string.IsNullOrWhiteSpace(workingDir))
305 {
306 processInfo.WorkingDirectory = workingDir;
307 }
308
309 var process = new Process { StartInfo = processInfo };
310 var outputBuilder = new StringBuilder();
311 var errorBuilder = new StringBuilder();
312
313 process.OutputDataReceived += (sender, e) =>
314 {
315 if (e.Data != null)
316 {
317 outputBuilder.AppendLine(e.Data);
318 }
319 };
320
321 process.ErrorDataReceived += (sender, e) =>
322 {
323 if (e.Data != null)
324 {
325 errorBuilder.AppendLine(e.Data);
326 }
327 };
328
329 try
330 {
331 LogTiming("PROCESS_START", "Starting Node.js process");
332 process.Start();
333 process.BeginOutputReadLine();
334 process.BeginErrorReadLine();
335 LogTiming("PROCESS_STARTED", "Process started, waiting for completion");
336
337 var completed = await Task.Run(() => process.WaitForExit(settings.TimeoutMs));
338 LogTiming("PROCESS_WAIT_COMPLETE", "Process wait completed");
339
340 if (!completed)
341 {
342 process.Kill();
343 throw new TimeoutException(
344 $"Script execution timed out after {settings.TimeoutMs}ms. " +
345 $"Increase timeout by setting TYPEAGENT_TIMEOUT environment variable (in milliseconds).");
346 }
347
348 var exitCode = process.ExitCode;
349 var output = outputBuilder.ToString().Trim();
350 var error = errorBuilder.ToString().Trim();
351
352 Log($"Exit code: {exitCode}");
353 LogTiming("OUTPUT_COLLECTED", "Output and error streams collected");
354
355 if (settings.VerboseLogging)
356 {
357 Log($"Output: {output}");
358 Log($"Error: {error}");
359 }
360
361 if (exitCode != 0)
362 {
363 throw new Exception($"Script failed with exit code {exitCode}. Error: {error}");
364 }
365
366 if (string.IsNullOrWhiteSpace(output))
367 {
368 throw new Exception("Script produced no output");
369 }
370
371 return output;
372 }
373 catch (Exception ex) when (ex is not TimeoutException)
374 {
375 Log($"ERROR: Failed to execute Node.js script: {ex.Message}");
376 throw new Exception($"Failed to execute script: {ex.Message}", ex);
377 }
378 }
379
380 private static string BuildUriString(string agentName, string prompt, string? filePath)
381 {
382 var uri = $"type-agent://?request={Uri.EscapeDataString(prompt)}";
383
384 if (!string.IsNullOrWhiteSpace(filePath))
385 {
386 uri += $"&file={Uri.EscapeDataString(filePath)}";
387 }
388
389 return uri;
390 }
391
392 public static void Log(string message)
393 {
394 try
395 {
396 var directory = Path.GetDirectoryName(LogFilePath);
397 if (directory != null && !Directory.Exists(directory))
398 {
399 Directory.CreateDirectory(directory);
400 }
401
402 var timestamp = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff");
403 var logMessage = $"[{timestamp}] {message}";
404
405 File.AppendAllText(LogFilePath, logMessage + Environment.NewLine);
406 }
407 catch
408 {
409 }
410 }
411
412 private static void LogTiming(string marker, string description)
413 {
414 var elapsed = _processStopwatch.ElapsedMilliseconds;
415 Log($"⏱️ TIMING [{marker}] +{elapsed}ms - {description}");
416 }
417}
418