// Copyright (c) Microsoft. All rights reserved. using System; using System.Diagnostics.CodeAnalysis; using System.Text.Json.Serialization; using Microsoft.Agents.AI.Workflows.Checkpointing; using Microsoft.Shared.Diagnostics; namespace Microsoft.Agents.AI.Workflows; /// /// Represents a value that can be exported / imported to a workflow, e.g. through an external request/response, or /// through checkpointing. Abstracts away delayed deserialization and type conversion where appropriate. /// public sealed class PortableValue { internal PortableValue(object value) { this._value = value; this.TypeId = new(value.GetType()); } [JsonConstructor] internal PortableValue(TypeId typeId, object value) { this.TypeId = Throw.IfNull(typeId); this._value = value; } /// public override bool Equals(object? obj) { if (obj is null) { return false; } if (obj is not PortableValue other) { Type targetType = obj.GetType(); return this.AsType(targetType)?.Equals(obj) is true; } return this.TypeId == other.TypeId && ((this.Value is null && other.Value is null) || this.Value?.Equals(other.Value) is true); } /// public override int GetHashCode() { return HashCode.Combine(this.TypeId, this.Value); } /// public static bool operator ==(PortableValue? left, PortableValue? right) { if (left is null) { return right is null; } return left.Equals(right); } /// public static bool operator !=(PortableValue? left, PortableValue? right) => !(left == right); /// /// The identifier of the type of the instance in . /// public TypeId TypeId { get; } [JsonIgnore] internal bool IsDelayedDeserialization => this.Value is IDelayedDeserialization; [JsonIgnore] internal bool IsDeserialized => this._deserializedValueCache is not null; private readonly object _value; private object? _deserializedValueCache; /// /// Gets the raw underlying value represented by this instance. /// [JsonInclude] internal object Value => this._deserializedValueCache ?? Throw.IfNull(this._value); /// /// Attempts to retrieve the underlying value as the specified type, deserializing if necessary. /// /// If the underlying value implements delayed deserialization, this method will attempt to /// deserialize it to the specified type. If the value is already of the requested type, it is returned directly. /// Otherwise, the default value for TValue is returned. /// /// The type to which the value should be cast or deserialized. /// The value cast or deserialized to type TValue if possible; otherwise, the default value for type TValue. public TValue? As() => this.Is(out TValue? value) ? value : default; /// /// Determines whether the current value can be represented as the specified type. /// /// The type to test for compatibility with the current value. /// true if the current value can be represented as type TValue; otherwise, false. public bool Is() => this.Is(out _); /// /// Determines whether the current value can be represented as the specified type. /// /// The type to test for compatibility with the current value. /// When this method returns, contains the value cast or deserialized to type TValue /// if the conversion succeeded, or null if the conversion failed. /// true if the current value can be represented as type TValue; otherwise, false. public bool Is([NotNullWhen(true)] out TValue? value) { if (this.Value is IDelayedDeserialization delayedDeserialization) { this._deserializedValueCache ??= delayedDeserialization.Deserialize(); } if (this.Value is TValue typedValue) { value = typedValue; return true; } value = default; return false; } /// /// Attempts to retrieve the underlying value as the specified type, deserializing if necessary. /// /// The type to which the value should be cast or deserialized. /// The value cast or deserialized to type targetType if possible; otherwise, null. public object? AsType(Type targetType) => this.IsType(targetType, out object? value) ? value : null; /// /// Determines whether the current instance can be assigned to the specified target type. /// /// The type to compare with the current instance. Cannot be null. /// true if the current instance can be assigned to targetType; otherwise, false. public bool IsType(Type targetType) => this.IsType(targetType, out _); /// /// Determines whether the current instance can be assigned to the specified target type. /// /// The type to compare with the current instance. Cannot be null. /// When this method returns, contains the value cast or deserialized to type TValue /// if the conversion succeeded, or null if the conversion failed. /// true if the current instance can be assigned to targetType; otherwise, false. public bool IsType(Type targetType, out object? value) { Throw.IfNull(targetType); if (this.Value is IDelayedDeserialization delayedDeserialization) { this._deserializedValueCache ??= delayedDeserialization.Deserialize(targetType); } if (this.Value is not null && targetType.IsAssignableFrom(this.Value.GetType())) { value = this.Value; return true; } value = null; return false; } }