summaryrefslogtreecommitdiff
path: root/src/Agent.Core/Tools/InternalToolValidator.cs
diff options
context:
space:
mode:
authortanaka takashi <isazaka@gmail.com>2026-08-09 21:08:20 +0900
committertanaka takashi <isazaka@gmail.com>2026-08-09 21:08:20 +0900
commitb07b41cd435991cd254957af79055080c85b346e (patch)
tree27720d3ce14e4d0b84670a55ac26a2f877ce3a20 /src/Agent.Core/Tools/InternalToolValidator.cs
parent2fefd29e86c3b926d0cf4a362251d3f119688612 (diff)
feat: AI Agent Sandbox MCP Server v1を実装
Specifications、Design、ImplementationPlanの優先順位を整理し、単一Task実行モデル、typed option優先、設定可能なresource上限、local Ollamaを必須とするRelease Gateを文書化。 .NET 10/C# 14の5 production project、3 test project、E2E Clientを構築。typed YAML、en/ja locale、fixed/user prompt、stdioおよびloopback限定Streamable HTTP、agent_run/agent_status、Session、Ollama text/tool/Vision、TaskGraphとtoken accountingを実装。 Windows sandboxとしてfixed AppContainer、canonical/reparse検査、能力別ACL、path+SHA-256 executable approval、child-process restriction、Job Object、task journal、atomic file transaction、crash recoveryを実装。WFPのIPv4/IPv6 default denyとexact allow、dynamic session、temporary loopback exemption、管理者/BFE診断を追加。 Security/Recovery test、MCP/Ollama integration test、stdio/HTTP Operational E2E、Release Bundle生成・checksum・manifest・fresh extraction検査を追加。Requirement TraceabilityとPhase 0〜11の進捗、初回セットアップ可能なREADMEを整備。 Debug/Release/publish/objをrepository rootのbuild/へ集約し、旧project-local bin/objへの依存を除去。artifacts/とbuild/をGit管理外に設定し、clean checkoutでもsolution buildからE2E Clientまで生成可能にした。
Diffstat (limited to 'src/Agent.Core/Tools/InternalToolValidator.cs')
-rw-r--r--src/Agent.Core/Tools/InternalToolValidator.cs86
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.");
+ }
+ }
+}