src/TechToolbox.Agent/Agent/ToolWrapper.cs
|
using System.Text.Json;
using TechToolbox.Agent.Execution; using TechToolbox.Agent.Registry; namespace TechToolbox.Agent.Agent; public static class ToolWrapper { private static readonly HashSet<string> SignedFilePolicyValues = new(StringComparer.OrdinalIgnoreCase) { "ignore", "strip" }; public static Dictionary<string, Func<string, Task<string>>> BuildTools( IReadOnlyDictionary<string, ToolSpec> registry, bool destructiveConfirmed, string signedFilePolicy, Func<string, IDictionary<string, object?>, object?>? toolExecutor = null) { var tools = new Dictionary<string, Func<string, Task<string>>>(StringComparer.OrdinalIgnoreCase); var normalizedSignedFilePolicy = NormalizeSignedFilePolicy(signedFilePolicy); var executor = toolExecutor ?? PowerShellBridge.RunTool; foreach (var kv in registry) { var toolName = kv.Key; var spec = kv.Value; tools[toolName] = async (jsonArgs) => { Dictionary<string, object?> args; // Parse JSON args safely try { args = string.IsNullOrWhiteSpace(jsonArgs) ? new Dictionary<string, object?>() : JsonSerializer.Deserialize<Dictionary<string, object?>>(jsonArgs) ?? new Dictionary<string, object?>(); } catch { return $"Error: Invalid JSON arguments for tool '{toolName}'."; } // Required parameters enforcement var missing = GetMissingRequiredParams(spec, args); if (missing.Count > 0) { return $"Missing required parameter(s): {string.Join(", ", missing)}."; } // Auto-confirm destructive tools if allowed if (Safety.IsDestructive(toolName) && destructiveConfirmed) { args["__confirm_destructive"] = true; } // Preserve Python agent behavior by passing signed-file policy to tools that support it. if (HasParameter(spec, "SignedFilePolicy") && !HasArgument(args, "SignedFilePolicy")) { args["SignedFilePolicy"] = normalizedSignedFilePolicy; } // Execute via PowerShell bridge object? result; try { result = executor(toolName, args); } catch (Exception ex) { return $"Tool '{toolName}' failed: {ex.Message}"; } // Normalize output return result switch { null => "null", string s => s, _ => JsonSerializer.Serialize(result) }; }; } return tools; } private static List<string> GetMissingRequiredParams(ToolSpec spec, Dictionary<string, object?> args) { var missing = new List<string>(); foreach (var param in spec.Parameters) { if (param.Value.Mandatory) { if (!args.TryGetValue(param.Key, out var value) || value is null || (value is string s && string.IsNullOrWhiteSpace(s))) { missing.Add(param.Key); } } } return missing; } private static string NormalizeSignedFilePolicy(string? policy) { if (string.IsNullOrWhiteSpace(policy)) return "ignore"; var normalized = policy.Trim().ToLowerInvariant(); return SignedFilePolicyValues.Contains(normalized) ? normalized : "ignore"; } private static bool HasParameter(ToolSpec spec, string parameterName) => spec.Parameters.Keys.Any(k => string.Equals(k, parameterName, StringComparison.OrdinalIgnoreCase)); private static bool HasArgument(Dictionary<string, object?> args, string parameterName) => args.Keys.Any(k => string.Equals(k, parameterName, StringComparison.OrdinalIgnoreCase)); } |