diff --git a/dotnet/agent-framework-dotnet.slnx b/dotnet/agent-framework-dotnet.slnx
index 56211e457f..3e845c75d9 100644
--- a/dotnet/agent-framework-dotnet.slnx
+++ b/dotnet/agent-framework-dotnet.slnx
@@ -314,7 +314,7 @@
-
+
diff --git a/dotnet/src/Microsoft.Agents.AI.Hosting.A2A.AspNetCore/EndpointRouteBuilderExtensions.cs b/dotnet/src/Microsoft.Agents.AI.Hosting.A2A.AspNetCore/EndpointRouteBuilderExtensions.cs
index cae9801148..b6b9c4da48 100644
--- a/dotnet/src/Microsoft.Agents.AI.Hosting.A2A.AspNetCore/EndpointRouteBuilderExtensions.cs
+++ b/dotnet/src/Microsoft.Agents.AI.Hosting.A2A.AspNetCore/EndpointRouteBuilderExtensions.cs
@@ -18,6 +18,21 @@ namespace Microsoft.AspNetCore.Builder;
///
public static class MicrosoftAgentAIHostingA2AEndpointRouteBuilderExtensions
{
+ ///
+ /// Attaches A2A (Agent2Agent) communication capabilities via Message processing to the specified web application.
+ ///
+ /// The to add the A2A endpoints to.
+ /// The configuration builder for .
+ /// The route group to use for A2A endpoints.
+ /// Configured for A2A integration.
+ ///
+ /// This method can be used to access A2A agents that support the
+ /// Curated Registries (Catalog-Based Discovery)
+ /// discovery mechanism.
+ ///
+ public static IEndpointConventionBuilder MapA2A(this IEndpointRouteBuilder endpoints, IHostedAgentBuilder agentBuilder, string path)
+ => endpoints.MapA2A(agentBuilder, path, _ => { });
+
///
/// Attaches A2A (Agent2Agent) communication capabilities via Message processing to the specified web application.
///
@@ -28,6 +43,25 @@ public static class MicrosoftAgentAIHostingA2AEndpointRouteBuilderExtensions
public static IEndpointConventionBuilder MapA2A(this IEndpointRouteBuilder endpoints, string agentName, string path)
=> endpoints.MapA2A(agentName, path, _ => { });
+ ///
+ /// Attaches A2A (Agent2Agent) communication capabilities via Message processing to the specified web application.
+ ///
+ /// The to add the A2A endpoints to.
+ /// The configuration builder for .
+ /// The route group to use for A2A endpoints.
+ /// The callback to configure .
+ /// Configured for A2A integration.
+ ///
+ /// This method can be used to access A2A agents that support the
+ /// Curated Registries (Catalog-Based Discovery)
+ /// discovery mechanism.
+ ///
+ public static IEndpointConventionBuilder MapA2A(this IEndpointRouteBuilder endpoints, IHostedAgentBuilder agentBuilder, string path, Action configureTaskManager)
+ {
+ ArgumentNullException.ThrowIfNull(agentBuilder);
+ return endpoints.MapA2A(agentBuilder.Name, path, configureTaskManager);
+ }
+
///
/// Attaches A2A (Agent2Agent) communication capabilities via Message processing to the specified web application.
///
@@ -38,10 +72,27 @@ public static class MicrosoftAgentAIHostingA2AEndpointRouteBuilderExtensions
/// Configured for A2A integration.
public static IEndpointConventionBuilder MapA2A(this IEndpointRouteBuilder endpoints, string agentName, string path, Action configureTaskManager)
{
+ ArgumentNullException.ThrowIfNull(endpoints);
var agent = endpoints.ServiceProvider.GetRequiredKeyedService(agentName);
return endpoints.MapA2A(agent, path, configureTaskManager);
}
+ ///
+ /// Attaches A2A (Agent2Agent) communication capabilities via Message processing to the specified web application.
+ ///
+ /// The to add the A2A endpoints to.
+ /// The configuration builder for .
+ /// The route group to use for A2A endpoints.
+ /// Agent card info to return on query.
+ /// Configured for A2A integration.
+ ///
+ /// This method can be used to access A2A agents that support the
+ /// Curated Registries (Catalog-Based Discovery)
+ /// discovery mechanism.
+ ///
+ public static IEndpointConventionBuilder MapA2A(this IEndpointRouteBuilder endpoints, IHostedAgentBuilder agentBuilder, string path, AgentCard agentCard)
+ => endpoints.MapA2A(agentBuilder, path, agentCard, _ => { });
+
///
/// Attaches A2A (Agent2Agent) communication capabilities via Message processing to the specified web application.
///
@@ -58,6 +109,26 @@ public static class MicrosoftAgentAIHostingA2AEndpointRouteBuilderExtensions
public static IEndpointConventionBuilder MapA2A(this IEndpointRouteBuilder endpoints, string agentName, string path, AgentCard agentCard)
=> endpoints.MapA2A(agentName, path, agentCard, _ => { });
+ ///
+ /// Attaches A2A (Agent2Agent) communication capabilities via Message processing to the specified web application.
+ ///
+ /// The to add the A2A endpoints to.
+ /// The configuration builder for .
+ /// The route group to use for A2A endpoints.
+ /// Agent card info to return on query.
+ /// The callback to configure .
+ /// Configured for A2A integration.
+ ///
+ /// This method can be used to access A2A agents that support the
+ /// Curated Registries (Catalog-Based Discovery)
+ /// discovery mechanism.
+ ///
+ public static IEndpointConventionBuilder MapA2A(this IEndpointRouteBuilder endpoints, IHostedAgentBuilder agentBuilder, string path, AgentCard agentCard, Action configureTaskManager)
+ {
+ ArgumentNullException.ThrowIfNull(agentBuilder);
+ return endpoints.MapA2A(agentBuilder.Name, path, agentCard, configureTaskManager);
+ }
+
///
/// Attaches A2A (Agent2Agent) communication capabilities via Message processing to the specified web application.
///
@@ -74,6 +145,7 @@ public static class MicrosoftAgentAIHostingA2AEndpointRouteBuilderExtensions
///
public static IEndpointConventionBuilder MapA2A(this IEndpointRouteBuilder endpoints, string agentName, string path, AgentCard agentCard, Action configureTaskManager)
{
+ ArgumentNullException.ThrowIfNull(endpoints);
var agent = endpoints.ServiceProvider.GetRequiredKeyedService(agentName);
return endpoints.MapA2A(agent, path, agentCard, configureTaskManager);
}
@@ -98,6 +170,9 @@ public static class MicrosoftAgentAIHostingA2AEndpointRouteBuilderExtensions
/// Configured for A2A integration.
public static IEndpointConventionBuilder MapA2A(this IEndpointRouteBuilder endpoints, AIAgent agent, string path, Action configureTaskManager)
{
+ ArgumentNullException.ThrowIfNull(endpoints);
+ ArgumentNullException.ThrowIfNull(agent);
+
var loggerFactory = endpoints.ServiceProvider.GetRequiredService();
var agentThreadStore = endpoints.ServiceProvider.GetKeyedService(agent.Name);
var taskManager = agent.MapA2A(loggerFactory: loggerFactory, agentThreadStore: agentThreadStore);
@@ -139,6 +214,9 @@ public static class MicrosoftAgentAIHostingA2AEndpointRouteBuilderExtensions
///
public static IEndpointConventionBuilder MapA2A(this IEndpointRouteBuilder endpoints, AIAgent agent, string path, AgentCard agentCard, Action configureTaskManager)
{
+ ArgumentNullException.ThrowIfNull(endpoints);
+ ArgumentNullException.ThrowIfNull(agent);
+
var loggerFactory = endpoints.ServiceProvider.GetRequiredService();
var agentThreadStore = endpoints.ServiceProvider.GetKeyedService(agent.Name);
var taskManager = agent.MapA2A(agentCard: agentCard, agentThreadStore: agentThreadStore, loggerFactory: loggerFactory);
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 5076e25c05..f300483f63 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
@@ -29,6 +29,6 @@
-
+
diff --git a/dotnet/src/Microsoft.Agents.AI.Hosting.OpenAI/EndpointRouteBuilderExtensions.Responses.cs b/dotnet/src/Microsoft.Agents.AI.Hosting.OpenAI/EndpointRouteBuilderExtensions.Responses.cs
index f4159011cf..9a395b9b12 100644
--- a/dotnet/src/Microsoft.Agents.AI.Hosting.OpenAI/EndpointRouteBuilderExtensions.Responses.cs
+++ b/dotnet/src/Microsoft.Agents.AI.Hosting.OpenAI/EndpointRouteBuilderExtensions.Responses.cs
@@ -3,6 +3,7 @@
using System;
using System.Diagnostics.CodeAnalysis;
using Microsoft.Agents.AI;
+using Microsoft.Agents.AI.Hosting;
using Microsoft.Agents.AI.Hosting.OpenAI;
using Microsoft.Agents.AI.Hosting.OpenAI.Conversations;
using Microsoft.Agents.AI.Hosting.OpenAI.Responses;
@@ -17,6 +18,29 @@ namespace Microsoft.AspNetCore.Builder;
///
public static partial class MicrosoftAgentAIHostingOpenAIEndpointRouteBuilderExtensions
{
+ ///
+ /// Maps OpenAI Responses API endpoints to the specified for the given .
+ ///
+ /// The to add the OpenAI Responses endpoints to.
+ /// The builder for to map the OpenAI Responses endpoints for.
+ public static IEndpointConventionBuilder MapOpenAIResponses(this IEndpointRouteBuilder endpoints, IHostedAgentBuilder agentBuilder)
+ => MapOpenAIResponses(endpoints, agentBuilder, path: null);
+
+ ///
+ /// Maps OpenAI Responses API endpoints to the specified for the given .
+ ///
+ /// The to add the OpenAI Responses endpoints to.
+ /// The builder for to map the OpenAI Responses endpoints for.
+ /// Custom route path for the OpenAI Responses endpoint.
+ public static IEndpointConventionBuilder MapOpenAIResponses(this IEndpointRouteBuilder endpoints, IHostedAgentBuilder agentBuilder, string? path)
+ {
+ ArgumentNullException.ThrowIfNull(endpoints);
+ ArgumentNullException.ThrowIfNull(agentBuilder);
+
+ var agent = endpoints.ServiceProvider.GetRequiredKeyedService(agentBuilder.Name);
+ return MapOpenAIResponses(endpoints, agent, path);
+ }
+
///
/// Maps OpenAI Responses API endpoints to the specified for the given .
///
diff --git a/dotnet/tests/Microsoft.Agents.AI.Hosting.A2A.Tests/Converters/MessageConverterTests.cs b/dotnet/tests/Microsoft.Agents.AI.Hosting.A2A.UnitTests/Converters/MessageConverterTests.cs
similarity index 97%
rename from dotnet/tests/Microsoft.Agents.AI.Hosting.A2A.Tests/Converters/MessageConverterTests.cs
rename to dotnet/tests/Microsoft.Agents.AI.Hosting.A2A.UnitTests/Converters/MessageConverterTests.cs
index 81ce582870..69eaf3a535 100644
--- a/dotnet/tests/Microsoft.Agents.AI.Hosting.A2A.Tests/Converters/MessageConverterTests.cs
+++ b/dotnet/tests/Microsoft.Agents.AI.Hosting.A2A.UnitTests/Converters/MessageConverterTests.cs
@@ -5,7 +5,7 @@ using A2A;
using Microsoft.Agents.AI.Hosting.A2A.Converters;
using Microsoft.Extensions.AI;
-namespace Microsoft.Agents.AI.Hosting.A2A.Tests.Converters;
+namespace Microsoft.Agents.AI.Hosting.A2A.UnitTests.Converters;
public class MessageConverterTests
{
diff --git a/dotnet/tests/Microsoft.Agents.AI.Hosting.A2A.UnitTests/EndpointRouteA2ABuilderExtensionsTests.cs b/dotnet/tests/Microsoft.Agents.AI.Hosting.A2A.UnitTests/EndpointRouteA2ABuilderExtensionsTests.cs
new file mode 100644
index 0000000000..1ae0dda908
--- /dev/null
+++ b/dotnet/tests/Microsoft.Agents.AI.Hosting.A2A.UnitTests/EndpointRouteA2ABuilderExtensionsTests.cs
@@ -0,0 +1,502 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System;
+using System.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
+using A2A;
+using Microsoft.AspNetCore.Builder;
+using Microsoft.Extensions.AI;
+using Microsoft.Extensions.DependencyInjection;
+
+namespace Microsoft.Agents.AI.Hosting.A2A.UnitTests;
+
+///
+/// Tests for MicrosoftAgentAIHostingA2AEndpointRouteBuilderExtensions.MapA2A method.
+///
+public sealed class EndpointRouteA2ABuilderExtensionsTests
+{
+ ///
+ /// Verifies that MapA2A throws ArgumentNullException for null endpoints.
+ ///
+ [Fact]
+ public void MapA2A_WithAgentBuilder_NullEndpoints_ThrowsArgumentNullException()
+ {
+ // Arrange
+ AspNetCore.Routing.IEndpointRouteBuilder endpoints = null!;
+ WebApplicationBuilder builder = WebApplication.CreateBuilder();
+ IChatClient mockChatClient = new DummyChatClient();
+ builder.Services.AddKeyedSingleton("chat-client", mockChatClient);
+ IHostedAgentBuilder agentBuilder = builder.AddAIAgent("agent", "Instructions", chatClientServiceKey: "chat-client");
+
+ // Act & Assert
+ ArgumentNullException exception = Assert.Throws(() =>
+ endpoints.MapA2A(agentBuilder, "/a2a"));
+
+ Assert.Equal("endpoints", exception.ParamName);
+ }
+
+ ///
+ /// Verifies that MapA2A throws ArgumentNullException for null agentBuilder.
+ ///
+ [Fact]
+ public void MapA2A_WithAgentBuilder_NullAgentBuilder_ThrowsArgumentNullException()
+ {
+ // Arrange
+ WebApplicationBuilder builder = WebApplication.CreateBuilder();
+ IChatClient mockChatClient = new DummyChatClient();
+ builder.Services.AddKeyedSingleton("chat-client", mockChatClient);
+ builder.AddAIAgent("agent", "Instructions", chatClientServiceKey: "chat-client");
+ builder.Services.AddLogging();
+ using WebApplication app = builder.Build();
+ IHostedAgentBuilder agentBuilder = null!;
+
+ // Act & Assert
+ ArgumentNullException exception = Assert.Throws(() =>
+ app.MapA2A(agentBuilder, "/a2a"));
+
+ Assert.Equal("agentBuilder", exception.ParamName);
+ }
+
+ ///
+ /// Verifies that MapA2A with IHostedAgentBuilder correctly maps the agent with default task manager configuration.
+ ///
+ [Fact]
+ public void MapA2A_WithAgentBuilder_DefaultConfiguration_Succeeds()
+ {
+ // Arrange
+ WebApplicationBuilder builder = WebApplication.CreateBuilder();
+ IChatClient mockChatClient = new DummyChatClient();
+ builder.Services.AddKeyedSingleton("chat-client", mockChatClient);
+ IHostedAgentBuilder agentBuilder = builder.AddAIAgent("agent", "Instructions", chatClientServiceKey: "chat-client");
+ builder.Services.AddLogging();
+ using WebApplication app = builder.Build();
+
+ // Act & Assert - Should not throw
+ var result = app.MapA2A(agentBuilder, "/a2a");
+ Assert.NotNull(result);
+ Assert.NotNull(app);
+ }
+
+ ///
+ /// Verifies that MapA2A with IHostedAgentBuilder and custom task manager configuration succeeds.
+ ///
+ [Fact]
+ public void MapA2A_WithAgentBuilder_CustomTaskManagerConfiguration_Succeeds()
+ {
+ // Arrange
+ WebApplicationBuilder builder = WebApplication.CreateBuilder();
+ IChatClient mockChatClient = new DummyChatClient();
+ builder.Services.AddKeyedSingleton("chat-client", mockChatClient);
+ IHostedAgentBuilder agentBuilder = builder.AddAIAgent("agent", "Instructions", chatClientServiceKey: "chat-client");
+ builder.Services.AddLogging();
+ using WebApplication app = builder.Build();
+
+ // Act & Assert - Should not throw
+ var result = app.MapA2A(agentBuilder, "/a2a", taskManager => { });
+ Assert.NotNull(result);
+ Assert.NotNull(app);
+ }
+
+ ///
+ /// Verifies that MapA2A with IHostedAgentBuilder and agent card succeeds.
+ ///
+ [Fact]
+ public void MapA2A_WithAgentBuilder_WithAgentCard_Succeeds()
+ {
+ // Arrange
+ WebApplicationBuilder builder = WebApplication.CreateBuilder();
+ IChatClient mockChatClient = new DummyChatClient();
+ builder.Services.AddKeyedSingleton("chat-client", mockChatClient);
+ IHostedAgentBuilder agentBuilder = builder.AddAIAgent("agent", "Instructions", chatClientServiceKey: "chat-client");
+ builder.Services.AddLogging();
+ using WebApplication app = builder.Build();
+
+ var agentCard = new AgentCard
+ {
+ Name = "Test Agent",
+ Description = "A test agent for A2A communication"
+ };
+
+ // Act & Assert - Should not throw
+ var result = app.MapA2A(agentBuilder, "/a2a", agentCard);
+ Assert.NotNull(result);
+ Assert.NotNull(app);
+ }
+
+ ///
+ /// Verifies that MapA2A with IHostedAgentBuilder, agent card, and custom task manager configuration succeeds.
+ ///
+ [Fact]
+ public void MapA2A_WithAgentBuilder_WithAgentCardAndCustomConfiguration_Succeeds()
+ {
+ // Arrange
+ WebApplicationBuilder builder = WebApplication.CreateBuilder();
+ IChatClient mockChatClient = new DummyChatClient();
+ builder.Services.AddKeyedSingleton("chat-client", mockChatClient);
+ IHostedAgentBuilder agentBuilder = builder.AddAIAgent("agent", "Instructions", chatClientServiceKey: "chat-client");
+ builder.Services.AddLogging();
+ using WebApplication app = builder.Build();
+
+ var agentCard = new AgentCard
+ {
+ Name = "Test Agent",
+ Description = "A test agent for A2A communication"
+ };
+
+ // Act & Assert - Should not throw
+ var result = app.MapA2A(agentBuilder, "/a2a", agentCard, taskManager => { });
+ Assert.NotNull(result);
+ Assert.NotNull(app);
+ }
+
+ ///
+ /// Verifies that MapA2A throws ArgumentNullException for null endpoints when using string agent name.
+ ///
+ [Fact]
+ public void MapA2A_WithAgentName_NullEndpoints_ThrowsArgumentNullException()
+ {
+ // Arrange
+ AspNetCore.Routing.IEndpointRouteBuilder endpoints = null!;
+
+ // Act & Assert
+ ArgumentNullException exception = Assert.Throws(() =>
+ endpoints.MapA2A("agent", "/a2a"));
+
+ Assert.Equal("endpoints", exception.ParamName);
+ }
+
+ ///
+ /// Verifies that MapA2A with string agent name correctly maps the agent.
+ ///
+ [Fact]
+ public void MapA2A_WithAgentName_DefaultConfiguration_Succeeds()
+ {
+ // Arrange
+ WebApplicationBuilder builder = WebApplication.CreateBuilder();
+ IChatClient mockChatClient = new DummyChatClient();
+ builder.Services.AddKeyedSingleton("chat-client", mockChatClient);
+ builder.AddAIAgent("agent", "Instructions", chatClientServiceKey: "chat-client");
+ builder.Services.AddLogging();
+ using WebApplication app = builder.Build();
+
+ // Act & Assert - Should not throw
+ var result = app.MapA2A("agent", "/a2a");
+ Assert.NotNull(result);
+ Assert.NotNull(app);
+ }
+
+ ///
+ /// Verifies that MapA2A with string agent name and custom task manager configuration succeeds.
+ ///
+ [Fact]
+ public void MapA2A_WithAgentName_CustomTaskManagerConfiguration_Succeeds()
+ {
+ // Arrange
+ WebApplicationBuilder builder = WebApplication.CreateBuilder();
+ IChatClient mockChatClient = new DummyChatClient();
+ builder.Services.AddKeyedSingleton("chat-client", mockChatClient);
+ builder.AddAIAgent("agent", "Instructions", chatClientServiceKey: "chat-client");
+ builder.Services.AddLogging();
+ using WebApplication app = builder.Build();
+
+ // Act & Assert - Should not throw
+ var result = app.MapA2A("agent", "/a2a", taskManager => { });
+ Assert.NotNull(result);
+ Assert.NotNull(app);
+ }
+
+ ///
+ /// Verifies that MapA2A with string agent name and agent card succeeds.
+ ///
+ [Fact]
+ public void MapA2A_WithAgentName_WithAgentCard_Succeeds()
+ {
+ // Arrange
+ WebApplicationBuilder builder = WebApplication.CreateBuilder();
+ IChatClient mockChatClient = new DummyChatClient();
+ builder.Services.AddKeyedSingleton("chat-client", mockChatClient);
+ builder.AddAIAgent("agent", "Instructions", chatClientServiceKey: "chat-client");
+ builder.Services.AddLogging();
+ using WebApplication app = builder.Build();
+
+ var agentCard = new AgentCard
+ {
+ Name = "Test Agent",
+ Description = "A test agent for A2A communication"
+ };
+
+ // Act & Assert - Should not throw
+ var result = app.MapA2A("agent", "/a2a", agentCard);
+ Assert.NotNull(result);
+ Assert.NotNull(app);
+ }
+
+ ///
+ /// Verifies that MapA2A with string agent name, agent card, and custom task manager configuration succeeds.
+ ///
+ [Fact]
+ public void MapA2A_WithAgentName_WithAgentCardAndCustomConfiguration_Succeeds()
+ {
+ // Arrange
+ WebApplicationBuilder builder = WebApplication.CreateBuilder();
+ IChatClient mockChatClient = new DummyChatClient();
+ builder.Services.AddKeyedSingleton("chat-client", mockChatClient);
+ builder.AddAIAgent("agent", "Instructions", chatClientServiceKey: "chat-client");
+ builder.Services.AddLogging();
+ using WebApplication app = builder.Build();
+
+ var agentCard = new AgentCard
+ {
+ Name = "Test Agent",
+ Description = "A test agent for A2A communication"
+ };
+
+ // Act & Assert - Should not throw
+ var result = app.MapA2A("agent", "/a2a", agentCard, taskManager => { });
+ Assert.NotNull(result);
+ Assert.NotNull(app);
+ }
+
+ ///
+ /// Verifies that MapA2A throws ArgumentNullException for null endpoints when using AIAgent.
+ ///
+ [Fact]
+ public void MapA2A_WithAIAgent_NullEndpoints_ThrowsArgumentNullException()
+ {
+ // Arrange
+ AspNetCore.Routing.IEndpointRouteBuilder endpoints = null!;
+
+ // Act & Assert
+ ArgumentNullException exception = Assert.Throws(() =>
+ endpoints.MapA2A((AIAgent)null!, "/a2a"));
+
+ Assert.Equal("endpoints", exception.ParamName);
+ }
+
+ ///
+ /// Verifies that MapA2A with AIAgent correctly maps the agent.
+ ///
+ [Fact]
+ public void MapA2A_WithAIAgent_DefaultConfiguration_Succeeds()
+ {
+ // Arrange
+ WebApplicationBuilder builder = WebApplication.CreateBuilder();
+ IChatClient mockChatClient = new DummyChatClient();
+ builder.Services.AddKeyedSingleton("chat-client", mockChatClient);
+ builder.AddAIAgent("agent", "Instructions", chatClientServiceKey: "chat-client");
+ builder.Services.AddLogging();
+ using WebApplication app = builder.Build();
+ AIAgent agent = app.Services.GetRequiredKeyedService("agent");
+
+ // Act & Assert - Should not throw
+ var result = app.MapA2A(agent, "/a2a");
+ Assert.NotNull(result);
+ Assert.NotNull(app);
+ }
+
+ ///
+ /// Verifies that MapA2A with AIAgent and custom task manager configuration succeeds.
+ ///
+ [Fact]
+ public void MapA2A_WithAIAgent_CustomTaskManagerConfiguration_Succeeds()
+ {
+ // Arrange
+ WebApplicationBuilder builder = WebApplication.CreateBuilder();
+ IChatClient mockChatClient = new DummyChatClient();
+ builder.Services.AddKeyedSingleton("chat-client", mockChatClient);
+ builder.AddAIAgent("agent", "Instructions", chatClientServiceKey: "chat-client");
+ builder.Services.AddLogging();
+ using WebApplication app = builder.Build();
+ AIAgent agent = app.Services.GetRequiredKeyedService("agent");
+
+ // Act & Assert - Should not throw
+ var result = app.MapA2A(agent, "/a2a", taskManager => { });
+ Assert.NotNull(result);
+ Assert.NotNull(app);
+ }
+
+ ///
+ /// Verifies that MapA2A with AIAgent and agent card succeeds.
+ ///
+ [Fact]
+ public void MapA2A_WithAIAgent_WithAgentCard_Succeeds()
+ {
+ // Arrange
+ WebApplicationBuilder builder = WebApplication.CreateBuilder();
+ IChatClient mockChatClient = new DummyChatClient();
+ builder.Services.AddKeyedSingleton("chat-client", mockChatClient);
+ builder.AddAIAgent("agent", "Instructions", chatClientServiceKey: "chat-client");
+ builder.Services.AddLogging();
+ using WebApplication app = builder.Build();
+ AIAgent agent = app.Services.GetRequiredKeyedService("agent");
+
+ var agentCard = new AgentCard
+ {
+ Name = "Test Agent",
+ Description = "A test agent for A2A communication"
+ };
+
+ // Act & Assert - Should not throw
+ var result = app.MapA2A(agent, "/a2a", agentCard);
+ Assert.NotNull(result);
+ Assert.NotNull(app);
+ }
+
+ ///
+ /// Verifies that MapA2A with AIAgent, agent card, and custom task manager configuration succeeds.
+ ///
+ [Fact]
+ public void MapA2A_WithAIAgent_WithAgentCardAndCustomConfiguration_Succeeds()
+ {
+ // Arrange
+ WebApplicationBuilder builder = WebApplication.CreateBuilder();
+ IChatClient mockChatClient = new DummyChatClient();
+ builder.Services.AddKeyedSingleton("chat-client", mockChatClient);
+ builder.AddAIAgent("agent", "Instructions", chatClientServiceKey: "chat-client");
+ builder.Services.AddLogging();
+ using WebApplication app = builder.Build();
+ AIAgent agent = app.Services.GetRequiredKeyedService("agent");
+
+ var agentCard = new AgentCard
+ {
+ Name = "Test Agent",
+ Description = "A test agent for A2A communication"
+ };
+
+ // Act & Assert - Should not throw
+ var result = app.MapA2A(agent, "/a2a", agentCard, taskManager => { });
+ Assert.NotNull(result);
+ Assert.NotNull(app);
+ }
+
+ ///
+ /// Verifies that MapA2A throws ArgumentNullException for null endpoints when using ITaskManager.
+ ///
+ [Fact]
+ public void MapA2A_WithTaskManager_NullEndpoints_ThrowsArgumentNullException()
+ {
+ // Arrange
+ AspNetCore.Routing.IEndpointRouteBuilder endpoints = null!;
+ ITaskManager taskManager = null!;
+
+ // Act & Assert
+ ArgumentNullException exception = Assert.Throws(() =>
+ endpoints.MapA2A(taskManager, "/a2a"));
+
+ Assert.Equal("endpoints", exception.ParamName);
+ }
+
+ ///
+ /// Verifies that multiple agents can be mapped to different paths.
+ ///
+ [Fact]
+ public void MapA2A_MultipleAgents_Succeeds()
+ {
+ // Arrange
+ WebApplicationBuilder builder = WebApplication.CreateBuilder();
+ IChatClient mockChatClient = new DummyChatClient();
+ builder.Services.AddKeyedSingleton("chat-client", mockChatClient);
+ IHostedAgentBuilder agent1Builder = builder.AddAIAgent("agent1", "Instructions1", chatClientServiceKey: "chat-client");
+ IHostedAgentBuilder agent2Builder = builder.AddAIAgent("agent2", "Instructions2", chatClientServiceKey: "chat-client");
+ builder.Services.AddLogging();
+ using WebApplication app = builder.Build();
+
+ // Act & Assert - Should not throw
+ app.MapA2A(agent1Builder, "/a2a/agent1");
+ app.MapA2A(agent2Builder, "/a2a/agent2");
+ Assert.NotNull(app);
+ }
+
+ ///
+ /// Verifies that custom paths can be specified for A2A endpoints.
+ ///
+ [Fact]
+ public void MapA2A_WithCustomPath_AcceptsValidPath()
+ {
+ // Arrange
+ WebApplicationBuilder builder = WebApplication.CreateBuilder();
+ IChatClient mockChatClient = new DummyChatClient();
+ builder.Services.AddKeyedSingleton("chat-client", mockChatClient);
+ IHostedAgentBuilder agentBuilder = builder.AddAIAgent("agent", "Instructions", chatClientServiceKey: "chat-client");
+ builder.Services.AddLogging();
+ using WebApplication app = builder.Build();
+
+ // Act & Assert - Should not throw
+ app.MapA2A(agentBuilder, "/custom/a2a/path");
+ Assert.NotNull(app);
+ }
+
+ ///
+ /// Verifies that task manager configuration callback is invoked correctly.
+ ///
+ [Fact]
+ public void MapA2A_WithAgentBuilder_TaskManagerConfigurationCallbackInvoked()
+ {
+ // Arrange
+ WebApplicationBuilder builder = WebApplication.CreateBuilder();
+ IChatClient mockChatClient = new DummyChatClient();
+ builder.Services.AddKeyedSingleton("chat-client", mockChatClient);
+ IHostedAgentBuilder agentBuilder = builder.AddAIAgent("agent", "Instructions", chatClientServiceKey: "chat-client");
+ builder.Services.AddLogging();
+ using WebApplication app = builder.Build();
+
+ bool configureCallbackInvoked = false;
+
+ // Act
+ app.MapA2A(agentBuilder, "/a2a", taskManager =>
+ {
+ configureCallbackInvoked = true;
+ Assert.NotNull(taskManager);
+ });
+
+ // Assert
+ Assert.True(configureCallbackInvoked);
+ }
+
+ ///
+ /// Verifies that agent card with all properties is accepted.
+ ///
+ [Fact]
+ public void MapA2A_WithAgentBuilder_FullAgentCard_Succeeds()
+ {
+ // Arrange
+ WebApplicationBuilder builder = WebApplication.CreateBuilder();
+ IChatClient mockChatClient = new DummyChatClient();
+ builder.Services.AddKeyedSingleton("chat-client", mockChatClient);
+ IHostedAgentBuilder agentBuilder = builder.AddAIAgent("agent", "Instructions", chatClientServiceKey: "chat-client");
+ builder.Services.AddLogging();
+ using WebApplication app = builder.Build();
+
+ var agentCard = new AgentCard
+ {
+ Name = "Test Agent",
+ Description = "A comprehensive test agent"
+ };
+
+ // Act & Assert - Should not throw
+ var result = app.MapA2A(agentBuilder, "/a2a", agentCard);
+ Assert.NotNull(result);
+ }
+
+ private sealed class DummyChatClient : IChatClient
+ {
+ public void Dispose()
+ {
+ throw new NotImplementedException();
+ }
+
+ public Task GetResponseAsync(IEnumerable messages, ChatOptions? options = null, CancellationToken cancellationToken = default)
+ {
+ throw new NotImplementedException();
+ }
+
+ public object? GetService(Type serviceType, object? serviceKey = null) =>
+ serviceType.IsInstanceOfType(this) ? this : null;
+
+ public IAsyncEnumerable GetStreamingResponseAsync(IEnumerable messages, ChatOptions? options = null, CancellationToken cancellationToken = default)
+ {
+ throw new NotImplementedException();
+ }
+ }
+}
diff --git a/dotnet/tests/Microsoft.Agents.AI.Hosting.A2A.Tests/Microsoft.Agents.AI.Hosting.A2A.Tests.csproj b/dotnet/tests/Microsoft.Agents.AI.Hosting.A2A.UnitTests/Microsoft.Agents.AI.Hosting.A2A.UnitTests.csproj
similarity index 82%
rename from dotnet/tests/Microsoft.Agents.AI.Hosting.A2A.Tests/Microsoft.Agents.AI.Hosting.A2A.Tests.csproj
rename to dotnet/tests/Microsoft.Agents.AI.Hosting.A2A.UnitTests/Microsoft.Agents.AI.Hosting.A2A.UnitTests.csproj
index 9e0a79d646..63387ae458 100644
--- a/dotnet/tests/Microsoft.Agents.AI.Hosting.A2A.Tests/Microsoft.Agents.AI.Hosting.A2A.Tests.csproj
+++ b/dotnet/tests/Microsoft.Agents.AI.Hosting.A2A.UnitTests/Microsoft.Agents.AI.Hosting.A2A.UnitTests.csproj
@@ -11,6 +11,7 @@
+
diff --git a/dotnet/tests/Microsoft.Agents.AI.Hosting.OpenAI.UnitTests/EndpointRouteBuilderExtensionsTests.cs b/dotnet/tests/Microsoft.Agents.AI.Hosting.OpenAI.UnitTests/EndpointRouteBuilderExtensionsTests.cs
index 38b13fbfac..e3effac07a 100644
--- a/dotnet/tests/Microsoft.Agents.AI.Hosting.OpenAI.UnitTests/EndpointRouteBuilderExtensionsTests.cs
+++ b/dotnet/tests/Microsoft.Agents.AI.Hosting.OpenAI.UnitTests/EndpointRouteBuilderExtensionsTests.cs
@@ -223,4 +223,174 @@ public sealed class EndpointRouteBuilderExtensionsTests
app.MapOpenAIResponses(responsesPath: "/custom/path/responses");
Assert.NotNull(app);
}
+
+ ///
+ /// Verifies that MapOpenAIResponses throws ArgumentNullException for null endpoints when using IHostedAgentBuilder.
+ ///
+ [Fact]
+ public void MapOpenAIResponses_WithAgentBuilder_NullEndpoints_ThrowsArgumentNullException()
+ {
+ // Arrange
+ AspNetCore.Routing.IEndpointRouteBuilder endpoints = null!;
+ WebApplicationBuilder builder = WebApplication.CreateBuilder();
+ IChatClient mockChatClient = new TestHelpers.SimpleMockChatClient();
+ builder.Services.AddKeyedSingleton("chat-client", mockChatClient);
+ IHostedAgentBuilder agentBuilder = builder.AddAIAgent("agent", "Instructions", chatClientServiceKey: "chat-client");
+
+ // Act & Assert
+ ArgumentNullException exception = Assert.Throws(() =>
+ endpoints.MapOpenAIResponses(agentBuilder));
+
+ Assert.Equal("endpoints", exception.ParamName);
+ }
+
+ ///
+ /// Verifies that MapOpenAIResponses throws ArgumentNullException for null agentBuilder.
+ ///
+ [Fact]
+ public void MapOpenAIResponses_WithAgentBuilder_NullAgentBuilder_ThrowsArgumentNullException()
+ {
+ // Arrange
+ WebApplicationBuilder builder = WebApplication.CreateBuilder();
+ IChatClient mockChatClient = new TestHelpers.SimpleMockChatClient();
+ builder.Services.AddKeyedSingleton("chat-client", mockChatClient);
+ builder.AddOpenAIResponses();
+ using WebApplication app = builder.Build();
+ IHostedAgentBuilder agentBuilder = null!;
+
+ // Act & Assert
+ ArgumentNullException exception = Assert.Throws(() =>
+ app.MapOpenAIResponses(agentBuilder));
+
+ Assert.Equal("agentBuilder", exception.ParamName);
+ }
+
+ ///
+ /// Verifies that MapOpenAIResponses with IHostedAgentBuilder correctly resolves and maps the agent.
+ ///
+ [Fact]
+ public void MapOpenAIResponses_WithAgentBuilder_Succeeds()
+ {
+ // Arrange
+ WebApplicationBuilder builder = WebApplication.CreateBuilder();
+ IChatClient mockChatClient = new TestHelpers.SimpleMockChatClient();
+ builder.Services.AddKeyedSingleton("chat-client", mockChatClient);
+ IHostedAgentBuilder agentBuilder = builder.AddAIAgent("agent", "Instructions", chatClientServiceKey: "chat-client");
+ builder.AddOpenAIResponses();
+ using WebApplication app = builder.Build();
+
+ // Act & Assert - Should not throw
+ app.MapOpenAIResponses(agentBuilder);
+ Assert.NotNull(app);
+ }
+
+ ///
+ /// Verifies that MapOpenAIResponses with IHostedAgentBuilder and custom path works correctly.
+ ///
+ [Fact]
+ public void MapOpenAIResponses_WithAgentBuilder_CustomPath_Succeeds()
+ {
+ // Arrange
+ WebApplicationBuilder builder = WebApplication.CreateBuilder();
+ IChatClient mockChatClient = new TestHelpers.SimpleMockChatClient();
+ builder.Services.AddKeyedSingleton("chat-client", mockChatClient);
+ IHostedAgentBuilder agentBuilder = builder.AddAIAgent("my-agent", "Instructions", chatClientServiceKey: "chat-client");
+ builder.AddOpenAIResponses();
+ using WebApplication app = builder.Build();
+
+ // Act & Assert - Should not throw
+ app.MapOpenAIResponses(agentBuilder, path: "/agents/my-agent/responses");
+ Assert.NotNull(app);
+ }
+
+ ///
+ /// Verifies that multiple agents can be mapped using IHostedAgentBuilder.
+ ///
+ [Fact]
+ public void MapOpenAIResponses_WithAgentBuilder_MultipleAgents_Succeeds()
+ {
+ // Arrange
+ WebApplicationBuilder builder = WebApplication.CreateBuilder();
+ IChatClient mockChatClient = new TestHelpers.SimpleMockChatClient();
+ builder.Services.AddKeyedSingleton("chat-client", mockChatClient);
+ IHostedAgentBuilder agent1Builder = builder.AddAIAgent("agent1", "Instructions1", chatClientServiceKey: "chat-client");
+ IHostedAgentBuilder agent2Builder = builder.AddAIAgent("agent2", "Instructions2", chatClientServiceKey: "chat-client");
+ builder.AddOpenAIResponses();
+ using WebApplication app = builder.Build();
+
+ // Act & Assert - Should not throw
+ app.MapOpenAIResponses(agent1Builder);
+ app.MapOpenAIResponses(agent2Builder);
+ Assert.NotNull(app);
+ }
+
+ ///
+ /// Verifies that IHostedAgentBuilder overload validates agent name characters.
+ ///
+ [Theory]
+ [InlineData("agent with spaces")]
+ [InlineData("agent