diff --git a/dotnet/samples/AgentWebChat/AgentWebChat.AgentHost/Program.cs b/dotnet/samples/AgentWebChat/AgentWebChat.AgentHost/Program.cs index ab71949751..ff0c8df71f 100644 --- a/dotnet/samples/AgentWebChat/AgentWebChat.AgentHost/Program.cs +++ b/dotnet/samples/AgentWebChat/AgentWebChat.AgentHost/Program.cs @@ -94,8 +94,8 @@ app.UseExceptionHandler(); app.MapActors(); // attach a2a with simple message communication -app.AttachA2A(agentName: "pirate", path: "/a2a/pirate"); -app.AttachA2A(agentName: "knights-and-knaves", path: "/a2a/knights-and-knaves", agentCard: new() +app.MapA2A(agentName: "pirate", path: "/a2a/pirate"); +app.MapA2A(agentName: "knights-and-knaves", path: "/a2a/knights-and-knaves", agentCard: new() { Name = "Knights and Knaves", Description = "An agent that helps you solve the knights and knaves puzzle.", diff --git a/dotnet/src/Microsoft.Agents.AI.Hosting.A2A.AspNetCore/WebApplicationExtensions.cs b/dotnet/src/Microsoft.Agents.AI.Hosting.A2A.AspNetCore/WebApplicationExtensions.cs index e1642b5a9f..3b9d21ac5d 100644 --- a/dotnet/src/Microsoft.Agents.AI.Hosting.A2A.AspNetCore/WebApplicationExtensions.cs +++ b/dotnet/src/Microsoft.Agents.AI.Hosting.A2A.AspNetCore/WebApplicationExtensions.cs @@ -20,14 +20,14 @@ public static class WebApplicationExtensions /// The web application used to configure the pipeline and routes. /// The name of the agent to use for A2A protocol integration. /// The route group to use for A2A endpoints. - public static void AttachA2A(this WebApplication app, string agentName, string path) + public static void MapA2A(this WebApplication app, string agentName, string path) { var agent = app.Services.GetRequiredKeyedService(agentName); var loggerFactory = app.Services.GetRequiredService(); var actorClient = app.Services.GetRequiredService(); - var taskManager = agent.AttachA2A(actorClient, loggerFactory: loggerFactory); - app.AttachA2A(taskManager, path); + var taskManager = agent.MapA2A(actorClient, loggerFactory: loggerFactory); + app.MapA2A(taskManager, path); } /// @@ -37,7 +37,7 @@ public static class WebApplicationExtensions /// The name of the agent to use for A2A protocol integration. /// The route group to use for A2A endpoints. /// Agent card info to return on query. - public static void AttachA2A( + public static void MapA2A( this WebApplication app, string agentName, string path, @@ -47,8 +47,8 @@ public static class WebApplicationExtensions var loggerFactory = app.Services.GetRequiredService(); var actorClient = app.Services.GetRequiredService(); - var taskManager = agent.AttachA2A(actorClient, agentCard: agentCard, loggerFactory: loggerFactory); - app.AttachA2A(taskManager, path); + var taskManager = agent.MapA2A(actorClient, agentCard: agentCard, loggerFactory: loggerFactory); + app.MapA2A(taskManager, path); } /// @@ -58,7 +58,7 @@ public static class WebApplicationExtensions /// The web application used to configure the pipeline and routes. /// Pre-configured A2A TaskManager to use for A2A endpoints handling. /// The route group to use for A2A endpoints. - public static void AttachA2A(this WebApplication app, TaskManager taskManager, string path) + public static void MapA2A(this WebApplication app, TaskManager taskManager, string path) { // note: current SDK version registers multiple `.well-known/agent.json` handlers here. // it makes app return HTTP 500, but will be fixed once new A2A SDK is released. diff --git a/dotnet/src/Microsoft.Agents.AI.Hosting.A2A/AIAgentExtensions.cs b/dotnet/src/Microsoft.Agents.AI.Hosting.A2A/AIAgentExtensions.cs index 2f46df2be1..2de1200a49 100644 --- a/dotnet/src/Microsoft.Agents.AI.Hosting.A2A/AIAgentExtensions.cs +++ b/dotnet/src/Microsoft.Agents.AI.Hosting.A2A/AIAgentExtensions.cs @@ -22,13 +22,14 @@ public static class AIAgentExtensions /// Instance of to configure for A2A messaging. New instance will be created if not passed. /// The logger factory to use for creating instances. /// The configured . - public static TaskManager AttachA2A( + public static TaskManager MapA2A( this AIAgent agent, IActorClient actorClient, TaskManager? taskManager = null, ILoggerFactory? loggerFactory = null) { ArgumentNullException.ThrowIfNull(agent, nameof(agent)); + ArgumentNullException.ThrowIfNull(agent.Name, nameof(agent.Name)); ArgumentNullException.ThrowIfNull(actorClient, nameof(actorClient)); taskManager ??= new(); @@ -49,14 +50,14 @@ public static class AIAgentExtensions /// Instance of to configure for A2A messaging. New instance will be created if not passed. /// The logger factory to use for creating instances. /// The configured . - public static TaskManager AttachA2A( + public static TaskManager MapA2A( this AIAgent agent, IActorClient actorClient, AgentCard agentCard, TaskManager? taskManager = null, ILoggerFactory? loggerFactory = null) { - taskManager = agent.AttachA2A(actorClient, taskManager, loggerFactory); + taskManager = agent.MapA2A(actorClient, taskManager, loggerFactory); taskManager.OnAgentCardQuery += (context, query) => { diff --git a/dotnet/src/Microsoft.Agents.AI.Hosting.A2A/Converters/ActorEntitiesConverter.cs b/dotnet/src/Microsoft.Agents.AI.Hosting.A2A/Converters/ActorEntitiesConverter.cs index 8e7cead201..e20f43cf22 100644 --- a/dotnet/src/Microsoft.Agents.AI.Hosting.A2A/Converters/ActorEntitiesConverter.cs +++ b/dotnet/src/Microsoft.Agents.AI.Hosting.A2A/Converters/ActorEntitiesConverter.cs @@ -10,18 +10,13 @@ namespace Microsoft.Agents.AI.Hosting.A2A.Converters; internal static class ActorEntitiesConverter { - public static Message ToMessage(this ActorResponse response) + public static Message ToMessage(this AgentRunResponse response, string contextId) { - var agentRunResponse = - response.Data.Deserialize(AgentHostingJsonUtilities.DefaultOptions.GetTypeInfo(typeof(AgentRunResponse))) as AgentRunResponse ?? - throw new ArgumentException("The ActorResponse data could not be deserialized to an AgentRunResponse.", nameof(response)); - - var contextId = response.ActorId.Key; - var parts = agentRunResponse.Messages.ToParts(); + var parts = response.Messages.ToParts(); return new Message { - MessageId = response.MessageId ?? Guid.NewGuid().ToString("N"), + MessageId = response.ResponseId ?? Guid.NewGuid().ToString("N"), ContextId = contextId, Role = MessageRole.Agent, Parts = parts diff --git a/dotnet/src/Microsoft.Agents.AI.Hosting.A2A/Internal/A2AAgentWrapper.cs b/dotnet/src/Microsoft.Agents.AI.Hosting.A2A/Internal/A2AAgentWrapper.cs index a0b361ca62..f9a9f7387a 100644 --- a/dotnet/src/Microsoft.Agents.AI.Hosting.A2A/Internal/A2AAgentWrapper.cs +++ b/dotnet/src/Microsoft.Agents.AI.Hosting.A2A/Internal/A2AAgentWrapper.cs @@ -1,13 +1,13 @@ // Copyright (c) Microsoft. All rights reserved. using System; -using System.Text.Json; using System.Threading; using System.Threading.Tasks; using A2A; using Microsoft.Agents.AI.Hosting.A2A.Converters; using Microsoft.Agents.AI.Runtime; using Microsoft.Extensions.Logging; +using Microsoft.Shared.Diagnostics; namespace Microsoft.Agents.AI.Hosting.A2A.Internal; @@ -16,51 +16,26 @@ namespace Microsoft.Agents.AI.Hosting.A2A.Internal; /// internal sealed class A2AAgentWrapper { - private readonly AIAgent _innerAgent; - private readonly IActorClient _actorClient; + private readonly AgentProxy _agentProxy; public A2AAgentWrapper( IActorClient actorClient, AIAgent innerAgent, ILoggerFactory? loggerFactory = null) { - this._actorClient = actorClient; - this._innerAgent = innerAgent ?? throw new ArgumentNullException(nameof(innerAgent)); + Throw.IfNullOrEmpty(innerAgent.Name); + + this._agentProxy = new AgentProxy(innerAgent.Name, actorClient); } public async Task ProcessMessageAsync(MessageSendParams messageSendParams, CancellationToken cancellationToken) { var contextId = messageSendParams.Message.ContextId ?? Guid.NewGuid().ToString("N"); - var messageId = messageSendParams.Message.MessageId; - - var actorId = new ActorId(type: this.GetActorType(), key: contextId!); - - // Verify request does not exist already - var existingResponseHandle = await this._actorClient.GetResponseAsync(actorId, messageId, cancellationToken).ConfigureAwait(false); - var existingResponse = await existingResponseHandle.GetResponseAsync(cancellationToken).ConfigureAwait(false); - if (existingResponse.Status is RequestStatus.Completed or RequestStatus.Failed) - { - return existingResponse.ToMessage(); - } - - // here we know we did not yet send the request, so lets do it var chatMessages = messageSendParams.ToChatMessages(); - var runRequest = new AgentRunRequest - { - Messages = chatMessages - }; - var @params = JsonSerializer.SerializeToElement(runRequest, AgentHostingJsonUtilities.DefaultOptions.GetTypeInfo(typeof(AgentRunRequest))); - var requestHandle = await this._actorClient.SendRequestAsync(new ActorRequest(actorId, messageId, method: "Run" /* ?refer to const here? */, @params: @params), cancellationToken).ConfigureAwait(false); - var response = await requestHandle.GetResponseAsync(cancellationToken).ConfigureAwait(false); + var thread = this._agentProxy.GetNewThread(contextId); + var response = await this._agentProxy.RunAsync(messages: chatMessages, thread: thread, options: null, cancellationToken: cancellationToken).ConfigureAwait(false); - return response.ToMessage(); - } - - private ActorType GetActorType() - { - // agent is registered in DI via name - ArgumentException.ThrowIfNullOrEmpty(this._innerAgent.Name); - return new ActorType(this._innerAgent.Name); + return response.ToMessage(contextId); } } diff --git a/dotnet/src/Microsoft.Agents.AI.Hosting.A2A/Microsoft.Agents.AI.Hosting.A2A.csproj b/dotnet/src/Microsoft.Agents.AI.Hosting.A2A/Microsoft.Agents.AI.Hosting.A2A.csproj index 138cb8d9f5..3d343577f2 100644 --- a/dotnet/src/Microsoft.Agents.AI.Hosting.A2A/Microsoft.Agents.AI.Hosting.A2A.csproj +++ b/dotnet/src/Microsoft.Agents.AI.Hosting.A2A/Microsoft.Agents.AI.Hosting.A2A.csproj @@ -8,6 +8,10 @@ alpha + + true + +