// Copyright (c) Microsoft. All rights reserved. using System; using System.Collections.Generic; using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.AI; using Microsoft.Shared.Diagnostics; namespace Microsoft.Agents.AI; /// /// Provides extension methods for working with and instances. /// public static class AgentResponseExtensions { /// /// Creates a from an instance. /// /// The to convert. /// A built from the specified . /// is . /// /// If the 's is already a /// instance, that instance is returned directly. /// Otherwise, a new is created and populated with the data from the . /// The resulting instance is a shallow copy; any reference-type members (e.g. ) /// will be shared between the two instances. /// public static ChatResponse AsChatResponse(this AgentResponse response) { Throw.IfNull(response); return response.RawRepresentation as ChatResponse ?? new() { AdditionalProperties = response.AdditionalProperties, CreatedAt = response.CreatedAt, Messages = response.Messages, RawRepresentation = response, ResponseId = response.ResponseId, Usage = response.Usage, ContinuationToken = response.ContinuationToken, }; } /// /// Creates a from an instance. /// /// The to convert. /// A built from the specified . /// is . /// /// If the 's is already a /// instance, that instance is returned directly. /// Otherwise, a new is created and populated with the data from the . /// The resulting instance is a shallow copy; any reference-type members (e.g. ) /// will be shared between the two instances. /// public static ChatResponseUpdate AsChatResponseUpdate(this AgentResponseUpdate responseUpdate) { Throw.IfNull(responseUpdate); return responseUpdate.RawRepresentation as ChatResponseUpdate ?? new() { AdditionalProperties = responseUpdate.AdditionalProperties, AuthorName = responseUpdate.AuthorName, Contents = responseUpdate.Contents, CreatedAt = responseUpdate.CreatedAt, MessageId = responseUpdate.MessageId, RawRepresentation = responseUpdate, ResponseId = responseUpdate.ResponseId, Role = responseUpdate.Role, ContinuationToken = responseUpdate.ContinuationToken, }; } /// /// Creates an asynchronous enumerable of instances from an asynchronous /// enumerable of instances. /// /// The sequence of instances to convert. /// An asynchronous enumerable of instances built from . /// is . /// /// Each is converted to a using /// . /// public static async IAsyncEnumerable AsChatResponseUpdatesAsync( this IAsyncEnumerable responseUpdates) { Throw.IfNull(responseUpdates); await foreach (var responseUpdate in responseUpdates.ConfigureAwait(false)) { yield return responseUpdate.AsChatResponseUpdate(); } } /// /// Combines a sequence of instances into a single . /// /// The sequence of updates to be combined into a single response. /// A single that represents the combined state of all the updates. /// is . /// /// As part of combining into a single , the method will attempt to reconstruct /// instances. This includes using to determine /// message boundaries, as well as coalescing contiguous items where applicable, e.g. multiple /// instances in a row may be combined into a single . /// public static AgentResponse ToAgentResponse( this IEnumerable updates) { _ = Throw.IfNull(updates); AgentResponseDetails additionalDetails = new(); ChatResponse chatResponse = AsChatResponseUpdatesWithAdditionalDetails(updates, additionalDetails) .ToChatResponse(); return new AgentResponse(chatResponse) { AgentId = additionalDetails.AgentId, }; } /// /// Asynchronously combines a sequence of instances into a single . /// /// The asynchronous sequence of updates to be combined into a single response. /// The to monitor for cancellation requests. The default is . /// A task that represents the asynchronous operation. The task result contains a single that represents the combined state of all the updates. /// is . /// /// /// This is the asynchronous version of . /// It performs the same combining logic but operates on an asynchronous enumerable of updates. /// /// /// As part of combining into a single , the method will attempt to reconstruct /// instances. This includes using to determine /// message boundaries, as well as coalescing contiguous items where applicable, e.g. multiple /// instances in a row may be combined into a single . /// /// public static Task ToAgentResponseAsync( this IAsyncEnumerable updates, CancellationToken cancellationToken = default) { _ = Throw.IfNull(updates); return ToAgentResponseAsync(updates, cancellationToken); static async Task ToAgentResponseAsync( IAsyncEnumerable updates, CancellationToken cancellationToken) { AgentResponseDetails additionalDetails = new(); ChatResponse chatResponse = await AsChatResponseUpdatesWithAdditionalDetailsAsync(updates, additionalDetails, cancellationToken) .ToChatResponseAsync(cancellationToken) .ConfigureAwait(false); return new AgentResponse(chatResponse) { AgentId = additionalDetails.AgentId, }; } } private static IEnumerable AsChatResponseUpdatesWithAdditionalDetails( IEnumerable updates, AgentResponseDetails additionalDetails) { foreach (var update in updates) { UpdateAdditionalDetails(update, additionalDetails); yield return update.AsChatResponseUpdate(); } } private static async IAsyncEnumerable AsChatResponseUpdatesWithAdditionalDetailsAsync( IAsyncEnumerable updates, AgentResponseDetails additionalDetails, [EnumeratorCancellation] CancellationToken cancellationToken) { await foreach (var update in updates.WithCancellation(cancellationToken).ConfigureAwait(false)) { UpdateAdditionalDetails(update, additionalDetails); yield return update.AsChatResponseUpdate(); } } private static void UpdateAdditionalDetails(AgentResponseUpdate update, AgentResponseDetails details) { if (update.AgentId is { Length: > 0 }) { details.AgentId = update.AgentId; } } private sealed class AgentResponseDetails { public string? AgentId { get; set; } } }