mirror of
https://github.com/microsoft/agent-framework.git
synced 2026-06-16 21:04:09 +08:00
0dbcc9fe9d
* update a2a agent to the latest a2a sdk (#5257) * Move A2A samples from 04-hosting to 02-agents (#5267) Move the A2A sample projects (A2AAgent_AsFunctionTools and A2AAgent_PollingForTaskCompletion) from samples/04-hosting/A2A/ to samples/02-agents/A2A/ to better align with the sample directory structure. Update solution file and samples README accordingly. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * .NET: Fix stream reconnection for A2AAgent (#5275) * Add SSE stream reconnection support to A2AAgent Implement automatic reconnection for SSE streams that disconnect mid-task, using the Last-Event-ID header to resume from where the stream left off. Changes: - Add InvokeStreamingWithReconnectAsync method to A2AAgent with configurable max retries and delay between attempts - Add new log messages for reconnection events - Add A2AAgent_StreamReconnection sample demonstrating the feature - Update existing polling sample to use simplified SendMessageAsync API - Add unit tests for stream reconnection logic Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * address comments * Address PR review feedback - Dispose SSE enumerator before GetTaskAsync fallback to release HTTP connection - Wrap StreamWriter in using blocks with leaveOpen:true and explicit UTF-8 encoding - Print update.Text instead of update object in stream reconnection sample Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * .NET: Use IA2AClientFactory to create A2AClient (#5277) * Refactor A2A extensions to use IA2AClientFactory and add ProtocolSelection sample - Update A2AAgentCardExtensions to accept IA2AClientFactory instead of A2AClientOptions - Update A2ACardResolverExtensions to accept IA2AClientFactory - Update A2AClientExtensions to accept IA2AClientFactory - Update A2AAgent to use IA2AClientFactory for client creation - Add A2AAgent_ProtocolSelection sample demonstrating protocol selection - Add comprehensive unit tests for all changes - Update README files with new sample reference Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Reorder params: options before loggerFactory in A2A extensions Move A2AClientOptions parameter before ILoggerFactory in AsAIAgent and GetAIAgentAsync extension methods to follow the repo convention of keeping LoggerFactory and CancellationToken as the last parameters. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * .NET: Migrate A2A hosting to A2A SDK v1 (#5363) * .NET: Migrate A2A hosting to A2A SDK v1 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * remove unused agent card --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * .NET: Split A2A endpoint mapping into protocol-specific methods (#5413) * .NET: Refactor A2A hosting registration into A2AServerServiceCollectionExtensions - Rename A2AHostingOptions to A2AServerRegistrationOptions - Move server registration logic from A2AEndpointRouteBuilderExtensions and AIAgentExtensions into new A2AServerServiceCollectionExtensions - Remove A2AProtocolBinding and AIAgentExtensions (consolidated) - Update samples and tests to use the new registration API Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * address copilot comments --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Remove unnecessary using directive in AgentWebChat.AgentHost Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * restore AsyncEnumerable package version * address copilot initial feedback * address automated code review and formatting issues * fix formatting issues * Add streaming support to A2A agent handler Add HandleNewMessageStreamingAsync to A2AAgentHandler that routes StreamingResponse requests through RunStreamingAsync, enqueuing an A2A Message for each AgentResponseUpdate. Add MessageConverter.ToParts(AgentResponseUpdate) extension to convert streaming update contents to A2A Parts with unsupported-content filtering. Add CreateMessageFromUpdate to map AgentResponseUpdate to A2A Message. Add 16 new tests covering the streaming path and converter. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Add streaming edge-case tests for A2AAgentHandler Add two tests covering gaps in the streaming path: - ExecuteAsync_Streaming_WhenNoUpdates_EnqueuesNoMessagesAndSavesSessionAsync: Verifies that when RunStreamingAsync yields an empty async enumerable, no messages are enqueued and only SaveSessionAsync runs. - ExecuteAsync_Streaming_CancellationTokenIsPropagatedToRunStreamingAsyncAsync: Verifies that the CancellationToken from ExecuteAsync is propagated through to the inner agent's RunCoreStreamingAsync call. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * address copilot comments --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
214 lines
5.5 KiB
C#
214 lines
5.5 KiB
C#
// Copyright (c) Microsoft. All rights reserved.
|
|
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using A2A;
|
|
using Microsoft.Agents.AI.Hosting.A2A.Converters;
|
|
using Microsoft.Extensions.AI;
|
|
|
|
namespace Microsoft.Agents.AI.Hosting.A2A.UnitTests.Converters;
|
|
|
|
public class MessageConverterTests
|
|
{
|
|
[Fact]
|
|
public void ToChatMessages_SendMessageRequest_Null_ReturnsEmptyCollection()
|
|
{
|
|
SendMessageRequest? sendMessageRequest = null;
|
|
|
|
var result = sendMessageRequest!.ToChatMessages();
|
|
|
|
Assert.NotNull(result);
|
|
Assert.Empty(result);
|
|
}
|
|
|
|
[Fact]
|
|
public void ToChatMessages_SendMessageRequest_WithNullMessage_ReturnsEmptyCollection()
|
|
{
|
|
var sendMessageRequest = new SendMessageRequest
|
|
{
|
|
Message = null!
|
|
};
|
|
|
|
var result = sendMessageRequest.ToChatMessages();
|
|
|
|
Assert.NotNull(result);
|
|
Assert.Empty(result);
|
|
}
|
|
|
|
[Fact]
|
|
public void ToChatMessages_SendMessageRequest_WithMessageWithoutParts_ReturnsEmptyCollection()
|
|
{
|
|
var sendMessageRequest = new SendMessageRequest
|
|
{
|
|
Message = new Message
|
|
{
|
|
MessageId = "test-id",
|
|
Role = Role.User,
|
|
Parts = null!
|
|
}
|
|
};
|
|
|
|
var result = sendMessageRequest.ToChatMessages();
|
|
|
|
Assert.NotNull(result);
|
|
Assert.Empty(result);
|
|
}
|
|
|
|
[Fact]
|
|
public void ToChatMessages_SendMessageRequest_WithValidTextMessage_ReturnsCorrectChatMessage()
|
|
{
|
|
var sendMessageRequest = new SendMessageRequest
|
|
{
|
|
Message = new Message
|
|
{
|
|
MessageId = "test-id",
|
|
Role = Role.User,
|
|
Parts =
|
|
[
|
|
new Part { Text = "Hello, world!" }
|
|
]
|
|
}
|
|
};
|
|
|
|
var result = sendMessageRequest.ToChatMessages();
|
|
|
|
Assert.NotNull(result);
|
|
Assert.Single(result);
|
|
|
|
var chatMessage = result.First();
|
|
Assert.Equal("test-id", chatMessage.MessageId);
|
|
Assert.Equal(ChatRole.User, chatMessage.Role);
|
|
Assert.Single(chatMessage.Contents);
|
|
|
|
var textContent = Assert.IsType<TextContent>(chatMessage.Contents.First());
|
|
Assert.Equal("Hello, world!", textContent.Text);
|
|
}
|
|
|
|
[Fact]
|
|
public void ToParts_NullList_ReturnsEmptyList()
|
|
{
|
|
// Arrange
|
|
IList<ChatMessage>? messages = null;
|
|
|
|
// Act
|
|
var result = messages!.ToParts();
|
|
|
|
// Assert
|
|
Assert.NotNull(result);
|
|
Assert.Empty(result);
|
|
}
|
|
|
|
[Fact]
|
|
public void ToParts_EmptyList_ReturnsEmptyList()
|
|
{
|
|
// Arrange
|
|
IList<ChatMessage> messages = [];
|
|
|
|
// Act
|
|
var result = messages.ToParts();
|
|
|
|
// Assert
|
|
Assert.NotNull(result);
|
|
Assert.Empty(result);
|
|
}
|
|
|
|
[Fact]
|
|
public void ToParts_WithTextContent_ReturnsTextPart()
|
|
{
|
|
// Arrange
|
|
IList<ChatMessage> messages =
|
|
[
|
|
new ChatMessage(ChatRole.Assistant, "Hello from the agent!")
|
|
];
|
|
|
|
// Act
|
|
var result = messages.ToParts();
|
|
|
|
// Assert
|
|
Assert.Single(result);
|
|
Assert.Equal("Hello from the agent!", result[0].Text);
|
|
}
|
|
|
|
[Fact]
|
|
public void ToParts_WithMultipleMessages_ReturnsAllParts()
|
|
{
|
|
// Arrange
|
|
IList<ChatMessage> messages =
|
|
[
|
|
new ChatMessage(ChatRole.User, "First message"),
|
|
new ChatMessage(ChatRole.Assistant, "Second message")
|
|
];
|
|
|
|
// Act
|
|
var result = messages.ToParts();
|
|
|
|
// Assert
|
|
Assert.Equal(2, result.Count);
|
|
Assert.Equal("First message", result[0].Text);
|
|
Assert.Equal("Second message", result[1].Text);
|
|
}
|
|
|
|
[Fact]
|
|
public void ToParts_AgentResponseUpdate_WithNoContents_ReturnsEmptyList()
|
|
{
|
|
// Arrange
|
|
var update = new AgentResponseUpdate();
|
|
|
|
// Act
|
|
var result = update.ToParts();
|
|
|
|
// Assert
|
|
Assert.NotNull(result);
|
|
Assert.Empty(result);
|
|
}
|
|
|
|
[Fact]
|
|
public void ToParts_AgentResponseUpdate_WithTextContent_ReturnsTextPart()
|
|
{
|
|
// Arrange
|
|
var update = new AgentResponseUpdate(ChatRole.Assistant, "Hello from streaming!");
|
|
|
|
// Act
|
|
var result = update.ToParts();
|
|
|
|
// Assert
|
|
Assert.Single(result);
|
|
Assert.Equal("Hello from streaming!", result[0].Text);
|
|
}
|
|
|
|
[Fact]
|
|
public void ToParts_AgentResponseUpdate_WithMultipleContents_ReturnsAllParts()
|
|
{
|
|
// Arrange
|
|
var update = new AgentResponseUpdate(ChatRole.Assistant, [
|
|
new TextContent("First chunk"),
|
|
new TextContent("Second chunk")
|
|
]);
|
|
|
|
// Act
|
|
var result = update.ToParts();
|
|
|
|
// Assert
|
|
Assert.Equal(2, result.Count);
|
|
Assert.Equal("First chunk", result[0].Text);
|
|
Assert.Equal("Second chunk", result[1].Text);
|
|
}
|
|
|
|
[Fact]
|
|
public void ToParts_AgentResponseUpdate_WithUnsupportedContent_FiltersOutNulls()
|
|
{
|
|
// Arrange - FunctionCallContent maps to null Part since it's not a supported A2A content type
|
|
var update = new AgentResponseUpdate(ChatRole.Assistant, [
|
|
new TextContent("Supported text"),
|
|
new FunctionCallContent("call-1", "myFunction")
|
|
]);
|
|
|
|
// Act
|
|
var result = update.ToParts();
|
|
|
|
// Assert - only the text part should be returned
|
|
Assert.Single(result);
|
|
Assert.Equal("Supported text", result[0].Text);
|
|
}
|
|
}
|