diff --git a/dotnet/samples/HelloHttpApi/HelloHttpApi.ApiService/ActorFrameworkWebApplicationExtensions.cs b/dotnet/samples/HelloHttpApi/HelloHttpApi.ApiService/ActorFrameworkWebApplicationExtensions.cs index a84fd5edd0..2295bfc0e5 100644 --- a/dotnet/samples/HelloHttpApi/HelloHttpApi.ApiService/ActorFrameworkWebApplicationExtensions.cs +++ b/dotnet/samples/HelloHttpApi/HelloHttpApi.ApiService/ActorFrameworkWebApplicationExtensions.cs @@ -7,6 +7,8 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.AI; using Microsoft.Extensions.AI.Agents.Runtime; +namespace HelloHttpApi.ApiService; + internal static class ActorFrameworkWebApplicationExtensions { public static void MapAgents(this WebApplication app) diff --git a/dotnet/samples/HelloHttpApi/HelloHttpApi.ApiService/ChatClientAgentActor.cs b/dotnet/samples/HelloHttpApi/HelloHttpApi.ApiService/ChatClientAgentActor.cs index 973400fbfc..88bcd7968e 100644 --- a/dotnet/samples/HelloHttpApi/HelloHttpApi.ApiService/ChatClientAgentActor.cs +++ b/dotnet/samples/HelloHttpApi/HelloHttpApi.ApiService/ChatClientAgentActor.cs @@ -2,13 +2,16 @@ using System.Diagnostics; using System.Text.Json; -using System.Text.Json.Serialization.Metadata; -using HelloHttpApi.ApiService; using Microsoft.Extensions.AI; using Microsoft.Extensions.AI.Agents; using Microsoft.Extensions.AI.Agents.Runtime; -internal sealed class ChatClientAgentActor(AIAgent agent, JsonSerializerOptions jsonSerializerOptions, IActorRuntimeContext context, ILogger logger) : IActor +namespace HelloHttpApi.ApiService; + +internal sealed class ChatClientAgentActor( + AIAgent agent, + IActorRuntimeContext context, + ILogger logger) : IActor { private string? _etag; private ChatClientAgentThread? _thread; @@ -31,8 +34,7 @@ internal sealed class ChatClientAgentActor(AIAgent agent, JsonSerializerOptions if (threadResult.Value is { } threadJson) { // Deserialize the thread state if it exist - this._thread = threadJson.Deserialize( - (JsonTypeInfo)jsonSerializerOptions.GetTypeInfo(typeof(ChatClientAgentThread))); + this._thread = threadJson.Deserialize(ChatClientAgentActorJsonContext.Default.ChatClientAgentThread); } } @@ -76,8 +78,7 @@ internal sealed class ChatClientAgentActor(AIAgent agent, JsonSerializerOptions List? messages; if (message.Params is { } payload) { - var arg = payload.Deserialize( - (JsonTypeInfo)jsonSerializerOptions.GetTypeInfo(typeof(ChatClientAgentRunRequest))); + var arg = payload.Deserialize(ChatClientAgentActorJsonContext.Default.ChatClientAgentRunRequest); messages = arg?.Messages; } @@ -86,12 +87,11 @@ internal sealed class ChatClientAgentActor(AIAgent agent, JsonSerializerOptions Log.ProcessingAgentRequest(logger, requestId, context.ActorId.ToString(), messages.Count); try { - var typeInfo = (JsonTypeInfo)jsonSerializerOptions.GetTypeInfo(typeof(AgentRunResponseUpdate)); var i = 0; var updates = new List(); await foreach (var update in agent.RunStreamingAsync(messages, this._thread, cancellationToken: cancellationToken).ConfigureAwait(false)) { - var updateJson = JsonSerializer.SerializeToElement(update, typeInfo); + var updateJson = JsonSerializer.SerializeToElement(update, AgentAbstractionsJsonUtilities.DefaultOptions.GetTypeInfo(typeof(AgentRunResponseUpdate))); context.OnProgressUpdate(requestId, i++, updateJson); updates.Add(update); Log.AgentStreamingUpdate(logger, requestId, i); @@ -99,7 +99,7 @@ internal sealed class ChatClientAgentActor(AIAgent agent, JsonSerializerOptions var serializedRunResponse = JsonSerializer.SerializeToElement( updates.ToAgentRunResponse(), - (JsonTypeInfo)jsonSerializerOptions.GetTypeInfo(typeof(AgentRunResponse))); + AgentAbstractionsJsonUtilities.DefaultOptions.GetTypeInfo(typeof(AgentRunResponseUpdate))); var writeResponse = await context.WriteAsync( new(this._etag, [new UpdateRequestOperation(requestId, RequestStatus.Completed, serializedRunResponse)]), cancellationToken) .ConfigureAwait(false); diff --git a/dotnet/samples/HelloHttpApi/HelloHttpApi.ApiService/ChatClientAgentActorJsonContext.cs b/dotnet/samples/HelloHttpApi/HelloHttpApi.ApiService/ChatClientAgentActorJsonContext.cs new file mode 100644 index 0000000000..52671785b1 --- /dev/null +++ b/dotnet/samples/HelloHttpApi/HelloHttpApi.ApiService/ChatClientAgentActorJsonContext.cs @@ -0,0 +1,19 @@ +// Copyright (c) Microsoft. All rights reserved. + +using System.Text.Json; +using System.Text.Json.Serialization; +using Microsoft.Extensions.AI.Agents; + +namespace HelloHttpApi.ApiService; + +/// +/// Source-generated JSON type information for use by ChatClientAgentActor. +/// +[JsonSourceGenerationOptions( + JsonSerializerDefaults.Web, + UseStringEnumConverter = true, + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, + WriteIndented = false)] +[JsonSerializable(typeof(ChatClientAgentThread))] +[JsonSerializable(typeof(ChatClientAgentRunRequest))] +internal sealed partial class ChatClientAgentActorJsonContext : JsonSerializerContext; diff --git a/dotnet/samples/HelloHttpApi/HelloHttpApi.ApiService/HostApplicationBuilderAgentExtensions.cs b/dotnet/samples/HelloHttpApi/HelloHttpApi.ApiService/HostApplicationBuilderAgentExtensions.cs index 7f02ecbcc6..6d02023114 100644 --- a/dotnet/samples/HelloHttpApi/HelloHttpApi.ApiService/HostApplicationBuilderAgentExtensions.cs +++ b/dotnet/samples/HelloHttpApi/HelloHttpApi.ApiService/HostApplicationBuilderAgentExtensions.cs @@ -1,6 +1,5 @@ // Copyright (c) Microsoft. All rights reserved. -using System.Text.Json; using Microsoft.Agents.Orchestration; using Microsoft.Extensions.AI; using Microsoft.Extensions.AI.Agents; @@ -32,7 +31,6 @@ public static class HostApplicationBuilderAgentExtensions new ActorType(agentKey), (sp, ctx) => new ChatClientAgentActor( sp.GetRequiredKeyedService(agentKey), - sp.GetService() ?? JsonSerializerOptions.Web, ctx, sp.GetRequiredService>())); diff --git a/dotnet/samples/HelloHttpApi/HelloHttpApi.ApiService/Log.cs b/dotnet/samples/HelloHttpApi/HelloHttpApi.ApiService/Log.cs index 9255b38cba..7a61897cc9 100644 --- a/dotnet/samples/HelloHttpApi/HelloHttpApi.ApiService/Log.cs +++ b/dotnet/samples/HelloHttpApi/HelloHttpApi.ApiService/Log.cs @@ -1,7 +1,9 @@ // Copyright (c) Microsoft. All rights reserved. -using HelloHttpApi.ApiService; using Microsoft.Extensions.AI.Agents.Runtime; + +namespace HelloHttpApi.ApiService; + /// /// High-performance logging messages using LoggerMessage source generator. /// diff --git a/dotnet/src/Microsoft.Extensions.AI.Agents.Runtime.Abstractions/ActorId.cs b/dotnet/src/Microsoft.Extensions.AI.Agents.Runtime.Abstractions/ActorId.cs index fd70faaf83..a6a589a06a 100644 --- a/dotnet/src/Microsoft.Extensions.AI.Agents.Runtime.Abstractions/ActorId.cs +++ b/dotnet/src/Microsoft.Extensions.AI.Agents.Runtime.Abstractions/ActorId.cs @@ -58,16 +58,34 @@ public readonly struct ActorId : IEquatable /// /// Convert a string of the format "type/key" into an . /// - /// The actor ID string. + /// The actor ID string. /// An instance of . - public static ActorId Parse(string actorId) + public static ActorId Parse(string value) { - if (!KeyValueParser.TryParse(actorId, out string? type, out string? key)) + if (!TryParse(value, out var result)) { - throw new FormatException($"Invalid actor ID: '{actorId}'. Expected format is 'type/key'."); + throw new FormatException($"Invalid actor ID: '{value}'. Expected format is 'type/key'."); } - return new ActorId(type, key); + return result; + } + + private static bool TryParse(string input, out ActorId actorId) + { + if (!string.IsNullOrEmpty(input)) + { + int separatorIndex = input.IndexOf('/'); + if (separatorIndex >= 0) + { + var type = input.Substring(0, separatorIndex); + var key = input.Substring(separatorIndex + 1); + actorId = new ActorId(type, key); + return true; + } + } + + actorId = default; + return false; } /// diff --git a/dotnet/src/Microsoft.Extensions.AI.Agents.Runtime.Abstractions/JsonSerializerExtensions.cs b/dotnet/src/Microsoft.Extensions.AI.Agents.Runtime.Abstractions/JsonSerializerExtensions.cs deleted file mode 100644 index 5f01bcc57d..0000000000 --- a/dotnet/src/Microsoft.Extensions.AI.Agents.Runtime.Abstractions/JsonSerializerExtensions.cs +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -using System.Text.Json; -using System.Text.Json.Serialization; -using System.Text.Json.Serialization.Metadata; - -namespace Microsoft.Extensions.AI.Agents.Runtime; - -/// -/// Provides extension methods for JSON serialization with source generation support. -/// -internal static class JsonSerializerExtensions -{ - /// - /// Gets the JsonTypeInfo for a type, preferring the one from options if available, - /// otherwise falling back to the source-generated context. - /// - /// The type to get JsonTypeInfo for. - /// The JsonSerializerOptions to check first. - /// The fallback JsonSerializerContext to use if not found in options. - /// The JsonTypeInfo for the requested type. - public static JsonTypeInfo GetTypeInfo(this JsonSerializerOptions options, JsonSerializerContext fallbackContext) - { - // Try to get from the options first (if a context is configured) - if (options.TypeInfoResolver?.GetTypeInfo(typeof(T), options) is JsonTypeInfo typeInfo) - { - return typeInfo; - } - - // Fall back to the provided source-generated context - return (JsonTypeInfo)fallbackContext.GetTypeInfo(typeof(T))!; - } -} diff --git a/dotnet/src/Microsoft.Extensions.AI.Agents.Runtime.Abstractions/KeyValueParser.cs b/dotnet/src/Microsoft.Extensions.AI.Agents.Runtime.Abstractions/KeyValueParser.cs deleted file mode 100644 index 8bd840a45d..0000000000 --- a/dotnet/src/Microsoft.Extensions.AI.Agents.Runtime.Abstractions/KeyValueParser.cs +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -using System.Diagnostics.CodeAnalysis; - -namespace Microsoft.Extensions.AI.Agents.Runtime; - -/// -/// Provides helper methods for parsing key-value string representations. -/// -internal static class KeyValueParser -{ - /// - /// Parses a string in the format "key/value" into a tuple containing the key and value. - /// - public static bool TryParse(string input, [NotNullWhen(true)] out string? key, [NotNullWhen(true)] out string? value) - { - if (!string.IsNullOrEmpty(input)) - { - int separatorIndex = input.IndexOf('/'); - if (separatorIndex >= 0) - { - key = input.Substring(0, separatorIndex); - value = input.Substring(separatorIndex + 1); - return true; - } - } - - key = value = null; - return false; - } -} diff --git a/dotnet/src/Microsoft.Extensions.AI.Agents.Runtime/ActorRuntimeBuilder.cs b/dotnet/src/Microsoft.Extensions.AI.Agents.Runtime/ActorRuntimeBuilder.cs index 6caa4d4539..a26abdb7b2 100644 --- a/dotnet/src/Microsoft.Extensions.AI.Agents.Runtime/ActorRuntimeBuilder.cs +++ b/dotnet/src/Microsoft.Extensions.AI.Agents.Runtime/ActorRuntimeBuilder.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Text.Json; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; @@ -88,9 +87,8 @@ internal sealed class ActorRuntimeBuilder : IActorRuntimeBuilder services.AddSingleton(); services.AddSingleton(sp => { - var jsonSerializerOptions = sp.GetService() ?? new(); var actorStateStorage = sp.GetRequiredService(); - return new InProcessActorRuntime(sp, this.ActorFactories, actorStateStorage, jsonSerializerOptions); + return new InProcessActorRuntime(sp, this.ActorFactories, actorStateStorage); }); } } diff --git a/dotnet/src/Microsoft.Extensions.AI.Agents.Runtime/InProcessActorContext.cs b/dotnet/src/Microsoft.Extensions.AI.Agents.Runtime/InProcessActorContext.cs index 5735f1ad88..e28a61dcb1 100644 --- a/dotnet/src/Microsoft.Extensions.AI.Agents.Runtime/InProcessActorContext.cs +++ b/dotnet/src/Microsoft.Extensions.AI.Agents.Runtime/InProcessActorContext.cs @@ -384,7 +384,7 @@ internal sealed class InProcessActorContext : IActorRuntimeContext, IAsyncDispos { ActorId = context.ActorId, MessageId = entry.Request.MessageId, - Data = JsonSerializer.SerializeToElement($"Error: {exception.Message}", context._runtime.JsonSerializerOptions.GetTypeInfo(ActorRuntimeJsonContext.Default)), + Data = JsonSerializer.SerializeToElement($"Error: {exception.Message}", ActorRuntimeJsonContext.Default.String), Status = RequestStatus.Failed, }; } diff --git a/dotnet/src/Microsoft.Extensions.AI.Agents.Runtime/InProcessActorRuntime.cs b/dotnet/src/Microsoft.Extensions.AI.Agents.Runtime/InProcessActorRuntime.cs index 360c3ae7fa..8609799a47 100644 --- a/dotnet/src/Microsoft.Extensions.AI.Agents.Runtime/InProcessActorRuntime.cs +++ b/dotnet/src/Microsoft.Extensions.AI.Agents.Runtime/InProcessActorRuntime.cs @@ -5,7 +5,6 @@ using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.Metrics; -using System.Text.Json; using System.Threading; using System.Threading.Tasks; using static Microsoft.Extensions.AI.Agents.Runtime.ActivityExtensions; @@ -16,8 +15,7 @@ namespace Microsoft.Extensions.AI.Agents.Runtime; internal sealed class InProcessActorRuntime( IServiceProvider serviceProvider, IReadOnlyDictionary> actorFactories, - IActorStateStorage storage, - JsonSerializerOptions jsonSerializerOptions) + IActorStateStorage storage) { private static readonly ActivitySource ActivitySource = new(ActorRuntimeOpenTelemetryConsts.InProcessSourceName); private static readonly Meter Meter = new(ActorRuntimeOpenTelemetryConsts.InProcessSourceName); @@ -38,7 +36,6 @@ internal sealed class InProcessActorRuntime( private readonly ConcurrentDictionary _actors = []; public IActorStateStorage Storage { get; } = storage; - public JsonSerializerOptions JsonSerializerOptions { get; } = jsonSerializerOptions; public IServiceProvider Services { get; } = serviceProvider; internal InProcessActorContext GetOrCreateActor(ActorId actorId) diff --git a/dotnet/src/Microsoft.Extensions.AI.Agents.Runtime/JsonSerializerExtensions.cs b/dotnet/src/Microsoft.Extensions.AI.Agents.Runtime/JsonSerializerExtensions.cs deleted file mode 100644 index 5f01bcc57d..0000000000 --- a/dotnet/src/Microsoft.Extensions.AI.Agents.Runtime/JsonSerializerExtensions.cs +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -using System.Text.Json; -using System.Text.Json.Serialization; -using System.Text.Json.Serialization.Metadata; - -namespace Microsoft.Extensions.AI.Agents.Runtime; - -/// -/// Provides extension methods for JSON serialization with source generation support. -/// -internal static class JsonSerializerExtensions -{ - /// - /// Gets the JsonTypeInfo for a type, preferring the one from options if available, - /// otherwise falling back to the source-generated context. - /// - /// The type to get JsonTypeInfo for. - /// The JsonSerializerOptions to check first. - /// The fallback JsonSerializerContext to use if not found in options. - /// The JsonTypeInfo for the requested type. - public static JsonTypeInfo GetTypeInfo(this JsonSerializerOptions options, JsonSerializerContext fallbackContext) - { - // Try to get from the options first (if a context is configured) - if (options.TypeInfoResolver?.GetTypeInfo(typeof(T), options) is JsonTypeInfo typeInfo) - { - return typeInfo; - } - - // Fall back to the provided source-generated context - return (JsonTypeInfo)fallbackContext.GetTypeInfo(typeof(T))!; - } -}