diff --git a/dotnet/Directory.Packages.props b/dotnet/Directory.Packages.props index f1fd531194..f01315c214 100644 --- a/dotnet/Directory.Packages.props +++ b/dotnet/Directory.Packages.props @@ -72,8 +72,8 @@ - - + + diff --git a/dotnet/samples/AgentWebChat/AgentWebChat.Web/A2AAgentClient.cs b/dotnet/samples/AgentWebChat/AgentWebChat.Web/A2AAgentClient.cs index 3a2d3560a9..db690950da 100644 --- a/dotnet/samples/AgentWebChat/AgentWebChat.Web/A2AAgentClient.cs +++ b/dotnet/samples/AgentWebChat/AgentWebChat.Web/A2AAgentClient.cs @@ -58,7 +58,7 @@ internal sealed class A2AAgentClient : AgentClientBase if (a2aResponse is AgentMessage message) { var responseMessage = message.ToChatMessage(); - if (responseMessage is not null) + if (responseMessage is { Contents.Count: > 0 }) { results.Add(new AgentRunResponseUpdate(responseMessage.Role, responseMessage.Contents) { @@ -78,11 +78,7 @@ internal sealed class A2AAgentClient : AgentClientBase foreach (var part in artifact.Parts) { - var aiContent = ConvertPartToAIContent(part); - if (aiContent != null) - { - (aiContents ??= []).Add(aiContent); - } + (aiContents ??= []).Add(part.ToAIContent()); } if (aiContents is not null) @@ -155,20 +151,6 @@ internal sealed class A2AAgentClient : AgentClientBase return (a2aClient, a2aCardResolver); }); - private static AIContent? ConvertPartToAIContent(Part part) => - part switch - { - TextPart textPart => new TextContent(textPart.Text) - { - RawRepresentation = textPart - }, - FilePart filePart when filePart.File is FileWithUri fileWithUrl => new HostedFileContent(fileWithUrl.Uri) - { - RawRepresentation = filePart - }, - _ => null - }; - private static AdditionalPropertiesDictionary? ConvertMetadataToAdditionalProperties(Dictionary? metadata) { if (metadata is not { Count: > 0 }) @@ -184,22 +166,3 @@ internal sealed class A2AAgentClient : AgentClientBase return additionalProperties; } } - -// Extension method to convert multiple chat messages to A2A messages -internal static class ChatMessageExtensions -{ - public static List ToA2AMessages(this IList chatMessages) - { - if (chatMessages is null || chatMessages.Count == 0) - { - return []; - } - - var result = new List(); - foreach (var chatMessage in chatMessages) - { - result.Add(chatMessage.ToA2AMessage()); - } - return result; - } -} diff --git a/dotnet/src/Microsoft.Agents.AI.A2A/A2AHostAgent.cs b/dotnet/src/Microsoft.Agents.AI.A2A/A2AHostAgent.cs index a59bbab39e..34fc058d38 100644 --- a/dotnet/src/Microsoft.Agents.AI.A2A/A2AHostAgent.cs +++ b/dotnet/src/Microsoft.Agents.AI.A2A/A2AHostAgent.cs @@ -4,6 +4,7 @@ using System; using System.Threading; using System.Threading.Tasks; using A2A; +using Microsoft.Extensions.AI; using Microsoft.Shared.Diagnostics; namespace Microsoft.Agents.AI.A2A; diff --git a/dotnet/src/Microsoft.Agents.AI.A2A/Extensions/A2AAIContentExtensions.cs b/dotnet/src/Microsoft.Agents.AI.A2A/Extensions/A2AAIContentExtensions.cs index 8c585c44f3..31e257e8bd 100644 --- a/dotnet/src/Microsoft.Agents.AI.A2A/Extensions/A2AAIContentExtensions.cs +++ b/dotnet/src/Microsoft.Agents.AI.A2A/Extensions/A2AAIContentExtensions.cs @@ -15,13 +15,13 @@ internal static class A2AAIContentExtensions /// /// The collection of AI contents to convert." /// The list of A2A objects. - internal static List? ToA2AParts(this IEnumerable contents) + internal static List? ToParts(this IEnumerable contents) { List? parts = null; foreach (var content in contents) { - var part = content.ToA2APart(); + var part = content.ToPart(); if (part is not null) { (parts ??= []).Add(part); @@ -30,18 +30,4 @@ internal static class A2AAIContentExtensions return parts; } - - /// - /// Converts a to a object."/> - /// - /// AI content to convert. - /// The corresponding A2A object, or null if the content type is not supported. - internal static Part? ToA2APart(this AIContent content) => - content switch - { - TextContent textContent => new TextPart { Text = textContent.Text }, - HostedFileContent hostedFileContent => new FilePart { File = new FileWithUri { Uri = hostedFileContent.FileId } }, - // Ignore unknown content types (FunctionCallContent, FunctionResultContent, etc.) - _ => null, - }; } diff --git a/dotnet/src/Microsoft.Agents.AI.A2A/Extensions/A2AMessageExtensions.cs b/dotnet/src/Microsoft.Agents.AI.A2A/Extensions/A2AMessageExtensions.cs deleted file mode 100644 index 90b15f5d8c..0000000000 --- a/dotnet/src/Microsoft.Agents.AI.A2A/Extensions/A2AMessageExtensions.cs +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -using System.Collections.Generic; -using Microsoft.Extensions.AI; - -namespace A2A; - -/// -/// Extension methods for the class. -/// -internal static class A2AMessageExtensions -{ - internal static ChatMessage ToChatMessage(this AgentMessage message) - { - List? aiContents = null; - - foreach (var part in message.Parts) - { - var content = part.ToAIContent(); - if (content is not null) - { - (aiContents ??= []).Add(content); - } - } - - return new ChatMessage(ChatRole.Assistant, aiContents) - { - AdditionalProperties = message.Metadata.ToAdditionalProperties(), - RawRepresentation = message, - }; - } -} diff --git a/dotnet/src/Microsoft.Agents.AI.A2A/Extensions/A2APartExtensions.cs b/dotnet/src/Microsoft.Agents.AI.A2A/Extensions/A2APartExtensions.cs deleted file mode 100644 index ac2da0ddaf..0000000000 --- a/dotnet/src/Microsoft.Agents.AI.A2A/Extensions/A2APartExtensions.cs +++ /dev/null @@ -1,35 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -using Microsoft.Extensions.AI; - -namespace A2A; - -/// -/// Extension methods for the class. -/// -internal static class A2APartExtensions -{ - /// - /// Converts an A2A to an . - /// - /// The A2A part to convert. - /// The corresponding , or null if the part type is not supported. - internal static AIContent? ToAIContent(this Part part) => - part switch - { - TextPart textPart => new TextContent(textPart.Text) - { - RawRepresentation = textPart, - AdditionalProperties = textPart.Metadata.ToAdditionalProperties() - }, - - FilePart filePart when filePart.File is FileWithUri fileWithUrl => new HostedFileContent(fileWithUrl.Uri) - { - RawRepresentation = filePart, - AdditionalProperties = filePart.Metadata.ToAdditionalProperties() - }, - - // Ignore unknown part types (DataPart, etc.) - _ => null - }; -} diff --git a/dotnet/src/Microsoft.Agents.AI.A2A/Extensions/ChatMessageExtensions.cs b/dotnet/src/Microsoft.Agents.AI.A2A/Extensions/ChatMessageExtensions.cs index 7165fcc628..b1f1bd643a 100644 --- a/dotnet/src/Microsoft.Agents.AI.A2A/Extensions/ChatMessageExtensions.cs +++ b/dotnet/src/Microsoft.Agents.AI.A2A/Extensions/ChatMessageExtensions.cs @@ -17,7 +17,7 @@ internal static class ChatMessageExtensions foreach (var message in messages) { - if (message.Contents.ToA2AParts() is { Count: > 0 } ps) + if (message.Contents.ToParts() is { Count: > 0 } ps) { allParts.AddRange(ps); } diff --git a/dotnet/src/Microsoft.Agents.AI.Hosting.A2A/Converters/MessageConverter.cs b/dotnet/src/Microsoft.Agents.AI.Hosting.A2A/Converters/MessageConverter.cs index 38f4edff39..5d2381a235 100644 --- a/dotnet/src/Microsoft.Agents.AI.Hosting.A2A/Converters/MessageConverter.cs +++ b/dotnet/src/Microsoft.Agents.AI.Hosting.A2A/Converters/MessageConverter.cs @@ -1,8 +1,6 @@ // Copyright (c) Microsoft. All rights reserved. -using System; using System.Collections.Generic; -using System.Text.Json; using A2A; using Microsoft.Extensions.AI; @@ -22,23 +20,16 @@ internal static class MessageConverter { foreach (var content in chatMessage.Contents) { - var part = ConvertAIContentToPart(content); + var part = content.ToPart(); if (part is not 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; } - /// /// Converts A2A MessageSendParams to a collection of Microsoft.Extensions.AI ChatMessage objects. /// @@ -54,203 +45,9 @@ internal static class MessageConverter var result = new List(); if (messageSendParams.Message?.Parts is not null) { - var chatMessage = ToChatMessage(messageSendParams.Message); - if (chatMessage is not null) - { - result.Add(chatMessage); - } + result.Add(messageSendParams.Message.ToChatMessage()); } return result; } - - /// - /// Converts collection of A2A to a collection of objects. - /// - /// A read-only collection of ChatMessage objects. - public static IReadOnlyCollection ToChatMessages(this ICollection messages) - { - if (messages is null || messages.Count == 0) - { - return []; - } - - var result = new List(); - foreach (var message in messages) - { - var chatMessage = ToChatMessage(message); - if (chatMessage is not null) - { - result.Add(chatMessage); - } - } - - return result; - } - - /// - /// Converts a single to a . - /// - /// The A2A message to convert. - /// A ChatMessage object, or null if conversion is not possible. - public static ChatMessage? ToChatMessage(this AgentMessage message) - { - if (message?.Parts is not { Count: > 0 }) - { - return null; - } - - var chatRole = ConvertMessageRoleToChatRole(message.Role); - - var content = new List(); - 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; - } - - /// - /// Converts A2A MessageRole to Microsoft.Extensions.AI ChatRole. - /// - /// The A2A message role. - /// The corresponding ChatRole. - private static ChatRole ConvertMessageRoleToChatRole(MessageRole messageRole) => messageRole switch - { - MessageRole.User => ChatRole.User, - MessageRole.Agent => ChatRole.Assistant, - _ => ChatRole.User - }; - - /// - /// Converts an A2A Part to Microsoft.Extensions.AI AIContent. - /// - /// The A2A part to convert. - /// An AIContent object, or null if conversion is not possible. -#pragma warning disable CA1859 // Use concrete types when possible for improved performance - private static AIContent? ConvertPartToAIContent(Part part) => - part switch - { - TextPart textPart => new TextContent(textPart.Text) - { - RawRepresentation = textPart, - AdditionalProperties = textPart.Metadata?.ToAdditionalPropertiesDictionary() - }, - // Ignore unknown content types (FilePart, DataPart, etc.) - _ => null - }; - - /// - /// Converts Microsoft.Extensions.AI ChatMessage back to A2A Message format. - /// This is useful for the reverse operation. - /// - /// The ChatMessage to convert. - /// An A2A Message object. - public static AgentMessage ToA2AMessage(this ChatMessage chatMessage) - { - if (chatMessage is null) - { - throw new ArgumentNullException(nameof(chatMessage)); - } - - var message = new AgentMessage - { - MessageId = chatMessage.MessageId ?? Guid.NewGuid().ToString("N"), - Role = ConvertChatRoleToMessageRole(chatMessage.Role), - Parts = [] - }; - - // Convert content to parts - foreach (var content in chatMessage.Contents) - { - var part = ConvertAIContentToPart(content); - if (part is not 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; - } - - /// - /// Converts Microsoft.Extensions.AI ChatRole to A2A MessageRole. - /// - /// The ChatRole to convert. - /// The corresponding MessageRole. - 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 - } - - /// - /// Converts Microsoft.Extensions.AI AIContent to A2A Part. - /// - /// The AIContent to convert. - /// A Part object, or null if conversion is not possible. -#pragma warning disable CA1859 // Use concrete types when possible for improved performance - private static Part? ConvertAIContentToPart(AIContent content) => - content switch - { - TextContent textContent => new TextPart - { - Text = textContent.Text - }, - // Ignore unknown content types (FunctionCallContent, FunctionResultContent, etc.) - _ => null - }; - - private static AdditionalPropertiesDictionary? ToAdditionalPropertiesDictionary(this Dictionary metadata) - { - if (metadata is not { Count: > 0 }) - { - return null; - } - - var additionalProperties = new AdditionalPropertiesDictionary(); - foreach (var kvp in metadata) - { - additionalProperties[kvp.Key] = kvp.Value; - } - return additionalProperties; - } } diff --git a/dotnet/tests/Microsoft.Agents.AI.A2A.UnitTests/A2AAgentTests.cs b/dotnet/tests/Microsoft.Agents.AI.A2A.UnitTests/A2AAgentTests.cs index a7c6b42359..39d486f114 100644 --- a/dotnet/tests/Microsoft.Agents.AI.A2A.UnitTests/A2AAgentTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.A2A.UnitTests/A2AAgentTests.cs @@ -371,7 +371,7 @@ public sealed class A2AAgentTests : IDisposable new(ChatRole.User, [ new TextContent("Check this file:"), - new HostedFileContent("https://example.com/file.pdf") + new UriContent("https://example.com/file.pdf", "application/pdf") ]) }; @@ -385,7 +385,7 @@ public sealed class A2AAgentTests : IDisposable Assert.IsType(message.Parts[0]); Assert.Equal("Check this file:", ((TextPart)message.Parts[0]).Text); Assert.IsType(message.Parts[1]); - Assert.Equal("https://example.com/file.pdf", ((FileWithUri)((FilePart)message.Parts[1]).File).Uri); + Assert.Equal("https://example.com/file.pdf", ((FilePart)message.Parts[1]).File.Uri?.ToString()); } public void Dispose() diff --git a/dotnet/tests/Microsoft.Agents.AI.A2A.UnitTests/Extensions/A2AAIContentExtensionsTests.cs b/dotnet/tests/Microsoft.Agents.AI.A2A.UnitTests/Extensions/A2AAIContentExtensionsTests.cs index 857ab0f1d6..358bdfb152 100644 --- a/dotnet/tests/Microsoft.Agents.AI.A2A.UnitTests/Extensions/A2AAIContentExtensionsTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.A2A.UnitTests/Extensions/A2AAIContentExtensionsTests.cs @@ -11,55 +11,6 @@ namespace Microsoft.Agents.AI.A2A.UnitTests; /// public sealed class A2AAIContentExtensionsTests { - [Fact] - public void ToA2APart_WithTextContent_ReturnsTextPart() - { - // Arrange - var textContent = new TextContent("Hello, world!"); - - // Act - var result = textContent.ToA2APart(); - - // Assert - Assert.NotNull(result); - - var textPart = Assert.IsType(result); - Assert.Equal("Hello, world!", textPart.Text); - } - - [Fact] - public void ToA2APart_WithHostedFileContent_ReturnsFilePart() - { - // Arrange - const string Uri = "https://example.com/file.txt"; - var hostedFileContent = new HostedFileContent(Uri); - - // Act - var result = hostedFileContent.ToA2APart(); - - // Assert - Assert.NotNull(result); - - var filePart = Assert.IsType(result); - Assert.NotNull(filePart.File); - - var fileWithUri = Assert.IsType(filePart.File); - Assert.Equal(Uri, fileWithUri.Uri); - } - - [Fact] - public void ToA2APart_WithUnsupportedContentType_ReturnsNull() - { - // Arrange - var unsupportedContent = new MockAIContent(); - - // Act - var result = unsupportedContent.ToA2APart(); - - // Assert - Assert.Null(result); - } - [Fact] public void ToA2AParts_WithEmptyCollection_ReturnsNull() { @@ -67,7 +18,7 @@ public sealed class A2AAIContentExtensionsTests var emptyContents = new List(); // Act - var result = emptyContents.ToA2AParts(); + var result = emptyContents.ToParts(); // Assert Assert.Null(result); @@ -80,31 +31,25 @@ public sealed class A2AAIContentExtensionsTests var contents = new List { new TextContent("First text"), - new HostedFileContent("https://example.com/file1.txt"), + new UriContent("https://example.com/file1.txt", "file/txt"), new TextContent("Second text"), - new HostedFileContent("https://example.com/file2.txt") }; // Act - var result = contents.ToA2AParts(); + var result = contents.ToParts(); // Assert Assert.NotNull(result); - Assert.Equal(4, result.Count); + Assert.Equal(3, result.Count); var firstTextPart = Assert.IsType(result[0]); Assert.Equal("First text", firstTextPart.Text); - var firstFilePart = Assert.IsType(result[1]); - var firstFileWithUri = Assert.IsType(firstFilePart.File); - Assert.Equal("https://example.com/file1.txt", firstFileWithUri.Uri); + var filePart = Assert.IsType(result[1]); + Assert.Equal("https://example.com/file1.txt", filePart.File.Uri?.ToString()); var secondTextPart = Assert.IsType(result[2]); Assert.Equal("Second text", secondTextPart.Text); - - var secondFilePart = Assert.IsType(result[3]); - var secondFileWithUri = Assert.IsType(secondFilePart.File); - Assert.Equal("https://example.com/file2.txt", secondFileWithUri.Uri); } [Fact] @@ -115,13 +60,13 @@ public sealed class A2AAIContentExtensionsTests { new TextContent("First text"), new MockAIContent(), // Unsupported - should be ignored - new HostedFileContent("https://example.com/file.txt"), + new UriContent("https://example.com/file.txt", "file/txt"), new MockAIContent(), // Unsupported - should be ignored new TextContent("Second text") }; // Act - var result = contents.ToA2AParts(); + var result = contents.ToParts(); // Assert Assert.NotNull(result); @@ -131,8 +76,7 @@ public sealed class A2AAIContentExtensionsTests Assert.Equal("First text", firstTextPart.Text); var filePart = Assert.IsType(result[1]); - var fileWithUri = Assert.IsType(filePart.File); - Assert.Equal("https://example.com/file.txt", fileWithUri.Uri); + Assert.Equal("https://example.com/file.txt", filePart.File.Uri?.ToString()); var secondTextPart = Assert.IsType(result[2]); Assert.Equal("Second text", secondTextPart.Text); diff --git a/dotnet/tests/Microsoft.Agents.AI.A2A.UnitTests/Extensions/A2AMessageExtensionsTests.cs b/dotnet/tests/Microsoft.Agents.AI.A2A.UnitTests/Extensions/A2AMessageExtensionsTests.cs deleted file mode 100644 index 96fca5f34c..0000000000 --- a/dotnet/tests/Microsoft.Agents.AI.A2A.UnitTests/Extensions/A2AMessageExtensionsTests.cs +++ /dev/null @@ -1,65 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -using System.Collections.Generic; -using System.Text.Json; -using A2A; -using Microsoft.Extensions.AI; - -namespace Microsoft.Agents.AI.A2A.UnitTests; - -/// -/// Unit tests for the class. -/// -public sealed class A2AMessageExtensionsTests -{ - [Fact] - public void ToChatMessage_WithMixedParts_ReturnsChatMessageWithMixedContents() - { - // Arrange - const string Uri = "https://example.com/image.jpg"; - - var metadata = new Dictionary - { - ["isUrgent"] = JsonDocument.Parse("true").RootElement - }; - - var message = new AgentMessage - { - MessageId = "mixed-parts-id", - Role = MessageRole.Agent, - Parts = - [ - new TextPart { Text = "Here's an image:" }, - new FilePart { File = new FileWithUri { Uri = Uri } }, - new TextPart { Text = "What do you think?" } - ], - Metadata = metadata - }; - - // Act - var result = message.ToChatMessage(); - - // Assert - Assert.NotNull(result); - Assert.Equal(ChatRole.Assistant, result.Role); - Assert.Equal(message, result.RawRepresentation); - - Assert.NotNull(result.Contents); - Assert.Equal(3, result.Contents.Count); - - var firstContent = Assert.IsType(result.Contents[0]); - Assert.Equal("Here's an image:", firstContent.Text); - - var fileContent = Assert.IsType(result.Contents[1]); - Assert.Equal(Uri, fileContent.FileId); - - var lastContent = Assert.IsType(result.Contents[2]); - Assert.Equal("What do you think?", lastContent.Text); - - Assert.NotNull(result.AdditionalProperties); - Assert.Single(result.AdditionalProperties); - - Assert.True(result.AdditionalProperties.ContainsKey("isUrgent")); - Assert.True(((JsonElement)result.AdditionalProperties["isUrgent"]!).GetBoolean()); - } -} diff --git a/dotnet/tests/Microsoft.Agents.AI.A2A.UnitTests/Extensions/A2APartExtensionsTests.cs b/dotnet/tests/Microsoft.Agents.AI.A2A.UnitTests/Extensions/A2APartExtensionsTests.cs deleted file mode 100644 index 21be340d56..0000000000 --- a/dotnet/tests/Microsoft.Agents.AI.A2A.UnitTests/Extensions/A2APartExtensionsTests.cs +++ /dev/null @@ -1,96 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -using System.Collections.Generic; -using System.Text.Json; -using A2A; -using Microsoft.Extensions.AI; - -namespace Microsoft.Agents.AI.A2A.UnitTests; - -/// -/// Unit tests for the class. -/// -public sealed class A2APartExtensionsTests -{ - [Fact] - public void ToAIContent_WithTextPart_ReturnsTextContent() - { - // Arrange - var textPart = new TextPart { Text = "Hello, world!" }; - - // Act - var result = textPart.ToAIContent(); - - // Assert - Assert.NotNull(result); - Assert.Equal(textPart, result.RawRepresentation); - - var textContent = Assert.IsType(result); - Assert.Equal("Hello, world!", textContent.Text); - } - - [Fact] - public void ToAIContent_WithTextPartWithMetadata_ReturnsTextContentWithAdditionalProperties() - { - // Arrange - var metadata = new Dictionary - { - ["key1"] = JsonDocument.Parse("\"value1\"").RootElement, - ["key2"] = JsonDocument.Parse("42").RootElement, - ["key3"] = JsonDocument.Parse("true").RootElement - }; - var textPart = new TextPart - { - Text = "Hello with metadata!", - Metadata = metadata - }; - - // Act - var result = textPart.ToAIContent(); - - // Assert - Assert.NotNull(result); - var textContent = Assert.IsType(result); - Assert.Equal("Hello with metadata!", textContent.Text); - Assert.NotNull(textContent.AdditionalProperties); - Assert.Equal(3, textContent.AdditionalProperties.Count); - Assert.True(textContent.AdditionalProperties.ContainsKey("key1")); - Assert.True(textContent.AdditionalProperties.ContainsKey("key2")); - Assert.True(textContent.AdditionalProperties.ContainsKey("key3")); - } - - [Fact] - public void ToAIContent_WithFilePartWithFileWithUri_ReturnsHostedFileContent() - { - // Arrange - const string Uri = "https://example.com/file.txt"; - var filePart = new FilePart { File = new FileWithUri { Uri = Uri } }; - - // Act - var result = filePart.ToAIContent(); - - // Assert - Assert.NotNull(result); - Assert.Equal(filePart, result.RawRepresentation); - - var hostedFileContent = Assert.IsType(result); - Assert.Equal(Uri, hostedFileContent.FileId); - Assert.Null(hostedFileContent.AdditionalProperties); - } - - [Fact] - public void ToAIContent_WithCustomPartType_ReturnsNull() - { - // Arrange - var customPart = new MockPart(); - - // Act - var result = customPart.ToAIContent(); - - // Assert - Assert.Null(result); - } - - // Mock class for testing unsupported scenarios - private sealed class MockPart : Part; -} diff --git a/dotnet/tests/Microsoft.Agents.AI.A2A.UnitTests/Extensions/ChatMessageExtensionsTests.cs b/dotnet/tests/Microsoft.Agents.AI.A2A.UnitTests/Extensions/ChatMessageExtensionsTests.cs index bee5762649..8d771c679c 100644 --- a/dotnet/tests/Microsoft.Agents.AI.A2A.UnitTests/Extensions/ChatMessageExtensionsTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.A2A.UnitTests/Extensions/ChatMessageExtensionsTests.cs @@ -17,7 +17,7 @@ public sealed class ChatMessageExtensionsTests // Arrange var contents = new List { - new HostedFileContent("https://example.com/report.pdf"), + new UriContent("https://example.com/report.pdf", "file/pdf"), new TextContent("please summarize the file content"), new TextContent("and send it to me over email") }; @@ -39,8 +39,7 @@ public sealed class ChatMessageExtensionsTests var filePart = Assert.IsType(a2aMessage.Parts[0]); Assert.NotNull(filePart.File); - var fileWithUri = Assert.IsType(filePart.File); - Assert.Equal("https://example.com/report.pdf", fileWithUri.Uri); + Assert.Equal("https://example.com/report.pdf", filePart.File.Uri?.ToString()); var secondTextPart = Assert.IsType(a2aMessage.Parts[1]); Assert.Equal("please summarize the file content", secondTextPart.Text); @@ -54,7 +53,7 @@ public sealed class ChatMessageExtensionsTests { // Arrange var firstMessage = new ChatMessage(ChatRole.User, [ - new HostedFileContent("https://example.com/report.pdf") + new UriContent("https://example.com/report.pdf", "file/pdf"), ]); var secondMessage = new ChatMessage(ChatRole.User, [ new TextContent("please summarize the file content") @@ -79,8 +78,7 @@ public sealed class ChatMessageExtensionsTests var filePart = Assert.IsType(a2aMessage.Parts[0]); Assert.NotNull(filePart.File); - var fileWithUri = Assert.IsType(filePart.File); - Assert.Equal("https://example.com/report.pdf", fileWithUri.Uri); + Assert.Equal("https://example.com/report.pdf", filePart.File.Uri?.ToString()); var secondTextPart = Assert.IsType(a2aMessage.Parts[1]); Assert.Equal("please summarize the file content", secondTextPart.Text); diff --git a/dotnet/tests/Microsoft.Agents.AI.Hosting.A2A.Tests/Converters/MessageConverterTests.cs b/dotnet/tests/Microsoft.Agents.AI.Hosting.A2A.Tests/Converters/MessageConverterTests.cs index 5791a0c238..81ce582870 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Hosting.A2A.Tests/Converters/MessageConverterTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Hosting.A2A.Tests/Converters/MessageConverterTests.cs @@ -1,9 +1,6 @@ // Copyright (c) Microsoft. All rights reserved. -using System; -using System.Collections.Generic; using System.Linq; -using System.Text.Json; using A2A; using Microsoft.Agents.AI.Hosting.A2A.Converters; using Microsoft.Extensions.AI; @@ -85,510 +82,4 @@ public class MessageConverterTests var textContent = Assert.IsType(chatMessage.Contents.First()); Assert.Equal("Hello, world!", textContent.Text); } - - [Fact] - public void ToChatMessages_MessageCollection_Null_ReturnsEmptyCollection() - { - ICollection? messages = null; - - var result = messages!.ToChatMessages(); - - Assert.NotNull(result); - Assert.Empty(result); - } - - [Fact] - public void ToChatMessages_MessageCollection_Empty_ReturnsEmptyCollection() - { - var messages = new List(); - - var result = messages.ToChatMessages(); - - Assert.NotNull(result); - Assert.Empty(result); - } - - [Fact] - public void ToChatMessages_MessageCollection_WithValidMessages_ReturnsCorrectChatMessages() - { - var messages = new List - { - new() - { - MessageId = "user-msg", - Role = MessageRole.User, - Parts = [new TextPart { Text = "User message" }] - }, - new() - { - MessageId = "agent-msg", - Role = MessageRole.Agent, - Parts = [new TextPart { Text = "Agent response" }] - } - }; - - var result = messages.ToChatMessages(); - - Assert.NotNull(result); - Assert.Equal(2, result.Count); - - var userMessage = result.First(); - Assert.Equal("user-msg", userMessage.MessageId); - Assert.Equal(ChatRole.User, userMessage.Role); - Assert.Equal("User message", ((TextContent)userMessage.Contents.First()).Text); - - var agentMessage = result.Skip(1).First(); - Assert.Equal("agent-msg", agentMessage.MessageId); - Assert.Equal(ChatRole.Assistant, agentMessage.Role); - Assert.Equal("Agent response", ((TextContent)agentMessage.Contents.First()).Text); - } - - [Fact] - public void ToChatMessages_MessageCollection_SkipsInvalidMessages_ReturnsValidChatMessages() - { - var messages = new List - { - new() - { - MessageId = "valid-msg", - Role = MessageRole.User, - Parts = [new TextPart { Text = "Valid message" }] - }, - new() - { - MessageId = "invalid-msg", - Role = MessageRole.User, - Parts = null! // Invalid - no parts - } - }; - - var result = messages.ToChatMessages(); - - Assert.NotNull(result); - Assert.Single(result); - - var validMessage = result.First(); - Assert.Equal("valid-msg", validMessage.MessageId); - Assert.Equal("Valid message", ((TextContent)validMessage.Contents.First()).Text); - } - - [Fact] - public void ToA2AMessage_NullChatMessage_ThrowsArgumentNullException() - { - ChatMessage? chatMessage = null; - - Assert.Throws(() => chatMessage!.ToA2AMessage()); - } - - [Fact] - public void ToA2AMessage_ValidChatMessage_ReturnsCorrectA2AMessage() - { - var chatMessage = new ChatMessage(ChatRole.User, "Hello, world!") - { - MessageId = "test-id" - }; - - var result = chatMessage.ToA2AMessage(); - - Assert.NotNull(result); - Assert.Equal("test-id", result.MessageId); - Assert.Equal(MessageRole.User, result.Role); - Assert.Single(result.Parts); - - var textPart = Assert.IsType(result.Parts.First()); - Assert.Equal("Hello, world!", textPart.Text); - } - - [Fact] - public void ToA2AMessage_ChatMessageWithoutMessageId_GeneratesNewMessageId() - { - var chatMessage = new ChatMessage(ChatRole.Assistant, "Response message"); - - var result = chatMessage.ToA2AMessage(); - - Assert.NotNull(result); - Assert.NotNull(result.MessageId); - Assert.NotEmpty(result.MessageId); - Assert.Equal(MessageRole.Agent, result.Role); - } - - [Fact] - public void ToA2AMessage_ChatMessageWithTextContent_ReturnsCorrectTextPart() - { - var textContent = new TextContent("Test content"); - var chatMessage = new ChatMessage(ChatRole.User, [textContent]); - - var result = chatMessage.ToA2AMessage(); - - Assert.NotNull(result); - Assert.Single(result.Parts); - - var textPart = Assert.IsType(result.Parts.First()); - Assert.Equal("Test content", textPart.Text); - } - - [Fact] - public void ToA2AMessage_ChatMessageWithUnsupportedContent_IgnoresUnsupportedContent() - { - var unsupportedContent = new DataContent(new byte[] { 1, 2, 3 }, "image/png"); - var chatMessage = new ChatMessage(ChatRole.User, [unsupportedContent]); - - var result = chatMessage.ToA2AMessage(); - - // Should create a message but ignore the unsupported content - Assert.NotNull(result); - Assert.Empty(result.Parts); - } - - [Fact] - public void ToA2AMessage_ChatMessageWithEmptyContent_CreatesTextPartFromMessageText() - { - var chatMessage = new ChatMessage(ChatRole.User, "Fallback text"); - - var result = chatMessage.ToA2AMessage(); - - Assert.NotNull(result); - Assert.Single(result.Parts); - - var textPart = Assert.IsType(result.Parts.First()); - Assert.Equal("Fallback text", textPart.Text); - } - - [Fact] - public void ConvertMessageRoleToChatRole_UserRole_ReturnsUserChatRole() - { - var message = new AgentMessage - { - MessageId = "test", - Role = MessageRole.User, - Parts = [new TextPart { Text = "Test" }] - }; - - var result = new List { message }.ToChatMessages(); - - var chatMessage = result.First(); - Assert.Equal(ChatRole.User, chatMessage.Role); - } - - [Fact] - public void ConvertMessageRoleToChatRole_AgentRole_ReturnsAssistantChatRole() - { - var message = new AgentMessage - { - MessageId = "test", - Role = MessageRole.Agent, - Parts = [new TextPart { Text = "Test" }] - }; - - var result = new List { message }.ToChatMessages(); - - var chatMessage = result.First(); - Assert.Equal(ChatRole.Assistant, chatMessage.Role); - } - - [Fact] - public void ConvertMessageRoleToChatRole_UnknownRole_ReturnsUserChatRole() - { - var message = new AgentMessage - { - MessageId = "test", - Role = (MessageRole)999, // Unknown role - Parts = [new TextPart { Text = "Test" }] - }; - - var result = new List { message }.ToChatMessages(); - - var chatMessage = result.First(); - Assert.Equal(ChatRole.User, chatMessage.Role); - } - - [Fact] - public void ConvertChatRoleToMessageRole_UserRole_ReturnsUserMessageRole() - { - var chatMessage = new ChatMessage(ChatRole.User, "Test message"); - - var result = chatMessage.ToA2AMessage(); - - Assert.Equal(MessageRole.User, result.Role); - } - - [Fact] - public void ConvertChatRoleToMessageRole_AssistantRole_ReturnsAgentMessageRole() - { - var chatMessage = new ChatMessage(ChatRole.Assistant, "Test message"); - - var result = chatMessage.ToA2AMessage(); - - Assert.Equal(MessageRole.Agent, result.Role); - } - - [Fact] - public void ConvertChatRoleToMessageRole_SystemRole_ReturnsUserMessageRole() - { - var chatMessage = new ChatMessage(ChatRole.System, "Test message"); - - var result = chatMessage.ToA2AMessage(); - - Assert.Equal(MessageRole.User, result.Role); - } - - [Fact] - public void ConvertChatRoleToMessageRole_ToolRole_ReturnsUserMessageRole() - { - var chatMessage = new ChatMessage(ChatRole.Tool, "Test message"); - - var result = chatMessage.ToA2AMessage(); - - Assert.Equal(MessageRole.User, result.Role); - } - - [Fact] - public void ConvertPartToAIContent_TextPart_ReturnsTextContent() - { - var textPart = new TextPart { Text = "Sample text" }; - var message = new AgentMessage - { - MessageId = "test", - Role = MessageRole.User, - Parts = [textPart] - }; - - var result = new List { message }.ToChatMessages(); - - var chatMessage = result.First(); - var textContent = Assert.IsType(chatMessage.Contents.First()); - Assert.Equal("Sample text", textContent.Text); - Assert.Equal(textPart, textContent.RawRepresentation); - } - - [Fact] - public void ConvertPartToAIContent_TextPartWithMetadata_PreservesMetadata() - { - var metadata = new Dictionary - { - ["key1"] = JsonDocument.Parse("\"value1\"").RootElement, - ["key2"] = JsonDocument.Parse("42").RootElement - }; - var textPart = new TextPart - { - Text = "Text with metadata", - Metadata = metadata - }; - var message = new AgentMessage - { - MessageId = "test", - Role = MessageRole.User, - Parts = [textPart] - }; - - var result = new List { message }.ToChatMessages(); - - var chatMessage = result.First(); - var textContent = Assert.IsType(chatMessage.Contents.First()); - Assert.NotNull(textContent.AdditionalProperties); - Assert.Equal(2, textContent.AdditionalProperties.Count); - Assert.True(textContent.AdditionalProperties.ContainsKey("key1")); - Assert.True(textContent.AdditionalProperties.ContainsKey("key2")); - } - - [Fact] - public void ConvertPartToAIContent_FilePart_IgnoresUnsupportedPart() - { - var filePart = new FilePart(); - var message = new AgentMessage - { - MessageId = "test", - Role = MessageRole.User, - Parts = [filePart] - }; - - var result = new List { message }.ToChatMessages(); - - // Should return empty collection since FilePart is ignored - Assert.NotNull(result); - Assert.Empty(result); - } - - [Fact] - public void ConvertPartToAIContent_DataPart_IgnoresUnsupportedPart() - { - var dataPart = new DataPart(); - var message = new AgentMessage - { - MessageId = "test", - Role = MessageRole.User, - Parts = [dataPart] - }; - - var result = new List { message }.ToChatMessages(); - - // Should return empty collection since DataPart is ignored - Assert.NotNull(result); - Assert.Empty(result); - } - - [Fact] - public void ConvertMessageToChatMessage_WithMetadata_PreservesMetadataInAdditionalProperties() - { - var metadata = new Dictionary - { - ["timestamp"] = JsonDocument.Parse("\"2024-01-01T00:00:00Z\"").RootElement, - ["priority"] = JsonDocument.Parse("1").RootElement - }; - var message = new AgentMessage - { - MessageId = "test-id", - Role = MessageRole.User, - Parts = [new TextPart { Text = "Test" }], - Metadata = metadata - }; - - var result = new List { message }.ToChatMessages(); - - var chatMessage = result.First(); - Assert.NotNull(chatMessage.AdditionalProperties); - Assert.Equal(2, chatMessage.AdditionalProperties.Count); - Assert.True(chatMessage.AdditionalProperties.ContainsKey("timestamp")); - Assert.True(chatMessage.AdditionalProperties.ContainsKey("priority")); - } - - [Fact] - public void ConvertMessageToChatMessage_WithRawRepresentation_PreservesOriginalMessage() - { - var message = new AgentMessage - { - MessageId = "test-id", - Role = MessageRole.Agent, - Parts = [new TextPart { Text = "Test response" }] - }; - - var result = new List { message }.ToChatMessages(); - - var chatMessage = result.First(); - Assert.Equal(message, chatMessage.RawRepresentation); - } - - [Fact] - public void ToChatMessages_MessageWithEmptyParts_ReturnsEmptyCollection() - { - var message = new AgentMessage - { - MessageId = "test-id", - Role = MessageRole.User, - Parts = [] // Empty list - }; - - var result = new List { message }.ToChatMessages(); - - Assert.NotNull(result); - Assert.Empty(result); - } - - [Fact] - public void ToA2AMessage_ChatMessageWithMultipleTextContents_CreatesMultipleParts() - { - var contents = new List - { - new TextContent("First part"), - new TextContent("Second part") - }; - var chatMessage = new ChatMessage(ChatRole.User, contents); - - var result = chatMessage.ToA2AMessage(); - - Assert.NotNull(result); - Assert.Equal(2, result.Parts.Count); - - var firstPart = Assert.IsType(result.Parts[0]); - var secondPart = Assert.IsType(result.Parts[1]); - Assert.Equal("First part", firstPart.Text); - Assert.Equal("Second part", secondPart.Text); - } - - [Fact] - public void ToAdditionalPropertiesDictionary_NullMetadata_ReturnsNull() - { - var message = new AgentMessage - { - MessageId = "test-id", - Role = MessageRole.User, - Parts = [new TextPart { Text = "Test" }], - Metadata = null - }; - - var result = new List { message }.ToChatMessages(); - - var chatMessage = result.First(); - Assert.Null(chatMessage.AdditionalProperties); - } - - [Fact] - public void ToAdditionalPropertiesDictionary_EmptyMetadata_ReturnsNull() - { - var message = new AgentMessage - { - MessageId = "test-id", - Role = MessageRole.User, - Parts = [new TextPart { Text = "Test" }], - Metadata = [] - }; - - var result = new List { message }.ToChatMessages(); - - var chatMessage = result.First(); - Assert.Null(chatMessage.AdditionalProperties); - } - - [Fact] - public void ConvertPartToAIContent_MixedPartsWithUnsupported_IgnoresUnsupportedParts() - { - var message = new AgentMessage - { - MessageId = "test", - Role = MessageRole.User, - Parts = [ - new TextPart { Text = "First part" }, - new DataPart(), // Unsupported - should be ignored - new TextPart { Text = "Second part" }, - new FilePart() // Unsupported - should be ignored - ] - }; - - var result = new List { message }.ToChatMessages(); - - Assert.NotNull(result); - Assert.Single(result); - - var chatMessage = result.First(); - Assert.Equal(2, chatMessage.Contents.Count); - - var firstContent = Assert.IsType(chatMessage.Contents[0]); - Assert.Equal("First part", firstContent.Text); - - var secondContent = Assert.IsType(chatMessage.Contents[1]); - Assert.Equal("Second part", secondContent.Text); - } - - [Fact] - public void ToA2AMessage_MixedContentWithUnsupported_IgnoresUnsupportedContent() - { - var contents = new List - { - new TextContent("First text"), - new DataContent(new byte[] { 1, 2, 3 }, "image/png"), // Unsupported - should be ignored - new TextContent("Second text") - }; - var chatMessage = new ChatMessage(ChatRole.User, contents); - - var result = chatMessage.ToA2AMessage(); - - Assert.NotNull(result); - Assert.Equal(2, result.Parts.Count); - - var firstPart = Assert.IsType(result.Parts[0]); - Assert.Equal("First text", firstPart.Text); - - var secondPart = Assert.IsType(result.Parts[1]); - Assert.Equal("Second text", secondPart.Text); - } }