mirror of
https://github.com/microsoft/agent-framework.git
synced 2026-06-16 21:04:09 +08:00
.NET: feat: Microsoft.Extensions.AI.Agents.Hosting.A2A package (#390)
* add timeout handling for message send * prepare a2a proj * fix it finally * add a holder for selected protocol * init types ; * see discoveredAgentCardJson * prettify json * correct usage * client setup for card * setp? * message:send * init task based communication * try call it via the agent thread * okay i got back the message wooooow! * nit * fix duplicates * yea matey! * fix knights-knaves for A2A-Task-based communication * fix a2a agents csproj * AI feedback * a2a does not support netstandard / netfx * try fix build + refactor * bump a2a for net9 only * rollback System.Net.ServerSentEvents & Microsoft.Bcl.AsyncInterfaces version upgrade; override in-place and retarget to net9;net8 for A2A * address PR comments x1 * refactor a2a interfaces * address PR comments x2 * fix cancel usage * separate project for A2A.AspNetCore * simplify * cleanup * cleanup dependencies * generate convertor tests / fix namespaces etc * setup actor client! * fix build * backoff conversations * fix duplicate message streaming * address PR comments x1 * remove internalsvisibleto * dont implement agent card query on my own: give it to the user * nit * rename and move projects * fix dotnet-format * address PR comments x1 * remove unreferenced project * rollback * rename * nit --------- Co-authored-by: Chris <66376200+crickman@users.noreply.github.com>
This commit is contained in:
+22
@@ -0,0 +1,22 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>$(ProjectsCoreTargetFrameworks)</TargetFrameworks>
|
||||
<TargetFrameworks Condition="'$(Configuration)' == 'Debug'">$(ProjectsCoreTargetFrameworks)</TargetFrameworks>
|
||||
<NoWarn>$(NoWarn);IDE1006;IDE0130;NU1504</NoWarn>
|
||||
<RootNamespace>Microsoft.Extensions.AI.Agents.Hosting.A2A.AspNetCore</RootNamespace>
|
||||
<VersionSuffix>alpha</VersionSuffix>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="A2A.AspNetCore" />
|
||||
|
||||
<PackageReference Include="System.Net.ServerSentEvents" VersionOverride="10.0.0-preview.5.25277.114" />
|
||||
<PackageReference Include="Microsoft.Bcl.AsyncInterfaces" VersionOverride="10.0.0-preview.5.25277.114" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Microsoft.Extensions.AI.Agents.Hosting.A2A\Microsoft.Extensions.AI.Agents.Hosting.A2A.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
+70
@@ -0,0 +1,70 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using A2A;
|
||||
using A2A.AspNetCore;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.Extensions.AI.Agents.Runtime;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Microsoft.Extensions.AI.Agents.Hosting.A2A.AspNetCore;
|
||||
|
||||
/// <summary>
|
||||
/// Provides extension methods for configuring A2A (Agent-to-Agent) communication in a host application builder.
|
||||
/// </summary>
|
||||
public static class WebApplicationExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Attaches A2A (Agent-to-Agent) communication capabilities via Message processing to the specified web application.
|
||||
/// </summary>
|
||||
/// <param name="app">The web application used to configure the pipeline and routes.</param>
|
||||
/// <param name="agentName">The name of the agent to use for A2A protocol integration.</param>
|
||||
/// <param name="path">The route group to use for A2A endpoints.</param>
|
||||
public static void AttachA2A(this WebApplication app, string agentName, string path)
|
||||
{
|
||||
var agent = app.Services.GetRequiredKeyedService<AIAgent>(agentName);
|
||||
var loggerFactory = app.Services.GetRequiredService<ILoggerFactory>();
|
||||
var actorClient = app.Services.GetRequiredService<IActorClient>();
|
||||
|
||||
var taskManager = agent.AttachA2A(actorClient, loggerFactory: loggerFactory);
|
||||
app.AttachA2A(taskManager, path);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attaches A2A (Agent-to-Agent) communication capabilities via Message processing to the specified web application.
|
||||
/// </summary>
|
||||
/// <param name="app">The web application used to configure the pipeline and routes.</param>
|
||||
/// <param name="agentName">The name of the agent to use for A2A protocol integration.</param>
|
||||
/// <param name="path">The route group to use for A2A endpoints.</param>
|
||||
/// <param name="agentCard">Agent card info to return on query.</param>
|
||||
public static void AttachA2A(
|
||||
this WebApplication app,
|
||||
string agentName,
|
||||
string path,
|
||||
AgentCard agentCard)
|
||||
{
|
||||
var agent = app.Services.GetRequiredKeyedService<AIAgent>(agentName);
|
||||
var loggerFactory = app.Services.GetRequiredService<ILoggerFactory>();
|
||||
var actorClient = app.Services.GetRequiredService<IActorClient>();
|
||||
|
||||
var taskManager = agent.AttachA2A(actorClient, agentCard: agentCard, loggerFactory: loggerFactory);
|
||||
app.AttachA2A(taskManager, path);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Maps HTTP A2A communication endpoints to the specified path using the provided TaskManager.
|
||||
/// TaskManager should be preconfigured before calling this method.
|
||||
/// </summary>
|
||||
/// <param name="app">The web application used to configure the pipeline and routes.</param>
|
||||
/// <param name="taskManager">Pre-configured A2A TaskManager to use for A2A endpoints handling.</param>
|
||||
/// <param name="path">The route group to use for A2A endpoints.</param>
|
||||
public static void AttachA2A(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.
|
||||
// see https://github.com/microsoft/agent-framework/issues/476 for details
|
||||
app.MapA2A(taskManager, path);
|
||||
|
||||
app.MapHttpA2A(taskManager, path);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using A2A;
|
||||
using Microsoft.Extensions.AI.Agents.Hosting.A2A.Internal;
|
||||
using Microsoft.Extensions.AI.Agents.Runtime;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Microsoft.Extensions.AI.Agents.Hosting.A2A;
|
||||
|
||||
/// <summary>
|
||||
/// Provides extension methods for attaching A2A (Agent-to-Agent) messaging capabilities to an <see cref="AIAgent"/>.
|
||||
/// </summary>
|
||||
public static class AIAgentExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Attaches A2A (Agent-to-Agent) messaging capabilities via Message processing to the specified <see cref="AIAgent"/>.
|
||||
/// </summary>
|
||||
/// <param name="agent">Agent to attach A2A messaging processing capabilities to.</param>
|
||||
/// <param name="actorClient">The actor client implementation to use.</param>
|
||||
/// <param name="taskManager">Instance of <see cref="TaskManager"/> to configure for A2A messaging. New instance will be created if not passed.</param>
|
||||
/// <param name="loggerFactory">The logger factory to use for creating <see cref="ILogger"/> instances.</param>
|
||||
/// <returns>The configured <see cref="TaskManager"/>.</returns>
|
||||
public static TaskManager AttachA2A(
|
||||
this AIAgent agent,
|
||||
IActorClient actorClient,
|
||||
TaskManager? taskManager = null,
|
||||
ILoggerFactory? loggerFactory = null)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(agent, nameof(agent));
|
||||
ArgumentNullException.ThrowIfNull(actorClient, nameof(actorClient));
|
||||
|
||||
taskManager ??= new();
|
||||
|
||||
var a2aAgentWrapper = new A2AAgentWrapper(actorClient, agent, loggerFactory);
|
||||
|
||||
taskManager.OnMessageReceived += a2aAgentWrapper.ProcessMessageAsync;
|
||||
|
||||
return taskManager;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attaches A2A (Agent-to-Agent) messaging capabilities via Message processing to the specified <see cref="AIAgent"/>.
|
||||
/// </summary>
|
||||
/// <param name="agent">Agent to attach A2A messaging processing capabilities to.</param>
|
||||
/// <param name="actorClient">The actor client implementation to use.</param>
|
||||
/// <param name="agentCard">The agent card to return on query.</param>
|
||||
/// <param name="taskManager">Instance of <see cref="TaskManager"/> to configure for A2A messaging. New instance will be created if not passed.</param>
|
||||
/// <param name="loggerFactory">The logger factory to use for creating <see cref="ILogger"/> instances.</param>
|
||||
/// <returns>The configured <see cref="TaskManager"/>.</returns>
|
||||
public static TaskManager AttachA2A(
|
||||
this AIAgent agent,
|
||||
IActorClient actorClient,
|
||||
AgentCard agentCard,
|
||||
TaskManager? taskManager = null,
|
||||
ILoggerFactory? loggerFactory = null)
|
||||
{
|
||||
taskManager = agent.AttachA2A(actorClient, taskManager, loggerFactory);
|
||||
|
||||
taskManager.OnAgentCardQuery += (context, query) =>
|
||||
{
|
||||
if (agentCard.Url is null)
|
||||
{
|
||||
// A2A SDK assigns the url on its own
|
||||
// we can help user if they did not set Url explicitly.
|
||||
agentCard.Url = context;
|
||||
}
|
||||
|
||||
return Task.FromResult(agentCard);
|
||||
};
|
||||
return taskManager;
|
||||
}
|
||||
}
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System;
|
||||
using System.Text.Json;
|
||||
using A2A;
|
||||
using Microsoft.Extensions.AI.Agents.Runtime;
|
||||
|
||||
namespace Microsoft.Extensions.AI.Agents.Hosting.A2A.Converters;
|
||||
|
||||
internal static class ActorEntitiesConverter
|
||||
{
|
||||
public static Message ToMessage(this ActorResponse response)
|
||||
{
|
||||
var agentRunResponse = response.Data.Deserialize(AgentHostingJsonUtilities.DefaultOptions.GetTypeInfo(typeof(AgentRunResponse))) as AgentRunResponse;
|
||||
if (agentRunResponse is null)
|
||||
{
|
||||
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();
|
||||
|
||||
return new Message
|
||||
{
|
||||
MessageId = response.MessageId ?? Guid.NewGuid().ToString(),
|
||||
ContextId = contextId,
|
||||
Role = MessageRole.Agent,
|
||||
Parts = parts
|
||||
};
|
||||
}
|
||||
|
||||
public static ActorRequestUpdate ToActorRequestUpdate(this Message message, RequestStatus status = RequestStatus.Completed)
|
||||
{
|
||||
// maybe we need to split to chatmessage-per-part, but the idea to map is clear
|
||||
var chatMessage = message.ToChatMessage();
|
||||
if (chatMessage is null)
|
||||
{
|
||||
throw new ArgumentException("The Message could not be converted to a ChatMessage.", nameof(message));
|
||||
}
|
||||
|
||||
var agentRunResponseUpdate = new AgentRunResponseUpdate(ChatRole.Assistant, chatMessage.Contents);
|
||||
var updateTypeInfo = AgentAbstractionsJsonUtilities.DefaultOptions.GetTypeInfo(typeof(AgentRunResponseUpdate));
|
||||
var jsonElement = JsonSerializer.SerializeToElement(agentRunResponseUpdate, updateTypeInfo);
|
||||
return new ActorRequestUpdate(status, jsonElement);
|
||||
}
|
||||
|
||||
public static AgentRunResponse ToAgentRunResponse(this Message message)
|
||||
{
|
||||
// maybe we need to split to chatmessage-per-part, but the idea to map is clear
|
||||
var chatMessage = message.ToChatMessage();
|
||||
if (chatMessage is null)
|
||||
{
|
||||
throw new ArgumentException("The Message could not be converted to a ChatMessage.", nameof(message));
|
||||
}
|
||||
|
||||
return new AgentRunResponse(chatMessage);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,261 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json;
|
||||
using A2A;
|
||||
|
||||
namespace Microsoft.Extensions.AI.Agents.Hosting.A2A.Converters;
|
||||
|
||||
internal static class MessageConverter
|
||||
{
|
||||
public static List<Part> ToParts(this IList<ChatMessage> chatMessages)
|
||||
{
|
||||
if (chatMessages is null || chatMessages.Count == 0)
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
var parts = new List<Part>();
|
||||
foreach (var chatMessage in chatMessages)
|
||||
{
|
||||
foreach (var content in chatMessage.Contents)
|
||||
{
|
||||
var part = ConvertAIContentToPart(content);
|
||||
if (part != null)
|
||||
{
|
||||
parts.Add(part);
|
||||
}
|
||||
}
|
||||
|
||||
// If no parts were created from content, create a text part from the message text
|
||||
if (chatMessage.Contents.Count == 0 && !string.IsNullOrEmpty(chatMessage.Text))
|
||||
{
|
||||
parts.Add(new TextPart { Text = chatMessage.Text });
|
||||
}
|
||||
}
|
||||
|
||||
return parts;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts A2A MessageSendParams to a collection of Microsoft.Extensions.AI ChatMessage objects.
|
||||
/// </summary>
|
||||
/// <param name="messageSendParams">The A2A message send parameters to convert.</param>
|
||||
/// <returns>A read-only collection of ChatMessage objects.</returns>
|
||||
public static List<ChatMessage> ToChatMessages(this MessageSendParams messageSendParams)
|
||||
{
|
||||
if (messageSendParams is null)
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
var result = new List<ChatMessage>();
|
||||
if (messageSendParams.Message?.Parts != null)
|
||||
{
|
||||
var chatMessage = ToChatMessage(messageSendParams.Message);
|
||||
if (chatMessage is not null)
|
||||
{
|
||||
result.Add(chatMessage);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts collection of A2A <see cref="Message"/> to a collection of <see cref="ChatMessage"/> objects.
|
||||
/// </summary>
|
||||
/// <returns>A read-only collection of ChatMessage objects.</returns>
|
||||
public static IReadOnlyCollection<ChatMessage> ToChatMessages(this ICollection<Message> messages)
|
||||
{
|
||||
if (messages is null || messages.Count == 0)
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
var result = new List<ChatMessage>();
|
||||
foreach (var message in messages)
|
||||
{
|
||||
var chatMessage = ToChatMessage(message);
|
||||
if (chatMessage is not null)
|
||||
{
|
||||
result.Add(chatMessage);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts a single <see cref="Message"/> to a <see cref="ChatMessage"/>.
|
||||
/// </summary>
|
||||
/// <param name="message">The A2A message to convert.</param>
|
||||
/// <returns>A ChatMessage object, or null if conversion is not possible.</returns>
|
||||
public static ChatMessage? ToChatMessage(this Message message)
|
||||
{
|
||||
if (message?.Parts == null || message.Parts.Count == 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var chatRole = ConvertMessageRoleToChatRole(message.Role);
|
||||
|
||||
var content = new List<AIContent>();
|
||||
foreach (var part in message.Parts)
|
||||
{
|
||||
var aiContent = ConvertPartToAIContent(part);
|
||||
if (aiContent is not null)
|
||||
{
|
||||
content.Add(aiContent);
|
||||
}
|
||||
}
|
||||
|
||||
// If no valid content was extracted, return null
|
||||
if (content.Count == 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// Create the ChatMessage with appropriate metadata
|
||||
var chatMessage = new ChatMessage(chatRole, content)
|
||||
{
|
||||
MessageId = message.MessageId,
|
||||
RawRepresentation = message
|
||||
};
|
||||
|
||||
// Add any additional properties if needed
|
||||
if (message.Metadata is not null)
|
||||
{
|
||||
chatMessage.AdditionalProperties = message.Metadata.ToAdditionalPropertiesDictionary();
|
||||
}
|
||||
|
||||
return chatMessage;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts A2A MessageRole to Microsoft.Extensions.AI ChatRole.
|
||||
/// </summary>
|
||||
/// <param name="messageRole">The A2A message role.</param>
|
||||
/// <returns>The corresponding ChatRole.</returns>
|
||||
private static ChatRole ConvertMessageRoleToChatRole(MessageRole messageRole) => messageRole switch
|
||||
{
|
||||
MessageRole.User => ChatRole.User,
|
||||
MessageRole.Agent => ChatRole.Assistant,
|
||||
_ => ChatRole.User
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Converts an A2A Part to Microsoft.Extensions.AI AIContent.
|
||||
/// </summary>
|
||||
/// <param name="part">The A2A part to convert.</param>
|
||||
/// <returns>An AIContent object, or null if conversion is not possible.</returns>
|
||||
#pragma warning disable CA1859 // Use concrete types when possible for improved performance
|
||||
private static AIContent? ConvertPartToAIContent(Part part)
|
||||
#pragma warning restore CA1859 // Use concrete types when possible for improved performance
|
||||
{
|
||||
var result = part switch
|
||||
{
|
||||
TextPart textPart => new TextContent(textPart.Text)
|
||||
{
|
||||
RawRepresentation = textPart,
|
||||
AdditionalProperties = textPart.Metadata?.ToAdditionalPropertiesDictionary()
|
||||
},
|
||||
FilePart or DataPart or _ => throw new NotSupportedException($"Part type '{part.GetType().Name}' is not supported. Only TextPart is supported.")
|
||||
};
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts Microsoft.Extensions.AI ChatMessage back to A2A Message format.
|
||||
/// This is useful for the reverse operation.
|
||||
/// </summary>
|
||||
/// <param name="chatMessage">The ChatMessage to convert.</param>
|
||||
/// <returns>An A2A Message object.</returns>
|
||||
public static Message ToA2AMessage(this ChatMessage chatMessage)
|
||||
{
|
||||
if (chatMessage == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(chatMessage));
|
||||
}
|
||||
|
||||
var message = new Message
|
||||
{
|
||||
MessageId = chatMessage.MessageId ?? System.Guid.NewGuid().ToString(),
|
||||
Role = ConvertChatRoleToMessageRole(chatMessage.Role),
|
||||
Parts = new List<Part>()
|
||||
};
|
||||
|
||||
// Convert content to parts
|
||||
foreach (var content in chatMessage.Contents)
|
||||
{
|
||||
var part = ConvertAIContentToPart(content);
|
||||
if (part != null)
|
||||
{
|
||||
message.Parts.Add(part);
|
||||
}
|
||||
}
|
||||
|
||||
// If no parts were created from content, create a text part from the message text
|
||||
if (message.Parts.Count == 0 && !string.IsNullOrEmpty(chatMessage.Text))
|
||||
{
|
||||
message.Parts.Add(new TextPart { Text = chatMessage.Text });
|
||||
}
|
||||
|
||||
return message;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts Microsoft.Extensions.AI ChatRole to A2A MessageRole.
|
||||
/// </summary>
|
||||
/// <param name="chatRole">The ChatRole to convert.</param>
|
||||
/// <returns>The corresponding MessageRole.</returns>
|
||||
private static MessageRole ConvertChatRoleToMessageRole(ChatRole chatRole)
|
||||
{
|
||||
if (chatRole == ChatRole.User)
|
||||
{
|
||||
return MessageRole.User;
|
||||
}
|
||||
if (chatRole == ChatRole.Assistant)
|
||||
{
|
||||
return MessageRole.Agent;
|
||||
}
|
||||
|
||||
return MessageRole.User; // Default fallback
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts Microsoft.Extensions.AI AIContent to A2A Part.
|
||||
/// </summary>
|
||||
/// <param name="content">The AIContent to convert.</param>
|
||||
/// <returns>A Part object, or null if conversion is not possible.</returns>
|
||||
#pragma warning disable CA1859 // Use concrete types when possible for improved performance
|
||||
private static Part? ConvertAIContentToPart(AIContent content)
|
||||
#pragma warning restore CA1859 // Use concrete types when possible for improved performance
|
||||
{
|
||||
return content switch
|
||||
{
|
||||
TextContent textContent => new TextPart
|
||||
{
|
||||
Text = textContent.Text
|
||||
},
|
||||
_ => throw new NotSupportedException($"Content type '{content.GetType().Name}' is not supported.")
|
||||
};
|
||||
}
|
||||
|
||||
private static AdditionalPropertiesDictionary? ToAdditionalPropertiesDictionary(this Dictionary<string, JsonElement> metadata)
|
||||
{
|
||||
if (metadata == null || metadata.Count == 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var additionalProperties = new AdditionalPropertiesDictionary();
|
||||
foreach (var kvp in metadata)
|
||||
{
|
||||
additionalProperties[kvp.Key] = kvp.Value;
|
||||
}
|
||||
return additionalProperties;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System;
|
||||
using System.Text.Json;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using A2A;
|
||||
using Microsoft.Extensions.AI.Agents.Hosting.A2A.Converters;
|
||||
using Microsoft.Extensions.AI.Agents.Runtime;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
|
||||
namespace Microsoft.Extensions.AI.Agents.Hosting.A2A.Internal;
|
||||
|
||||
/// <summary>
|
||||
/// A2A agent that wraps an existing AIAgent and provides A2A-specific thread wrapping.
|
||||
/// </summary>
|
||||
internal sealed class A2AAgentWrapper
|
||||
{
|
||||
private readonly ILogger _logger;
|
||||
private readonly AIAgent _innerAgent;
|
||||
private readonly IActorClient _actorClient;
|
||||
|
||||
public A2AAgentWrapper(
|
||||
IActorClient actorClient,
|
||||
AIAgent innerAgent,
|
||||
ILoggerFactory? loggerFactory = null)
|
||||
{
|
||||
this._logger = (loggerFactory ?? NullLoggerFactory.Instance).CreateLogger<A2AAgentWrapper>();
|
||||
|
||||
this._actorClient = actorClient;
|
||||
this._innerAgent = innerAgent ?? throw new ArgumentNullException(nameof(innerAgent));
|
||||
}
|
||||
|
||||
public async Task<Message> ProcessMessageAsync(MessageSendParams messageSendParams, CancellationToken cancellationToken)
|
||||
{
|
||||
var contextId = messageSendParams.Message.ContextId ?? Guid.NewGuid().ToString();
|
||||
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);
|
||||
|
||||
return response.ToMessage();
|
||||
}
|
||||
|
||||
private ActorType GetActorType()
|
||||
{
|
||||
// agent is registered in DI via name
|
||||
ArgumentException.ThrowIfNullOrEmpty(this._innerAgent.Name);
|
||||
return new ActorType(this._innerAgent.Name);
|
||||
}
|
||||
}
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>$(ProjectsCoreTargetFrameworks)</TargetFrameworks>
|
||||
<TargetFrameworks Condition="'$(Configuration)' == 'Debug'">$(ProjectsCoreTargetFrameworks)</TargetFrameworks>
|
||||
<NoWarn>$(NoWarn);IDE1006;IDE0130;NU1504</NoWarn>
|
||||
<RootNamespace>Microsoft.Extensions.AI.Agents.Hosting.A2A</RootNamespace>
|
||||
<VersionSuffix>alpha</VersionSuffix>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="A2A" />
|
||||
|
||||
<PackageReference Include="System.Net.ServerSentEvents" VersionOverride="10.0.0-preview.5.25277.114" />
|
||||
<PackageReference Include="Microsoft.Bcl.AsyncInterfaces" VersionOverride="10.0.0-preview.5.25277.114" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Microsoft.Extensions.AI.Agents.Abstractions\Microsoft.Extensions.AI.Agents.Abstractions.csproj" />
|
||||
<ProjectReference Include="..\Microsoft.Extensions.AI.Agents.Hosting\Microsoft.Extensions.AI.Agents.Hosting.csproj" />
|
||||
<ProjectReference Include="..\Microsoft.Extensions.AI.Agents.Runtime.Abstractions\Microsoft.Extensions.AI.Agents.Runtime.Abstractions.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<InternalsVisibleTo Include="Microsoft.Extensions.AI.Agents.A2A.AspNetCore" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<InternalsVisibleTo Include="AgentWebChat.Web" />
|
||||
<InternalsVisibleTo Include="Microsoft.Extensions.AI.Agents.Hosting.A2A.Tests" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
Reference in New Issue
Block a user