// Copyright (c) Microsoft. All rights reserved. using System.Diagnostics.CodeAnalysis; using System.Text.Encodings.Web; using System.Text.Json; using System.Text.Json.Serialization; using Microsoft.Agents.AI.DurableTask.State; using Microsoft.Extensions.AI; namespace Microsoft.Agents.AI.DurableTask; /// Provides JSON serialization utilities and source-generated contracts for Durable Agent types. /// /// /// This mirrors the pattern used by other libraries (e.g. WorkflowsJsonUtilities) to enable Native AOT and trimming /// friendly serialization without relying on runtime reflection. It establishes a singleton /// instance that is preconfigured with: /// /// /// baseline defaults. /// for default null-value suppression. /// to tolerate numbers encoded as strings. /// Chained type info resolvers from shared agent abstractions to cover cross-package types (e.g. , ). /// /// /// Keep the list of [JsonSerializable] types in sync with the Durable Agent data model anytime new state or request/response /// containers are introduced that must round-trip via JSON. /// /// internal static partial class DurableAgentJsonUtilities { /// /// Gets the singleton used for Durable Agent serialization. /// public static JsonSerializerOptions DefaultOptions { get; } = CreateDefaultOptions(); /// /// Serializes a sequence of chat messages using the durable agent default options. /// /// The messages to serialize. /// A representing the serialized messages. public static JsonElement Serialize(this IEnumerable messages) => JsonSerializer.SerializeToElement(messages, DefaultOptions.GetTypeInfo(typeof(IEnumerable))); /// /// Deserializes chat messages from a using durable agent options. /// /// The JSON element containing the messages. /// The deserialized list of chat messages. public static List DeserializeMessages(this JsonElement element) => (List?)element.Deserialize(DefaultOptions.GetTypeInfo(typeof(List))) ?? []; /// /// Creates the configured instance for durable agents. /// /// The configured options. [UnconditionalSuppressMessage("ReflectionAnalysis", "IL3050:RequiresDynamicCode", Justification = "Converter is guarded by IsReflectionEnabledByDefault check.")] [UnconditionalSuppressMessage("Trimming", "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access", Justification = "Converter is guarded by IsReflectionEnabledByDefault check.")] private static JsonSerializerOptions CreateDefaultOptions() { // Base configuration from the source-generated context below. JsonSerializerOptions options = new(JsonContext.Default.Options) { Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, // same as AgentAbstractionsJsonUtilities and AIJsonUtilities }; // Chain in shared abstractions resolver (Microsoft.Extensions.AI + Agent abstractions) so dependent types are covered. options.TypeInfoResolverChain.Clear(); options.TypeInfoResolverChain.Add(AgentAbstractionsJsonUtilities.DefaultOptions.TypeInfoResolver!); options.TypeInfoResolverChain.Add(JsonContext.Default.Options.TypeInfoResolver!); if (JsonSerializer.IsReflectionEnabledByDefault) { options.Converters.Add(new JsonStringEnumConverter()); } options.MakeReadOnly(); return options; } // Keep in sync with CreateDefaultOptions above. [JsonSourceGenerationOptions(JsonSerializerDefaults.Web, DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, NumberHandling = JsonNumberHandling.AllowReadingFromString)] // Durable Agent State Types [JsonSerializable(typeof(DurableAgentState))] [JsonSerializable(typeof(DurableAgentSession))] // Request Types [JsonSerializable(typeof(RunRequest))] // Primitive / Supporting Types [JsonSerializable(typeof(ChatMessage))] [JsonSerializable(typeof(JsonElement))] [ExcludeFromCodeCoverage] internal sealed partial class JsonContext : JsonSerializerContext; }