diff --git a/dotnet/src/Microsoft.Agents.AI.Abstractions/AgentAbstractionsJsonUtilities.cs b/dotnet/src/Microsoft.Agents.AI.Abstractions/AgentAbstractionsJsonUtilities.cs index 8943e29c79..d5003cace0 100644 --- a/dotnet/src/Microsoft.Agents.AI.Abstractions/AgentAbstractionsJsonUtilities.cs +++ b/dotnet/src/Microsoft.Agents.AI.Abstractions/AgentAbstractionsJsonUtilities.cs @@ -50,12 +50,16 @@ public static partial class AgentAbstractionsJsonUtilities Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, // same as AIJsonUtilities }; - // Chain with all supported types from Microsoft.Extensions.AI.Abstractions. + // Chain in the resolvers from both AIJsonUtilities and our source generated context. + // We want AIJsonUtilities first to ensure any M.E.AI types are handled via its resolver. + options.TypeInfoResolverChain.Clear(); + options.TypeInfoResolverChain.Add(AIJsonUtilities.DefaultOptions.TypeInfoResolver!); + options.TypeInfoResolverChain.Add(JsonContext.Default.Options.TypeInfoResolver!); + // If reflection-based serialization is enabled by default, this includes // the default type info resolver that utilizes reflection, but we need to manually // apply the same converter AIJsonUtilities adds for string-based enum serialization, // as that's not propagated as part of the resolver. - options.TypeInfoResolverChain.Add(AIJsonUtilities.DefaultOptions.TypeInfoResolver!); if (JsonSerializer.IsReflectionEnabledByDefault) { options.Converters.Add(new JsonStringEnumConverter()); diff --git a/dotnet/src/Microsoft.Agents.AI.Hosting.OpenAI/ChatCompletions/ChatCompletionsJsonSerializerOptions.cs b/dotnet/src/Microsoft.Agents.AI.Hosting.OpenAI/ChatCompletions/ChatCompletionsJsonSerializerOptions.cs index b009b82d29..301cae1f8f 100644 --- a/dotnet/src/Microsoft.Agents.AI.Hosting.OpenAI/ChatCompletions/ChatCompletionsJsonSerializerOptions.cs +++ b/dotnet/src/Microsoft.Agents.AI.Hosting.OpenAI/ChatCompletions/ChatCompletionsJsonSerializerOptions.cs @@ -17,7 +17,13 @@ internal static class ChatCompletionsJsonSerializerOptions private static JsonSerializerOptions Create() { JsonSerializerOptions options = new(ChatCompletionsJsonContext.Default.Options); + + // Chain in the resolvers from both AgentAbstractionsJsonUtilities and our source generated context. + // We want AgentAbstractionsJsonUtilities first to ensure any M.E.AI types are handled via its resolver. + options.TypeInfoResolverChain.Clear(); options.TypeInfoResolverChain.Add(AgentAbstractionsJsonUtilities.DefaultOptions.TypeInfoResolver!); + options.TypeInfoResolverChain.Add(ChatCompletionsJsonContext.Default.Options.TypeInfoResolver!); + options.MakeReadOnly(); return options; } diff --git a/dotnet/src/Microsoft.Agents.AI.Hosting.OpenAI/OpenAIHostingJsonUtilities.cs b/dotnet/src/Microsoft.Agents.AI.Hosting.OpenAI/OpenAIHostingJsonUtilities.cs index ceac8b872f..49ceef622a 100644 --- a/dotnet/src/Microsoft.Agents.AI.Hosting.OpenAI/OpenAIHostingJsonUtilities.cs +++ b/dotnet/src/Microsoft.Agents.AI.Hosting.OpenAI/OpenAIHostingJsonUtilities.cs @@ -24,7 +24,13 @@ internal static class OpenAIHostingJsonUtilities private static JsonSerializerOptions CreateDefaultOptions() { JsonSerializerOptions options = new(OpenAIHostingJsonContext.Default.Options); + + // Chain in the resolvers from both AgentAbstractionsJsonUtilities and our source generated context. + // We want AgentAbstractionsJsonUtilities first to ensure any M.E.AI types are handled via its resolver. + options.TypeInfoResolverChain.Clear(); options.TypeInfoResolverChain.Add(AgentAbstractionsJsonUtilities.DefaultOptions.TypeInfoResolver!); + options.TypeInfoResolverChain.Add(OpenAIHostingJsonContext.Default.Options.TypeInfoResolver!); + options.MakeReadOnly(); return options; } diff --git a/dotnet/src/Microsoft.Agents.AI.Mem0/Mem0JsonUtilities.cs b/dotnet/src/Microsoft.Agents.AI.Mem0/Mem0JsonUtilities.cs index eb50d31f70..d139cb0f76 100644 --- a/dotnet/src/Microsoft.Agents.AI.Mem0/Mem0JsonUtilities.cs +++ b/dotnet/src/Microsoft.Agents.AI.Mem0/Mem0JsonUtilities.cs @@ -4,7 +4,6 @@ using System.Diagnostics.CodeAnalysis; using System.Text.Encodings.Web; using System.Text.Json; using System.Text.Json.Serialization; -using Microsoft.Extensions.AI; namespace Microsoft.Agents.AI.Mem0; @@ -44,8 +43,12 @@ public static partial class Mem0JsonUtilities Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, // same as in AIJsonUtilities }; - // Chain with all supported types from Microsoft.Extensions.AI.Abstractions. - options.TypeInfoResolverChain.Add(AIJsonUtilities.DefaultOptions.TypeInfoResolver!); + // Chain in the resolvers from both AgentAbstractionsJsonUtilities and our source generated context. + // We want AgentAbstractionsJsonUtilities first to ensure any M.E.AI types are handled via its resolver. + options.TypeInfoResolverChain.Clear(); + options.TypeInfoResolverChain.Add(AgentAbstractionsJsonUtilities.DefaultOptions.TypeInfoResolver!); + options.TypeInfoResolverChain.Add(JsonContext.Default.Options.TypeInfoResolver!); + if (JsonSerializer.IsReflectionEnabledByDefault) { options.Converters.Add(new JsonStringEnumConverter()); diff --git a/dotnet/src/Microsoft.Agents.AI/AgentJsonUtilities.cs b/dotnet/src/Microsoft.Agents.AI/AgentJsonUtilities.cs index 5ef6978c00..36bef4a2af 100644 --- a/dotnet/src/Microsoft.Agents.AI/AgentJsonUtilities.cs +++ b/dotnet/src/Microsoft.Agents.AI/AgentJsonUtilities.cs @@ -44,8 +44,12 @@ internal static partial class AgentJsonUtilities Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, // same as in AgentAbstractionsJsonUtilities and AIJsonUtilities }; - // Chain with all supported types from Microsoft.Agents.AI.Abstractions. + // Chain in the resolvers from both AgentAbstractionsJsonUtilities and our source generated context. + // We want AgentAbstractionsJsonUtilities first to ensure any M.E.AI types are handled via its resolver. + options.TypeInfoResolverChain.Clear(); options.TypeInfoResolverChain.Add(AgentAbstractionsJsonUtilities.DefaultOptions.TypeInfoResolver!); + options.TypeInfoResolverChain.Add(JsonContext.Default.Options.TypeInfoResolver!); + if (JsonSerializer.IsReflectionEnabledByDefault) { options.Converters.Add(new JsonStringEnumConverter()); diff --git a/dotnet/tests/Microsoft.Agents.AI.Abstractions.UnitTests/InMemoryChatMessageStoreTests.cs b/dotnet/tests/Microsoft.Agents.AI.Abstractions.UnitTests/InMemoryChatMessageStoreTests.cs index fedd0ce591..4c793d17f4 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Abstractions.UnitTests/InMemoryChatMessageStoreTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Abstractions.UnitTests/InMemoryChatMessageStoreTests.cs @@ -114,6 +114,23 @@ public class InMemoryChatMessageStoreTests Assert.Equal("B", newStore[1].Text); } + [Fact] + public async Task SerializeAndDeserializeWorksWithExperimentalContentTypesAsync() + { + var store = new InMemoryChatMessageStore + { + new ChatMessage(ChatRole.User, [new FunctionApprovalRequestContent("call123", new FunctionCallContent("call123", "some_func"))]), + new ChatMessage(ChatRole.Assistant, [new FunctionApprovalResponseContent("call123", true, new FunctionCallContent("call123", "some_func"))]) + }; + + var jsonElement = store.Serialize(); + var newStore = new InMemoryChatMessageStore(jsonElement); + + Assert.Equal(2, newStore.Count); + Assert.IsType(newStore[0].Contents[0]); + Assert.IsType(newStore[1].Contents[0]); + } + [Fact] public async Task AddMessagesAsyncWithEmptyMessagesDoesNotChangeStoreAsync() {