mirror of
https://github.com/microsoft/agent-framework.git
synced 2026-06-16 21:04:09 +08:00
.NET: Use extension methods from A2A package for converting between MEAI & A2A model classes (#1600)
* use extension methods from A2A package for converting between MEAI and A2A model classes. * Update dotnet/tests/Microsoft.Agents.AI.A2A.UnitTests/A2AAgentTests.cs Co-authored-by: Stephen Toub <stoub@microsoft.com> * remove unused using --------- Co-authored-by: Stephen Toub <stoub@microsoft.com>
This commit is contained in:
committed by
GitHub
Unverified
parent
22d76e5780
commit
d7f422b92c
@@ -72,8 +72,8 @@
|
||||
<!-- Agent SDKs -->
|
||||
<PackageVersion Include="Microsoft.Agents.CopilotStudio.Client" Version="1.2.41" />
|
||||
<!-- A2A -->
|
||||
<PackageVersion Include="A2A" Version="0.3.1-preview" />
|
||||
<PackageVersion Include="A2A.AspNetCore" Version="0.3.1-preview" />
|
||||
<PackageVersion Include="A2A" Version="0.3.3-preview" />
|
||||
<PackageVersion Include="A2A.AspNetCore" Version="0.3.3-preview" />
|
||||
<!-- MCP -->
|
||||
<PackageVersion Include="ModelContextProtocol" Version="0.4.0-preview.2" />
|
||||
<!-- Inference SDKs -->
|
||||
|
||||
@@ -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<string, JsonElement>? 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<AgentMessage> ToA2AMessages(this IList<ChatMessage> chatMessages)
|
||||
{
|
||||
if (chatMessages is null || chatMessages.Count == 0)
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
var result = new List<AgentMessage>();
|
||||
foreach (var chatMessage in chatMessages)
|
||||
{
|
||||
result.Add(chatMessage.ToA2AMessage());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -15,13 +15,13 @@ internal static class A2AAIContentExtensions
|
||||
/// </summary>
|
||||
/// <param name="contents">The collection of AI contents to convert.</param>"
|
||||
/// <returns>The list of A2A <see cref="Part"/> objects.</returns>
|
||||
internal static List<Part>? ToA2AParts(this IEnumerable<AIContent> contents)
|
||||
internal static List<Part>? ToParts(this IEnumerable<AIContent> contents)
|
||||
{
|
||||
List<Part>? 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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts a <see cref="AIContent"/> to a <see cref="Part"/> object."/>
|
||||
/// </summary>
|
||||
/// <param name="content">AI content to convert.</param>
|
||||
/// <returns>The corresponding A2A <see cref="Part"/> object, or null if the content type is not supported.</returns>
|
||||
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,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.Extensions.AI;
|
||||
|
||||
namespace A2A;
|
||||
|
||||
/// <summary>
|
||||
/// Extension methods for the <see cref="AgentMessage"/> class.
|
||||
/// </summary>
|
||||
internal static class A2AMessageExtensions
|
||||
{
|
||||
internal static ChatMessage ToChatMessage(this AgentMessage message)
|
||||
{
|
||||
List<AIContent>? 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,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using Microsoft.Extensions.AI;
|
||||
|
||||
namespace A2A;
|
||||
|
||||
/// <summary>
|
||||
/// Extension methods for the <see cref="Part"/> class.
|
||||
/// </summary>
|
||||
internal static class A2APartExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Converts an A2A <see cref="Part"/> to an <see cref="AIContent"/>.
|
||||
/// </summary>
|
||||
/// <param name="part">The A2A part to convert.</param>
|
||||
/// <returns>The corresponding <see cref="AIContent"/>, or null if the part type is not supported.</returns>
|
||||
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
|
||||
};
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts A2A MessageSendParams to a collection of Microsoft.Extensions.AI ChatMessage objects.
|
||||
/// </summary>
|
||||
@@ -54,203 +45,9 @@ internal static class MessageConverter
|
||||
var result = new List<ChatMessage>();
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts collection of A2A <see cref="AgentMessage"/> to a collection of <see cref="ChatMessage"/> objects.
|
||||
/// </summary>
|
||||
/// <returns>A read-only collection of ChatMessage objects.</returns>
|
||||
public static IReadOnlyCollection<ChatMessage> ToChatMessages(this ICollection<AgentMessage> messages)
|
||||
{
|
||||
if (messages is null || messages.Count == 0)
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
var result = new List<ChatMessage>();
|
||||
foreach (var message in messages)
|
||||
{
|
||||
var chatMessage = ToChatMessage(message);
|
||||
if (chatMessage is not null)
|
||||
{
|
||||
result.Add(chatMessage);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts a single <see cref="AgentMessage"/> to a <see cref="ChatMessage"/>.
|
||||
/// </summary>
|
||||
/// <param name="message">The A2A message to convert.</param>
|
||||
/// <returns>A ChatMessage object, or null if conversion is not possible.</returns>
|
||||
public static ChatMessage? ToChatMessage(this AgentMessage message)
|
||||
{
|
||||
if (message?.Parts is not { Count: > 0 })
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var chatRole = ConvertMessageRoleToChatRole(message.Role);
|
||||
|
||||
var content = new List<AIContent>();
|
||||
foreach (var part in message.Parts)
|
||||
{
|
||||
var aiContent = ConvertPartToAIContent(part);
|
||||
if (aiContent is not null)
|
||||
{
|
||||
content.Add(aiContent);
|
||||
}
|
||||
}
|
||||
|
||||
// If no valid content was extracted, return null
|
||||
if (content.Count == 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// Create the ChatMessage with appropriate metadata
|
||||
var chatMessage = new ChatMessage(chatRole, content)
|
||||
{
|
||||
MessageId = message.MessageId,
|
||||
RawRepresentation = message
|
||||
};
|
||||
|
||||
// Add any additional properties if needed
|
||||
if (message.Metadata is not null)
|
||||
{
|
||||
chatMessage.AdditionalProperties = message.Metadata.ToAdditionalPropertiesDictionary();
|
||||
}
|
||||
|
||||
return chatMessage;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts A2A MessageRole to Microsoft.Extensions.AI ChatRole.
|
||||
/// </summary>
|
||||
/// <param name="messageRole">The A2A message role.</param>
|
||||
/// <returns>The corresponding ChatRole.</returns>
|
||||
private static ChatRole ConvertMessageRoleToChatRole(MessageRole messageRole) => messageRole switch
|
||||
{
|
||||
MessageRole.User => ChatRole.User,
|
||||
MessageRole.Agent => ChatRole.Assistant,
|
||||
_ => ChatRole.User
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Converts an A2A Part to Microsoft.Extensions.AI AIContent.
|
||||
/// </summary>
|
||||
/// <param name="part">The A2A part to convert.</param>
|
||||
/// <returns>An AIContent object, or null if conversion is not possible.</returns>
|
||||
#pragma warning disable CA1859 // Use concrete types when possible for improved performance
|
||||
private static AIContent? ConvertPartToAIContent(Part part) =>
|
||||
part switch
|
||||
{
|
||||
TextPart textPart => new TextContent(textPart.Text)
|
||||
{
|
||||
RawRepresentation = textPart,
|
||||
AdditionalProperties = textPart.Metadata?.ToAdditionalPropertiesDictionary()
|
||||
},
|
||||
// Ignore unknown content types (FilePart, DataPart, etc.)
|
||||
_ => null
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Converts Microsoft.Extensions.AI ChatMessage back to A2A Message format.
|
||||
/// This is useful for the reverse operation.
|
||||
/// </summary>
|
||||
/// <param name="chatMessage">The ChatMessage to convert.</param>
|
||||
/// <returns>An A2A Message object.</returns>
|
||||
public static 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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts Microsoft.Extensions.AI ChatRole to A2A MessageRole.
|
||||
/// </summary>
|
||||
/// <param name="chatRole">The ChatRole to convert.</param>
|
||||
/// <returns>The corresponding MessageRole.</returns>
|
||||
private static MessageRole ConvertChatRoleToMessageRole(ChatRole chatRole)
|
||||
{
|
||||
if (chatRole == ChatRole.User)
|
||||
{
|
||||
return MessageRole.User;
|
||||
}
|
||||
if (chatRole == ChatRole.Assistant)
|
||||
{
|
||||
return MessageRole.Agent;
|
||||
}
|
||||
|
||||
return MessageRole.User; // Default fallback
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts Microsoft.Extensions.AI AIContent to A2A Part.
|
||||
/// </summary>
|
||||
/// <param name="content">The AIContent to convert.</param>
|
||||
/// <returns>A Part object, or null if conversion is not possible.</returns>
|
||||
#pragma warning disable CA1859 // Use concrete types when possible for improved performance
|
||||
private static Part? ConvertAIContentToPart(AIContent content) =>
|
||||
content switch
|
||||
{
|
||||
TextContent textContent => new TextPart
|
||||
{
|
||||
Text = textContent.Text
|
||||
},
|
||||
// Ignore unknown content types (FunctionCallContent, FunctionResultContent, etc.)
|
||||
_ => null
|
||||
};
|
||||
|
||||
private static AdditionalPropertiesDictionary? ToAdditionalPropertiesDictionary(this Dictionary<string, JsonElement> 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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<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", ((FileWithUri)((FilePart)message.Parts[1]).File).Uri);
|
||||
Assert.Equal("https://example.com/file.pdf", ((FilePart)message.Parts[1]).File.Uri?.ToString());
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
|
||||
+9
-65
@@ -11,55 +11,6 @@ namespace Microsoft.Agents.AI.A2A.UnitTests;
|
||||
/// </summary>
|
||||
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<TextPart>(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<FilePart>(result);
|
||||
Assert.NotNull(filePart.File);
|
||||
|
||||
var fileWithUri = Assert.IsType<FileWithUri>(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<AIContent>();
|
||||
|
||||
// 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<AIContent>
|
||||
{
|
||||
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<TextPart>(result[0]);
|
||||
Assert.Equal("First text", firstTextPart.Text);
|
||||
|
||||
var firstFilePart = Assert.IsType<FilePart>(result[1]);
|
||||
var firstFileWithUri = Assert.IsType<FileWithUri>(firstFilePart.File);
|
||||
Assert.Equal("https://example.com/file1.txt", firstFileWithUri.Uri);
|
||||
var filePart = Assert.IsType<FilePart>(result[1]);
|
||||
Assert.Equal("https://example.com/file1.txt", filePart.File.Uri?.ToString());
|
||||
|
||||
var secondTextPart = Assert.IsType<TextPart>(result[2]);
|
||||
Assert.Equal("Second text", secondTextPart.Text);
|
||||
|
||||
var secondFilePart = Assert.IsType<FilePart>(result[3]);
|
||||
var secondFileWithUri = Assert.IsType<FileWithUri>(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<FilePart>(result[1]);
|
||||
var fileWithUri = Assert.IsType<FileWithUri>(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<TextPart>(result[2]);
|
||||
Assert.Equal("Second text", secondTextPart.Text);
|
||||
|
||||
-65
@@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// Unit tests for the <see cref="A2AMessageExtensions"/> class.
|
||||
/// </summary>
|
||||
public sealed class A2AMessageExtensionsTests
|
||||
{
|
||||
[Fact]
|
||||
public void ToChatMessage_WithMixedParts_ReturnsChatMessageWithMixedContents()
|
||||
{
|
||||
// Arrange
|
||||
const string Uri = "https://example.com/image.jpg";
|
||||
|
||||
var metadata = new Dictionary<string, JsonElement>
|
||||
{
|
||||
["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<TextContent>(result.Contents[0]);
|
||||
Assert.Equal("Here's an image:", firstContent.Text);
|
||||
|
||||
var fileContent = Assert.IsType<HostedFileContent>(result.Contents[1]);
|
||||
Assert.Equal(Uri, fileContent.FileId);
|
||||
|
||||
var lastContent = Assert.IsType<TextContent>(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());
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// Unit tests for the <see cref="A2APartExtensions"/> class.
|
||||
/// </summary>
|
||||
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<TextContent>(result);
|
||||
Assert.Equal("Hello, world!", textContent.Text);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ToAIContent_WithTextPartWithMetadata_ReturnsTextContentWithAdditionalProperties()
|
||||
{
|
||||
// Arrange
|
||||
var metadata = new Dictionary<string, JsonElement>
|
||||
{
|
||||
["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<TextContent>(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<HostedFileContent>(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;
|
||||
}
|
||||
+4
-6
@@ -17,7 +17,7 @@ public sealed class ChatMessageExtensionsTests
|
||||
// Arrange
|
||||
var contents = new List<AIContent>
|
||||
{
|
||||
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<FilePart>(a2aMessage.Parts[0]);
|
||||
Assert.NotNull(filePart.File);
|
||||
var fileWithUri = Assert.IsType<FileWithUri>(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<TextPart>(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<FilePart>(a2aMessage.Parts[0]);
|
||||
Assert.NotNull(filePart.File);
|
||||
var fileWithUri = Assert.IsType<FileWithUri>(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<TextPart>(a2aMessage.Parts[1]);
|
||||
Assert.Equal("please summarize the file content", secondTextPart.Text);
|
||||
|
||||
-509
@@ -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<TextContent>(chatMessage.Contents.First());
|
||||
Assert.Equal("Hello, world!", textContent.Text);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ToChatMessages_MessageCollection_Null_ReturnsEmptyCollection()
|
||||
{
|
||||
ICollection<AgentMessage>? messages = null;
|
||||
|
||||
var result = messages!.ToChatMessages();
|
||||
|
||||
Assert.NotNull(result);
|
||||
Assert.Empty(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ToChatMessages_MessageCollection_Empty_ReturnsEmptyCollection()
|
||||
{
|
||||
var messages = new List<AgentMessage>();
|
||||
|
||||
var result = messages.ToChatMessages();
|
||||
|
||||
Assert.NotNull(result);
|
||||
Assert.Empty(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ToChatMessages_MessageCollection_WithValidMessages_ReturnsCorrectChatMessages()
|
||||
{
|
||||
var messages = new List<AgentMessage>
|
||||
{
|
||||
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<AgentMessage>
|
||||
{
|
||||
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<ArgumentNullException>(() => 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<TextPart>(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<TextPart>(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<TextPart>(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<AgentMessage> { 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<AgentMessage> { 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<AgentMessage> { 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<AgentMessage> { message }.ToChatMessages();
|
||||
|
||||
var chatMessage = result.First();
|
||||
var textContent = Assert.IsType<TextContent>(chatMessage.Contents.First());
|
||||
Assert.Equal("Sample text", textContent.Text);
|
||||
Assert.Equal(textPart, textContent.RawRepresentation);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ConvertPartToAIContent_TextPartWithMetadata_PreservesMetadata()
|
||||
{
|
||||
var metadata = new Dictionary<string, JsonElement>
|
||||
{
|
||||
["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<AgentMessage> { message }.ToChatMessages();
|
||||
|
||||
var chatMessage = result.First();
|
||||
var textContent = Assert.IsType<TextContent>(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<AgentMessage> { 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<AgentMessage> { 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<string, JsonElement>
|
||||
{
|
||||
["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<AgentMessage> { 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<AgentMessage> { 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<AgentMessage> { message }.ToChatMessages();
|
||||
|
||||
Assert.NotNull(result);
|
||||
Assert.Empty(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ToA2AMessage_ChatMessageWithMultipleTextContents_CreatesMultipleParts()
|
||||
{
|
||||
var contents = new List<AIContent>
|
||||
{
|
||||
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<TextPart>(result.Parts[0]);
|
||||
var secondPart = Assert.IsType<TextPart>(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<AgentMessage> { 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<AgentMessage> { 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<AgentMessage> { message }.ToChatMessages();
|
||||
|
||||
Assert.NotNull(result);
|
||||
Assert.Single(result);
|
||||
|
||||
var chatMessage = result.First();
|
||||
Assert.Equal(2, chatMessage.Contents.Count);
|
||||
|
||||
var firstContent = Assert.IsType<TextContent>(chatMessage.Contents[0]);
|
||||
Assert.Equal("First part", firstContent.Text);
|
||||
|
||||
var secondContent = Assert.IsType<TextContent>(chatMessage.Contents[1]);
|
||||
Assert.Equal("Second part", secondContent.Text);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ToA2AMessage_MixedContentWithUnsupported_IgnoresUnsupportedContent()
|
||||
{
|
||||
var contents = new List<AIContent>
|
||||
{
|
||||
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<TextPart>(result.Parts[0]);
|
||||
Assert.Equal("First text", firstPart.Text);
|
||||
|
||||
var secondPart = Assert.IsType<TextPart>(result.Parts[1]);
|
||||
Assert.Equal("Second text", secondPart.Text);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user