// Copyright (c) Microsoft. All rights reserved.
using System;
using System.Collections.Generic;
using System.Text.Json;
using System.Text.Json.Serialization;
namespace Microsoft.Agents.AI.Hosting.OpenAI.Responses.Models;
///
/// Represents a reference to a conversation, which can be either a conversation ID (string) or a conversation object.
///
[JsonConverter(typeof(ConversationReferenceJsonConverter))]
internal sealed class ConversationReference
{
///
/// The conversation ID.
///
[JsonPropertyName("id")]
public string? Id { get; init; }
///
/// The conversation metadata (optional, only when passing a conversation object).
///
[JsonPropertyName("metadata")]
public Dictionary? Metadata { get; init; }
///
/// Creates a conversation reference from a conversation ID.
///
public static ConversationReference FromId(string id) => new() { Id = id };
///
/// Creates a conversation reference from a conversation object.
///
public static ConversationReference FromObject(string id, Dictionary? metadata = null) =>
new() { Id = id, Metadata = metadata };
}
///
/// JSON converter for ConversationReference that handles both string (conversation ID) and object representations.
///
internal sealed class ConversationReferenceJsonConverter : JsonConverter
{
///
public override ConversationReference? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
if (reader.TokenType == JsonTokenType.String)
{
// Handle string format: just the conversation ID
var id = reader.GetString();
return id is null ? null : ConversationReference.FromId(id);
}
else if (reader.TokenType == JsonTokenType.StartObject)
{
// Handle object format: { "id": "...", "metadata": {...} }
using var doc = JsonDocument.ParseValue(ref reader);
var root = doc.RootElement;
var id = root.TryGetProperty("id", out var idProp) ? idProp.GetString() : null;
Dictionary? metadata = null;
if (root.TryGetProperty("metadata", out var metadataProp) && metadataProp.ValueKind == JsonValueKind.Object)
{
metadata = JsonSerializer.Deserialize(metadataProp.GetRawText(), OpenAIHostingJsonContext.Default.DictionaryStringString);
}
return id is null ? null : ConversationReference.FromObject(id, metadata);
}
else if (reader.TokenType == JsonTokenType.Null)
{
return null;
}
throw new JsonException($"Unexpected token type for ConversationReference: {reader.TokenType}");
}
///
public override void Write(Utf8JsonWriter writer, ConversationReference value, JsonSerializerOptions options)
{
if (value is null)
{
writer.WriteNullValue();
return;
}
// Ideally if only ID is present and no metadata, we would serialize as a simple string.
// However, while a request's "conversation" property can be either a string or an object
// containing a string, a response's "conversation" property is always an object. Since
// here we don't know which scenario we're in, we always serialize as an object, which works
// in any scenario.
writer.WriteStartObject();
writer.WriteString("id", value.Id);
if (value.Metadata is not null)
{
writer.WritePropertyName("metadata");
JsonSerializer.Serialize(writer, value.Metadata, OpenAIHostingJsonContext.Default.DictionaryStringString);
}
writer.WriteEndObject();
}
}