From 9e78f5055e1d9839bd8c30ef46e17d082bbc71c3 Mon Sep 17 00:00:00 2001 From: Thorsten Sommer Date: Mon, 2 Nov 2026 19:20:02 +0100 Subject: [PATCH] Generated the JSON contracts of ERI servers and search services at compile time --- .../Tools/ERIClient/ERIClientBase.cs | 7 +++--- .../Tools/ERIClient/ERIClientJsonContext.cs | 23 +++++++++++++++++++ .../WebSearch/WebSearchJson.cs | 5 ++-- .../WebSearch/WebSearchJsonContext.cs | 21 +++++++++++++++++ app/Tests/Serialization/JsonCoverageTests.cs | 2 ++ 5 files changed, 53 insertions(+), 5 deletions(-) create mode 100644 app/MindWork AI Studio/Tools/ERIClient/ERIClientJsonContext.cs create mode 100644 app/MindWork AI Studio/Tools/ToolCallingSystem/ToolCallingImplementations/WebSearch/WebSearchJsonContext.cs diff --git a/app/MindWork AI Studio/Tools/ERIClient/ERIClientBase.cs b/app/MindWork AI Studio/Tools/ERIClient/ERIClientBase.cs index 7d8f0cbe8..9b616d85f 100644 --- a/app/MindWork AI Studio/Tools/ERIClient/ERIClientBase.cs +++ b/app/MindWork AI Studio/Tools/ERIClient/ERIClientBase.cs @@ -9,7 +9,7 @@ public abstract class ERIClientBase(IERIDataSource dataSource) : IDisposable { protected readonly IERIDataSource DataSource = dataSource; - internal static readonly JsonSerializerOptions JSON_OPTIONS = new() + internal static readonly JsonSerializerOptions JSON_OPTIONS = new JsonSerializerOptions { WriteIndented = true, AllowTrailingCommas = true, @@ -20,8 +20,9 @@ public abstract class ERIClientBase(IERIDataSource dataSource) : IDisposable Converters = { new JsonStringEnumConverter(JsonNamingPolicy.SnakeCaseUpper), - } - }; + }, + TypeInfoResolver = ERIClientJsonContext.Default, + }.AsReadOnly(); protected readonly HttpClient HttpClient = ExternalHttpClientTimeout.CreateHttpClient(new Uri($"{dataSource.Hostname}:{dataSource.Port}"), ExternalHttpTrustPolicy.ALLOW_CUSTOM_ROOTS_WHEN_HOST_WHITELISTED); diff --git a/app/MindWork AI Studio/Tools/ERIClient/ERIClientJsonContext.cs b/app/MindWork AI Studio/Tools/ERIClient/ERIClientJsonContext.cs new file mode 100644 index 000000000..30db29e2b --- /dev/null +++ b/app/MindWork AI Studio/Tools/ERIClient/ERIClientJsonContext.cs @@ -0,0 +1,23 @@ +using System.Text.Json.Serialization; + +using AIStudio.Tools.ERIClient.DataModel; + +namespace AIStudio.Tools.ERIClient; + +/// +/// The contracts with which System.Text.Json reads and writes the calls to ERI servers, built at compile time. +/// +[JsonSourceGenerationOptions(GenerationMode = JsonSourceGenerationMode.Metadata)] + +// The request: +[JsonSerializable(typeof(RetrievalRequest))] + +// The answers: +[JsonSerializable(typeof(List))] +[JsonSerializable(typeof(AuthResponse))] +[JsonSerializable(typeof(DataSourceInfo))] +[JsonSerializable(typeof(List))] +[JsonSerializable(typeof(List))] +[JsonSerializable(typeof(List))] +[JsonSerializable(typeof(SecurityRequirements))] +internal sealed partial class ERIClientJsonContext : JsonSerializerContext; \ No newline at end of file diff --git a/app/MindWork AI Studio/Tools/ToolCallingSystem/ToolCallingImplementations/WebSearch/WebSearchJson.cs b/app/MindWork AI Studio/Tools/ToolCallingSystem/ToolCallingImplementations/WebSearch/WebSearchJson.cs index 6ac1d24aa..136c31e8a 100644 --- a/app/MindWork AI Studio/Tools/ToolCallingSystem/ToolCallingImplementations/WebSearch/WebSearchJson.cs +++ b/app/MindWork AI Studio/Tools/ToolCallingSystem/ToolCallingImplementations/WebSearch/WebSearchJson.cs @@ -14,10 +14,11 @@ namespace AIStudio.Tools.ToolCallingSystem.ToolCallingImplementations.WebSearch; /// internal static class WebSearchJson { - public static readonly JsonSerializerOptions OPTIONS = new() + public static readonly JsonSerializerOptions OPTIONS = new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower, PropertyNameCaseInsensitive = true, DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, - }; + TypeInfoResolver = WebSearchJsonContext.Default, + }.AsReadOnly(); } \ No newline at end of file diff --git a/app/MindWork AI Studio/Tools/ToolCallingSystem/ToolCallingImplementations/WebSearch/WebSearchJsonContext.cs b/app/MindWork AI Studio/Tools/ToolCallingSystem/ToolCallingImplementations/WebSearch/WebSearchJsonContext.cs new file mode 100644 index 000000000..8185029af --- /dev/null +++ b/app/MindWork AI Studio/Tools/ToolCallingSystem/ToolCallingImplementations/WebSearch/WebSearchJsonContext.cs @@ -0,0 +1,21 @@ +using System.Text.Json.Serialization; + +using AIStudio.Tools.ToolCallingSystem.ToolCallingImplementations.WebSearch.Staan; +using AIStudio.Tools.ToolCallingSystem.ToolCallingImplementations.WebSearch.Tavily; + +namespace AIStudio.Tools.ToolCallingSystem.ToolCallingImplementations.WebSearch; + +/// +/// The contracts with which System.Text.Json reads and writes the calls to the search services, built at +/// compile time. +/// +/// +/// SearXNG is missing on purpose: its answers differ between instances, so its client reads them as a tree +/// of JSON nodes instead. +/// +[JsonSourceGenerationOptions(GenerationMode = JsonSourceGenerationMode.Metadata)] +[JsonSerializable(typeof(StaanSearchRequest))] +[JsonSerializable(typeof(StaanSearchResponse))] +[JsonSerializable(typeof(TavilySearchRequest))] +[JsonSerializable(typeof(TavilySearchResponse))] +internal sealed partial class WebSearchJsonContext : JsonSerializerContext; \ No newline at end of file diff --git a/app/Tests/Serialization/JsonCoverageTests.cs b/app/Tests/Serialization/JsonCoverageTests.cs index e40776b90..716585793 100644 --- a/app/Tests/Serialization/JsonCoverageTests.cs +++ b/app/Tests/Serialization/JsonCoverageTests.cs @@ -18,6 +18,8 @@ public sealed class JsonCoverageTests [TestCaseSource(typeof(JsonTestRoots), nameof(JsonTestRoots.ProviderAnswers))] [TestCaseSource(typeof(JsonTestRoots), nameof(JsonTestRoots.RuntimeRequests))] [TestCaseSource(typeof(JsonTestRoots), nameof(JsonTestRoots.RuntimeAnswers))] + [TestCaseSource(typeof(JsonTestRoots), nameof(JsonTestRoots.ServiceRequests))] + [TestCaseSource(typeof(JsonTestRoots), nameof(JsonTestRoots.ServiceAnswers))] public void EveryContractComesFromSourceGeneration(JsonTestRoot root) { var typesWithoutGeneratedContract = JsonTypeGraph.Collect(root.Type, root.Options)