diff --git a/dotnet/src/Microsoft.Agents.AI.Hosting.OpenAI/Responses/Converters/ItemResourceExtensions.cs b/dotnet/src/Microsoft.Agents.AI.Hosting.OpenAI/Responses/Converters/ItemResourceExtensions.cs deleted file mode 100644 index c9e92dda60..0000000000 --- a/dotnet/src/Microsoft.Agents.AI.Hosting.OpenAI/Responses/Converters/ItemResourceExtensions.cs +++ /dev/null @@ -1,344 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text.Json; -using Microsoft.Agents.AI.Hosting.OpenAI.Responses.Models; -using Microsoft.Extensions.AI; - -namespace Microsoft.Agents.AI.Hosting.OpenAI.Responses.Converters; - -/// -/// Extension methods for converting between ItemResource and ChatMessage. -/// -internal static class ItemResourceExtensions -{ - /// - /// Converts an ItemResource to a ChatMessage. - /// - /// The ItemResource to convert. - /// A ChatMessage. - /// Thrown when the ItemResource type is not supported for conversion to ChatMessage. - public static ChatMessage ToChatMessage(this ItemResource itemResource) - { - if (itemResource is ResponsesMessageItemResource messageItem) - { - var content = messageItem switch - { - ResponsesAssistantMessageItemResource assistant => assistant.Content, - ResponsesUserMessageItemResource user => user.Content, - ResponsesSystemMessageItemResource system => system.Content, - ResponsesDeveloperMessageItemResource developer => developer.Content, - _ => throw new NotSupportedException($"Message item type {messageItem.GetType().Name} not supported") - }; - - // Convert ItemContent to AIContent using the existing converter - var aiContents = content - .Select(ItemContentConverter.ToAIContent) - .Where(c => c is not null) - .ToList(); - return new ChatMessage(messageItem.Role, aiContents!); - } - - if (itemResource is FunctionToolCallItemResource functionCall) - { - return new ChatMessage(ChatRole.Assistant, [ - functionCall.ToFunctionCallContent() - ]); - } - - if (itemResource is FunctionToolCallOutputItemResource functionOutput) - { - return new ChatMessage(ChatRole.Tool, [ - functionOutput.ToFunctionResultContent() - ]); - } - - throw new NotSupportedException($"ItemResource type {itemResource.GetType().Name} not supported for conversion to ChatMessage"); - } - - /// - /// Converts a ChatMessage to ItemResource objects. - /// This method requires an IdGenerator to create unique IDs for the generated resources. - /// - /// The chat message to convert. - /// The ID generator to use for creating IDs. - /// The JSON serializer options to use. - /// An enumerable of ItemResource objects. - public static IEnumerable ToItemResources(this ChatMessage message, IdGenerator idGenerator, JsonSerializerOptions jsonSerializerOptions) - { - return message.ToItemResource(idGenerator, jsonSerializerOptions); - } - - /// - /// Converts a ChatMessage to a single ItemResource (message type) when it contains only message content. - /// For messages with function calls, use ToItemResources instead. - /// - /// The chat message to convert. - /// The ID to assign to the message resource. - /// A ResponsesMessageItemResource. - /// Thrown when the message contains function calls or function results. - public static ResponsesMessageItemResource ToMessageItemResource(this ChatMessage message, string id) - { - // Check if the message contains function calls or function results - if (message.Contents.Any(c => c is FunctionCallContent or FunctionResultContent)) - { - throw new InvalidOperationException("Cannot convert a ChatMessage with function calls or function results to a single MessageItemResource. Use ToItemResources instead."); - } - - // Convert all contents to ItemContent - var contents = message.Contents - .Select(ItemContentConverter.ToItemContent) - .Where(c => c is not null) - .ToList(); - - // Create the appropriate message item resource based on role - return message.Role.Value switch - { - "assistant" => new ResponsesAssistantMessageItemResource - { - Id = id, - Status = ResponsesMessageItemResourceStatus.Completed, - Content = contents! - }, - "user" => new ResponsesUserMessageItemResource - { - Id = id, - Status = ResponsesMessageItemResourceStatus.Completed, - Content = contents! - }, - "system" => new ResponsesSystemMessageItemResource - { - Id = id, - Status = ResponsesMessageItemResourceStatus.Completed, - Content = contents! - }, - "developer" => new ResponsesDeveloperMessageItemResource - { - Id = id, - Status = ResponsesMessageItemResourceStatus.Completed, - Content = contents! - }, - _ => new ResponsesAssistantMessageItemResource - { - Id = id, - Status = ResponsesMessageItemResourceStatus.Completed, - Content = contents! - } - }; - } - - /// - /// Converts multiple ItemResources to ChatMessages. - /// Adjacent message items with the same role will be combined into a single ChatMessage. - /// - /// The ItemResources to convert. - /// An enumerable of ChatMessages. - public static IEnumerable ToChatMessages(this IEnumerable itemResources) - { - ChatMessage? currentMessage = null; - var currentContents = new List(); - - foreach (var item in itemResources) - { - if (item is ResponsesMessageItemResource messageItem) - { - // Check if we should start a new message or continue the current one - if (currentMessage is not null && currentMessage.Role != messageItem.Role) - { - // Yield the current message and start a new one - yield return new ChatMessage(currentMessage.Role, [.. currentContents]); - currentContents = []; - } - - // Add contents from this message item - var aiContents = messageItem switch - { - ResponsesAssistantMessageItemResource assistant => assistant.Content, - ResponsesUserMessageItemResource user => user.Content, - ResponsesSystemMessageItemResource system => system.Content, - ResponsesDeveloperMessageItemResource developer => developer.Content, - _ => [] - }; - - foreach (var content in aiContents) - { - if (ItemContentConverter.ToAIContent(content) is { } aiContent) - { - currentContents.Add(aiContent); - } - } - - // Initialize or update current message - currentMessage = new ChatMessage(messageItem.Role, [.. currentContents]); - } - else if (item is FunctionToolCallItemResource functionCall) - { - // Function calls are always from assistant - if (currentMessage is not null && currentMessage.Role != ChatRole.Assistant) - { - yield return new ChatMessage(currentMessage.Role, [.. currentContents]); - currentContents = []; - currentMessage = null; - } - - currentContents.Add(functionCall.ToFunctionCallContent()); - currentMessage = new ChatMessage(ChatRole.Assistant, [.. currentContents]); - } - else if (item is FunctionToolCallOutputItemResource functionOutput) - { - // Function outputs are always from tool role - if (currentMessage is not null && currentMessage.Role != ChatRole.Tool) - { - yield return new ChatMessage(currentMessage.Role, [.. currentContents]); - currentContents = []; - currentMessage = null; - } - - currentContents.Add(functionOutput.ToFunctionResultContent()); - currentMessage = new ChatMessage(ChatRole.Tool, [.. currentContents]); - } - } - - // Yield the last message if any - if (currentMessage is not null && currentContents.Count > 0) - { - yield return new ChatMessage(currentMessage.Role, [.. currentContents]); - } - } - - /// - /// Converts a FunctionToolCallItemResource to FunctionCallContent. - /// Uses the official Microsoft.Extensions.AI pattern via CreateFromParsedArguments to properly handle parsing errors. - /// - /// The function call item resource to convert. - /// A FunctionCallContent with properly parsed arguments. If parsing fails, the Exception property will be set. - public static FunctionCallContent ToFunctionCallContent(this FunctionToolCallItemResource functionCall) - { - // Use the same pattern as Microsoft.Extensions.AI.OpenAI's ParseCallContent method - // This properly handles parsing errors by setting the Exception property on FunctionCallContent - return FunctionCallContent.CreateFromParsedArguments( - functionCall.Arguments ?? "{}", - functionCall.CallId, - functionCall.Name, - static json => (IDictionary)JsonSerializer.Deserialize(json, OpenAIHostingJsonUtilities.DefaultOptions.GetTypeInfo(typeof(IDictionary)))!); - } - - /// - /// Converts a FunctionToolCallOutputItemResource to FunctionResultContent. - /// - /// The function output item resource to convert. - /// A FunctionResultContent. - public static FunctionResultContent ToFunctionResultContent(this FunctionToolCallOutputItemResource functionOutput) - { - return new FunctionResultContent(functionOutput.CallId, functionOutput.Output); - } - - /// - /// Converts a ChatMessage to ItemParam objects (input models without IDs). - /// This is useful for creating items in the Conversations API. - /// Filters out events that don't map well to ItemParams (e.g., messages with no convertible content). - /// - /// The chat message to convert. - /// An enumerable of ItemParam objects. - public static IEnumerable ToItemParams(this ChatMessage message) - { - // Separate function call/result contents from regular message contents - foreach (AIContent content in message.Contents) - { - switch (content) - { - case FunctionCallContent functionCallContent: - yield return new FunctionToolCallItemParam - { - CallId = functionCallContent.CallId, - Name = functionCallContent.Name, - Arguments = JsonSerializer.Serialize( - functionCallContent.Arguments, - OpenAIHostingJsonUtilities.DefaultOptions.GetTypeInfo(typeof(IDictionary))) - }; - break; - - case FunctionResultContent functionResultContent: - string output = functionResultContent.Exception is not null - ? $"{functionResultContent.Exception.GetType().Name}(\"{functionResultContent.Exception.Message}\")" - : $"{functionResultContent.Result?.ToString() ?? "(null)"}"; - yield return new FunctionToolCallOutputItemParam - { - CallId = functionResultContent.CallId, - Output = output - }; - break; - } - } - - // Convert regular message contents - List regularContents = []; - foreach (AIContent content in message.Contents) - { - if (content is not FunctionCallContent and not FunctionResultContent && - ItemContentConverter.ToItemContent(content) is { } itemContent) - { - regularContents.Add(itemContent); - } - } - - // Only create a message item if we have convertible contents - // This filters out messages that contain only non-convertible content (e.g., UsageContent) - if (regularContents.Count > 0) - { - InputMessageContent messageContent = InputMessageContent.FromContents(regularContents); - - yield return message.Role.Value.ToUpperInvariant() switch - { - "USER" => new ResponsesUserMessageItemParam { Content = messageContent }, - "ASSISTANT" => new ResponsesAssistantMessageItemParam { Content = messageContent }, - "SYSTEM" => new ResponsesSystemMessageItemParam { Content = messageContent }, - "DEVELOPER" => new ResponsesDeveloperMessageItemParam { Content = messageContent }, - _ => new ResponsesUserMessageItemParam { Content = messageContent } - }; - } - } - - /// - /// Converts a ChatMessage to a single ItemParam (message type) when it contains only message content. - /// For messages with function calls, use ToItemParams instead. - /// - /// The chat message to convert. - /// A ResponsesMessageItemParam. - /// Thrown when the message contains function calls or function results. - public static ResponsesMessageItemParam ToMessageItemParam(this ChatMessage message) - { - // Check if the message contains function calls or function results - if (message.Contents.Any(c => c is FunctionCallContent or FunctionResultContent)) - { - throw new InvalidOperationException("Cannot convert a ChatMessage with function calls or function results to a single MessageItemParam. Use ToItemParams instead."); - } - - // Convert all contents to ItemContent - List contents = []; - foreach (AIContent content in message.Contents) - { - if (ItemContentConverter.ToItemContent(content) is { } itemContent) - { - contents.Add(itemContent); - } - } - - // Create InputMessageContent - InputMessageContent messageContent = contents.Count > 0 - ? InputMessageContent.FromContents(contents) - : InputMessageContent.FromText(message.Text ?? string.Empty); - - // Create the appropriate message item param based on role - return message.Role.Value.ToUpperInvariant() switch - { - "USER" => new ResponsesUserMessageItemParam { Content = messageContent }, - "ASSISTANT" => new ResponsesAssistantMessageItemParam { Content = messageContent }, - "SYSTEM" => new ResponsesSystemMessageItemParam { Content = messageContent }, - "DEVELOPER" => new ResponsesDeveloperMessageItemParam { Content = messageContent }, - _ => new ResponsesUserMessageItemParam { Content = messageContent } - }; - } -}