mirror of
https://github.com/microsoft/agent-framework.git
synced 2026-06-16 21:04:09 +08:00
.NET: Split A2A endpoint mapping into protocol-specific methods (#5413)
* .NET: Refactor A2A hosting registration into A2AServerServiceCollectionExtensions - Rename A2AHostingOptions to A2AServerRegistrationOptions - Move server registration logic from A2AEndpointRouteBuilderExtensions and AIAgentExtensions into new A2AServerServiceCollectionExtensions - Remove A2AProtocolBinding and AIAgentExtensions (consolidated) - Update samples and tests to use the new registration API Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * address copilot comments --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
co-authored by
Copilot
parent
275363d15e
commit
c54483f81e
+85
-135
@@ -6,183 +6,133 @@ using A2A;
|
||||
using A2A.AspNetCore;
|
||||
using Microsoft.Agents.AI;
|
||||
using Microsoft.Agents.AI.Hosting;
|
||||
using Microsoft.Agents.AI.Hosting.A2A;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Microsoft.Shared.DiagnosticIds;
|
||||
|
||||
namespace Microsoft.AspNetCore.Builder;
|
||||
|
||||
/// <summary>
|
||||
/// Provides extension methods for configuring A2A endpoints for AI agents.
|
||||
/// Provides extension methods for mapping A2A protocol endpoints for AI agents.
|
||||
/// </summary>
|
||||
[Experimental(DiagnosticIds.Experiments.AIResponseContinuations)]
|
||||
public static class A2AEndpointRouteBuilderExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Maps A2A endpoints for the specified agent to the given path.
|
||||
/// Maps A2A HTTP+JSON endpoints for the specified agent to the given path.
|
||||
/// An <see cref="A2AServer"/> for the agent must be registered first by calling
|
||||
/// <c>AddA2AServer</c> during service registration.
|
||||
/// </summary>
|
||||
/// <param name="endpoints">The <see cref="IEndpointRouteBuilder"/> to add the A2A endpoints to.</param>
|
||||
/// <param name="agentBuilder">The configuration builder for <see cref="AIAgent"/>.</param>
|
||||
/// <param name="agentBuilder">The configuration builder for the agent.</param>
|
||||
/// <param name="path">The route path prefix for A2A endpoints.</param>
|
||||
/// <param name="protocolBindings">The A2A protocol binding(s) to expose. When <see langword="null"/>, defaults to <see cref="A2AProtocolBinding.HttpJson"/>.</param>
|
||||
/// <param name="agentRunMode">The agent run mode that controls how the agent responds to A2A requests. When <see langword="null"/>, defaults to <see cref="AgentRunMode.DisallowBackground"/>.</param>
|
||||
/// <returns>An <see cref="IEndpointConventionBuilder"/> for further endpoint configuration.</returns>
|
||||
public static IEndpointConventionBuilder MapA2A(this IEndpointRouteBuilder endpoints, IHostedAgentBuilder agentBuilder, string path, A2AProtocolBinding? protocolBindings, AgentRunMode? agentRunMode = null)
|
||||
public static IEndpointConventionBuilder MapA2AHttpJson(this IEndpointRouteBuilder endpoints, IHostedAgentBuilder agentBuilder, string path)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(agentBuilder);
|
||||
|
||||
return endpoints.MapA2A(agentBuilder.Name, path, protocolBindings, agentRunMode);
|
||||
return endpoints.MapA2AHttpJson(agentBuilder.Name, path);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Maps A2A endpoints for the specified agent to the given path.
|
||||
/// Maps A2A HTTP+JSON endpoints for the specified agent to the given path.
|
||||
/// An <see cref="A2AServer"/> for the agent must be registered first by calling
|
||||
/// <c>AddA2AServer</c> during service registration.
|
||||
/// </summary>
|
||||
/// <param name="endpoints">The <see cref="IEndpointRouteBuilder"/> to add the A2A endpoints to.</param>
|
||||
/// <param name="agentBuilder">The configuration builder for <see cref="AIAgent"/>.</param>
|
||||
/// <param name="agent">The agent whose name identifies the registered A2A server.</param>
|
||||
/// <param name="path">The route path prefix for A2A endpoints.</param>
|
||||
/// <param name="configureOptions">An optional callback to configure <see cref="A2AHostingOptions"/>.</param>
|
||||
/// <returns>An <see cref="IEndpointConventionBuilder"/> for further endpoint configuration.</returns>
|
||||
public static IEndpointConventionBuilder MapA2A(this IEndpointRouteBuilder endpoints, IHostedAgentBuilder agentBuilder, string path, Action<A2AHostingOptions>? configureOptions = null)
|
||||
public static IEndpointConventionBuilder MapA2AHttpJson(this IEndpointRouteBuilder endpoints, AIAgent agent, string path)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(agentBuilder);
|
||||
|
||||
return endpoints.MapA2A(agentBuilder.Name, path, configureOptions);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Maps A2A endpoints for the agent with the specified name to the given path.
|
||||
/// </summary>
|
||||
/// <param name="endpoints">The <see cref="IEndpointRouteBuilder"/> to add the A2A endpoints to.</param>
|
||||
/// <param name="agentName">The name of the agent to use for A2A protocol integration.</param>
|
||||
/// <param name="path">The route path prefix for A2A endpoints.</param>
|
||||
/// <param name="protocolBindings">The A2A protocol binding(s) to expose. When <see langword="null"/>, defaults to <see cref="A2AProtocolBinding.HttpJson"/>.</param>
|
||||
/// <param name="agentRunMode">The agent run mode that controls how the agent responds to A2A requests. When <see langword="null"/>, defaults to <see cref="AgentRunMode.DisallowBackground"/>.</param>
|
||||
/// <returns>An <see cref="IEndpointConventionBuilder"/> for further endpoint configuration.</returns>
|
||||
public static IEndpointConventionBuilder MapA2A(this IEndpointRouteBuilder endpoints, string agentName, string path, A2AProtocolBinding? protocolBindings, AgentRunMode? agentRunMode = null)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(endpoints);
|
||||
ArgumentException.ThrowIfNullOrEmpty(agentName);
|
||||
|
||||
var agent = endpoints.ServiceProvider.GetRequiredKeyedService<AIAgent>(agentName);
|
||||
|
||||
return endpoints.MapA2A(agent, path, protocolBindings, agentRunMode);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Maps A2A endpoints for the agent with the specified name to the given path.
|
||||
/// </summary>
|
||||
/// <param name="endpoints">The <see cref="IEndpointRouteBuilder"/> to add the A2A endpoints to.</param>
|
||||
/// <param name="agentName">The name of the agent to use for A2A protocol integration.</param>
|
||||
/// <param name="path">The route path prefix for A2A endpoints.</param>
|
||||
/// <param name="configureOptions">An optional callback to configure <see cref="A2AHostingOptions"/>.</param>
|
||||
/// <returns>An <see cref="IEndpointConventionBuilder"/> for further endpoint configuration.</returns>
|
||||
public static IEndpointConventionBuilder MapA2A(this IEndpointRouteBuilder endpoints, string agentName, string path, Action<A2AHostingOptions>? configureOptions = null)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(endpoints);
|
||||
ArgumentException.ThrowIfNullOrEmpty(agentName);
|
||||
|
||||
var agent = endpoints.ServiceProvider.GetRequiredKeyedService<AIAgent>(agentName);
|
||||
|
||||
return endpoints.MapA2A(agent, path, configureOptions);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Maps A2A endpoints for the specified agent to the given path.
|
||||
/// </summary>
|
||||
/// <param name="endpoints">The <see cref="IEndpointRouteBuilder"/> to add the A2A endpoints to.</param>
|
||||
/// <param name="agent">The agent to use for A2A protocol integration.</param>
|
||||
/// <param name="path">The route path prefix for A2A endpoints.</param>
|
||||
/// <param name="protocolBindings">The A2A protocol binding(s) to expose. When <see langword="null"/>, defaults to <see cref="A2AProtocolBinding.HttpJson"/>.</param>
|
||||
/// <param name="agentRunMode">The agent run mode that controls how the agent responds to A2A requests. When <see langword="null"/>, defaults to <see cref="AgentRunMode.DisallowBackground"/>.</param>
|
||||
/// <returns>An <see cref="IEndpointConventionBuilder"/> for further endpoint configuration.</returns>
|
||||
public static IEndpointConventionBuilder MapA2A(this IEndpointRouteBuilder endpoints, AIAgent agent, string path, A2AProtocolBinding? protocolBindings, AgentRunMode? agentRunMode = null)
|
||||
{
|
||||
Action<A2AHostingOptions>? configureOptions = null;
|
||||
|
||||
if (protocolBindings is not null || agentRunMode is not null)
|
||||
{
|
||||
configureOptions = options =>
|
||||
{
|
||||
options.ProtocolBindings = protocolBindings;
|
||||
options.AgentRunMode = agentRunMode;
|
||||
};
|
||||
}
|
||||
|
||||
return endpoints.MapA2A(agent, path, configureOptions);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Maps A2A endpoints for the specified agent to the given path.
|
||||
/// </summary>
|
||||
/// <param name="endpoints">The <see cref="IEndpointRouteBuilder"/> to add the A2A endpoints to.</param>
|
||||
/// <param name="agent">The agent to use for A2A protocol integration.</param>
|
||||
/// <param name="path">The route path prefix for A2A endpoints.</param>
|
||||
/// <param name="configureOptions">An optional callback to configure <see cref="A2AHostingOptions"/>.</param>
|
||||
/// <returns>An <see cref="IEndpointConventionBuilder"/> for further endpoint configuration.</returns>
|
||||
public static IEndpointConventionBuilder MapA2A(this IEndpointRouteBuilder endpoints, AIAgent agent, string path, Action<A2AHostingOptions>? configureOptions = null)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(endpoints);
|
||||
ArgumentNullException.ThrowIfNull(agent);
|
||||
ArgumentException.ThrowIfNullOrWhiteSpace(path);
|
||||
ArgumentException.ThrowIfNullOrWhiteSpace(agent.Name, nameof(agent) + "." + nameof(agent.Name));
|
||||
|
||||
A2AHostingOptions? options = null;
|
||||
if (configureOptions is not null)
|
||||
{
|
||||
options = new A2AHostingOptions();
|
||||
configureOptions(options);
|
||||
}
|
||||
|
||||
var a2aServer = CreateA2AServer(endpoints, agent, options);
|
||||
|
||||
return MapA2AEndpoints(endpoints, a2aServer, path, options?.ProtocolBindings);
|
||||
return endpoints.MapA2AHttpJson(agent.Name, path);
|
||||
}
|
||||
|
||||
private static A2AServer CreateA2AServer(IEndpointRouteBuilder endpoints, AIAgent agent, A2AHostingOptions? options)
|
||||
/// <summary>
|
||||
/// Maps A2A HTTP+JSON endpoints for the agent with the specified name to the given path.
|
||||
/// An <see cref="A2AServer"/> for the agent must be registered first by calling
|
||||
/// <c>AddA2AServer</c> during service registration.
|
||||
/// </summary>
|
||||
/// <param name="endpoints">The <see cref="IEndpointRouteBuilder"/> to add the A2A endpoints to.</param>
|
||||
/// <param name="agentName">The name of the agent to use for A2A protocol integration.</param>
|
||||
/// <param name="path">The route path prefix for A2A endpoints.</param>
|
||||
/// <returns>An <see cref="IEndpointConventionBuilder"/> for further endpoint configuration.</returns>
|
||||
public static IEndpointConventionBuilder MapA2AHttpJson(this IEndpointRouteBuilder endpoints, string agentName, string path)
|
||||
{
|
||||
var agentHandler = endpoints.ServiceProvider.GetKeyedService<IAgentHandler>(agent.Name);
|
||||
if (agentHandler is null)
|
||||
{
|
||||
var agentSessionStore = endpoints.ServiceProvider.GetKeyedService<AgentSessionStore>(agent.Name);
|
||||
agentHandler = agent.MapA2A(agentSessionStore: agentSessionStore, runMode: options?.AgentRunMode);
|
||||
}
|
||||
ArgumentNullException.ThrowIfNull(endpoints);
|
||||
ArgumentException.ThrowIfNullOrEmpty(agentName);
|
||||
ArgumentException.ThrowIfNullOrWhiteSpace(path);
|
||||
|
||||
var loggerFactory = endpoints.ServiceProvider.GetService<ILoggerFactory>() ?? NullLoggerFactory.Instance;
|
||||
var taskStore = endpoints.ServiceProvider.GetKeyedService<ITaskStore>(agent.Name) ?? new InMemoryTaskStore();
|
||||
var a2aServer = endpoints.ServiceProvider.GetKeyedService<A2AServer>(agentName)
|
||||
?? throw new InvalidOperationException(
|
||||
$"No A2AServer is registered for agent '{agentName}'. " +
|
||||
$"Call services.AddA2AServer(\"{agentName}\") or agentBuilder.AddA2AServer() during service registration to register one.");
|
||||
|
||||
return new A2AServer(
|
||||
agentHandler,
|
||||
taskStore,
|
||||
new ChannelEventNotifier(),
|
||||
loggerFactory.CreateLogger<A2AServer>(),
|
||||
options?.ServerOptions);
|
||||
// TODO: The stub AgentCard is temporary and will be removed once the A2A SDK either removes the
|
||||
// agentCard parameter of MapHttpA2A or makes it optional. MapHttpA2A exposes the agent card via a
|
||||
// GET {path}/card endpoint that is not part of the A2A spec, so it is not expected to be consumed
|
||||
// by any agent - returning a stub agent card here is safe.
|
||||
var stubAgentCard = new AgentCard { Name = "A2A Agent" };
|
||||
|
||||
return endpoints.MapHttpA2A(a2aServer, stubAgentCard, path);
|
||||
}
|
||||
|
||||
private static IEndpointConventionBuilder MapA2AEndpoints(IEndpointRouteBuilder endpoints, A2AServer a2aServer, string path, A2AProtocolBinding? protocolBindings)
|
||||
/// <summary>
|
||||
/// Maps A2A JSON-RPC endpoints for the specified agent to the given path.
|
||||
/// An <see cref="A2AServer"/> for the agent must be registered first by calling
|
||||
/// <c>AddA2AServer</c> during service registration.
|
||||
/// </summary>
|
||||
/// <param name="endpoints">The <see cref="IEndpointRouteBuilder"/> to add the A2A endpoints to.</param>
|
||||
/// <param name="agentBuilder">The configuration builder for the agent.</param>
|
||||
/// <param name="path">The route path prefix for A2A endpoints.</param>
|
||||
/// <returns>An <see cref="IEndpointConventionBuilder"/> for further endpoint configuration.</returns>
|
||||
public static IEndpointConventionBuilder MapA2AJsonRpc(this IEndpointRouteBuilder endpoints, IHostedAgentBuilder agentBuilder, string path)
|
||||
{
|
||||
protocolBindings ??= A2AProtocolBinding.HttpJson;
|
||||
ArgumentNullException.ThrowIfNull(agentBuilder);
|
||||
|
||||
IEndpointConventionBuilder? result = null;
|
||||
return endpoints.MapA2AJsonRpc(agentBuilder.Name, path);
|
||||
}
|
||||
|
||||
if (protocolBindings.Value.HasFlag(A2AProtocolBinding.JsonRpc))
|
||||
{
|
||||
result = endpoints.MapA2A(a2aServer, path);
|
||||
}
|
||||
/// <summary>
|
||||
/// Maps A2A JSON-RPC endpoints for the specified agent to the given path.
|
||||
/// An <see cref="A2AServer"/> for the agent must be registered first by calling
|
||||
/// <c>AddA2AServer</c> during service registration.
|
||||
/// </summary>
|
||||
/// <param name="endpoints">The <see cref="IEndpointRouteBuilder"/> to add the A2A endpoints to.</param>
|
||||
/// <param name="agent">The agent whose name identifies the registered A2A server.</param>
|
||||
/// <param name="path">The route path prefix for A2A endpoints.</param>
|
||||
/// <returns>An <see cref="IEndpointConventionBuilder"/> for further endpoint configuration.</returns>
|
||||
public static IEndpointConventionBuilder MapA2AJsonRpc(this IEndpointRouteBuilder endpoints, AIAgent agent, string path)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(agent);
|
||||
ArgumentException.ThrowIfNullOrWhiteSpace(agent.Name, nameof(agent) + "." + nameof(agent.Name));
|
||||
|
||||
if (protocolBindings.Value.HasFlag(A2AProtocolBinding.HttpJson))
|
||||
{
|
||||
// TODO: The stub AgentCard is temporary and will be removed once the A2A SDK either removes the
|
||||
// agentCard parameter of MapHttpA2A or makes it optional. MapHttpA2A exposes the agent card via a
|
||||
// GET {path}/card endpoint that is not part of the A2A spec, so it is not expected to be consumed
|
||||
// by any agent - returning a stub agent card here is safe.
|
||||
var stubAgentCard = new AgentCard { Name = "A2A Agent" };
|
||||
return endpoints.MapA2AJsonRpc(agent.Name, path);
|
||||
}
|
||||
|
||||
result = endpoints.MapHttpA2A(a2aServer, stubAgentCard, path);
|
||||
}
|
||||
/// <summary>
|
||||
/// Maps A2A JSON-RPC endpoints for the agent with the specified name to the given path.
|
||||
/// An <see cref="A2AServer"/> for the agent must be registered first by calling
|
||||
/// <c>AddA2AServer</c> during service registration.
|
||||
/// </summary>
|
||||
/// <param name="endpoints">The <see cref="IEndpointRouteBuilder"/> to add the A2A endpoints to.</param>
|
||||
/// <param name="agentName">The name of the agent to use for A2A protocol integration.</param>
|
||||
/// <param name="path">The route path prefix for A2A endpoints.</param>
|
||||
/// <returns>An <see cref="IEndpointConventionBuilder"/> for further endpoint configuration.</returns>
|
||||
public static IEndpointConventionBuilder MapA2AJsonRpc(this IEndpointRouteBuilder endpoints, string agentName, string path)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(endpoints);
|
||||
ArgumentException.ThrowIfNullOrEmpty(agentName);
|
||||
ArgumentException.ThrowIfNullOrWhiteSpace(path);
|
||||
|
||||
return result ?? throw new InvalidOperationException("At least one A2A protocol binding must be specified.");
|
||||
var a2aServer = endpoints.ServiceProvider.GetKeyedService<A2AServer>(agentName)
|
||||
?? throw new InvalidOperationException(
|
||||
$"No A2AServer is registered for agent '{agentName}'. " +
|
||||
$"Call services.AddA2AServer(\"{agentName}\") or agentBuilder.AddA2AServer() during service registration to register one.");
|
||||
|
||||
return endpoints.MapA2A(a2aServer, path);
|
||||
}
|
||||
}
|
||||
|
||||
+6
-3
@@ -1,9 +1,12 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>$(TargetFrameworksCore)</TargetFrameworks>
|
||||
<RootNamespace>Microsoft.Agents.AI.Hosting.A2A.AspNetCore</RootNamespace>
|
||||
<VersionSuffix>preview</VersionSuffix>
|
||||
<!-- RT0002: Microsoft.Agents.AI.Hosting.A2A is intentionally referenced as a transitive dependency
|
||||
so that consumers of this package automatically get the AddA2AServer registration extensions. -->
|
||||
<NoWarn>$(NoWarn);RT0002</NoWarn>
|
||||
</PropertyGroup>
|
||||
|
||||
<Import Project="$(RepoRoot)/dotnet/nuget/nuget-package.props" />
|
||||
@@ -13,7 +16,7 @@
|
||||
<InjectSharedDiagnosticIds>true</InjectSharedDiagnosticIds>
|
||||
<InjectExperimentalAttributeOnLegacy>true</InjectExperimentalAttributeOnLegacy>
|
||||
</PropertyGroup>
|
||||
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="A2A.AspNetCore" />
|
||||
</ItemGroup>
|
||||
@@ -21,7 +24,7 @@
|
||||
<ItemGroup Condition="!$([MSBuild]::IsTargetFrameworkCompatible($(TargetFramework), 'net10.0'))">
|
||||
<PackageReference Include="System.Linq.AsyncEnumerable" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Microsoft.Agents.AI.Hosting.A2A\Microsoft.Agents.AI.Hosting.A2A.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Microsoft.Shared.DiagnosticIds;
|
||||
|
||||
namespace Microsoft.Agents.AI.Hosting.A2A;
|
||||
|
||||
/// <summary>
|
||||
/// Specifies which A2A protocol binding(s) to expose when mapping A2A endpoints.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is a flags enum. Combine values using the bitwise OR operator to enable multiple bindings
|
||||
/// (e.g., <c>A2AProtocolBinding.HttpJson | A2AProtocolBinding.JsonRpc</c>).
|
||||
/// </remarks>
|
||||
[Flags]
|
||||
[Experimental(DiagnosticIds.Experiments.AIResponseContinuations)]
|
||||
public enum A2AProtocolBinding
|
||||
{
|
||||
/// <summary>
|
||||
/// Expose the agent via the HTTP+JSON/REST protocol binding.
|
||||
/// </summary>
|
||||
HttpJson = 1,
|
||||
|
||||
/// <summary>
|
||||
/// Expose the agent via the JSON-RPC protocol binding.
|
||||
/// </summary>
|
||||
JsonRpc = 2,
|
||||
}
|
||||
+2
-12
@@ -7,21 +7,11 @@ using Microsoft.Shared.DiagnosticIds;
|
||||
namespace Microsoft.Agents.AI.Hosting.A2A;
|
||||
|
||||
/// <summary>
|
||||
/// Options for configuring A2A endpoint hosting behavior.
|
||||
/// Options for configuring A2A server registration.
|
||||
/// </summary>
|
||||
[Experimental(DiagnosticIds.Experiments.AIResponseContinuations)]
|
||||
public sealed class A2AHostingOptions
|
||||
public sealed class A2AServerRegistrationOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the A2A protocol binding(s) to expose.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// When <see langword="null"/>, defaults to <see cref="A2AProtocolBinding.HttpJson"/>.
|
||||
/// Use the bitwise OR operator to enable multiple bindings
|
||||
/// (e.g., <c>A2AProtocolBinding.HttpJson | A2AProtocolBinding.JsonRpc</c>).
|
||||
/// </remarks>
|
||||
public A2AProtocolBinding? ProtocolBindings { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the agent run mode that controls how the agent responds to A2A requests.
|
||||
/// </summary>
|
||||
@@ -0,0 +1,160 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using A2A;
|
||||
using Microsoft.Agents.AI;
|
||||
using Microsoft.Agents.AI.Hosting;
|
||||
using Microsoft.Agents.AI.Hosting.A2A;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Microsoft.Shared.DiagnosticIds;
|
||||
|
||||
namespace Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
/// <summary>
|
||||
/// Provides extension methods for registering A2A server instances in the dependency injection container.
|
||||
/// </summary>
|
||||
[Experimental(DiagnosticIds.Experiments.AIResponseContinuations)]
|
||||
public static class A2AServerServiceCollectionExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Registers an <see cref="A2AServer"/> in the dependency injection container, keyed by the agent name
|
||||
/// specified in the <paramref name="agentBuilder"/>. This method only registers the server; to expose it
|
||||
/// as an HTTP endpoint, call one of the <c>MapA2AHttpJson</c> or <c>MapA2AJsonRpc</c> endpoint mapping
|
||||
/// methods during application startup.
|
||||
/// </summary>
|
||||
/// <param name="agentBuilder">The agent builder whose name identifies the agent.</param>
|
||||
/// <param name="configureOptions">An optional callback to configure <see cref="A2AServerRegistrationOptions"/>.</param>
|
||||
/// <returns>The <paramref name="agentBuilder"/> for chaining.</returns>
|
||||
public static IHostedAgentBuilder AddA2AServer(this IHostedAgentBuilder agentBuilder, Action<A2AServerRegistrationOptions>? configureOptions = null)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(agentBuilder);
|
||||
|
||||
agentBuilder.ServiceCollection.AddA2AServer(agentBuilder.Name, configureOptions);
|
||||
|
||||
return agentBuilder;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Registers an <see cref="A2AServer"/> in the dependency injection container, keyed by the specified
|
||||
/// agent name. This method only registers the server; to expose it as an HTTP endpoint, call one of the
|
||||
/// <c>MapA2AHttpJson</c> or <c>MapA2AJsonRpc</c> endpoint mapping methods during application startup.
|
||||
/// </summary>
|
||||
/// <param name="builder">The host application builder to configure.</param>
|
||||
/// <param name="agentName">The name of the agent to create an A2A server for.</param>
|
||||
/// <param name="configureOptions">An optional callback to configure <see cref="A2AServerRegistrationOptions"/>.</param>
|
||||
/// <returns>The <paramref name="builder"/> for chaining.</returns>
|
||||
public static IHostApplicationBuilder AddA2AServer(this IHostApplicationBuilder builder, string agentName, Action<A2AServerRegistrationOptions>? configureOptions = null)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(builder);
|
||||
|
||||
builder.Services.AddA2AServer(agentName, configureOptions);
|
||||
|
||||
return builder;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Registers an <see cref="A2AServer"/> in the dependency injection container for the specified
|
||||
/// <see cref="AIAgent"/> instance, keyed by the agent's <see cref="AIAgent.Name"/>. This method only
|
||||
/// registers the server; to expose it as an HTTP endpoint, call one of the <c>MapA2AHttpJson</c> or
|
||||
/// <c>MapA2AJsonRpc</c> endpoint mapping methods during application startup.
|
||||
/// </summary>
|
||||
/// <param name="builder">The host application builder to configure.</param>
|
||||
/// <param name="agent">The agent instance to create an A2A server for.</param>
|
||||
/// <param name="configureOptions">An optional callback to configure <see cref="A2AServerRegistrationOptions"/>.</param>
|
||||
/// <returns>The <paramref name="builder"/> for chaining.</returns>
|
||||
public static IHostApplicationBuilder AddA2AServer(this IHostApplicationBuilder builder, AIAgent agent, Action<A2AServerRegistrationOptions>? configureOptions = null)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(builder);
|
||||
|
||||
builder.Services.AddA2AServer(agent, configureOptions);
|
||||
|
||||
return builder;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Registers an <see cref="A2AServer"/> in the dependency injection container, keyed by the specified
|
||||
/// agent name. This method only registers the server; to expose it as an HTTP endpoint, call one of the
|
||||
/// <c>MapA2AHttpJson</c> or <c>MapA2AJsonRpc</c> endpoint mapping methods during application startup.
|
||||
/// </summary>
|
||||
/// <param name="services">The service collection to add the A2A server to.</param>
|
||||
/// <param name="agentName">The name of the agent to create an A2A server for.</param>
|
||||
/// <param name="configureOptions">An optional callback to configure <see cref="A2AServerRegistrationOptions"/>.</param>
|
||||
/// <returns>The <paramref name="services"/> for chaining.</returns>
|
||||
public static IServiceCollection AddA2AServer(this IServiceCollection services, string agentName, Action<A2AServerRegistrationOptions>? configureOptions = null)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(services);
|
||||
ArgumentException.ThrowIfNullOrEmpty(agentName);
|
||||
|
||||
A2AServerRegistrationOptions? options = null;
|
||||
if (configureOptions is not null)
|
||||
{
|
||||
options = new A2AServerRegistrationOptions();
|
||||
configureOptions(options);
|
||||
}
|
||||
|
||||
services.AddKeyedSingleton(agentName, (sp, _) =>
|
||||
{
|
||||
var agent = sp.GetRequiredKeyedService<AIAgent>(agentName);
|
||||
return CreateA2AServer(sp, agent, options);
|
||||
});
|
||||
|
||||
return services;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Registers an <see cref="A2AServer"/> in the dependency injection container for the specified
|
||||
/// <see cref="AIAgent"/> instance, keyed by the agent's <see cref="AIAgent.Name"/>. This method only
|
||||
/// registers the server; to expose it as an HTTP endpoint, call one of the <c>MapA2AHttpJson</c> or
|
||||
/// <c>MapA2AJsonRpc</c> endpoint mapping methods during application startup.
|
||||
/// </summary>
|
||||
/// <param name="services">The service collection to add the A2A server to.</param>
|
||||
/// <param name="agent">The agent instance to create an A2A server for.</param>
|
||||
/// <param name="configureOptions">An optional callback to configure <see cref="A2AServerRegistrationOptions"/>.</param>
|
||||
/// <returns>The <paramref name="services"/> for chaining.</returns>
|
||||
public static IServiceCollection AddA2AServer(this IServiceCollection services, AIAgent agent, Action<A2AServerRegistrationOptions>? configureOptions = null)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(services);
|
||||
ArgumentNullException.ThrowIfNull(agent);
|
||||
ArgumentException.ThrowIfNullOrWhiteSpace(agent.Name, nameof(agent) + "." + nameof(agent.Name));
|
||||
|
||||
A2AServerRegistrationOptions? options = null;
|
||||
if (configureOptions is not null)
|
||||
{
|
||||
options = new A2AServerRegistrationOptions();
|
||||
configureOptions(options);
|
||||
}
|
||||
|
||||
services.AddKeyedSingleton(agent.Name, (sp, _) => CreateA2AServer(sp, agent, options));
|
||||
|
||||
return services;
|
||||
}
|
||||
|
||||
private static A2AServer CreateA2AServer(IServiceProvider serviceProvider, AIAgent agent, A2AServerRegistrationOptions? options)
|
||||
{
|
||||
var agentHandler = serviceProvider.GetKeyedService<IAgentHandler>(agent.Name);
|
||||
if (agentHandler is null)
|
||||
{
|
||||
var agentSessionStore = serviceProvider.GetKeyedService<AgentSessionStore>(agent.Name);
|
||||
var runMode = options?.AgentRunMode ?? AgentRunMode.DisallowBackground;
|
||||
|
||||
var hostAgent = new AIHostAgent(
|
||||
innerAgent: agent,
|
||||
sessionStore: agentSessionStore ?? new InMemoryAgentSessionStore());
|
||||
|
||||
agentHandler = new A2AAgentHandler(hostAgent, runMode);
|
||||
}
|
||||
|
||||
var loggerFactory = serviceProvider.GetService<ILoggerFactory>() ?? NullLoggerFactory.Instance;
|
||||
var taskStore = serviceProvider.GetKeyedService<ITaskStore>(agent.Name) ?? new InMemoryTaskStore();
|
||||
|
||||
return new A2AServer(
|
||||
agentHandler,
|
||||
taskStore,
|
||||
new ChannelEventNotifier(),
|
||||
loggerFactory.CreateLogger<A2AServer>(),
|
||||
options?.ServerOptions);
|
||||
}
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using A2A;
|
||||
using Microsoft.Shared.DiagnosticIds;
|
||||
|
||||
namespace Microsoft.Agents.AI.Hosting.A2A;
|
||||
|
||||
/// <summary>
|
||||
/// Provides extension methods for attaching A2A (Agent2Agent) messaging capabilities to an <see cref="AIAgent"/>.
|
||||
/// </summary>
|
||||
[Experimental(DiagnosticIds.Experiments.AIResponseContinuations)]
|
||||
public static class AIAgentExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates an <see cref="IAgentHandler"/> that bridges the specified <see cref="AIAgent"/> to
|
||||
/// the A2A (Agent2Agent) protocol.
|
||||
/// </summary>
|
||||
/// <param name="agent">Agent to attach A2A messaging processing capabilities to.</param>
|
||||
/// <param name="agentSessionStore">The store to store session contents and metadata.</param>
|
||||
/// <param name="runMode">Controls the response behavior of the agent run.</param>
|
||||
/// <returns>An <see cref="IAgentHandler"/> that handles A2A message execution and cancellation.</returns>
|
||||
public static IAgentHandler MapA2A(
|
||||
this AIAgent agent,
|
||||
AgentSessionStore? agentSessionStore = null,
|
||||
AgentRunMode? runMode = null)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(agent);
|
||||
ArgumentNullException.ThrowIfNull(agent.Name);
|
||||
|
||||
runMode ??= AgentRunMode.DisallowBackground;
|
||||
|
||||
var hostAgent = new AIHostAgent(
|
||||
innerAgent: agent,
|
||||
sessionStore: agentSessionStore ?? new InMemoryAgentSessionStore());
|
||||
|
||||
return new A2AAgentHandler(hostAgent, runMode);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user