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