diff --git a/dotnet/Directory.Packages.props b/dotnet/Directory.Packages.props index 5e83e0d577..037e61ab3d 100644 --- a/dotnet/Directory.Packages.props +++ b/dotnet/Directory.Packages.props @@ -33,14 +33,15 @@ - + + - + @@ -101,10 +102,10 @@ - - + + - + diff --git a/dotnet/src/Microsoft.Agents.AI.A2A/Extensions/A2AMetadataExtensions.cs b/dotnet/src/Microsoft.Agents.AI.A2A/Extensions/A2AMetadataExtensions.cs deleted file mode 100644 index 3c81c6abe8..0000000000 --- a/dotnet/src/Microsoft.Agents.AI.A2A/Extensions/A2AMetadataExtensions.cs +++ /dev/null @@ -1,36 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -using System.Collections.Generic; -using System.Text.Json; -using Microsoft.Extensions.AI; - -namespace A2A; - -/// -/// Extension methods for A2A metadata dictionary. -/// -internal static class A2AMetadataExtensions -{ - /// - /// Converts a dictionary of metadata to an . - /// - /// - /// This method can be replaced by the one from A2A SDK once it is public. - /// - /// The metadata dictionary to convert. - /// The converted , or null if the input is null or empty. - internal static AdditionalPropertiesDictionary? ToAdditionalProperties(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/src/Microsoft.Agents.AI.A2A/Extensions/AdditionalPropertiesDictionaryExtensions.cs b/dotnet/src/Microsoft.Agents.AI.A2A/Extensions/AdditionalPropertiesDictionaryExtensions.cs deleted file mode 100644 index a3340d2ca8..0000000000 --- a/dotnet/src/Microsoft.Agents.AI.A2A/Extensions/AdditionalPropertiesDictionaryExtensions.cs +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -using System.Collections.Generic; -using System.Text.Json; -using Microsoft.Agents.AI; - -namespace Microsoft.Extensions.AI; - -/// -/// Extension methods for AdditionalPropertiesDictionary. -/// -internal static class AdditionalPropertiesDictionaryExtensions -{ - /// - /// Converts an to a dictionary of values suitable for A2A metadata. - /// - /// - /// This method can be replaced by the one from A2A SDK once it is available. - /// - /// The additional properties dictionary to convert, or null. - /// A dictionary of JSON elements representing the metadata, or null if the input is null or empty. - internal static Dictionary? ToA2AMetadata(this AdditionalPropertiesDictionary? additionalProperties) - { - if (additionalProperties is not { Count: > 0 }) - { - return null; - } - - var metadata = new Dictionary(); - - foreach (var kvp in additionalProperties) - { - if (kvp.Value is JsonElement) - { - metadata[kvp.Key] = (JsonElement)kvp.Value!; - continue; - } - - metadata[kvp.Key] = JsonSerializer.SerializeToElement(kvp.Value, A2AJsonUtilities.DefaultOptions.GetTypeInfo(typeof(object))); - } - - return metadata; - } -} diff --git a/dotnet/src/Microsoft.Agents.AI.Hosting.A2A/Converters/A2AMetadataExtensions.cs b/dotnet/src/Microsoft.Agents.AI.Hosting.A2A/Converters/A2AMetadataExtensions.cs deleted file mode 100644 index 010264bb65..0000000000 --- a/dotnet/src/Microsoft.Agents.AI.Hosting.A2A/Converters/A2AMetadataExtensions.cs +++ /dev/null @@ -1,36 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -using System.Collections.Generic; -using System.Text.Json; -using Microsoft.Extensions.AI; - -namespace Microsoft.Agents.AI.Hosting.A2A.Converters; - -/// -/// Extension methods for A2A metadata dictionary. -/// -internal static class A2AMetadataExtensions -{ - /// - /// Converts a dictionary of metadata to an . - /// - /// - /// This method can be replaced by the one from A2A SDK once it is public. - /// - /// The metadata dictionary to convert. - /// The converted , or null if the input is null or empty. - internal static AdditionalPropertiesDictionary? ToAdditionalProperties(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/src/Microsoft.Agents.AI.Hosting.A2A/Converters/AdditionalPropertiesDictionaryExtensions.cs b/dotnet/src/Microsoft.Agents.AI.Hosting.A2A/Converters/AdditionalPropertiesDictionaryExtensions.cs deleted file mode 100644 index e557ff4e07..0000000000 --- a/dotnet/src/Microsoft.Agents.AI.Hosting.A2A/Converters/AdditionalPropertiesDictionaryExtensions.cs +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -using System.Collections.Generic; -using System.Text.Json; -using Microsoft.Extensions.AI; - -namespace Microsoft.Agents.AI.Hosting.A2A.Converters; - -/// -/// Extension methods for AdditionalPropertiesDictionary. -/// -internal static class AdditionalPropertiesDictionaryExtensions -{ - /// - /// Converts an to a dictionary of values suitable for A2A metadata. - /// - /// - /// This method can be replaced by the one from A2A SDK once it is available. - /// - /// The additional properties dictionary to convert, or null. - /// A dictionary of JSON elements representing the metadata, or null if the input is null or empty. - internal static Dictionary? ToA2AMetadata(this AdditionalPropertiesDictionary? additionalProperties) - { - if (additionalProperties is not { Count: > 0 }) - { - return null; - } - - var metadata = new Dictionary(); - - foreach (var kvp in additionalProperties) - { - if (kvp.Value is JsonElement) - { - metadata[kvp.Key] = (JsonElement)kvp.Value!; - continue; - } - - metadata[kvp.Key] = JsonSerializer.SerializeToElement(kvp.Value, A2AHostingJsonUtilities.DefaultOptions.GetTypeInfo(typeof(object))); - } - - return metadata; - } -} diff --git a/dotnet/src/Microsoft.Agents.AI.Workflows.Declarative.Mcp/DefaultMcpToolHandler.cs b/dotnet/src/Microsoft.Agents.AI.Workflows.Declarative.Mcp/DefaultMcpToolHandler.cs index 751f518277..107f4f0260 100644 --- a/dotnet/src/Microsoft.Agents.AI.Workflows.Declarative.Mcp/DefaultMcpToolHandler.cs +++ b/dotnet/src/Microsoft.Agents.AI.Workflows.Declarative.Mcp/DefaultMcpToolHandler.cs @@ -5,6 +5,7 @@ using System.Globalization; using System.Linq; using System.Net.Http; +using System.Text; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.AI; @@ -222,31 +223,36 @@ private static void PopulateResultContent(McpServerToolResultContent resultConte } } - private static AIContent ConvertContentBlock(ContentBlock block) + internal static AIContent ConvertContentBlock(ContentBlock block) { return block switch { TextContentBlock text => new TextContent(text.Text), - ImageContentBlock image => CreateDataContentFromBase64(image.Data, image.MimeType ?? "image/*"), - AudioContentBlock audio => CreateDataContentFromBase64(audio.Data, audio.MimeType ?? "audio/*"), + ImageContentBlock image => CreateDataContent(image.Data, image.MimeType ?? "image/*"), + AudioContentBlock audio => CreateDataContent(audio.Data, audio.MimeType ?? "audio/*"), _ => new TextContent(block.ToString() ?? string.Empty), }; } - private static DataContent CreateDataContentFromBase64(string? base64Data, string mediaType) + private static DataContent CreateDataContent(ReadOnlyMemory base64Utf8Data, string mediaType) { - if (string.IsNullOrEmpty(base64Data)) + if (base64Utf8Data.IsEmpty) { return new DataContent($"data:{mediaType};base64,", mediaType); } +#if NET8_0_OR_GREATER + string base64 = Encoding.UTF8.GetString(base64Utf8Data.Span); +#else + string base64 = Encoding.UTF8.GetString(base64Utf8Data.ToArray()); +#endif + // If it's already a data URI, use it directly - if (base64Data.StartsWith("data:", StringComparison.OrdinalIgnoreCase)) + if (base64.StartsWith("data:", StringComparison.OrdinalIgnoreCase)) { - return new DataContent(base64Data, mediaType); + return new DataContent(base64, mediaType); } - // Otherwise, construct a data URI from the base64 data - return new DataContent($"data:{mediaType};base64,{base64Data}", mediaType); + return new DataContent($"data:{mediaType};base64,{base64}", mediaType); } } diff --git a/dotnet/tests/Microsoft.Agents.AI.A2A.UnitTests/Extensions/A2AMetadataExtensionsTests.cs b/dotnet/tests/Microsoft.Agents.AI.A2A.UnitTests/Extensions/A2AMetadataExtensionsTests.cs deleted file mode 100644 index 1307b9f4b6..0000000000 --- a/dotnet/tests/Microsoft.Agents.AI.A2A.UnitTests/Extensions/A2AMetadataExtensionsTests.cs +++ /dev/null @@ -1,67 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -using System.Collections.Generic; -using System.Text.Json; -using A2A; - -namespace Microsoft.Agents.AI.A2A.UnitTests; - -/// -/// Unit tests for the class. -/// -public sealed class A2AMetadataExtensionsTests -{ - [Fact] - public void ToAdditionalProperties_WithNullMetadata_ReturnsNull() - { - // Arrange - Dictionary? metadata = null; - - // Act - var result = metadata.ToAdditionalProperties(); - - // Assert - Assert.Null(result); - } - - [Fact] - public void ToAdditionalProperties_WithEmptyMetadata_ReturnsNull() - { - // Arrange - var metadata = new Dictionary(); - - // Act - var result = metadata.ToAdditionalProperties(); - - // Assert - Assert.Null(result); - } - - [Fact] - public void ToAdditionalProperties_WithMultipleProperties_ReturnsAdditionalPropertiesDictionaryWithAllProperties() - { - // Arrange - var metadata = new Dictionary - { - { "stringKey", JsonSerializer.SerializeToElement("stringValue") }, - { "numberKey", JsonSerializer.SerializeToElement(42) }, - { "booleanKey", JsonSerializer.SerializeToElement(true) } - }; - - // Act - var result = metadata.ToAdditionalProperties(); - - // Assert - Assert.NotNull(result); - Assert.Equal(3, result.Count); - - Assert.True(result.ContainsKey("stringKey")); - Assert.Equal("stringValue", ((JsonElement)result["stringKey"]!).GetString()); - - Assert.True(result.ContainsKey("numberKey")); - Assert.Equal(42, ((JsonElement)result["numberKey"]!).GetInt32()); - - Assert.True(result.ContainsKey("booleanKey")); - Assert.True(((JsonElement)result["booleanKey"]!).GetBoolean()); - } -} diff --git a/dotnet/tests/Microsoft.Agents.AI.A2A.UnitTests/Extensions/AdditionalPropertiesDictionaryExtensionsTests.cs b/dotnet/tests/Microsoft.Agents.AI.A2A.UnitTests/Extensions/AdditionalPropertiesDictionaryExtensionsTests.cs deleted file mode 100644 index 4972b8857f..0000000000 --- a/dotnet/tests/Microsoft.Agents.AI.A2A.UnitTests/Extensions/AdditionalPropertiesDictionaryExtensionsTests.cs +++ /dev/null @@ -1,186 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -using System.Collections.Generic; -using System.Text.Json; -using Microsoft.Extensions.AI; - -namespace Microsoft.Agents.AI.A2A.UnitTests; - -/// -/// Unit tests for the class. -/// -public sealed class AdditionalPropertiesDictionaryExtensionsTests -{ - [Fact] - public void ToA2AMetadata_WithNullAdditionalProperties_ReturnsNull() - { - // Arrange - AdditionalPropertiesDictionary? additionalProperties = null; - - // Act - Dictionary? result = additionalProperties.ToA2AMetadata(); - - // Assert - Assert.Null(result); - } - - [Fact] - public void ToA2AMetadata_WithEmptyAdditionalProperties_ReturnsNull() - { - // Arrange - AdditionalPropertiesDictionary additionalProperties = []; - - // Act - Dictionary? result = additionalProperties.ToA2AMetadata(); - - // Assert - Assert.Null(result); - } - - [Fact] - public void ToA2AMetadata_WithStringValue_ReturnsMetadataWithJsonElement() - { - // Arrange - AdditionalPropertiesDictionary additionalProperties = new() - { - { "stringKey", "stringValue" } - }; - - // Act - Dictionary? result = additionalProperties.ToA2AMetadata(); - - // Assert - Assert.NotNull(result); - Assert.Single(result); - Assert.True(result.ContainsKey("stringKey")); - Assert.Equal("stringValue", result["stringKey"].GetString()); - } - - [Fact] - public void ToA2AMetadata_WithNumericValue_ReturnsMetadataWithJsonElement() - { - // Arrange - AdditionalPropertiesDictionary additionalProperties = new() - { - { "numberKey", 42 } - }; - - // Act - Dictionary? result = additionalProperties.ToA2AMetadata(); - - // Assert - Assert.NotNull(result); - Assert.Single(result); - Assert.True(result.ContainsKey("numberKey")); - Assert.Equal(42, result["numberKey"].GetInt32()); - } - - [Fact] - public void ToA2AMetadata_WithBooleanValue_ReturnsMetadataWithJsonElement() - { - // Arrange - AdditionalPropertiesDictionary additionalProperties = new() - { - { "booleanKey", true } - }; - - // Act - Dictionary? result = additionalProperties.ToA2AMetadata(); - - // Assert - Assert.NotNull(result); - Assert.Single(result); - Assert.True(result.ContainsKey("booleanKey")); - Assert.True(result["booleanKey"].GetBoolean()); - } - - [Fact] - public void ToA2AMetadata_WithMultipleProperties_ReturnsMetadataWithAllProperties() - { - // Arrange - AdditionalPropertiesDictionary additionalProperties = new() - { - { "stringKey", "stringValue" }, - { "numberKey", 42 }, - { "booleanKey", true } - }; - - // Act - Dictionary? result = additionalProperties.ToA2AMetadata(); - - // Assert - Assert.NotNull(result); - Assert.Equal(3, result.Count); - - Assert.True(result.ContainsKey("stringKey")); - Assert.Equal("stringValue", result["stringKey"].GetString()); - - Assert.True(result.ContainsKey("numberKey")); - Assert.Equal(42, result["numberKey"].GetInt32()); - - Assert.True(result.ContainsKey("booleanKey")); - Assert.True(result["booleanKey"].GetBoolean()); - } - - [Fact] - public void ToA2AMetadata_WithArrayValue_ReturnsMetadataWithJsonElement() - { - // Arrange - int[] arrayValue = [1, 2, 3]; - AdditionalPropertiesDictionary additionalProperties = new() - { - { "arrayKey", arrayValue } - }; - - // Act - Dictionary? result = additionalProperties.ToA2AMetadata(); - - // Assert - Assert.NotNull(result); - Assert.Single(result); - Assert.True(result.ContainsKey("arrayKey")); - Assert.Equal(JsonValueKind.Array, result["arrayKey"].ValueKind); - Assert.Equal(3, result["arrayKey"].GetArrayLength()); - } - - [Fact] - public void ToA2AMetadata_WithNullValue_ReturnsMetadataWithNullJsonElement() - { - // Arrange - AdditionalPropertiesDictionary additionalProperties = new() - { - { "nullKey", null! } - }; - - // Act - Dictionary? result = additionalProperties.ToA2AMetadata(); - - // Assert - Assert.NotNull(result); - Assert.Single(result); - Assert.True(result.ContainsKey("nullKey")); - Assert.Equal(JsonValueKind.Null, result["nullKey"].ValueKind); - } - - [Fact] - public void ToA2AMetadata_WithJsonElementValue_ReturnsMetadataWithJsonElement() - { - // Arrange - JsonElement jsonElement = JsonSerializer.SerializeToElement(new { name = "test", value = 123 }); - AdditionalPropertiesDictionary additionalProperties = new() - { - { "jsonElementKey", jsonElement } - }; - - // Act - Dictionary? result = additionalProperties.ToA2AMetadata(); - - // Assert - Assert.NotNull(result); - Assert.Single(result); - Assert.True(result.ContainsKey("jsonElementKey")); - Assert.Equal(JsonValueKind.Object, result["jsonElementKey"].ValueKind); - Assert.Equal("test", result["jsonElementKey"].GetProperty("name").GetString()); - Assert.Equal(123, result["jsonElementKey"].GetProperty("value").GetInt32()); - } -} diff --git a/dotnet/tests/Microsoft.Agents.AI.Hosting.A2A.UnitTests/Converters/AdditionalPropertiesDictionaryExtensionsTests.cs b/dotnet/tests/Microsoft.Agents.AI.Hosting.A2A.UnitTests/Converters/AdditionalPropertiesDictionaryExtensionsTests.cs deleted file mode 100644 index e0c8c4e96b..0000000000 --- a/dotnet/tests/Microsoft.Agents.AI.Hosting.A2A.UnitTests/Converters/AdditionalPropertiesDictionaryExtensionsTests.cs +++ /dev/null @@ -1,187 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -using System.Collections.Generic; -using System.Text.Json; -using Microsoft.Agents.AI.Hosting.A2A.Converters; -using Microsoft.Extensions.AI; - -namespace Microsoft.Agents.AI.Hosting.A2A.UnitTests.Converters; - -/// -/// Unit tests for the class. -/// -public sealed class AdditionalPropertiesDictionaryExtensionsTests -{ - [Fact] - public void ToA2AMetadata_WithNullAdditionalProperties_ReturnsNull() - { - // Arrange - AdditionalPropertiesDictionary? additionalProperties = null; - - // Act - Dictionary? result = additionalProperties.ToA2AMetadata(); - - // Assert - Assert.Null(result); - } - - [Fact] - public void ToA2AMetadata_WithEmptyAdditionalProperties_ReturnsNull() - { - // Arrange - AdditionalPropertiesDictionary additionalProperties = []; - - // Act - Dictionary? result = additionalProperties.ToA2AMetadata(); - - // Assert - Assert.Null(result); - } - - [Fact] - public void ToA2AMetadata_WithStringValue_ReturnsMetadataWithJsonElement() - { - // Arrange - AdditionalPropertiesDictionary additionalProperties = new() - { - { "stringKey", "stringValue" } - }; - - // Act - Dictionary? result = additionalProperties.ToA2AMetadata(); - - // Assert - Assert.NotNull(result); - Assert.Single(result); - Assert.True(result.ContainsKey("stringKey")); - Assert.Equal("stringValue", result["stringKey"].GetString()); - } - - [Fact] - public void ToA2AMetadata_WithNumericValue_ReturnsMetadataWithJsonElement() - { - // Arrange - AdditionalPropertiesDictionary additionalProperties = new() - { - { "numberKey", 42 } - }; - - // Act - Dictionary? result = additionalProperties.ToA2AMetadata(); - - // Assert - Assert.NotNull(result); - Assert.Single(result); - Assert.True(result.ContainsKey("numberKey")); - Assert.Equal(42, result["numberKey"].GetInt32()); - } - - [Fact] - public void ToA2AMetadata_WithBooleanValue_ReturnsMetadataWithJsonElement() - { - // Arrange - AdditionalPropertiesDictionary additionalProperties = new() - { - { "booleanKey", true } - }; - - // Act - Dictionary? result = additionalProperties.ToA2AMetadata(); - - // Assert - Assert.NotNull(result); - Assert.Single(result); - Assert.True(result.ContainsKey("booleanKey")); - Assert.True(result["booleanKey"].GetBoolean()); - } - - [Fact] - public void ToA2AMetadata_WithMultipleProperties_ReturnsMetadataWithAllProperties() - { - // Arrange - AdditionalPropertiesDictionary additionalProperties = new() - { - { "stringKey", "stringValue" }, - { "numberKey", 42 }, - { "booleanKey", true } - }; - - // Act - Dictionary? result = additionalProperties.ToA2AMetadata(); - - // Assert - Assert.NotNull(result); - Assert.Equal(3, result.Count); - - Assert.True(result.ContainsKey("stringKey")); - Assert.Equal("stringValue", result["stringKey"].GetString()); - - Assert.True(result.ContainsKey("numberKey")); - Assert.Equal(42, result["numberKey"].GetInt32()); - - Assert.True(result.ContainsKey("booleanKey")); - Assert.True(result["booleanKey"].GetBoolean()); - } - - [Fact] - public void ToA2AMetadata_WithArrayValue_ReturnsMetadataWithJsonElement() - { - // Arrange - int[] arrayValue = [1, 2, 3]; - AdditionalPropertiesDictionary additionalProperties = new() - { - { "arrayKey", arrayValue } - }; - - // Act - Dictionary? result = additionalProperties.ToA2AMetadata(); - - // Assert - Assert.NotNull(result); - Assert.Single(result); - Assert.True(result.ContainsKey("arrayKey")); - Assert.Equal(JsonValueKind.Array, result["arrayKey"].ValueKind); - Assert.Equal(3, result["arrayKey"].GetArrayLength()); - } - - [Fact] - public void ToA2AMetadata_WithNullValue_ReturnsMetadataWithNullJsonElement() - { - // Arrange - AdditionalPropertiesDictionary additionalProperties = new() - { - { "nullKey", null! } - }; - - // Act - Dictionary? result = additionalProperties.ToA2AMetadata(); - - // Assert - Assert.NotNull(result); - Assert.Single(result); - Assert.True(result.ContainsKey("nullKey")); - Assert.Equal(JsonValueKind.Null, result["nullKey"].ValueKind); - } - - [Fact] - public void ToA2AMetadata_WithJsonElementValue_ReturnsMetadataWithJsonElement() - { - // Arrange - JsonElement jsonElement = JsonSerializer.SerializeToElement(new { name = "test", value = 123 }); - AdditionalPropertiesDictionary additionalProperties = new() - { - { "jsonElementKey", jsonElement } - }; - - // Act - Dictionary? result = additionalProperties.ToA2AMetadata(); - - // Assert - Assert.NotNull(result); - Assert.Single(result); - Assert.True(result.ContainsKey("jsonElementKey")); - Assert.Equal(JsonValueKind.Object, result["jsonElementKey"].ValueKind); - Assert.Equal("test", result["jsonElementKey"].GetProperty("name").GetString()); - Assert.Equal(123, result["jsonElementKey"].GetProperty("value").GetInt32()); - } -} diff --git a/dotnet/tests/Microsoft.Agents.AI.Workflows.Declarative.Mcp.UnitTests/DefaultMcpToolHandlerTests.cs b/dotnet/tests/Microsoft.Agents.AI.Workflows.Declarative.Mcp.UnitTests/DefaultMcpToolHandlerTests.cs index 858ea9db14..abfa95cc36 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Workflows.Declarative.Mcp.UnitTests/DefaultMcpToolHandlerTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Workflows.Declarative.Mcp.UnitTests/DefaultMcpToolHandlerTests.cs @@ -3,9 +3,12 @@ using System; using System.Collections.Generic; using System.Net.Http; +using System.Text; using System.Threading; using System.Threading.Tasks; using FluentAssertions; +using Microsoft.Extensions.AI; +using ModelContextProtocol.Protocol; namespace Microsoft.Agents.AI.Workflows.Declarative.Mcp.UnitTests; @@ -342,4 +345,148 @@ public async Task DefaultMcpToolHandler_ShouldImplementIAsyncDisposableAsync() } #endregion + + #region ConvertContentBlock Tests + + [Fact] + public void ConvertContentBlock_TextContentBlock_ShouldReturnTextContent() + { + // Arrange + TextContentBlock block = new() { Text = "hello world" }; + + // Act + AIContent result = DefaultMcpToolHandler.ConvertContentBlock(block); + + // Assert + result.Should().BeOfType() + .Which.Text.Should().Be("hello world"); + } + + [Fact] + public void ConvertContentBlock_ImageContentBlock_WithEmptyData_ShouldReturnDataContentWithEmptyUri() + { + // Arrange + ImageContentBlock block = new() { Data = ReadOnlyMemory.Empty, MimeType = "image/png" }; + + // Act + AIContent result = DefaultMcpToolHandler.ConvertContentBlock(block); + + // Assert + DataContent dataContent = result.Should().BeOfType().Subject; + dataContent.MediaType.Should().Be("image/png"); + dataContent.Uri.Should().Be("data:image/png;base64,"); + } + + [Fact] + public void ConvertContentBlock_ImageContentBlock_WithBase64Payload_ShouldReturnDataContent() + { + // Arrange + byte[] base64Bytes = Encoding.UTF8.GetBytes("iVBORw0KGgo="); + ImageContentBlock block = new() { Data = new ReadOnlyMemory(base64Bytes), MimeType = "image/png" }; + + // Act + AIContent result = DefaultMcpToolHandler.ConvertContentBlock(block); + + // Assert + DataContent dataContent = result.Should().BeOfType().Subject; + dataContent.MediaType.Should().Be("image/png"); + dataContent.Uri.Should().Be("data:image/png;base64,iVBORw0KGgo="); + } + + [Fact] + public void ConvertContentBlock_ImageContentBlock_WithDataUri_ShouldReturnDataContentDirectly() + { + // Arrange + const string DataUri = "data:image/jpeg;base64,/9j/4AAQ"; + byte[] dataUriBytes = Encoding.UTF8.GetBytes(DataUri); + ImageContentBlock block = new() { Data = new ReadOnlyMemory(dataUriBytes), MimeType = "image/jpeg" }; + + // Act + AIContent result = DefaultMcpToolHandler.ConvertContentBlock(block); + + // Assert + DataContent dataContent = result.Should().BeOfType().Subject; + dataContent.MediaType.Should().Be("image/jpeg"); + dataContent.Uri.Should().Be(DataUri); + } + + [Fact] + public void ConvertContentBlock_ImageContentBlock_WithNullMimeType_ShouldDefaultToImageWildcard() + { + // Arrange + byte[] base64Bytes = Encoding.UTF8.GetBytes("iVBORw0KGgo="); + ImageContentBlock block = new() { Data = new ReadOnlyMemory(base64Bytes), MimeType = null! }; + + // Act + AIContent result = DefaultMcpToolHandler.ConvertContentBlock(block); + + // Assert + DataContent dataContent = result.Should().BeOfType().Subject; + dataContent.MediaType.Should().Be("image/*"); + } + + [Fact] + public void ConvertContentBlock_AudioContentBlock_WithEmptyData_ShouldReturnDataContentWithEmptyUri() + { + // Arrange + AudioContentBlock block = new() { Data = ReadOnlyMemory.Empty, MimeType = "audio/wav" }; + + // Act + AIContent result = DefaultMcpToolHandler.ConvertContentBlock(block); + + // Assert + DataContent dataContent = result.Should().BeOfType().Subject; + dataContent.MediaType.Should().Be("audio/wav"); + dataContent.Uri.Should().Be("data:audio/wav;base64,"); + } + + [Fact] + public void ConvertContentBlock_AudioContentBlock_WithBase64Payload_ShouldReturnDataContent() + { + // Arrange + byte[] base64Bytes = Encoding.UTF8.GetBytes("UklGRiQA"); + AudioContentBlock block = new() { Data = new ReadOnlyMemory(base64Bytes), MimeType = "audio/wav" }; + + // Act + AIContent result = DefaultMcpToolHandler.ConvertContentBlock(block); + + // Assert + DataContent dataContent = result.Should().BeOfType().Subject; + dataContent.MediaType.Should().Be("audio/wav"); + dataContent.Uri.Should().Be("data:audio/wav;base64,UklGRiQA"); + } + + [Fact] + public void ConvertContentBlock_AudioContentBlock_WithDataUri_ShouldReturnDataContentDirectly() + { + // Arrange + const string DataUri = "data:audio/mp3;base64,//uQxAAA"; + byte[] dataUriBytes = Encoding.UTF8.GetBytes(DataUri); + AudioContentBlock block = new() { Data = new ReadOnlyMemory(dataUriBytes), MimeType = "audio/mp3" }; + + // Act + AIContent result = DefaultMcpToolHandler.ConvertContentBlock(block); + + // Assert + DataContent dataContent = result.Should().BeOfType().Subject; + dataContent.MediaType.Should().Be("audio/mp3"); + dataContent.Uri.Should().Be(DataUri); + } + + [Fact] + public void ConvertContentBlock_AudioContentBlock_WithNullMimeType_ShouldDefaultToAudioWildcard() + { + // Arrange + byte[] base64Bytes = Encoding.UTF8.GetBytes("UklGRiQA"); + AudioContentBlock block = new() { Data = new ReadOnlyMemory(base64Bytes), MimeType = null! }; + + // Act + AIContent result = DefaultMcpToolHandler.ConvertContentBlock(block); + + // Assert + DataContent dataContent = result.Should().BeOfType().Subject; + dataContent.MediaType.Should().Be("audio/*"); + } + + #endregion }