src/TechToolbox.Agent/Execution/PowerShellBridge.cs
|
using System.Management.Automation;
using TechToolbox.Agent.Agent; namespace TechToolbox.Agent.Execution; public static class PowerShellBridge { public static object? RunTool(string toolName, IDictionary<string, object?> args) { if (string.IsNullOrWhiteSpace(toolName)) throw new ArgumentException("Tool name must not be empty.", nameof(toolName)); if (TryRunBuiltInTool(toolName, args, out var builtInResult)) return builtInResult; // Safety check for destructive tools Safety.RequireDestructiveConfirmation(toolName, args); using var ps = PowerShell.Create(); // Import the TechToolbox module explicitly // (Assumes the module is discoverable via PSModulePath) ps.AddCommand("Import-Module") .AddParameter("Name", "TechToolbox") .AddParameter("ErrorAction", "Stop") .Invoke(); if (ps.HadErrors) throw new InvalidOperationException($"Failed to import TechToolbox module: {ps.Streams.Error[0]}"); ps.Commands.Clear(); // Add the tool command ps.AddCommand(toolName); // Add parameters foreach (var kv in args) { // Skip internal agent control keys if (kv.Key.StartsWith("__", StringComparison.Ordinal)) continue; ps.AddParameter(kv.Key, kv.Value); } // Execute var results = ps.Invoke(); if (ps.HadErrors) throw new InvalidOperationException($"Tool '{toolName}' failed: {ps.Streams.Error[0]}"); // Normalize output if (results is null || results.Count == 0) return null; if (results.Count == 1) return results[0].BaseObject; return results.Select(r => r.BaseObject).ToList(); } private static bool TryRunBuiltInTool(string toolName, IDictionary<string, object?> args, out object? result) { result = null; if (toolName.Equals("READ-FILE", StringComparison.OrdinalIgnoreCase)) { var path = GetRequiredStringArg(args, "path"); if (!File.Exists(path)) throw new FileNotFoundException($"File not found: {path}", path); result = File.ReadAllText(path); return true; } if (toolName.Equals("LIST-DIRECTORY", StringComparison.OrdinalIgnoreCase)) { var path = GetRequiredStringArg(args, "path"); if (!Directory.Exists(path)) throw new DirectoryNotFoundException($"Directory not found: {path}"); var entries = Directory .EnumerateFileSystemEntries(path) .Select(p => { var name = Path.GetFileName(p); return Directory.Exists(p) ? $"{name}/" : name; }) .OrderBy(n => n, StringComparer.OrdinalIgnoreCase) .ToList(); result = entries; return true; } if (toolName.Equals("WRITE-FILE", StringComparison.OrdinalIgnoreCase)) { var path = GetRequiredStringArg(args, "path"); var content = GetRequiredStringArg(args, "content"); var dir = Path.GetDirectoryName(path); if (!string.IsNullOrWhiteSpace(dir)) { Directory.CreateDirectory(dir); } File.WriteAllText(path, content); result = "ok"; return true; } return false; } private static string GetRequiredStringArg(IDictionary<string, object?> args, string name) { var arg = args.FirstOrDefault(kv => string.Equals(kv.Key, name, StringComparison.OrdinalIgnoreCase)); var value = arg.Value; string? text = value switch { null => null, string s => s, System.Text.Json.JsonElement el when el.ValueKind == System.Text.Json.JsonValueKind.String => el.GetString(), System.Text.Json.JsonElement el => el.ToString(), _ => value.ToString() }; if (string.IsNullOrWhiteSpace(text)) throw new ArgumentException($"Missing required parameter '{name}'.", name); return text; } } |