.NET: Use IA2AClientFactory to create A2AClient (#5277)

* Refactor A2A extensions to use IA2AClientFactory and add ProtocolSelection sample

- Update A2AAgentCardExtensions to accept IA2AClientFactory instead of A2AClientOptions
- Update A2ACardResolverExtensions to accept IA2AClientFactory
- Update A2AClientExtensions to accept IA2AClientFactory
- Update A2AAgent to use IA2AClientFactory for client creation
- Add A2AAgent_ProtocolSelection sample demonstrating protocol selection
- Add comprehensive unit tests for all changes
- Update README files with new sample reference

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Reorder params: options before loggerFactory in A2A extensions

Move A2AClientOptions parameter before ILoggerFactory in AsAIAgent
and GetAIAgentAsync extension methods to follow the repo convention
of keeping LoggerFactory and CancellationToken as the last parameters.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
SergeyMenshykh
2026-04-15 17:29:36 +01:00
committed by GitHub
co-authored by Copilot
parent 6ad9279f0f
commit 7417eeb7e6
14 changed files with 283 additions and 33 deletions
@@ -27,7 +27,7 @@ public sealed class A2AAgent : AIAgent
{
private static readonly AIAgentMetadata s_agentMetadata = new("a2a");
private readonly A2AClient _a2aClient;
private readonly IA2AClient _a2aClient;
private readonly string? _id;
private readonly string? _name;
private readonly string? _description;
@@ -41,7 +41,7 @@ public sealed class A2AAgent : AIAgent
/// <param name="name">The the name of the agent.</param>
/// <param name="description">The description of the agent.</param>
/// <param name="loggerFactory">Optional logger factory to use for logging.</param>
public A2AAgent(A2AClient a2aClient, string? id = null, string? name = null, string? description = null, ILoggerFactory? loggerFactory = null)
public A2AAgent(IA2AClient a2aClient, string? id = null, string? name = null, string? description = null, ILoggerFactory? loggerFactory = null)
{
_ = Throw.IfNull(a2aClient);
@@ -224,7 +224,7 @@ public sealed class A2AAgent : AIAgent
/// <inheritdoc/>
public override object? GetService(Type serviceType, object? serviceKey = null)
=> base.GetService(serviceType, serviceKey)
?? (serviceType == typeof(A2AClient) ? this._a2aClient
?? (serviceType == typeof(IA2AClient) ? this._a2aClient
: serviceType == typeof(AIAgentMetadata) ? s_agentMetadata
: null);
@@ -1,7 +1,5 @@
// Copyright (c) Microsoft. All rights reserved.
using System;
using System.Linq;
using System.Net.Http;
using Microsoft.Agents.AI;
using Microsoft.Extensions.Logging;
@@ -26,16 +24,15 @@ public static class A2AAgentCardExtensions
/// </remarks>
/// <param name="card">The <see cref="AgentCard" /> to use for the agent creation.</param>
/// <param name="httpClient">The <see cref="HttpClient"/> to use for HTTP requests.</param>
/// <param name="options">
/// Optional <see cref="A2AClientOptions"/> controlling protocol binding preference.
/// When not provided, defaults to preferring HTTP+JSON first, with JSON-RPC as fallback.
/// </param>
/// <param name="loggerFactory">The logger factory for enabling logging within the agent.</param>
/// <returns>An <see cref="AIAgent"/> instance backed by the A2A agent.</returns>
public static AIAgent AsAIAgent(this AgentCard card, HttpClient? httpClient = null, ILoggerFactory? loggerFactory = null)
public static AIAgent AsAIAgent(this AgentCard card, HttpClient? httpClient = null, A2AClientOptions? options = null, ILoggerFactory? loggerFactory = null)
{
// TODO: Refactor to support interface selection from card.SupportedInterfaces.
var url = card.SupportedInterfaces?.FirstOrDefault()?.Url
?? throw new InvalidOperationException("The AgentCard does not have any SupportedInterfaces with a URL.");
// Create the A2A client using the agent URL from the card.
var a2aClient = new A2AClient(new Uri(url), httpClient);
var a2aClient = A2AClientFactory.Create(card, httpClient, options);
return a2aClient.AsAIAgent(name: card.Name, description: card.Description, loggerFactory: loggerFactory);
}
@@ -34,14 +34,18 @@ public static class A2ACardResolverExtensions
/// </remarks>
/// <param name="resolver">The <see cref="A2ACardResolver" /> to use for the agent creation.</param>
/// <param name="httpClient">The <see cref="HttpClient"/> to use for HTTP requests.</param>
/// <param name="options">
/// Optional <see cref="A2AClientOptions"/> controlling protocol binding preference.
/// When not provided, defaults to preferring HTTP+JSON first, with JSON-RPC as fallback.
/// </param>
/// <param name="loggerFactory">The logger factory for enabling logging within the agent.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests. The default is <see cref="CancellationToken.None"/>.</param>
/// <returns>An <see cref="AIAgent"/> instance backed by the A2A agent.</returns>
public static async Task<AIAgent> GetAIAgentAsync(this A2ACardResolver resolver, HttpClient? httpClient = null, ILoggerFactory? loggerFactory = null, CancellationToken cancellationToken = default)
public static async Task<AIAgent> GetAIAgentAsync(this A2ACardResolver resolver, HttpClient? httpClient = null, A2AClientOptions? options = null, ILoggerFactory? loggerFactory = null, CancellationToken cancellationToken = default)
{
// Obtain the agent card from the resolver.
var agentCard = await resolver.GetAgentCardAsync(cancellationToken).ConfigureAwait(false);
return agentCard.AsAIAgent(httpClient, loggerFactory);
return agentCard.AsAIAgent(httpClient, options, loggerFactory);
}
}
@@ -7,7 +7,7 @@ using Microsoft.Extensions.Logging;
namespace A2A;
/// <summary>
/// Provides extension methods for <see cref="A2AClient"/>
/// Provides extension methods for <see cref="IA2AClient"/>
/// to simplify the creation of A2A agents.
/// </summary>
/// <remarks>
@@ -29,12 +29,12 @@ public static class A2AClientExtensions
/// <see href="https://github.com/a2aproject/A2A/blob/main/docs/topics/agent-discovery.md#3-direct-configuration--private-discovery">Direct Configuration / Private Discovery</see>
/// discovery mechanism.
/// </remarks>
/// <param name="client">The <see cref="A2AClient" /> to use for the agent.</param>
/// <param name="client">The <see cref="IA2AClient" /> to use for the agent.</param>
/// <param name="id">The unique identifier for the agent.</param>
/// <param name="name">The the name of the agent.</param>
/// <param name="description">The description of the agent.</param>
/// <param name="loggerFactory">Optional logger factory for enabling logging within the agent.</param>
/// <returns>An <see cref="AIAgent"/> instance backed by the A2A agent.</returns>
public static AIAgent AsAIAgent(this A2AClient client, string? id = null, string? name = null, string? description = null, ILoggerFactory? loggerFactory = null) =>
public static AIAgent AsAIAgent(this IA2AClient client, string? id = null, string? name = null, string? description = null, ILoggerFactory? loggerFactory = null) =>
new A2AAgent(client, id, name, description, loggerFactory);
}