// 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; }
}
}