mirror of
https://github.com/microsoft/agent-framework.git
synced 2026-06-16 21:04:09 +08:00
update a2a agent to the latest a2a sdk (#5257)
This commit is contained in:
committed by
GitHub
Unverified
parent
485af07b8c
commit
6173e63f0b
@@ -32,18 +32,18 @@
|
||||
<!-- Newtonsoft.Json -->
|
||||
<PackageVersion Include="Newtonsoft.Json" Version="13.0.4" />
|
||||
<!-- System.* -->
|
||||
<PackageVersion Include="Microsoft.Bcl.AsyncInterfaces" Version="10.0.4" />
|
||||
<PackageVersion Include="Microsoft.Bcl.AsyncInterfaces" Version="10.0.5" />
|
||||
<PackageVersion Include="Microsoft.Bcl.HashCode" Version="6.0.0" />
|
||||
<PackageVersion Include="Microsoft.Bcl.Memory" Version="10.0.4" />
|
||||
<PackageVersion Include="Microsoft.Bcl.Memory" Version="10.0.5" />
|
||||
<PackageVersion Include="System.ClientModel" Version="1.10.0" />
|
||||
<PackageVersion Include="System.CodeDom" Version="10.0.0" />
|
||||
<PackageVersion Include="System.Collections.Immutable" Version="10.0.1" />
|
||||
<PackageVersion Include="System.CommandLine" Version="2.0.0-rc.2.25502.107" />
|
||||
<PackageVersion Include="System.Diagnostics.DiagnosticSource" Version="10.0.4" />
|
||||
<PackageVersion Include="System.Linq.AsyncEnumerable" Version="10.0.4" />
|
||||
<PackageVersion Include="System.Diagnostics.DiagnosticSource" Version="10.0.5" />
|
||||
<PackageVersion Include="System.Linq.AsyncEnumerable" Version="10.0.5" />
|
||||
<PackageVersion Include="System.Net.Http.Json" Version="10.0.0" />
|
||||
<PackageVersion Include="System.Net.ServerSentEvents" Version="10.0.4" />
|
||||
<PackageVersion Include="System.Text.Json" Version="10.0.4" />
|
||||
<PackageVersion Include="System.Net.ServerSentEvents" Version="10.0.5" />
|
||||
<PackageVersion Include="System.Text.Json" Version="10.0.5" />
|
||||
<PackageVersion Include="System.Threading.Channels" Version="10.0.4" />
|
||||
<PackageVersion Include="System.Threading.Tasks.Extensions" Version="4.6.3" />
|
||||
<PackageVersion Include="System.Net.Security" Version="4.3.2" />
|
||||
@@ -64,7 +64,7 @@
|
||||
<PackageVersion Include="Swashbuckle.AspNetCore.SwaggerUI" Version="10.0.0" />
|
||||
<!-- Microsoft.Extensions.* -->
|
||||
<PackageVersion Include="Microsoft.Extensions.AI" Version="10.4.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.AI.Abstractions" Version="10.4.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.AI.Abstractions" Version="10.4.1" />
|
||||
<PackageVersion Include="Microsoft.Extensions.AI.Evaluation" Version="10.4.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.AI.Evaluation.Quality" Version="10.4.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.AI.Evaluation.Safety" Version="10.3.0-preview.1.26109.11" />
|
||||
@@ -77,11 +77,11 @@
|
||||
<PackageVersion Include="Microsoft.Extensions.Configuration.Json" Version="10.0.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Configuration.UserSecrets" Version="10.0.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="10.0.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="10.0.4" />
|
||||
<PackageVersion Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="10.0.5" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Hosting" Version="10.0.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Http.Resilience" Version="10.0.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Logging" Version="10.0.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.4" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.5" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Logging.Console" Version="10.0.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.ServiceDiscovery" Version="10.0.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.VectorData.Abstractions" Version="9.7.0" />
|
||||
@@ -102,8 +102,8 @@
|
||||
<PackageVersion Include="Microsoft.Agents.Authentication.Msal" Version="1.3.171-beta" />
|
||||
<PackageVersion Include="Microsoft.Agents.Hosting.AspNetCore" Version="1.3.171-beta" />
|
||||
<!-- A2A -->
|
||||
<PackageVersion Include="A2A" Version="0.3.4-preview" />
|
||||
<PackageVersion Include="A2A.AspNetCore" Version="0.3.4-preview" />
|
||||
<PackageVersion Include="A2A" Version="1.0.0-preview2" />
|
||||
<PackageVersion Include="A2A.AspNetCore" Version="1.0.0-preview2" />
|
||||
<!-- MCP -->
|
||||
<PackageVersion Include="ModelContextProtocol" Version="1.1.0" />
|
||||
<!-- Inference SDKs -->
|
||||
|
||||
+1
-2
@@ -2,7 +2,7 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<TargetFrameworks>net10.0</TargetFrameworks>
|
||||
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
@@ -13,7 +13,6 @@
|
||||
<PackageReference Include="Azure.AI.OpenAI" />
|
||||
<PackageReference Include="Azure.Identity" />
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting" />
|
||||
<PackageReference Include="System.Net.ServerSentEvents" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -62,12 +62,10 @@ public static class Program
|
||||
}
|
||||
|
||||
var agentResponse = await hostAgent.Agent!.RunAsync(message, session, cancellationToken: cancellationToken);
|
||||
foreach (var chatMessage in agentResponse.Messages)
|
||||
{
|
||||
Console.ForegroundColor = ConsoleColor.Cyan;
|
||||
Console.WriteLine($"\nAgent: {chatMessage.Text}");
|
||||
Console.ResetColor();
|
||||
}
|
||||
|
||||
Console.ForegroundColor = ConsoleColor.Cyan;
|
||||
Console.WriteLine($"\nAgent: {agentResponse.Text}");
|
||||
Console.ResetColor();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
||||
@@ -13,7 +13,7 @@ namespace A2AServer;
|
||||
|
||||
internal static class HostAgentFactory
|
||||
{
|
||||
internal static async Task<(AIAgent, AgentCard)> CreateFoundryHostAgentAsync(string agentType, string model, string endpoint, string agentName, IList<AITool>? tools = null)
|
||||
internal static async Task<(AIAgent, AgentCard)> CreateFoundryHostAgentAsync(string agentType, string model, string endpoint, string agentName, string[] agentUrls, IList<AITool>? tools = null)
|
||||
{
|
||||
// WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production.
|
||||
// In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid
|
||||
@@ -25,16 +25,16 @@ internal static class HostAgentFactory
|
||||
|
||||
AgentCard agentCard = agentType.ToUpperInvariant() switch
|
||||
{
|
||||
"INVOICE" => GetInvoiceAgentCard(),
|
||||
"POLICY" => GetPolicyAgentCard(),
|
||||
"LOGISTICS" => GetLogisticsAgentCard(),
|
||||
"INVOICE" => GetInvoiceAgentCard(agentUrls),
|
||||
"POLICY" => GetPolicyAgentCard(agentUrls),
|
||||
"LOGISTICS" => GetLogisticsAgentCard(agentUrls),
|
||||
_ => throw new ArgumentException($"Unsupported agent type: {agentType}"),
|
||||
};
|
||||
|
||||
return new(agent, agentCard);
|
||||
}
|
||||
|
||||
internal static async Task<(AIAgent, AgentCard)> CreateChatCompletionHostAgentAsync(string agentType, string model, string apiKey, string name, string instructions, IList<AITool>? tools = null)
|
||||
internal static async Task<(AIAgent, AgentCard)> CreateChatCompletionHostAgentAsync(string agentType, string model, string apiKey, string name, string instructions, string[] agentUrls, IList<AITool>? tools = null)
|
||||
{
|
||||
AIAgent agent = new OpenAIClient(apiKey)
|
||||
.GetChatClient(model)
|
||||
@@ -42,9 +42,9 @@ internal static class HostAgentFactory
|
||||
|
||||
AgentCard agentCard = agentType.ToUpperInvariant() switch
|
||||
{
|
||||
"INVOICE" => GetInvoiceAgentCard(),
|
||||
"POLICY" => GetPolicyAgentCard(),
|
||||
"LOGISTICS" => GetLogisticsAgentCard(),
|
||||
"INVOICE" => GetInvoiceAgentCard(agentUrls),
|
||||
"POLICY" => GetPolicyAgentCard(agentUrls),
|
||||
"LOGISTICS" => GetLogisticsAgentCard(agentUrls),
|
||||
_ => throw new ArgumentException($"Unsupported agent type: {agentType}"),
|
||||
};
|
||||
|
||||
@@ -52,7 +52,7 @@ internal static class HostAgentFactory
|
||||
}
|
||||
|
||||
#region private
|
||||
private static AgentCard GetInvoiceAgentCard()
|
||||
private static AgentCard GetInvoiceAgentCard(string[] agentUrls)
|
||||
{
|
||||
var capabilities = new AgentCapabilities()
|
||||
{
|
||||
@@ -81,10 +81,11 @@ internal static class HostAgentFactory
|
||||
DefaultOutputModes = ["text"],
|
||||
Capabilities = capabilities,
|
||||
Skills = [invoiceQuery],
|
||||
SupportedInterfaces = CreateAgentInterfaces(agentUrls)
|
||||
};
|
||||
}
|
||||
|
||||
private static AgentCard GetPolicyAgentCard()
|
||||
private static AgentCard GetPolicyAgentCard(string[] agentUrls)
|
||||
{
|
||||
var capabilities = new AgentCapabilities()
|
||||
{
|
||||
@@ -113,10 +114,11 @@ internal static class HostAgentFactory
|
||||
DefaultOutputModes = ["text"],
|
||||
Capabilities = capabilities,
|
||||
Skills = [policyQuery],
|
||||
SupportedInterfaces = CreateAgentInterfaces(agentUrls)
|
||||
};
|
||||
}
|
||||
|
||||
private static AgentCard GetLogisticsAgentCard()
|
||||
private static AgentCard GetLogisticsAgentCard(string[] agentUrls)
|
||||
{
|
||||
var capabilities = new AgentCapabilities()
|
||||
{
|
||||
@@ -145,7 +147,18 @@ internal static class HostAgentFactory
|
||||
DefaultOutputModes = ["text"],
|
||||
Capabilities = capabilities,
|
||||
Skills = [logisticsQuery],
|
||||
SupportedInterfaces = CreateAgentInterfaces(agentUrls)
|
||||
};
|
||||
}
|
||||
|
||||
private static List<AgentInterface> CreateAgentInterfaces(string[] agentUrls)
|
||||
{
|
||||
return agentUrls.Select(url => new AgentInterface
|
||||
{
|
||||
Url = url,
|
||||
ProtocolBinding = "JSONRPC",
|
||||
ProtocolVersion = "1.0",
|
||||
}).ToList();
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
|
||||
@@ -38,14 +38,15 @@ IConfigurationRoot configuration = new ConfigurationBuilder()
|
||||
string? apiKey = configuration["OPENAI_API_KEY"];
|
||||
string model = configuration["OPENAI_CHAT_MODEL_NAME"] ?? "gpt-5.4-mini";
|
||||
string? endpoint = configuration["AZURE_AI_PROJECT_ENDPOINT"];
|
||||
string[] agentUrls = (app.Configuration["urls"] ?? "http://localhost:5000").Split(';');
|
||||
|
||||
var invoiceQueryPlugin = new InvoiceQuery();
|
||||
IList<AITool> tools =
|
||||
[
|
||||
[
|
||||
AIFunctionFactory.Create(invoiceQueryPlugin.QueryInvoices),
|
||||
AIFunctionFactory.Create(invoiceQueryPlugin.QueryByTransactionId),
|
||||
AIFunctionFactory.Create(invoiceQueryPlugin.QueryByInvoiceId)
|
||||
];
|
||||
];
|
||||
|
||||
AIAgent hostA2AAgent;
|
||||
AgentCard hostA2AAgentCard;
|
||||
@@ -54,9 +55,9 @@ if (!string.IsNullOrEmpty(endpoint) && !string.IsNullOrEmpty(agentName))
|
||||
{
|
||||
(hostA2AAgent, hostA2AAgentCard) = agentType.ToUpperInvariant() switch
|
||||
{
|
||||
"INVOICE" => await HostAgentFactory.CreateFoundryHostAgentAsync(agentType, model, endpoint, agentName, tools),
|
||||
"POLICY" => await HostAgentFactory.CreateFoundryHostAgentAsync(agentType, model, endpoint, agentName),
|
||||
"LOGISTICS" => await HostAgentFactory.CreateFoundryHostAgentAsync(agentType, model, endpoint, agentName),
|
||||
"INVOICE" => await HostAgentFactory.CreateFoundryHostAgentAsync(agentType, model, endpoint, agentName, agentUrls, tools),
|
||||
"POLICY" => await HostAgentFactory.CreateFoundryHostAgentAsync(agentType, model, endpoint, agentName, agentUrls),
|
||||
"LOGISTICS" => await HostAgentFactory.CreateFoundryHostAgentAsync(agentType, model, endpoint, agentName, agentUrls),
|
||||
_ => throw new ArgumentException($"Unsupported agent type: {agentType}"),
|
||||
};
|
||||
}
|
||||
@@ -68,7 +69,7 @@ else if (!string.IsNullOrEmpty(apiKey))
|
||||
agentType, model, apiKey, "InvoiceAgent",
|
||||
"""
|
||||
You specialize in handling queries related to invoices.
|
||||
""", tools),
|
||||
""", agentUrls, tools),
|
||||
"POLICY" => await HostAgentFactory.CreateChatCompletionHostAgentAsync(
|
||||
agentType, model, apiKey, "PolicyAgent",
|
||||
"""
|
||||
@@ -84,7 +85,7 @@ else if (!string.IsNullOrEmpty(apiKey))
|
||||
resolution in SAP CRM and notify the customer via email within 2 business days, referencing the
|
||||
original invoice and the credit memo number. Use the 'Formal Credit Notification' email
|
||||
template."
|
||||
"""),
|
||||
""", agentUrls),
|
||||
"LOGISTICS" => await HostAgentFactory.CreateChatCompletionHostAgentAsync(
|
||||
agentType, model, apiKey, "LogisticsAgent",
|
||||
"""
|
||||
@@ -95,7 +96,7 @@ else if (!string.IsNullOrEmpty(apiKey))
|
||||
Shipment number: SHPMT-SAP-001
|
||||
Item: TSHIRT-RED-L
|
||||
Quantity: 900
|
||||
"""),
|
||||
""", agentUrls),
|
||||
_ => throw new ArgumentException($"Unsupported agent type: {agentType}"),
|
||||
};
|
||||
}
|
||||
@@ -104,10 +105,9 @@ else
|
||||
throw new ArgumentException("Either A2AServer:ApiKey or A2AServer:ConnectionString & agentName must be provided");
|
||||
}
|
||||
|
||||
var a2aTaskManager = app.MapA2A(
|
||||
app.MapA2A(
|
||||
hostA2AAgent,
|
||||
path: "/",
|
||||
agentCard: hostA2AAgentCard,
|
||||
taskManager => app.MapWellKnownAgentCard(taskManager, "/"));
|
||||
agentCard: hostA2AAgentCard);
|
||||
|
||||
await app.RunAsync();
|
||||
|
||||
@@ -43,20 +43,21 @@ internal sealed class A2AAgentClient : AgentClientBase
|
||||
{
|
||||
// Convert all messages to A2A parts and create a single message
|
||||
var parts = messages.ToParts();
|
||||
var a2aMessage = new AgentMessage
|
||||
var a2aMessage = new Message
|
||||
{
|
||||
MessageId = Guid.NewGuid().ToString("N"),
|
||||
ContextId = contextId,
|
||||
Role = MessageRole.User,
|
||||
Role = Role.User,
|
||||
Parts = parts
|
||||
};
|
||||
|
||||
var messageSendParams = new MessageSendParams { Message = a2aMessage };
|
||||
var messageSendParams = new SendMessageRequest { Message = a2aMessage };
|
||||
var a2aResponse = await a2aClient.SendMessageAsync(messageSendParams, cancellationToken);
|
||||
|
||||
// Handle different response types
|
||||
if (a2aResponse is AgentMessage message)
|
||||
if (a2aResponse.PayloadCase == SendMessageResponseCase.Message)
|
||||
{
|
||||
var message = a2aResponse.Message!;
|
||||
var responseMessage = message.ToChatMessage();
|
||||
if (responseMessage is { Contents.Count: > 0 })
|
||||
{
|
||||
@@ -67,9 +68,10 @@ internal sealed class A2AAgentClient : AgentClientBase
|
||||
});
|
||||
}
|
||||
}
|
||||
else if (a2aResponse is AgentTask agentTask)
|
||||
else if (a2aResponse.PayloadCase == SendMessageResponseCase.Task)
|
||||
{
|
||||
// Manually convert AgentTask artifacts to ChatMessages since the extension method is internal
|
||||
var agentTask = a2aResponse.Task!;
|
||||
if (agentTask.Artifacts is not null)
|
||||
{
|
||||
foreach (var artifact in agentTask.Artifacts)
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net.ServerSentEvents;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text.Json;
|
||||
using System.Threading;
|
||||
@@ -100,64 +99,47 @@ public sealed class A2AAgent : AIAgent
|
||||
|
||||
this._logger.LogA2AAgentInvokingAgent(nameof(RunAsync), this.Id, this.Name);
|
||||
|
||||
A2AResponse? a2aResponse = null;
|
||||
|
||||
if (GetContinuationToken(messages, options) is { } token)
|
||||
{
|
||||
a2aResponse = await this._a2aClient.GetTaskAsync(token.TaskId, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
MessageSendParams sendParams = new()
|
||||
{
|
||||
Message = CreateA2AMessage(typedSession, messages),
|
||||
Metadata = options?.AdditionalProperties?.ToA2AMetadata()
|
||||
};
|
||||
AgentTask agentTask = await this._a2aClient.GetTaskAsync(new GetTaskRequest { Id = token.TaskId }, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
a2aResponse = await this._a2aClient.SendMessageAsync(sendParams, cancellationToken).ConfigureAwait(false);
|
||||
this._logger.LogAgentChatClientInvokedAgent(nameof(RunAsync), this.Id, this.Name);
|
||||
|
||||
UpdateSession(typedSession, agentTask.ContextId, agentTask.Id);
|
||||
|
||||
return this.ConvertToAgentResponse(agentTask);
|
||||
}
|
||||
|
||||
SendMessageRequest sendParams = new()
|
||||
{
|
||||
Message = CreateA2AMessage(typedSession, messages),
|
||||
Metadata = options?.AdditionalProperties?.ToA2AMetadata(),
|
||||
Configuration = new SendMessageConfiguration { ReturnImmediately = options?.AllowBackgroundResponses is true }
|
||||
};
|
||||
|
||||
SendMessageResponse a2aResponse = await this._a2aClient.SendMessageAsync(sendParams, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
this._logger.LogAgentChatClientInvokedAgent(nameof(RunAsync), this.Id, this.Name);
|
||||
|
||||
if (a2aResponse is AgentMessage message)
|
||||
if (a2aResponse.PayloadCase == SendMessageResponseCase.Message)
|
||||
{
|
||||
var message = a2aResponse.Message!;
|
||||
|
||||
UpdateSession(typedSession, message.ContextId);
|
||||
|
||||
return new AgentResponse
|
||||
{
|
||||
AgentId = this.Id,
|
||||
ResponseId = message.MessageId,
|
||||
FinishReason = ChatFinishReason.Stop,
|
||||
RawRepresentation = message,
|
||||
Messages = [message.ToChatMessage()],
|
||||
AdditionalProperties = message.Metadata?.ToAdditionalProperties(),
|
||||
};
|
||||
return this.ConvertToAgentResponse(message);
|
||||
}
|
||||
|
||||
if (a2aResponse is AgentTask agentTask)
|
||||
if (a2aResponse.PayloadCase == SendMessageResponseCase.Task)
|
||||
{
|
||||
var agentTask = a2aResponse.Task!;
|
||||
|
||||
UpdateSession(typedSession, agentTask.ContextId, agentTask.Id);
|
||||
|
||||
var response = new AgentResponse
|
||||
{
|
||||
AgentId = this.Id,
|
||||
ResponseId = agentTask.Id,
|
||||
FinishReason = MapTaskStateToFinishReason(agentTask.Status.State),
|
||||
RawRepresentation = agentTask,
|
||||
Messages = agentTask.ToChatMessages() ?? [],
|
||||
ContinuationToken = CreateContinuationToken(agentTask.Id, agentTask.Status.State),
|
||||
AdditionalProperties = agentTask.Metadata?.ToAdditionalProperties(),
|
||||
};
|
||||
|
||||
if (agentTask.ToChatMessages() is { Count: > 0 } taskMessages)
|
||||
{
|
||||
response.Messages = taskMessages;
|
||||
}
|
||||
|
||||
return response;
|
||||
return this.ConvertToAgentResponse(agentTask);
|
||||
}
|
||||
|
||||
throw new NotSupportedException($"Only Message and AgentTask responses are supported from A2A agents. Received: {a2aResponse.GetType().FullName ?? "null"}");
|
||||
throw new NotSupportedException($"Only Message and AgentTask responses are supported from A2A agents. Received: {a2aResponse.PayloadCase}");
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
@@ -169,59 +151,61 @@ public sealed class A2AAgent : AIAgent
|
||||
|
||||
this._logger.LogA2AAgentInvokingAgent(nameof(RunStreamingAsync), this.Id, this.Name);
|
||||
|
||||
ConfiguredCancelableAsyncEnumerable<SseItem<A2AEvent>> a2aSseEvents;
|
||||
ConfiguredCancelableAsyncEnumerable<StreamResponse> streamEvents;
|
||||
|
||||
if (options?.ContinuationToken is not null)
|
||||
if (GetContinuationToken(messages, options) is { } token)
|
||||
{
|
||||
// Task stream resumption is not well defined in the A2A v2.* specification, leaving it to the agent implementations.
|
||||
// The v3.0 specification improves this by defining task stream reconnection that allows obtaining the same stream
|
||||
// from the beginning, but it does not define stream resumption from a specific point in the stream.
|
||||
// Therefore, the code should be updated once the A2A .NET library supports the A2A v3.0 specification,
|
||||
// and AF has the necessary model to allow consumers to know whether they need to resume the stream and add new updates to
|
||||
// the existing ones or reconnect the stream and obtain all updates again.
|
||||
// For more details, see the following issue: https://github.com/microsoft/agent-framework/issues/1764
|
||||
throw new InvalidOperationException("Reconnecting to task streams using continuation tokens is not supported yet.");
|
||||
// a2aSseEvents = this._a2aClient.SubscribeToTaskAsync(token.TaskId, cancellationToken).ConfigureAwait(false);
|
||||
streamEvents = this._a2aClient.SubscribeToTaskAsync(new SubscribeToTaskRequest { Id = token.TaskId }, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
MessageSendParams sendParams = new()
|
||||
else
|
||||
{
|
||||
Message = CreateA2AMessage(typedSession, messages),
|
||||
Metadata = options?.AdditionalProperties?.ToA2AMetadata()
|
||||
};
|
||||
SendMessageRequest sendParams = new()
|
||||
{
|
||||
Message = CreateA2AMessage(typedSession, messages),
|
||||
Metadata = options?.AdditionalProperties?.ToA2AMetadata()
|
||||
};
|
||||
|
||||
a2aSseEvents = this._a2aClient.SendMessageStreamingAsync(sendParams, cancellationToken).ConfigureAwait(false);
|
||||
streamEvents = this._a2aClient.SendStreamingMessageAsync(sendParams, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
this._logger.LogAgentChatClientInvokedAgent(nameof(RunStreamingAsync), this.Id, this.Name);
|
||||
|
||||
string? contextId = null;
|
||||
string? taskId = null;
|
||||
|
||||
await foreach (var sseEvent in a2aSseEvents)
|
||||
await foreach (var streamResponse in streamEvents)
|
||||
{
|
||||
if (sseEvent.Data is AgentMessage message)
|
||||
switch (streamResponse.PayloadCase)
|
||||
{
|
||||
contextId = message.ContextId;
|
||||
case StreamResponseCase.Message:
|
||||
var message = streamResponse.Message!;
|
||||
contextId = message.ContextId;
|
||||
yield return this.ConvertToAgentResponseUpdate(message);
|
||||
break;
|
||||
|
||||
yield return this.ConvertToAgentResponseUpdate(message);
|
||||
}
|
||||
else if (sseEvent.Data is AgentTask task)
|
||||
{
|
||||
contextId = task.ContextId;
|
||||
taskId = task.Id;
|
||||
case StreamResponseCase.Task:
|
||||
var task = streamResponse.Task!;
|
||||
contextId = task.ContextId;
|
||||
taskId = task.Id;
|
||||
yield return this.ConvertToAgentResponseUpdate(task);
|
||||
break;
|
||||
|
||||
yield return this.ConvertToAgentResponseUpdate(task);
|
||||
}
|
||||
else if (sseEvent.Data is TaskUpdateEvent taskUpdateEvent)
|
||||
{
|
||||
contextId = taskUpdateEvent.ContextId;
|
||||
taskId = taskUpdateEvent.TaskId;
|
||||
case StreamResponseCase.StatusUpdate:
|
||||
var statusUpdate = streamResponse.StatusUpdate!;
|
||||
contextId = statusUpdate.ContextId;
|
||||
taskId = statusUpdate.TaskId;
|
||||
yield return this.ConvertToAgentResponseUpdate(statusUpdate);
|
||||
break;
|
||||
|
||||
yield return this.ConvertToAgentResponseUpdate(taskUpdateEvent);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new NotSupportedException($"Only message, task, task update events are supported from A2A agents. Received: {sseEvent.Data.GetType().FullName ?? "null"}");
|
||||
case StreamResponseCase.ArtifactUpdate:
|
||||
var artifactUpdate = streamResponse.ArtifactUpdate!;
|
||||
contextId = artifactUpdate.ContextId;
|
||||
taskId = artifactUpdate.TaskId;
|
||||
yield return this.ConvertToAgentResponseUpdate(artifactUpdate);
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new NotSupportedException($"Only message, task, task update events are supported from A2A agents. Received: {streamResponse.PayloadCase}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -284,7 +268,7 @@ public sealed class A2AAgent : AIAgent
|
||||
session.TaskId = taskId;
|
||||
}
|
||||
|
||||
private static AgentMessage CreateA2AMessage(A2AAgentSession typedSession, IEnumerable<ChatMessage> messages)
|
||||
private static Message CreateA2AMessage(A2AAgentSession typedSession, IEnumerable<ChatMessage> messages)
|
||||
{
|
||||
var a2aMessage = messages.ToA2AMessage();
|
||||
|
||||
@@ -324,7 +308,34 @@ public sealed class A2AAgent : AIAgent
|
||||
return null;
|
||||
}
|
||||
|
||||
private AgentResponseUpdate ConvertToAgentResponseUpdate(AgentMessage message)
|
||||
private AgentResponse ConvertToAgentResponse(Message message)
|
||||
{
|
||||
return new AgentResponse
|
||||
{
|
||||
AgentId = this.Id,
|
||||
ResponseId = message.MessageId,
|
||||
FinishReason = ChatFinishReason.Stop,
|
||||
RawRepresentation = message,
|
||||
Messages = [message.ToChatMessage()],
|
||||
AdditionalProperties = message.Metadata?.ToAdditionalProperties(),
|
||||
};
|
||||
}
|
||||
|
||||
private AgentResponse ConvertToAgentResponse(AgentTask agentTask)
|
||||
{
|
||||
return new AgentResponse
|
||||
{
|
||||
AgentId = this.Id,
|
||||
ResponseId = agentTask.Id,
|
||||
FinishReason = MapTaskStateToFinishReason(agentTask.Status.State),
|
||||
RawRepresentation = agentTask,
|
||||
Messages = agentTask.ToChatMessages() ?? [],
|
||||
ContinuationToken = CreateContinuationToken(agentTask.Id, agentTask.Status.State),
|
||||
AdditionalProperties = agentTask.Metadata?.ToAdditionalProperties(),
|
||||
};
|
||||
}
|
||||
|
||||
private AgentResponseUpdate ConvertToAgentResponseUpdate(Message message)
|
||||
{
|
||||
return new AgentResponseUpdate
|
||||
{
|
||||
@@ -353,28 +364,30 @@ public sealed class A2AAgent : AIAgent
|
||||
};
|
||||
}
|
||||
|
||||
private AgentResponseUpdate ConvertToAgentResponseUpdate(TaskUpdateEvent taskUpdateEvent)
|
||||
private AgentResponseUpdate ConvertToAgentResponseUpdate(TaskStatusUpdateEvent statusUpdateEvent)
|
||||
{
|
||||
AgentResponseUpdate responseUpdate = new()
|
||||
return new AgentResponseUpdate
|
||||
{
|
||||
AgentId = this.Id,
|
||||
ResponseId = taskUpdateEvent.TaskId,
|
||||
RawRepresentation = taskUpdateEvent,
|
||||
ResponseId = statusUpdateEvent.TaskId,
|
||||
RawRepresentation = statusUpdateEvent,
|
||||
Role = ChatRole.Assistant,
|
||||
AdditionalProperties = taskUpdateEvent.Metadata?.ToAdditionalProperties() ?? [],
|
||||
FinishReason = MapTaskStateToFinishReason(statusUpdateEvent.Status.State),
|
||||
AdditionalProperties = statusUpdateEvent.Metadata?.ToAdditionalProperties() ?? [],
|
||||
};
|
||||
}
|
||||
|
||||
if (taskUpdateEvent is TaskArtifactUpdateEvent artifactUpdateEvent)
|
||||
private AgentResponseUpdate ConvertToAgentResponseUpdate(TaskArtifactUpdateEvent artifactUpdateEvent)
|
||||
{
|
||||
return new AgentResponseUpdate
|
||||
{
|
||||
responseUpdate.Contents = artifactUpdateEvent.Artifact.ToAIContents();
|
||||
responseUpdate.RawRepresentation = artifactUpdateEvent;
|
||||
}
|
||||
else if (taskUpdateEvent is TaskStatusUpdateEvent statusUpdateEvent)
|
||||
{
|
||||
responseUpdate.FinishReason = MapTaskStateToFinishReason(statusUpdateEvent.Status.State);
|
||||
}
|
||||
|
||||
return responseUpdate;
|
||||
AgentId = this.Id,
|
||||
ResponseId = artifactUpdateEvent.TaskId,
|
||||
RawRepresentation = artifactUpdateEvent,
|
||||
Role = ChatRole.Assistant,
|
||||
Contents = artifactUpdateEvent.Artifact.ToAIContents(),
|
||||
AdditionalProperties = artifactUpdateEvent.Metadata?.ToAdditionalProperties() ?? [],
|
||||
};
|
||||
}
|
||||
|
||||
private static ChatFinishReason? MapTaskStateToFinishReason(TaskState state)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using Microsoft.Agents.AI;
|
||||
using Microsoft.Extensions.Logging;
|
||||
@@ -29,8 +30,12 @@ public static class A2AAgentCardExtensions
|
||||
/// <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)
|
||||
{
|
||||
// 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(card.Url), httpClient);
|
||||
var a2aClient = new A2AClient(new Uri(url), httpClient);
|
||||
|
||||
return a2aClient.AsAIAgent(name: card.Name, description: card.Description, loggerFactory: loggerFactory);
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ namespace Microsoft.Extensions.AI;
|
||||
/// </summary>
|
||||
internal static class ChatMessageExtensions
|
||||
{
|
||||
internal static AgentMessage ToA2AMessage(this IEnumerable<ChatMessage> messages)
|
||||
internal static Message ToA2AMessage(this IEnumerable<ChatMessage> messages)
|
||||
{
|
||||
List<Part> allParts = [];
|
||||
|
||||
@@ -23,10 +23,10 @@ internal static class ChatMessageExtensions
|
||||
}
|
||||
}
|
||||
|
||||
return new AgentMessage
|
||||
return new Message
|
||||
{
|
||||
MessageId = Guid.NewGuid().ToString("N"),
|
||||
Role = MessageRole.User,
|
||||
Role = Role.User,
|
||||
Parts = allParts,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>$(TargetFrameworksCore)</TargetFrameworks>
|
||||
<VersionSuffix>preview</VersionSuffix>
|
||||
<NoWarn>$(NoWarn);MEAI001</NoWarn>
|
||||
</PropertyGroup>
|
||||
|
||||
@@ -6,9 +6,7 @@ using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Net.ServerSentEvents;
|
||||
using System.Text;
|
||||
using System.Text.Encodings.Web;
|
||||
using System.Text.Json;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
@@ -89,14 +87,17 @@ public sealed class A2AAgentTests : IDisposable
|
||||
public async Task RunAsync_WithValidUserMessage_RunsSuccessfullyAsync()
|
||||
{
|
||||
// Arrange
|
||||
this._handler.ResponseToReturn = new AgentMessage
|
||||
this._handler.ResponseToReturn = new SendMessageResponse
|
||||
{
|
||||
MessageId = "response-123",
|
||||
Role = MessageRole.Agent,
|
||||
Parts =
|
||||
[
|
||||
new TextPart { Text = "Hello! How can I help you today?" }
|
||||
]
|
||||
Message = new Message
|
||||
{
|
||||
MessageId = "response-123",
|
||||
Role = Role.Agent,
|
||||
Parts =
|
||||
[
|
||||
new Part { Text = "Hello! How can I help you today?" }
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
var inputMessages = new List<ChatMessage>
|
||||
@@ -108,11 +109,11 @@ public sealed class A2AAgentTests : IDisposable
|
||||
var result = await this._agent.RunAsync(inputMessages);
|
||||
|
||||
// Assert input message sent to A2AClient
|
||||
var inputMessage = this._handler.CapturedMessageSendParams?.Message;
|
||||
var inputMessage = this._handler.CapturedSendMessageRequest?.Message;
|
||||
Assert.NotNull(inputMessage);
|
||||
Assert.Single(inputMessage.Parts);
|
||||
Assert.Equal(MessageRole.User, inputMessage.Role);
|
||||
Assert.Equal("Hello, world!", ((TextPart)inputMessage.Parts[0]).Text);
|
||||
Assert.Equal(Role.User, inputMessage.Role);
|
||||
Assert.Equal("Hello, world!", inputMessage.Parts[0].Text);
|
||||
|
||||
// Assert response from A2AClient is converted correctly
|
||||
Assert.NotNull(result);
|
||||
@@ -120,8 +121,8 @@ public sealed class A2AAgentTests : IDisposable
|
||||
Assert.Equal("response-123", result.ResponseId);
|
||||
|
||||
Assert.NotNull(result.RawRepresentation);
|
||||
Assert.IsType<AgentMessage>(result.RawRepresentation);
|
||||
Assert.Equal("response-123", ((AgentMessage)result.RawRepresentation).MessageId);
|
||||
Assert.IsType<Message>(result.RawRepresentation);
|
||||
Assert.Equal("response-123", ((Message)result.RawRepresentation).MessageId);
|
||||
|
||||
Assert.Single(result.Messages);
|
||||
Assert.Equal(ChatRole.Assistant, result.Messages[0].Role);
|
||||
@@ -133,15 +134,18 @@ public sealed class A2AAgentTests : IDisposable
|
||||
public async Task RunAsync_WithNewSession_UpdatesSessionConversationIdAsync()
|
||||
{
|
||||
// Arrange
|
||||
this._handler.ResponseToReturn = new AgentMessage
|
||||
this._handler.ResponseToReturn = new SendMessageResponse
|
||||
{
|
||||
MessageId = "response-123",
|
||||
Role = MessageRole.Agent,
|
||||
Parts =
|
||||
[
|
||||
new TextPart { Text = "Response" }
|
||||
],
|
||||
ContextId = "new-context-id"
|
||||
Message = new Message
|
||||
{
|
||||
MessageId = "response-123",
|
||||
Role = Role.Agent,
|
||||
Parts =
|
||||
[
|
||||
new Part { Text = "Response" }
|
||||
],
|
||||
ContextId = "new-context-id"
|
||||
}
|
||||
};
|
||||
|
||||
var inputMessages = new List<ChatMessage>
|
||||
@@ -177,7 +181,7 @@ public sealed class A2AAgentTests : IDisposable
|
||||
await this._agent.RunAsync(inputMessages, session);
|
||||
|
||||
// Assert
|
||||
var message = this._handler.CapturedMessageSendParams?.Message;
|
||||
var message = this._handler.CapturedSendMessageRequest?.Message;
|
||||
Assert.NotNull(message);
|
||||
Assert.Equal("existing-context-id", message.ContextId);
|
||||
}
|
||||
@@ -191,15 +195,18 @@ public sealed class A2AAgentTests : IDisposable
|
||||
new(ChatRole.User, "Test message")
|
||||
};
|
||||
|
||||
this._handler.ResponseToReturn = new AgentMessage
|
||||
this._handler.ResponseToReturn = new SendMessageResponse
|
||||
{
|
||||
MessageId = "response-123",
|
||||
Role = MessageRole.Agent,
|
||||
Parts =
|
||||
[
|
||||
new TextPart { Text = "Response" }
|
||||
],
|
||||
ContextId = "different-context"
|
||||
Message = new Message
|
||||
{
|
||||
MessageId = "response-123",
|
||||
Role = Role.Agent,
|
||||
Parts =
|
||||
[
|
||||
new Part { Text = "Response" }
|
||||
],
|
||||
ContextId = "different-context"
|
||||
}
|
||||
};
|
||||
|
||||
var session = await this._agent.CreateSessionAsync();
|
||||
@@ -219,12 +226,15 @@ public sealed class A2AAgentTests : IDisposable
|
||||
new(ChatRole.User, "Hello, streaming!")
|
||||
};
|
||||
|
||||
this._handler.StreamingResponseToReturn = new AgentMessage()
|
||||
this._handler.StreamingResponseToReturn = new StreamResponse
|
||||
{
|
||||
MessageId = "stream-1",
|
||||
Role = MessageRole.Agent,
|
||||
Parts = [new TextPart { Text = "Hello" }],
|
||||
ContextId = "stream-context"
|
||||
Message = new Message
|
||||
{
|
||||
MessageId = "stream-1",
|
||||
Role = Role.Agent,
|
||||
Parts = [new Part { Text = "Hello" }],
|
||||
ContextId = "stream-context"
|
||||
}
|
||||
};
|
||||
|
||||
// Act
|
||||
@@ -238,11 +248,11 @@ public sealed class A2AAgentTests : IDisposable
|
||||
Assert.Single(updates);
|
||||
|
||||
// Assert input message sent to A2AClient
|
||||
var inputMessage = this._handler.CapturedMessageSendParams?.Message;
|
||||
var inputMessage = this._handler.CapturedSendMessageRequest?.Message;
|
||||
Assert.NotNull(inputMessage);
|
||||
Assert.Single(inputMessage.Parts);
|
||||
Assert.Equal(MessageRole.User, inputMessage.Role);
|
||||
Assert.Equal("Hello, streaming!", ((TextPart)inputMessage.Parts[0]).Text);
|
||||
Assert.Equal(Role.User, inputMessage.Role);
|
||||
Assert.Equal("Hello, streaming!", inputMessage.Parts[0].Text);
|
||||
|
||||
// Assert response from A2AClient is converted correctly
|
||||
Assert.Equal(ChatRole.Assistant, updates[0].Role);
|
||||
@@ -251,8 +261,8 @@ public sealed class A2AAgentTests : IDisposable
|
||||
Assert.Equal(this._agent.Id, updates[0].AgentId);
|
||||
Assert.Equal("stream-1", updates[0].ResponseId);
|
||||
Assert.Equal(ChatFinishReason.Stop, updates[0].FinishReason);
|
||||
Assert.IsType<AgentMessage>(updates[0].RawRepresentation);
|
||||
Assert.Equal("stream-1", ((AgentMessage)updates[0].RawRepresentation!).MessageId);
|
||||
Assert.IsType<Message>(updates[0].RawRepresentation);
|
||||
Assert.Equal("stream-1", ((Message)updates[0].RawRepresentation!).MessageId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -264,12 +274,15 @@ public sealed class A2AAgentTests : IDisposable
|
||||
new(ChatRole.User, "Test streaming")
|
||||
};
|
||||
|
||||
this._handler.StreamingResponseToReturn = new AgentMessage()
|
||||
this._handler.StreamingResponseToReturn = new StreamResponse
|
||||
{
|
||||
MessageId = "stream-1",
|
||||
Role = MessageRole.Agent,
|
||||
Parts = [new TextPart { Text = "Response" }],
|
||||
ContextId = "new-stream-context"
|
||||
Message = new Message
|
||||
{
|
||||
MessageId = "stream-1",
|
||||
Role = Role.Agent,
|
||||
Parts = [new Part { Text = "Response" }],
|
||||
ContextId = "new-stream-context"
|
||||
}
|
||||
};
|
||||
|
||||
var session = await this._agent.CreateSessionAsync();
|
||||
@@ -294,7 +307,7 @@ public sealed class A2AAgentTests : IDisposable
|
||||
new(ChatRole.User, "Test streaming")
|
||||
};
|
||||
|
||||
this._handler.StreamingResponseToReturn = new AgentMessage();
|
||||
this._handler.StreamingResponseToReturn = new StreamResponse { Message = new Message() };
|
||||
|
||||
var session = await this._agent.CreateSessionAsync();
|
||||
var a2aSession = (A2AAgentSession)session;
|
||||
@@ -307,7 +320,7 @@ public sealed class A2AAgentTests : IDisposable
|
||||
}
|
||||
|
||||
// Assert
|
||||
var message = this._handler.CapturedMessageSendParams?.Message;
|
||||
var message = this._handler.CapturedSendMessageRequest?.Message;
|
||||
Assert.NotNull(message);
|
||||
Assert.Equal("existing-context-id", message.ContextId);
|
||||
}
|
||||
@@ -325,12 +338,15 @@ public sealed class A2AAgentTests : IDisposable
|
||||
new(ChatRole.User, "Test streaming")
|
||||
};
|
||||
|
||||
this._handler.StreamingResponseToReturn = new AgentMessage()
|
||||
this._handler.StreamingResponseToReturn = new StreamResponse
|
||||
{
|
||||
MessageId = "stream-1",
|
||||
Role = MessageRole.Agent,
|
||||
Parts = [new TextPart { Text = "Response" }],
|
||||
ContextId = "different-context"
|
||||
Message = new Message
|
||||
{
|
||||
MessageId = "stream-1",
|
||||
Role = Role.Agent,
|
||||
Parts = [new Part { Text = "Response" }],
|
||||
ContextId = "different-context"
|
||||
}
|
||||
};
|
||||
|
||||
// Act
|
||||
@@ -346,12 +362,15 @@ public sealed class A2AAgentTests : IDisposable
|
||||
public async Task RunStreamingAsync_AllowsNonUserRoleMessagesAsync()
|
||||
{
|
||||
// Arrange
|
||||
this._handler.StreamingResponseToReturn = new AgentMessage()
|
||||
this._handler.StreamingResponseToReturn = new StreamResponse
|
||||
{
|
||||
MessageId = "stream-1",
|
||||
Role = MessageRole.Agent,
|
||||
Parts = [new TextPart { Text = "Response" }],
|
||||
ContextId = "new-stream-context"
|
||||
Message = new Message
|
||||
{
|
||||
MessageId = "stream-1",
|
||||
Role = Role.Agent,
|
||||
Parts = [new Part { Text = "Response" }],
|
||||
ContextId = "new-stream-context"
|
||||
}
|
||||
};
|
||||
|
||||
var inputMessages = new List<ChatMessage>
|
||||
@@ -385,13 +404,13 @@ public sealed class A2AAgentTests : IDisposable
|
||||
await this._agent.RunAsync(inputMessages);
|
||||
|
||||
// Assert
|
||||
var message = this._handler.CapturedMessageSendParams?.Message;
|
||||
var message = this._handler.CapturedSendMessageRequest?.Message;
|
||||
Assert.NotNull(message);
|
||||
Assert.Equal(2, message.Parts.Count);
|
||||
Assert.IsType<TextPart>(message.Parts[0]);
|
||||
Assert.Equal("Check this file:", ((TextPart)message.Parts[0]).Text);
|
||||
Assert.IsType<FilePart>(message.Parts[1]);
|
||||
Assert.Equal("https://example.com/file.pdf", ((FilePart)message.Parts[1]).File.Uri?.ToString());
|
||||
Assert.Equal(PartContentCase.Text, message.Parts[0].ContentCase);
|
||||
Assert.Equal("Check this file:", message.Parts[0].Text);
|
||||
Assert.Equal(PartContentCase.Url, message.Parts[1].ContentCase);
|
||||
Assert.Equal("https://example.com/file.pdf", message.Parts[1].Url);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -413,10 +432,11 @@ public sealed class A2AAgentTests : IDisposable
|
||||
public async Task RunAsync_WithContinuationToken_CallsGetTaskAsyncAsync()
|
||||
{
|
||||
// Arrange
|
||||
this._handler.ResponseToReturn = new AgentTask
|
||||
this._handler.AgentTaskToReturn = new AgentTask
|
||||
{
|
||||
Id = "task-123",
|
||||
ContextId = "context-123"
|
||||
ContextId = "context-123",
|
||||
Status = new() { State = TaskState.Submitted }
|
||||
};
|
||||
|
||||
var options = new AgentRunOptions { ContinuationToken = new A2AContinuationToken("task-123") };
|
||||
@@ -425,19 +445,22 @@ public sealed class A2AAgentTests : IDisposable
|
||||
await this._agent.RunAsync([], options: options);
|
||||
|
||||
// Assert
|
||||
Assert.Equal("tasks/get", this._handler.CapturedJsonRpcRequest?.Method);
|
||||
Assert.Equal("task-123", this._handler.CapturedTaskIdParams?.Id);
|
||||
Assert.Equal("GetTask", this._handler.CapturedJsonRpcRequest?.Method);
|
||||
Assert.Equal("task-123", this._handler.CapturedGetTaskRequest?.Id);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task RunAsync_WithTaskInSessionAndMessage_AddTaskAsReferencesToMessageAsync()
|
||||
{
|
||||
// Arrange
|
||||
this._handler.ResponseToReturn = new AgentMessage
|
||||
this._handler.ResponseToReturn = new SendMessageResponse
|
||||
{
|
||||
MessageId = "response-123",
|
||||
Role = MessageRole.Agent,
|
||||
Parts = [new TextPart { Text = "Response to task" }]
|
||||
Message = new Message
|
||||
{
|
||||
MessageId = "response-123",
|
||||
Role = Role.Agent,
|
||||
Parts = [new Part { Text = "Response to task" }]
|
||||
}
|
||||
};
|
||||
|
||||
var session = (A2AAgentSession)await this._agent.CreateSessionAsync();
|
||||
@@ -449,7 +472,7 @@ public sealed class A2AAgentTests : IDisposable
|
||||
await this._agent.RunAsync(inputMessage, session);
|
||||
|
||||
// Assert
|
||||
var message = this._handler.CapturedMessageSendParams?.Message;
|
||||
var message = this._handler.CapturedSendMessageRequest?.Message;
|
||||
Assert.Null(message?.TaskId);
|
||||
Assert.NotNull(message?.ReferenceTaskIds);
|
||||
Assert.Contains("task-123", message.ReferenceTaskIds);
|
||||
@@ -459,11 +482,14 @@ public sealed class A2AAgentTests : IDisposable
|
||||
public async Task RunAsync_WithAgentTask_UpdatesSessionTaskIdAsync()
|
||||
{
|
||||
// Arrange
|
||||
this._handler.ResponseToReturn = new AgentTask
|
||||
this._handler.ResponseToReturn = new SendMessageResponse
|
||||
{
|
||||
Id = "task-456",
|
||||
ContextId = "context-789",
|
||||
Status = new() { State = TaskState.Submitted }
|
||||
Task = new AgentTask
|
||||
{
|
||||
Id = "task-456",
|
||||
ContextId = "context-789",
|
||||
Status = new() { State = TaskState.Submitted }
|
||||
}
|
||||
};
|
||||
|
||||
var session = await this._agent.CreateSessionAsync();
|
||||
@@ -480,16 +506,19 @@ public sealed class A2AAgentTests : IDisposable
|
||||
public async Task RunAsync_WithAgentTaskResponse_ReturnsTaskResponseCorrectlyAsync()
|
||||
{
|
||||
// Arrange
|
||||
this._handler.ResponseToReturn = new AgentTask
|
||||
this._handler.ResponseToReturn = new SendMessageResponse
|
||||
{
|
||||
Id = "task-789",
|
||||
ContextId = "context-456",
|
||||
Status = new() { State = TaskState.Submitted },
|
||||
Metadata = new Dictionary<string, JsonElement>
|
||||
Task = new AgentTask
|
||||
{
|
||||
Id = "task-789",
|
||||
ContextId = "context-456",
|
||||
Status = new() { State = TaskState.Submitted },
|
||||
Metadata = new Dictionary<string, JsonElement>
|
||||
{
|
||||
{ "key1", JsonSerializer.SerializeToElement("value1") },
|
||||
{ "count", JsonSerializer.SerializeToElement(42) }
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var session = await this._agent.CreateSessionAsync();
|
||||
@@ -532,11 +561,14 @@ public sealed class A2AAgentTests : IDisposable
|
||||
public async Task RunAsync_WithVariousTaskStates_ReturnsCorrectTokenAsync(TaskState taskState)
|
||||
{
|
||||
// Arrange
|
||||
this._handler.ResponseToReturn = new AgentTask
|
||||
this._handler.ResponseToReturn = new SendMessageResponse
|
||||
{
|
||||
Id = "task-123",
|
||||
ContextId = "context-123",
|
||||
Status = new() { State = taskState }
|
||||
Task = new AgentTask
|
||||
{
|
||||
Id = "task-123",
|
||||
ContextId = "context-123",
|
||||
Status = new() { State = taskState }
|
||||
}
|
||||
};
|
||||
|
||||
// Act
|
||||
@@ -583,15 +615,76 @@ public sealed class A2AAgentTests : IDisposable
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task RunStreamingAsync_WithContinuationToken_UsesSubscribeToTaskMethodAsync()
|
||||
{
|
||||
// Arrange
|
||||
this._handler.StreamingResponseToReturn = new StreamResponse
|
||||
{
|
||||
Message = new Message
|
||||
{
|
||||
MessageId = "response-123",
|
||||
Role = Role.Agent,
|
||||
Parts = [new Part { Text = "Continuation response" }]
|
||||
}
|
||||
};
|
||||
|
||||
var options = new AgentRunOptions { ContinuationToken = new A2AContinuationToken("task-456") };
|
||||
|
||||
// Act
|
||||
await foreach (var _ in this._agent.RunStreamingAsync([], null, options))
|
||||
{
|
||||
// Just iterate through to trigger the logic
|
||||
}
|
||||
|
||||
// Assert - verify SubscribeToTask was called (not SendStreamingMessage)
|
||||
Assert.Single(this._handler.CapturedJsonRpcRequests);
|
||||
Assert.Equal("SubscribeToTask", this._handler.CapturedJsonRpcRequests[0].Method);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task RunStreamingAsync_WithContinuationToken_PassesCorrectTaskIdAsync()
|
||||
{
|
||||
// Arrange
|
||||
this._handler.StreamingResponseToReturn = new StreamResponse
|
||||
{
|
||||
Message = new Message
|
||||
{
|
||||
MessageId = "response-123",
|
||||
Role = Role.Agent,
|
||||
Parts = [new Part { Text = "Continuation response" }]
|
||||
}
|
||||
};
|
||||
|
||||
const string ExpectedTaskId = "my-task-789";
|
||||
var options = new AgentRunOptions { ContinuationToken = new A2AContinuationToken(ExpectedTaskId) };
|
||||
|
||||
// Act
|
||||
await foreach (var _ in this._agent.RunStreamingAsync([], null, options))
|
||||
{
|
||||
// Just iterate through to trigger the logic
|
||||
}
|
||||
|
||||
// Assert - verify the task ID was passed correctly
|
||||
Assert.NotEmpty(this._handler.CapturedJsonRpcRequests);
|
||||
var subscribeRequest = this._handler.CapturedJsonRpcRequests[0];
|
||||
var subscribeParams = subscribeRequest.Params?.Deserialize<SubscribeToTaskRequest>(A2AJsonUtilities.DefaultOptions);
|
||||
Assert.NotNull(subscribeParams);
|
||||
Assert.Equal(ExpectedTaskId, subscribeParams.Id);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task RunStreamingAsync_WithTaskInSessionAndMessage_AddTaskAsReferencesToMessageAsync()
|
||||
{
|
||||
// Arrange
|
||||
this._handler.StreamingResponseToReturn = new AgentMessage
|
||||
this._handler.StreamingResponseToReturn = new StreamResponse
|
||||
{
|
||||
MessageId = "response-123",
|
||||
Role = MessageRole.Agent,
|
||||
Parts = [new TextPart { Text = "Response to task" }]
|
||||
Message = new Message
|
||||
{
|
||||
MessageId = "response-123",
|
||||
Role = Role.Agent,
|
||||
Parts = [new Part { Text = "Response to task" }]
|
||||
}
|
||||
};
|
||||
|
||||
var session = (A2AAgentSession)await this._agent.CreateSessionAsync();
|
||||
@@ -604,7 +697,7 @@ public sealed class A2AAgentTests : IDisposable
|
||||
}
|
||||
|
||||
// Assert
|
||||
var message = this._handler.CapturedMessageSendParams?.Message;
|
||||
var message = this._handler.CapturedSendMessageRequest?.Message;
|
||||
Assert.Null(message?.TaskId);
|
||||
Assert.NotNull(message?.ReferenceTaskIds);
|
||||
Assert.Contains("task-123", message.ReferenceTaskIds);
|
||||
@@ -614,11 +707,14 @@ public sealed class A2AAgentTests : IDisposable
|
||||
public async Task RunStreamingAsync_WithAgentTask_UpdatesSessionTaskIdAsync()
|
||||
{
|
||||
// Arrange
|
||||
this._handler.StreamingResponseToReturn = new AgentTask
|
||||
this._handler.StreamingResponseToReturn = new StreamResponse
|
||||
{
|
||||
Id = "task-456",
|
||||
ContextId = "context-789",
|
||||
Status = new() { State = TaskState.Submitted }
|
||||
Task = new AgentTask
|
||||
{
|
||||
Id = "task-456",
|
||||
ContextId = "context-789",
|
||||
Status = new() { State = TaskState.Submitted }
|
||||
}
|
||||
};
|
||||
|
||||
var session = await this._agent.CreateSessionAsync();
|
||||
@@ -642,15 +738,18 @@ public sealed class A2AAgentTests : IDisposable
|
||||
const string ContextId = "ctx-456";
|
||||
const string MessageText = "Hello from agent!";
|
||||
|
||||
this._handler.StreamingResponseToReturn = new AgentMessage
|
||||
this._handler.StreamingResponseToReturn = new StreamResponse
|
||||
{
|
||||
MessageId = MessageId,
|
||||
Role = MessageRole.Agent,
|
||||
ContextId = ContextId,
|
||||
Parts =
|
||||
[
|
||||
new TextPart { Text = MessageText }
|
||||
]
|
||||
Message = new Message
|
||||
{
|
||||
MessageId = MessageId,
|
||||
Role = Role.Agent,
|
||||
ContextId = ContextId,
|
||||
Parts =
|
||||
[
|
||||
new Part { Text = MessageText }
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
// Act
|
||||
@@ -670,8 +769,8 @@ public sealed class A2AAgentTests : IDisposable
|
||||
Assert.Equal(this._agent.Id, update0.AgentId);
|
||||
Assert.Equal(MessageText, update0.Text);
|
||||
Assert.Equal(ChatFinishReason.Stop, update0.FinishReason);
|
||||
Assert.IsType<AgentMessage>(update0.RawRepresentation);
|
||||
Assert.Equal(MessageId, ((AgentMessage)update0.RawRepresentation!).MessageId);
|
||||
Assert.IsType<Message>(update0.RawRepresentation);
|
||||
Assert.Equal(MessageId, ((Message)update0.RawRepresentation!).MessageId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -681,18 +780,21 @@ public sealed class A2AAgentTests : IDisposable
|
||||
const string TaskId = "task-789";
|
||||
const string ContextId = "ctx-012";
|
||||
|
||||
this._handler.StreamingResponseToReturn = new AgentTask
|
||||
this._handler.StreamingResponseToReturn = new StreamResponse
|
||||
{
|
||||
Id = TaskId,
|
||||
ContextId = ContextId,
|
||||
Status = new() { State = TaskState.Submitted },
|
||||
Artifacts = [
|
||||
Task = new AgentTask
|
||||
{
|
||||
Id = TaskId,
|
||||
ContextId = ContextId,
|
||||
Status = new() { State = TaskState.Submitted },
|
||||
Artifacts = [
|
||||
new()
|
||||
{
|
||||
ArtifactId = "art-123",
|
||||
Parts = [new TextPart { Text = "Task artifact content" }]
|
||||
Parts = [new Part { Text = "Task artifact content" }]
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
var session = await this._agent.CreateSessionAsync();
|
||||
@@ -728,11 +830,14 @@ public sealed class A2AAgentTests : IDisposable
|
||||
const string TaskId = "task-status-123";
|
||||
const string ContextId = "ctx-status-456";
|
||||
|
||||
this._handler.StreamingResponseToReturn = new TaskStatusUpdateEvent
|
||||
this._handler.StreamingResponseToReturn = new StreamResponse
|
||||
{
|
||||
TaskId = TaskId,
|
||||
ContextId = ContextId,
|
||||
Status = new() { State = TaskState.Working }
|
||||
StatusUpdate = new TaskStatusUpdateEvent
|
||||
{
|
||||
TaskId = TaskId,
|
||||
ContextId = ContextId,
|
||||
Status = new() { State = TaskState.Working }
|
||||
}
|
||||
};
|
||||
|
||||
var session = await this._agent.CreateSessionAsync();
|
||||
@@ -768,14 +873,17 @@ public sealed class A2AAgentTests : IDisposable
|
||||
const string ContextId = "ctx-artifact-456";
|
||||
const string ArtifactContent = "Task artifact data";
|
||||
|
||||
this._handler.StreamingResponseToReturn = new TaskArtifactUpdateEvent
|
||||
this._handler.StreamingResponseToReturn = new StreamResponse
|
||||
{
|
||||
TaskId = TaskId,
|
||||
ContextId = ContextId,
|
||||
Artifact = new()
|
||||
ArtifactUpdate = new TaskArtifactUpdateEvent
|
||||
{
|
||||
ArtifactId = "artifact-789",
|
||||
Parts = [new TextPart { Text = ArtifactContent }]
|
||||
TaskId = TaskId,
|
||||
ContextId = ContextId,
|
||||
Artifact = new()
|
||||
{
|
||||
ArtifactId = "artifact-789",
|
||||
Parts = [new Part { Text = ArtifactContent }]
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -848,15 +956,18 @@ public sealed class A2AAgentTests : IDisposable
|
||||
public async Task RunAsync_WithAgentMessageResponseMetadata_ReturnsMetadataAsAdditionalPropertiesAsync()
|
||||
{
|
||||
// Arrange
|
||||
this._handler.ResponseToReturn = new AgentMessage
|
||||
this._handler.ResponseToReturn = new SendMessageResponse
|
||||
{
|
||||
MessageId = "response-123",
|
||||
Role = MessageRole.Agent,
|
||||
Parts = [new TextPart { Text = "Response with metadata" }],
|
||||
Metadata = new Dictionary<string, JsonElement>
|
||||
Message = new Message
|
||||
{
|
||||
{ "responseKey1", JsonSerializer.SerializeToElement("responseValue1") },
|
||||
{ "responseCount", JsonSerializer.SerializeToElement(99) }
|
||||
MessageId = "response-123",
|
||||
Role = Role.Agent,
|
||||
Parts = [new Part { Text = "Response with metadata" }],
|
||||
Metadata = new Dictionary<string, JsonElement>
|
||||
{
|
||||
{ "responseKey1", JsonSerializer.SerializeToElement("responseValue1") },
|
||||
{ "responseCount", JsonSerializer.SerializeToElement(99) }
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -877,14 +988,17 @@ public sealed class A2AAgentTests : IDisposable
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task RunAsync_WithAdditionalProperties_PropagatesThemAsMetadataToMessageSendParamsAsync()
|
||||
public async Task RunAsync_WithAdditionalProperties_PropagatesThemAsMetadataToSendMessageRequestAsync()
|
||||
{
|
||||
// Arrange
|
||||
this._handler.ResponseToReturn = new AgentMessage
|
||||
this._handler.ResponseToReturn = new SendMessageResponse
|
||||
{
|
||||
MessageId = "response-123",
|
||||
Role = MessageRole.Agent,
|
||||
Parts = [new TextPart { Text = "Response" }]
|
||||
Message = new Message
|
||||
{
|
||||
MessageId = "response-123",
|
||||
Role = Role.Agent,
|
||||
Parts = [new Part { Text = "Response" }]
|
||||
}
|
||||
};
|
||||
|
||||
var inputMessages = new List<ChatMessage>
|
||||
@@ -906,22 +1020,25 @@ public sealed class A2AAgentTests : IDisposable
|
||||
await this._agent.RunAsync(inputMessages, null, options);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(this._handler.CapturedMessageSendParams);
|
||||
Assert.NotNull(this._handler.CapturedMessageSendParams.Metadata);
|
||||
Assert.Equal("value1", this._handler.CapturedMessageSendParams.Metadata["key1"].GetString());
|
||||
Assert.Equal(42, this._handler.CapturedMessageSendParams.Metadata["key2"].GetInt32());
|
||||
Assert.True(this._handler.CapturedMessageSendParams.Metadata["key3"].GetBoolean());
|
||||
Assert.NotNull(this._handler.CapturedSendMessageRequest);
|
||||
Assert.NotNull(this._handler.CapturedSendMessageRequest.Metadata);
|
||||
Assert.Equal("value1", this._handler.CapturedSendMessageRequest.Metadata["key1"].GetString());
|
||||
Assert.Equal(42, this._handler.CapturedSendMessageRequest.Metadata["key2"].GetInt32());
|
||||
Assert.True(this._handler.CapturedSendMessageRequest.Metadata["key3"].GetBoolean());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task RunAsync_WithNullAdditionalProperties_DoesNotSetMetadataAsync()
|
||||
{
|
||||
// Arrange
|
||||
this._handler.ResponseToReturn = new AgentMessage
|
||||
this._handler.ResponseToReturn = new SendMessageResponse
|
||||
{
|
||||
MessageId = "response-123",
|
||||
Role = MessageRole.Agent,
|
||||
Parts = [new TextPart { Text = "Response" }]
|
||||
Message = new Message
|
||||
{
|
||||
MessageId = "response-123",
|
||||
Role = Role.Agent,
|
||||
Parts = [new Part { Text = "Response" }]
|
||||
}
|
||||
};
|
||||
|
||||
var inputMessages = new List<ChatMessage>
|
||||
@@ -938,19 +1055,22 @@ public sealed class A2AAgentTests : IDisposable
|
||||
await this._agent.RunAsync(inputMessages, null, options);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(this._handler.CapturedMessageSendParams);
|
||||
Assert.Null(this._handler.CapturedMessageSendParams.Metadata);
|
||||
Assert.NotNull(this._handler.CapturedSendMessageRequest);
|
||||
Assert.Null(this._handler.CapturedSendMessageRequest.Metadata);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task RunStreamingAsync_WithAdditionalProperties_PropagatesThemAsMetadataToMessageSendParamsAsync()
|
||||
public async Task RunStreamingAsync_WithAdditionalProperties_PropagatesThemAsMetadataToSendMessageRequestAsync()
|
||||
{
|
||||
// Arrange
|
||||
this._handler.StreamingResponseToReturn = new AgentMessage
|
||||
this._handler.StreamingResponseToReturn = new StreamResponse
|
||||
{
|
||||
MessageId = "stream-123",
|
||||
Role = MessageRole.Agent,
|
||||
Parts = [new TextPart { Text = "Streaming response" }]
|
||||
Message = new Message
|
||||
{
|
||||
MessageId = "stream-123",
|
||||
Role = Role.Agent,
|
||||
Parts = [new Part { Text = "Streaming response" }]
|
||||
}
|
||||
};
|
||||
|
||||
var inputMessages = new List<ChatMessage>
|
||||
@@ -974,22 +1094,25 @@ public sealed class A2AAgentTests : IDisposable
|
||||
}
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(this._handler.CapturedMessageSendParams);
|
||||
Assert.NotNull(this._handler.CapturedMessageSendParams.Metadata);
|
||||
Assert.Equal("streamValue1", this._handler.CapturedMessageSendParams.Metadata["streamKey1"].GetString());
|
||||
Assert.Equal(100, this._handler.CapturedMessageSendParams.Metadata["streamKey2"].GetInt32());
|
||||
Assert.False(this._handler.CapturedMessageSendParams.Metadata["streamKey3"].GetBoolean());
|
||||
Assert.NotNull(this._handler.CapturedSendMessageRequest);
|
||||
Assert.NotNull(this._handler.CapturedSendMessageRequest.Metadata);
|
||||
Assert.Equal("streamValue1", this._handler.CapturedSendMessageRequest.Metadata["streamKey1"].GetString());
|
||||
Assert.Equal(100, this._handler.CapturedSendMessageRequest.Metadata["streamKey2"].GetInt32());
|
||||
Assert.False(this._handler.CapturedSendMessageRequest.Metadata["streamKey3"].GetBoolean());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task RunStreamingAsync_WithNullAdditionalProperties_DoesNotSetMetadataAsync()
|
||||
{
|
||||
// Arrange
|
||||
this._handler.StreamingResponseToReturn = new AgentMessage
|
||||
this._handler.StreamingResponseToReturn = new StreamResponse
|
||||
{
|
||||
MessageId = "stream-123",
|
||||
Role = MessageRole.Agent,
|
||||
Parts = [new TextPart { Text = "Streaming response" }]
|
||||
Message = new Message
|
||||
{
|
||||
MessageId = "stream-123",
|
||||
Role = Role.Agent,
|
||||
Parts = [new Part { Text = "Streaming response" }]
|
||||
}
|
||||
};
|
||||
|
||||
var inputMessages = new List<ChatMessage>
|
||||
@@ -1008,8 +1131,115 @@ public sealed class A2AAgentTests : IDisposable
|
||||
}
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(this._handler.CapturedMessageSendParams);
|
||||
Assert.Null(this._handler.CapturedMessageSendParams.Metadata);
|
||||
Assert.NotNull(this._handler.CapturedSendMessageRequest);
|
||||
Assert.Null(this._handler.CapturedSendMessageRequest.Metadata);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task RunAsync_WithDefaultOptions_SetsBlockingToTrueAsync()
|
||||
{
|
||||
// Arrange
|
||||
var inputMessages = new List<ChatMessage>
|
||||
{
|
||||
new(ChatRole.User, "Test message")
|
||||
};
|
||||
|
||||
// Act
|
||||
await this._agent.RunAsync(inputMessages);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(this._handler.CapturedSendMessageRequest);
|
||||
Assert.NotNull(this._handler.CapturedSendMessageRequest.Configuration);
|
||||
Assert.False(this._handler.CapturedSendMessageRequest.Configuration.ReturnImmediately);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task RunAsync_WithAllowBackgroundResponsesTrue_SetsReturnImmediatelyToTrueAsync()
|
||||
{
|
||||
// Arrange
|
||||
var inputMessages = new List<ChatMessage>
|
||||
{
|
||||
new(ChatRole.User, "Test message")
|
||||
};
|
||||
|
||||
var session = await this._agent.CreateSessionAsync();
|
||||
var options = new AgentRunOptions { AllowBackgroundResponses = true };
|
||||
|
||||
// Act
|
||||
await this._agent.RunAsync(inputMessages, session, options);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(this._handler.CapturedSendMessageRequest);
|
||||
Assert.NotNull(this._handler.CapturedSendMessageRequest.Configuration);
|
||||
Assert.True(this._handler.CapturedSendMessageRequest.Configuration.ReturnImmediately);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task RunAsync_WithAllowBackgroundResponsesFalse_SetsReturnImmediatelyToFalseAsync()
|
||||
{
|
||||
// Arrange
|
||||
var inputMessages = new List<ChatMessage>
|
||||
{
|
||||
new(ChatRole.User, "Test message")
|
||||
};
|
||||
|
||||
var options = new AgentRunOptions { AllowBackgroundResponses = false };
|
||||
|
||||
// Act
|
||||
await this._agent.RunAsync(inputMessages, null, options);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(this._handler.CapturedSendMessageRequest);
|
||||
Assert.NotNull(this._handler.CapturedSendMessageRequest.Configuration);
|
||||
Assert.False(this._handler.CapturedSendMessageRequest.Configuration.ReturnImmediately);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task RunAsync_WithNullOptions_SetsReturnImmediatelyToFalseAsync()
|
||||
{
|
||||
// Arrange
|
||||
var inputMessages = new List<ChatMessage>
|
||||
{
|
||||
new(ChatRole.User, "Test message")
|
||||
};
|
||||
|
||||
// Act
|
||||
await this._agent.RunAsync(inputMessages, null, null);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(this._handler.CapturedSendMessageRequest);
|
||||
Assert.NotNull(this._handler.CapturedSendMessageRequest.Configuration);
|
||||
Assert.False(this._handler.CapturedSendMessageRequest.Configuration.ReturnImmediately);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task RunStreamingAsync_SendMessageRequest_DoesNotSetReturnImmediatelyConfigurationAsync()
|
||||
{
|
||||
// Arrange
|
||||
this._handler.StreamingResponseToReturn = new StreamResponse
|
||||
{
|
||||
Message = new Message
|
||||
{
|
||||
MessageId = "response-123",
|
||||
Role = Role.Agent,
|
||||
Parts = [new Part { Text = "Streaming response" }]
|
||||
}
|
||||
};
|
||||
|
||||
var inputMessages = new List<ChatMessage>
|
||||
{
|
||||
new(ChatRole.User, "Test message")
|
||||
};
|
||||
|
||||
// Act
|
||||
await foreach (var _ in this._agent.RunStreamingAsync(inputMessages))
|
||||
{
|
||||
// Just iterate through to trigger the logic
|
||||
}
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(this._handler.CapturedSendMessageRequest);
|
||||
Assert.Null(this._handler.CapturedSendMessageRequest.Configuration);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -1256,6 +1486,7 @@ public sealed class A2AAgentTests : IDisposable
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
this._a2aClient.Dispose();
|
||||
this._handler.Dispose();
|
||||
this._httpClient.Dispose();
|
||||
}
|
||||
@@ -1269,13 +1500,17 @@ public sealed class A2AAgentTests : IDisposable
|
||||
{
|
||||
public JsonRpcRequest? CapturedJsonRpcRequest { get; set; }
|
||||
|
||||
public MessageSendParams? CapturedMessageSendParams { get; set; }
|
||||
public List<JsonRpcRequest> CapturedJsonRpcRequests { get; } = [];
|
||||
|
||||
public TaskIdParams? CapturedTaskIdParams { get; set; }
|
||||
public SendMessageRequest? CapturedSendMessageRequest { get; set; }
|
||||
|
||||
public A2AEvent? ResponseToReturn { get; set; }
|
||||
public GetTaskRequest? CapturedGetTaskRequest { get; set; }
|
||||
|
||||
public A2AEvent? StreamingResponseToReturn { get; set; }
|
||||
public SendMessageResponse? ResponseToReturn { get; set; }
|
||||
|
||||
public AgentTask? AgentTaskToReturn { get; set; }
|
||||
|
||||
public StreamResponse? StreamingResponseToReturn { get; set; }
|
||||
|
||||
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
|
||||
{
|
||||
@@ -1286,22 +1521,46 @@ public sealed class A2AAgentTests : IDisposable
|
||||
|
||||
this.CapturedJsonRpcRequest = JsonSerializer.Deserialize<JsonRpcRequest>(content);
|
||||
|
||||
try
|
||||
if (this.CapturedJsonRpcRequest is not null)
|
||||
{
|
||||
this.CapturedMessageSendParams = this.CapturedJsonRpcRequest?.Params?.Deserialize<MessageSendParams>();
|
||||
this.CapturedJsonRpcRequests.Add(this.CapturedJsonRpcRequest);
|
||||
}
|
||||
catch { /* Ignore deserialization errors for non-MessageSendParams requests */ }
|
||||
|
||||
try
|
||||
{
|
||||
this.CapturedTaskIdParams = this.CapturedJsonRpcRequest?.Params?.Deserialize<TaskIdParams>();
|
||||
this.CapturedSendMessageRequest = this.CapturedJsonRpcRequest?.Params?.Deserialize<SendMessageRequest>(A2AJsonUtilities.DefaultOptions);
|
||||
}
|
||||
catch { /* Ignore deserialization errors for non-SendMessageRequest requests */ }
|
||||
|
||||
try
|
||||
{
|
||||
this.CapturedGetTaskRequest = this.CapturedJsonRpcRequest?.Params?.Deserialize<GetTaskRequest>(A2AJsonUtilities.DefaultOptions);
|
||||
}
|
||||
catch { /* Ignore deserialization errors for non-GetTaskRequest requests */ }
|
||||
|
||||
// Return the pre-configured AgentTask response (for tasks/get)
|
||||
if (this.AgentTaskToReturn is not null && this.CapturedJsonRpcRequest?.Method == "GetTask")
|
||||
{
|
||||
var jsonRpcResponse = new JsonRpcResponse
|
||||
{
|
||||
Id = "response-id",
|
||||
Result = JsonSerializer.SerializeToNode(this.AgentTaskToReturn, A2AJsonUtilities.DefaultOptions)
|
||||
};
|
||||
|
||||
return new HttpResponseMessage(HttpStatusCode.OK)
|
||||
{
|
||||
Content = new StringContent(JsonSerializer.Serialize(jsonRpcResponse), Encoding.UTF8, "application/json")
|
||||
};
|
||||
}
|
||||
catch { /* Ignore deserialization errors for non-TaskIdParams requests */ }
|
||||
|
||||
// Return the pre-configured non-streaming response
|
||||
if (this.ResponseToReturn is not null)
|
||||
{
|
||||
var jsonRpcResponse = JsonRpcResponse.CreateJsonRpcResponse("response-id", this.ResponseToReturn);
|
||||
var jsonRpcResponse = new JsonRpcResponse
|
||||
{
|
||||
Id = "response-id",
|
||||
Result = JsonSerializer.SerializeToNode(this.ResponseToReturn, A2AJsonUtilities.DefaultOptions)
|
||||
};
|
||||
|
||||
return new HttpResponseMessage(HttpStatusCode.OK)
|
||||
{
|
||||
@@ -1311,22 +1570,18 @@ public sealed class A2AAgentTests : IDisposable
|
||||
// Return the pre-configured streaming response
|
||||
else if (this.StreamingResponseToReturn is not null)
|
||||
{
|
||||
var jsonRpcResponse = new JsonRpcResponse
|
||||
{
|
||||
Id = "response-id",
|
||||
Result = JsonSerializer.SerializeToNode(this.StreamingResponseToReturn, A2AJsonUtilities.DefaultOptions)
|
||||
};
|
||||
|
||||
var stream = new MemoryStream();
|
||||
|
||||
await SseFormatter.WriteAsync(
|
||||
new SseItem<JsonRpcResponse>[]
|
||||
{
|
||||
new(JsonRpcResponse.CreateJsonRpcResponse("response-id", this.StreamingResponseToReturn!))
|
||||
}.ToAsyncEnumerable(),
|
||||
stream,
|
||||
(item, writer) =>
|
||||
{
|
||||
using Utf8JsonWriter json = new(writer, new() { Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping });
|
||||
JsonSerializer.Serialize(json, item.Data);
|
||||
},
|
||||
cancellationToken
|
||||
);
|
||||
|
||||
var writer = new StreamWriter(stream);
|
||||
await writer.WriteAsync($"data: {JsonSerializer.Serialize(jsonRpcResponse, A2AJsonUtilities.DefaultOptions)}\n\n");
|
||||
#pragma warning disable CA2016 // Forward the 'CancellationToken' parameter to methods; overload doesn't exist downlevel
|
||||
await writer.FlushAsync();
|
||||
#pragma warning restore CA2016
|
||||
stream.Position = 0;
|
||||
|
||||
return new HttpResponseMessage(HttpStatusCode.OK)
|
||||
@@ -1339,7 +1594,11 @@ public sealed class A2AAgentTests : IDisposable
|
||||
}
|
||||
else
|
||||
{
|
||||
var jsonRpcResponse = JsonRpcResponse.CreateJsonRpcResponse<A2AEvent>("response-id", new AgentMessage());
|
||||
var jsonRpcResponse = new JsonRpcResponse
|
||||
{
|
||||
Id = "response-id",
|
||||
Result = JsonSerializer.SerializeToNode(new SendMessageResponse { Message = new Message() }, A2AJsonUtilities.DefaultOptions)
|
||||
};
|
||||
|
||||
return new HttpResponseMessage(HttpStatusCode.OK)
|
||||
{
|
||||
|
||||
+12
-12
@@ -42,14 +42,14 @@ public sealed class A2AAIContentExtensionsTests
|
||||
Assert.NotNull(result);
|
||||
Assert.Equal(3, result.Count);
|
||||
|
||||
var firstTextPart = Assert.IsType<TextPart>(result[0]);
|
||||
Assert.Equal("First text", firstTextPart.Text);
|
||||
Assert.Equal(PartContentCase.Text, result[0].ContentCase);
|
||||
Assert.Equal("First text", result[0].Text);
|
||||
|
||||
var filePart = Assert.IsType<FilePart>(result[1]);
|
||||
Assert.Equal("https://example.com/file1.txt", filePart.File.Uri?.ToString());
|
||||
Assert.Equal(PartContentCase.Url, result[1].ContentCase);
|
||||
Assert.Equal("https://example.com/file1.txt", result[1].Url);
|
||||
|
||||
var secondTextPart = Assert.IsType<TextPart>(result[2]);
|
||||
Assert.Equal("Second text", secondTextPart.Text);
|
||||
Assert.Equal(PartContentCase.Text, result[2].ContentCase);
|
||||
Assert.Equal("Second text", result[2].Text);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -72,14 +72,14 @@ public sealed class A2AAIContentExtensionsTests
|
||||
Assert.NotNull(result);
|
||||
Assert.Equal(3, result.Count);
|
||||
|
||||
var firstTextPart = Assert.IsType<TextPart>(result[0]);
|
||||
Assert.Equal("First text", firstTextPart.Text);
|
||||
Assert.Equal(PartContentCase.Text, result[0].ContentCase);
|
||||
Assert.Equal("First text", result[0].Text);
|
||||
|
||||
var filePart = Assert.IsType<FilePart>(result[1]);
|
||||
Assert.Equal("https://example.com/file.txt", filePart.File.Uri?.ToString());
|
||||
Assert.Equal(PartContentCase.Url, result[1].ContentCase);
|
||||
Assert.Equal("https://example.com/file.txt", result[1].Url);
|
||||
|
||||
var secondTextPart = Assert.IsType<TextPart>(result[2]);
|
||||
Assert.Equal("Second text", secondTextPart.Text);
|
||||
Assert.Equal(PartContentCase.Text, result[2].ContentCase);
|
||||
Assert.Equal("Second text", result[2].Text);
|
||||
}
|
||||
|
||||
// Mock class for testing unsupported scenarios
|
||||
|
||||
+48
-7
@@ -3,6 +3,7 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Text;
|
||||
@@ -26,7 +27,7 @@ public sealed class A2AAgentCardExtensionsTests
|
||||
{
|
||||
Name = "Test Agent",
|
||||
Description = "A test agent for unit testing",
|
||||
Url = "http://test-endpoint/agent"
|
||||
SupportedInterfaces = [new AgentInterface { Url = "http://test-endpoint/agent" }]
|
||||
};
|
||||
}
|
||||
|
||||
@@ -50,10 +51,10 @@ public sealed class A2AAgentCardExtensionsTests
|
||||
using var handler = new HttpMessageHandlerStub();
|
||||
using var httpClient = new HttpClient(handler, false);
|
||||
|
||||
handler.ResponsesToReturn.Enqueue(new AgentMessage
|
||||
handler.ResponsesToReturn.Enqueue(new Message
|
||||
{
|
||||
Role = MessageRole.Agent,
|
||||
Parts = [new TextPart { Text = "Response" }],
|
||||
Role = Role.Agent,
|
||||
Parts = [Part.FromText("Response")],
|
||||
});
|
||||
|
||||
var agent = this._agentCard.AsAIAgent(httpClient);
|
||||
@@ -66,6 +67,41 @@ public sealed class A2AAgentCardExtensionsTests
|
||||
Assert.Equal(new Uri("http://test-endpoint/agent"), handler.CapturedUris[0]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task AsAIAgent_WithMultipleInterfaces_UsesFirstInterfaceAsync()
|
||||
{
|
||||
// Arrange
|
||||
var card = new AgentCard
|
||||
{
|
||||
Name = "Multi-Interface Agent",
|
||||
Description = "An agent with multiple interfaces",
|
||||
SupportedInterfaces =
|
||||
[
|
||||
new AgentInterface { Url = "http://first/agent" },
|
||||
new AgentInterface { Url = "http://second/agent", ProtocolBinding = "grpc" },
|
||||
new AgentInterface { Url = "http://third/agent", ProtocolBinding = "http" },
|
||||
]
|
||||
};
|
||||
|
||||
using var handler = new HttpMessageHandlerStub();
|
||||
using var httpClient = new HttpClient(handler, false);
|
||||
|
||||
handler.ResponsesToReturn.Enqueue(new Message
|
||||
{
|
||||
Role = Role.Agent,
|
||||
Parts = [Part.FromText("Response")],
|
||||
});
|
||||
|
||||
var agent = card.AsAIAgent(httpClient);
|
||||
|
||||
// Act
|
||||
await agent.RunAsync("Test input");
|
||||
|
||||
// Assert
|
||||
Assert.Single(handler.CapturedUris);
|
||||
Assert.Equal(new Uri("http://first/agent"), handler.CapturedUris[0]);
|
||||
}
|
||||
|
||||
internal sealed class HttpMessageHandlerStub : HttpMessageHandler
|
||||
{
|
||||
public Queue ResponsesToReturn { get; } = new();
|
||||
@@ -86,13 +122,18 @@ public sealed class A2AAgentCardExtensionsTests
|
||||
Content = new StringContent(json, Encoding.UTF8, "application/json")
|
||||
};
|
||||
}
|
||||
else if (response is AgentMessage message)
|
||||
else if (response is Message message)
|
||||
{
|
||||
var jsonRpcResponse = JsonRpcResponse.CreateJsonRpcResponse<A2AEvent>("response-id", message);
|
||||
var sendMessageResponse = new SendMessageResponse { Message = message };
|
||||
var jsonRpcResponse = new JsonRpcResponse
|
||||
{
|
||||
Id = "response-id",
|
||||
Result = JsonSerializer.SerializeToNode(sendMessageResponse, A2AJsonUtilities.DefaultOptions)
|
||||
};
|
||||
|
||||
return new HttpResponseMessage(HttpStatusCode.OK)
|
||||
{
|
||||
Content = new StringContent(JsonSerializer.Serialize(jsonRpcResponse), Encoding.UTF8, "application/json")
|
||||
Content = new StringContent(JsonSerializer.Serialize(jsonRpcResponse, A2AJsonUtilities.DefaultOptions), Encoding.UTF8, "application/json")
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
+10
-10
@@ -40,7 +40,7 @@ public sealed class A2AAgentTaskExtensionsTests
|
||||
{
|
||||
Id = "task1",
|
||||
Artifacts = [],
|
||||
Status = new AgentTaskStatus { State = TaskState.Completed },
|
||||
Status = new TaskStatus { State = TaskState.Completed },
|
||||
};
|
||||
|
||||
// Act
|
||||
@@ -58,7 +58,7 @@ public sealed class A2AAgentTaskExtensionsTests
|
||||
{
|
||||
Id = "task1",
|
||||
Artifacts = null,
|
||||
Status = new AgentTaskStatus { State = TaskState.Completed },
|
||||
Status = new TaskStatus { State = TaskState.Completed },
|
||||
};
|
||||
|
||||
// Act
|
||||
@@ -76,7 +76,7 @@ public sealed class A2AAgentTaskExtensionsTests
|
||||
{
|
||||
Id = "task1",
|
||||
Artifacts = [],
|
||||
Status = new AgentTaskStatus { State = TaskState.Completed },
|
||||
Status = new TaskStatus { State = TaskState.Completed },
|
||||
};
|
||||
|
||||
// Act
|
||||
@@ -94,7 +94,7 @@ public sealed class A2AAgentTaskExtensionsTests
|
||||
{
|
||||
Id = "task1",
|
||||
Artifacts = null,
|
||||
Status = new AgentTaskStatus { State = TaskState.Completed },
|
||||
Status = new TaskStatus { State = TaskState.Completed },
|
||||
};
|
||||
|
||||
// Act
|
||||
@@ -110,14 +110,14 @@ public sealed class A2AAgentTaskExtensionsTests
|
||||
// Arrange
|
||||
var artifact = new Artifact
|
||||
{
|
||||
Parts = [new TextPart { Text = "response" }],
|
||||
Parts = [Part.FromText("response")],
|
||||
};
|
||||
|
||||
var agentTask = new AgentTask
|
||||
{
|
||||
Id = "task1",
|
||||
Artifacts = [artifact],
|
||||
Status = new AgentTaskStatus { State = TaskState.Completed },
|
||||
Status = new TaskStatus { State = TaskState.Completed },
|
||||
};
|
||||
|
||||
// Act
|
||||
@@ -136,15 +136,15 @@ public sealed class A2AAgentTaskExtensionsTests
|
||||
// Arrange
|
||||
var artifact1 = new Artifact
|
||||
{
|
||||
Parts = [new TextPart { Text = "content1" }],
|
||||
Parts = [Part.FromText("content1")],
|
||||
};
|
||||
|
||||
var artifact2 = new Artifact
|
||||
{
|
||||
Parts =
|
||||
[
|
||||
new TextPart { Text = "content2" },
|
||||
new TextPart { Text = "content3" }
|
||||
Part.FromText("content2"),
|
||||
Part.FromText("content3")
|
||||
],
|
||||
};
|
||||
|
||||
@@ -152,7 +152,7 @@ public sealed class A2AAgentTaskExtensionsTests
|
||||
{
|
||||
Id = "task1",
|
||||
Artifacts = [artifact1, artifact2],
|
||||
Status = new AgentTaskStatus { State = TaskState.Completed },
|
||||
Status = new TaskStatus { State = TaskState.Completed },
|
||||
};
|
||||
|
||||
// Act
|
||||
|
||||
+6
-6
@@ -22,9 +22,9 @@ public sealed class A2AArtifactExtensionsTests
|
||||
Name = "comprehensive-artifact",
|
||||
Parts =
|
||||
[
|
||||
new TextPart { Text = "First part" },
|
||||
new TextPart { Text = "Second part" },
|
||||
new TextPart { Text = "Third part" }
|
||||
Part.FromText("First part"),
|
||||
Part.FromText("Second part"),
|
||||
Part.FromText("Third part")
|
||||
],
|
||||
Metadata = new Dictionary<string, JsonElement>
|
||||
{
|
||||
@@ -66,9 +66,9 @@ public sealed class A2AArtifactExtensionsTests
|
||||
Name = "test",
|
||||
Parts =
|
||||
[
|
||||
new TextPart { Text = "Part 1" },
|
||||
new TextPart { Text = "Part 2" },
|
||||
new TextPart { Text = "Part 3" }
|
||||
Part.FromText("Part 1"),
|
||||
Part.FromText("Part 2"),
|
||||
Part.FromText("Part 3")
|
||||
],
|
||||
Metadata = null
|
||||
};
|
||||
|
||||
+13
-8
@@ -37,7 +37,7 @@ public sealed class A2ACardResolverExtensionsTests : IDisposable
|
||||
{
|
||||
Name = "Test Agent",
|
||||
Description = "A test agent for unit testing",
|
||||
Url = "http://test-endpoint/agent"
|
||||
SupportedInterfaces = [new AgentInterface { Url = "http://test-endpoint/agent" }]
|
||||
});
|
||||
|
||||
// Act
|
||||
@@ -60,12 +60,12 @@ public sealed class A2ACardResolverExtensionsTests : IDisposable
|
||||
// Arrange
|
||||
this._handler.ResponsesToReturn.Enqueue(new AgentCard
|
||||
{
|
||||
Url = "http://test-endpoint/agent"
|
||||
SupportedInterfaces = [new AgentInterface { Url = "http://test-endpoint/agent" }]
|
||||
});
|
||||
this._handler.ResponsesToReturn.Enqueue(new AgentMessage
|
||||
this._handler.ResponsesToReturn.Enqueue(new Message
|
||||
{
|
||||
Role = MessageRole.Agent,
|
||||
Parts = [new TextPart { Text = "Response" }],
|
||||
Role = Role.Agent,
|
||||
Parts = [Part.FromText("Response")],
|
||||
});
|
||||
|
||||
var agent = await this._resolver.GetAIAgentAsync(this._httpClient);
|
||||
@@ -104,13 +104,18 @@ public sealed class A2ACardResolverExtensionsTests : IDisposable
|
||||
Content = new StringContent(json, Encoding.UTF8, "application/json")
|
||||
};
|
||||
}
|
||||
else if (response is AgentMessage message)
|
||||
else if (response is Message message)
|
||||
{
|
||||
var jsonRpcResponse = JsonRpcResponse.CreateJsonRpcResponse<A2AEvent>("response-id", message);
|
||||
var sendMessageResponse = new SendMessageResponse { Message = message };
|
||||
var jsonRpcResponse = new JsonRpcResponse
|
||||
{
|
||||
Id = "response-id",
|
||||
Result = JsonSerializer.SerializeToNode(sendMessageResponse, A2AJsonUtilities.DefaultOptions)
|
||||
};
|
||||
|
||||
return new HttpResponseMessage(HttpStatusCode.OK)
|
||||
{
|
||||
Content = new StringContent(JsonSerializer.Serialize(jsonRpcResponse), Encoding.UTF8, "application/json")
|
||||
Content = new StringContent(JsonSerializer.Serialize(jsonRpcResponse, A2AJsonUtilities.DefaultOptions), Encoding.UTF8, "application/json")
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
+14
-16
@@ -32,20 +32,19 @@ public sealed class ChatMessageExtensionsTests
|
||||
Assert.NotNull(a2aMessage.MessageId);
|
||||
Assert.NotEmpty(a2aMessage.MessageId);
|
||||
|
||||
Assert.Equal(MessageRole.User, a2aMessage.Role);
|
||||
Assert.Equal(Role.User, a2aMessage.Role);
|
||||
|
||||
Assert.NotNull(a2aMessage.Parts);
|
||||
Assert.Equal(3, a2aMessage.Parts.Count);
|
||||
|
||||
var filePart = Assert.IsType<FilePart>(a2aMessage.Parts[0]);
|
||||
Assert.NotNull(filePart.File);
|
||||
Assert.Equal("https://example.com/report.pdf", filePart.File.Uri?.ToString());
|
||||
Assert.Equal(PartContentCase.Url, a2aMessage.Parts[0].ContentCase);
|
||||
Assert.Equal("https://example.com/report.pdf", a2aMessage.Parts[0].Url);
|
||||
|
||||
var secondTextPart = Assert.IsType<TextPart>(a2aMessage.Parts[1]);
|
||||
Assert.Equal("please summarize the file content", secondTextPart.Text);
|
||||
Assert.Equal(PartContentCase.Text, a2aMessage.Parts[1].ContentCase);
|
||||
Assert.Equal("please summarize the file content", a2aMessage.Parts[1].Text);
|
||||
|
||||
var thirdTextPart = Assert.IsType<TextPart>(a2aMessage.Parts[2]);
|
||||
Assert.Equal("and send it to me over email", thirdTextPart.Text);
|
||||
Assert.Equal(PartContentCase.Text, a2aMessage.Parts[2].ContentCase);
|
||||
Assert.Equal("and send it to me over email", a2aMessage.Parts[2].Text);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -71,19 +70,18 @@ public sealed class ChatMessageExtensionsTests
|
||||
Assert.NotNull(a2aMessage.MessageId);
|
||||
Assert.NotEmpty(a2aMessage.MessageId);
|
||||
|
||||
Assert.Equal(MessageRole.User, a2aMessage.Role);
|
||||
Assert.Equal(Role.User, a2aMessage.Role);
|
||||
|
||||
Assert.NotNull(a2aMessage.Parts);
|
||||
Assert.Equal(3, a2aMessage.Parts.Count);
|
||||
|
||||
var filePart = Assert.IsType<FilePart>(a2aMessage.Parts[0]);
|
||||
Assert.NotNull(filePart.File);
|
||||
Assert.Equal("https://example.com/report.pdf", filePart.File.Uri?.ToString());
|
||||
Assert.Equal(PartContentCase.Url, a2aMessage.Parts[0].ContentCase);
|
||||
Assert.Equal("https://example.com/report.pdf", a2aMessage.Parts[0].Url);
|
||||
|
||||
var secondTextPart = Assert.IsType<TextPart>(a2aMessage.Parts[1]);
|
||||
Assert.Equal("please summarize the file content", secondTextPart.Text);
|
||||
Assert.Equal(PartContentCase.Text, a2aMessage.Parts[1].ContentCase);
|
||||
Assert.Equal("please summarize the file content", a2aMessage.Parts[1].Text);
|
||||
|
||||
var thirdTextPart = Assert.IsType<TextPart>(a2aMessage.Parts[2]);
|
||||
Assert.Equal("and send it to me over email", thirdTextPart.Text);
|
||||
Assert.Equal(PartContentCase.Text, a2aMessage.Parts[2].ContentCase);
|
||||
Assert.Equal("and send it to me over email", a2aMessage.Parts[2].Text);
|
||||
}
|
||||
}
|
||||
|
||||
+4
@@ -1,5 +1,9 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>$(TargetFrameworksCore)</TargetFrameworks>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\src\Microsoft.Agents.AI.A2A\Microsoft.Agents.AI.A2A.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
Reference in New Issue
Block a user