// Copyright (c) Microsoft. All rights reserved.
using System;
#if NET
using System.Buffers;
#endif
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
#if NET
using System.Text;
#endif
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Text.Json.Serialization.Metadata;
using Microsoft.Shared.Diagnostics;
using Microsoft.Extensions.AI;
namespace Microsoft.Agents.AI;
///
/// Represents the response to an run request, containing messages and metadata about the interaction.
///
///
///
/// provides one or more response messages and metadata about the response.
/// A typical response will contain a single message, however a response may contain multiple messages
/// in a variety of scenarios. For example, if the agent internally invokes functions or tools, performs
/// RAG retrievals or has other complex logic, a single run by the agent may produce many messages showing
/// the intermediate progress that the agent made towards producing the agent result.
///
///
/// To get the text result of the response, use the property or simply call on the .
///
///
public class AgentRunResponse
{
/// The response messages.
private IList? _messages;
/// Initializes a new instance of the class.
public AgentRunResponse()
{
}
/// Initializes a new instance of the class.
/// The response message to include in this response.
/// is .
public AgentRunResponse(ChatMessage message)
{
_ = Throw.IfNull(message);
this.Messages.Add(message);
}
///
/// Initializes a new instance of the class from an existing .
///
/// The from which to populate this .
/// is .
///
/// This constructor creates an agent response that wraps an existing , preserving all
/// metadata and storing the original response in for access to
/// the underlying implementation details.
///
public AgentRunResponse(ChatResponse response)
{
_ = Throw.IfNull(response);
this.AdditionalProperties = response.AdditionalProperties;
this.CreatedAt = response.CreatedAt;
this.Messages = response.Messages;
this.RawRepresentation = response;
this.ResponseId = response.ResponseId;
this.Usage = response.Usage;
this.ContinuationToken = response.ContinuationToken;
}
///
/// Initializes a new instance of the class with the specified collection of messages.
///
/// The collection of response messages, or to create an empty response.
public AgentRunResponse(IList? messages)
{
this._messages = messages;
}
///
/// Gets or sets the collection of messages to be represented by this response.
///
///
/// A collection of instances representing the agent's response.
/// If the backing collection is , accessing this property will create an empty list.
///
///
///
/// This property provides access to all messages generated during the agent's execution. While most
/// responses contain a single assistant message, complex agent behaviors may produce multiple messages
/// showing intermediate steps, function calls, or different types of content.
///
///
/// The collection is mutable and can be modified after creation. Setting this property to
/// will cause subsequent access to return an empty list.
///
///
[AllowNull]
public IList Messages
{
get => this._messages ??= new List(1);
set => this._messages = value;
}
///
/// Gets the concatenated text content of all messages in this response.
///
///
/// A string containing the combined text from all instances
/// across all messages in , or an empty string if no text content is present.
///
///
/// This property provides a convenient way to access the textual response without needing to
/// iterate through individual messages and content items. Non-text content is ignored.
///
[JsonIgnore]
public string Text => this._messages?.ConcatText() ?? string.Empty;
///
/// Gets all user input requests present in the response messages.
///
///
/// An enumerable collection of instances found
/// across all messages in the response.
///
///
/// User input requests indicate that the agent is asking for additional information
/// from the user before it can continue processing. This property aggregates all such
/// requests across all messages in the response.
///
[JsonIgnore]
public IEnumerable UserInputRequests => this._messages?.SelectMany(x => x.Contents).OfType() ?? [];
///
/// Gets or sets the identifier of the agent that generated this response.
///
///
/// A unique string identifier for the agent, or if not specified.
///
///
/// This identifier helps track which agent generated the response in multi-agent scenarios
/// or for debugging and telemetry purposes.
///
public string? AgentId { get; set; }
///
/// Gets or sets the unique identifier for this specific response.
///
///
/// A unique string identifier for this response instance, or if not assigned.
///
public string? ResponseId { get; set; }
///
/// Gets or sets the continuation token for getting the result of a background agent response.
///
///
/// implementations that support background responses will return
/// a continuation token if background responses are allowed in
/// and the result of the response has not been obtained yet. If the response has completed and the result has been obtained,
/// the token will be .
///
/// This property should be used in conjunction with to
/// continue to poll for the completion of the response. Pass this token to
/// on subsequent calls to
/// to poll for completion.
///
///
public ResponseContinuationToken? ContinuationToken { get; set; }
///
/// Gets or sets the timestamp indicating when this response was created.
///
///
/// A representing when the response was generated,
/// or if not specified.
///
///
/// The creation timestamp is useful for auditing, logging, and understanding
/// the chronology of agentic interactions.
///
public DateTimeOffset? CreatedAt { get; set; }
///
/// Gets or sets the resource usage information for generating this response.
///
///
/// A instance containing token counts and other usage metrics,
/// or if usage information is not available.
///
public UsageDetails? Usage { get; set; }
/// Gets or sets the raw representation of the run response from an underlying implementation.
///
/// If a is created to represent some underlying object from another object
/// model, this property can be used to store that original object. This can be useful for debugging or
/// for enabling a consumer to access the underlying object model if needed.
///
[JsonIgnore]
public object? RawRepresentation { get; set; }
///
/// Gets or sets additional properties associated with this response.
///
///
/// An containing custom properties,
/// or if no additional properties are present.
///
///
/// Additional properties provide a way to include custom metadata or provider-specific
/// information that doesn't fit into the standard response schema. This is useful for
/// preserving implementation-specific details or extending the response with custom data.
///
public AdditionalPropertiesDictionary? AdditionalProperties { get; set; }
///
public override string ToString() => this.Text;
///
/// Converts this into a collection of instances
/// suitable for streaming scenarios.
///
///
/// An array of instances that collectively represent
/// the same information as this response.
///
///
///
/// This method is useful for converting complete responses back into streaming format,
/// which may be needed for scenarios that require uniform handling of both streaming
/// and non-streaming agent responses.
///
///
/// Each message in becomes a separate update, and usage information
/// is included as an additional update if present. The order of updates preserves the
/// original message sequence.
///
///
public AgentRunResponseUpdate[] ToAgentRunResponseUpdates()
{
AgentRunResponseUpdate? extra = null;
if (this.AdditionalProperties is not null || this.Usage is not null)
{
extra = new AgentRunResponseUpdate
{
AdditionalProperties = this.AdditionalProperties,
};
if (this.Usage is { } usage)
{
extra.Contents.Add(new UsageContent(usage));
}
}
int messageCount = this._messages?.Count ?? 0;
var updates = new AgentRunResponseUpdate[messageCount + (extra is not null ? 1 : 0)];
int i;
for (i = 0; i < messageCount; i++)
{
ChatMessage message = this._messages![i];
updates[i] = new AgentRunResponseUpdate
{
AdditionalProperties = message.AdditionalProperties,
AuthorName = message.AuthorName,
Contents = message.Contents,
RawRepresentation = message.RawRepresentation,
Role = message.Role,
AgentId = this.AgentId,
ResponseId = this.ResponseId,
MessageId = message.MessageId,
CreatedAt = this.CreatedAt,
};
}
if (extra is not null)
{
updates[i] = extra;
}
return updates;
}
///
/// Deserializes the response text into the given type.
///
/// The output type to deserialize into.
/// The result as the requested type.
/// The result is not parsable into the requested type.
public T Deserialize() =>
this.Deserialize(AgentAbstractionsJsonUtilities.DefaultOptions);
///
/// Deserializes the response text into the given type using the specified serializer options.
///
/// The output type to deserialize into.
/// The JSON serialization options to use.
/// The result as the requested type.
/// The result is not parsable into the requested type.
public T Deserialize(JsonSerializerOptions serializerOptions)
{
_ = Throw.IfNull(serializerOptions);
var structuredOutput = this.GetResultCore(serializerOptions, out var failureReason);
return failureReason switch
{
FailureReason.ResultDidNotContainJson => throw new InvalidOperationException("The response did not contain JSON to be deserialized."),
FailureReason.DeserializationProducedNull => throw new InvalidOperationException("The deserialized response is null."),
_ => structuredOutput!,
};
}
///
/// Tries to deserialize response text into the given type.
///
/// The output type to deserialize into.
/// The parsed structured output.
/// if parsing was successful; otherwise, .
public bool TryDeserialize([NotNullWhen(true)] out T? structuredOutput) =>
this.TryDeserialize(AgentAbstractionsJsonUtilities.DefaultOptions, out structuredOutput);
///
/// Tries to deserialize response text into the given type using the specified serializer options.
///
/// The output type to deserialize into.
/// The JSON serialization options to use.
/// The parsed structured output.
/// if parsing was successful; otherwise, .
public bool TryDeserialize(JsonSerializerOptions serializerOptions, [NotNullWhen(true)] out T? structuredOutput)
{
_ = Throw.IfNull(serializerOptions);
try
{
structuredOutput = this.GetResultCore(serializerOptions, out var failureReason);
return failureReason is null;
}
catch
{
structuredOutput = default;
return false;
}
}
private static T? DeserializeFirstTopLevelObject(string json, JsonTypeInfo typeInfo)
{
#if NET
// We need to deserialize only the first top-level object as a workaround for a common LLM backend
// issue. GPT 3.5 Turbo commonly returns multiple top-level objects after doing a function call.
// See https://community.openai.com/t/2-json-objects-returned-when-using-function-calling-and-json-mode/574348
var utf8ByteLength = Encoding.UTF8.GetByteCount(json);
var buffer = ArrayPool.Shared.Rent(utf8ByteLength);
try
{
var utf8SpanLength = Encoding.UTF8.GetBytes(json, 0, json.Length, buffer, 0);
var reader = new Utf8JsonReader(new ReadOnlySpan(buffer, 0, utf8SpanLength), new() { AllowMultipleValues = true });
return JsonSerializer.Deserialize(ref reader, typeInfo);
}
finally
{
ArrayPool.Shared.Return(buffer);
}
#else
return JsonSerializer.Deserialize(json, typeInfo);
#endif
}
private T? GetResultCore(JsonSerializerOptions serializerOptions, out FailureReason? failureReason)
{
var json = this.Text;
if (string.IsNullOrEmpty(json))
{
failureReason = FailureReason.ResultDidNotContainJson;
return default;
}
// If there's an exception here, we want it to propagate, since the Result property is meant to throw directly
T? deserialized = DeserializeFirstTopLevelObject(json!, (JsonTypeInfo)serializerOptions.GetTypeInfo(typeof(T)));
if (deserialized is null)
{
failureReason = FailureReason.DeserializationProducedNull;
return default;
}
failureReason = default;
return deserialized;
}
private enum FailureReason
{
ResultDidNotContainJson,
DeserializationProducedNull
}
}