diff options
Diffstat (limited to 'src/Agent.Core/Tools/InternalToolValidator.cs')
| -rw-r--r-- | src/Agent.Core/Tools/InternalToolValidator.cs | 86 |
1 files changed, 86 insertions, 0 deletions
diff --git a/src/Agent.Core/Tools/InternalToolValidator.cs b/src/Agent.Core/Tools/InternalToolValidator.cs new file mode 100644 index 0000000..c48719e --- /dev/null +++ b/src/Agent.Core/Tools/InternalToolValidator.cs @@ -0,0 +1,86 @@ +using System.Text.Json; + +namespace Agent.Core.Tools; + +public static class InternalToolValidator +{ + public static void Validate(string name, JsonElement arguments) + { + InternalToolDefinition definition = InternalToolCatalog.Get(name); + if (arguments.ValueKind != JsonValueKind.Object) throw new ToolValidationException("Tool arguments must be an object."); + JsonElement properties = definition.Parameters.GetProperty("properties"); + var known = properties.EnumerateObject().Select(static item => item.Name).ToHashSet(StringComparer.Ordinal); + foreach (JsonProperty argument in arguments.EnumerateObject()) + { + if (!known.Contains(argument.Name)) throw new ToolValidationException($"Unknown argument '{argument.Name}'."); + ValidateValue(argument.Name, argument.Value, properties.GetProperty(argument.Name)); + } + foreach (JsonElement required in definition.Parameters.GetProperty("required").EnumerateArray()) + { + string requiredName = required.GetString()!; + if (!arguments.TryGetProperty(requiredName, out _)) throw new ToolValidationException($"Missing required argument '{requiredName}'."); + } + ValidateOperationSpecific(name, arguments); + } + + private static void ValidateValue(string name, JsonElement value, JsonElement schema) + { + if (value.ValueKind == JsonValueKind.Null && AllowsNull(schema)) return; + bool validType = GetTypes(schema).Any(type => type switch + { + "string" => value.ValueKind == JsonValueKind.String, + "integer" => value.ValueKind == JsonValueKind.Number && value.TryGetInt64(out _), + "array" => value.ValueKind == JsonValueKind.Array, + _ => false + }); + if (!validType) throw new ToolValidationException($"Argument '{name}' has an invalid type."); + if (schema.TryGetProperty("enum", out JsonElement enumValues) && !enumValues.EnumerateArray().Any(item => JsonElement.DeepEquals(item, value))) + throw new ToolValidationException($"Argument '{name}' has an unsupported value."); + if (value.ValueKind == JsonValueKind.Number) + { + long number = value.GetInt64(); + if (schema.TryGetProperty("minimum", out JsonElement minimum) && number < minimum.GetInt64()) throw new ToolValidationException($"Argument '{name}' is below its minimum."); + if (schema.TryGetProperty("maximum", out JsonElement maximum) && number > maximum.GetInt64()) throw new ToolValidationException($"Argument '{name}' exceeds its maximum."); + } + if (value.ValueKind == JsonValueKind.Array && schema.TryGetProperty("items", out JsonElement items)) + foreach (JsonElement item in value.EnumerateArray()) ValidateValue(name, item, items); + } + + private static IEnumerable<string> GetTypes(JsonElement schema) + { + JsonElement type = schema.GetProperty("type"); + return type.ValueKind == JsonValueKind.Array + ? type.EnumerateArray().Select(static item => item.GetString()!).ToArray() + : [type.GetString()!]; + } + private static bool AllowsNull(JsonElement schema) => GetTypes(schema).Contains("null", StringComparer.Ordinal); + + private static void ValidateOperationSpecific(string name, JsonElement arguments) + { + if (name == "file_read" && arguments.GetProperty("mode").GetString() == "range" && !arguments.TryGetProperty("length", out _)) + throw new ToolValidationException("Range reads require length."); + if (name == "file_edit") + { + string operation = arguments.GetProperty("operation").GetString()!; + if (operation is "overwrite" or "atomic_replace" && (!arguments.TryGetProperty("content", out JsonElement content) || content.ValueKind != JsonValueKind.String)) + throw new ToolValidationException($"{operation} requires content."); + if (operation == "patch" && (!arguments.TryGetProperty("patch", out JsonElement patch) || patch.ValueKind != JsonValueKind.String)) + throw new ToolValidationException("patch requires patch text."); + } + if (name == "task_update") + { + string operation = arguments.GetProperty("operation").GetString()!; + if (operation == "add") Require(arguments, "description", "budget_class", "optimistic_tokens", "most_likely_tokens", "pessimistic_tokens"); + if (operation == "add_dependency") Require(arguments, "task_id", "depends_on"); + if (operation == "set_state") Require(arguments, "task_id", "state"); + if (operation == "set_estimate") Require(arguments, "task_id", "budget_class", "optimistic_tokens", "most_likely_tokens", "pessimistic_tokens"); + } + + static void Require(JsonElement value, params string[] properties) + { + foreach (string property in properties) + if (!value.TryGetProperty(property, out JsonElement item) || item.ValueKind == JsonValueKind.Null) + throw new ToolValidationException($"{property} is required for this operation."); + } + } +} |
