using Microsoft.Extensions.AI; using ModelContextProtocol.Protocol.Types; using ModelContextProtocol.Utils; using ModelContextProtocol.Utils.Json; using System.Runtime.InteropServices; using System.Text.Json; namespace ModelContextProtocol; /// Provides helpers for conversions related to . public static class AIContentExtensions { /// Creates a from a . /// The message to convert. /// The created . public static ChatMessage ToChatMessage(this PromptMessage promptMessage) { Throw.IfNull(promptMessage); return new() { RawRepresentation = promptMessage, Role = promptMessage.Role == Role.User ? ChatRole.User : ChatRole.Assistant, Contents = [ToAIContent(promptMessage.Content)] }; } /// Creates s from a . /// The messages to convert. /// The created . public static IList ToChatMessages(this GetPromptResult promptResult) { Throw.IfNull(promptResult); return promptResult.Messages.Select(m => m.ToChatMessage()).ToList(); } /// Gets instances for the specified . /// The message for which to extract its contents as instances. /// The converted content. public static IList ToPromptMessages(this ChatMessage chatMessage) { Throw.IfNull(chatMessage); Role r = chatMessage.Role == ChatRole.User ? Role.User : Role.Assistant; List messages = []; foreach (var content in chatMessage.Contents) { if (content is TextContent or DataContent) { messages.Add(new PromptMessage { Role = r, Content = content.ToContent() }); } } return messages; } /// Creates a new from the content of a . /// The to convert. /// The created . public static AIContent ToAIContent(this Content content) { Throw.IfNull(content); AIContent ac; if (content is { Type: "image" or "audio", MimeType: not null, Data: not null }) { ac = new DataContent(Convert.FromBase64String(content.Data), content.MimeType); } else if (content is { Type: "resource" } && content.Resource is { } resourceContents) { ac = resourceContents.ToAIContent(); } else { ac = new TextContent(content.Text); } ac.RawRepresentation = content; return ac; } /// Creates a new from the content of a . /// The to convert. /// The created . public static AIContent ToAIContent(this ResourceContents content) { Throw.IfNull(content); AIContent ac = content switch { BlobResourceContents blobResource => new DataContent(Convert.FromBase64String(blobResource.Blob), blobResource.MimeType ?? "application/octet-stream"), TextResourceContents textResource => new TextContent(textResource.Text), _ => throw new NotSupportedException($"Resource type '{content.GetType().Name}' is not supported.") }; (ac.AdditionalProperties ??= [])["uri"] = content.Uri; ac.RawRepresentation = content; return ac; } /// Creates a list of from a sequence of . /// The instances to convert. /// The created instances. public static IList ToAIContents(this IEnumerable contents) { Throw.IfNull(contents); return [.. contents.Select(ToAIContent)]; } /// Creates a list of from a sequence of . /// The instances to convert. /// The created instances. public static IList ToAIContents(this IEnumerable contents) { Throw.IfNull(contents); return [.. contents.Select(ToAIContent)]; } /// Extracts the data from a as a Base64 string. internal static string GetBase64Data(this DataContent dataContent) { #if NET return Convert.ToBase64String(dataContent.Data.Span); #else return MemoryMarshal.TryGetArray(dataContent.Data, out ArraySegment segment) ? Convert.ToBase64String(segment.Array!, segment.Offset, segment.Count) : Convert.ToBase64String(dataContent.Data.ToArray()); #endif } internal static Content ToContent(this AIContent content) => content switch { TextContent textContent => new() { Text = textContent.Text, Type = "text", }, DataContent dataContent => new() { Data = dataContent.GetBase64Data(), MimeType = dataContent.MediaType, Type = dataContent.HasTopLevelMediaType("image") ? "image" : dataContent.HasTopLevelMediaType("audio") ? "audio" : "resource", }, _ => new() { Text = JsonSerializer.Serialize(content, McpJsonUtilities.DefaultOptions.GetTypeInfo(typeof(object))), Type = "text", } }; }