// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System.Text.Json;
using System.Text.Json.Serialization.Metadata;
namespace Microsoft.Teams.Core.Schema;
///
/// Represents a dictionary for storing extended properties as key-value pairs.
///
public class ExtendedPropertiesDictionary : Dictionary
{
///
/// Initializes a new empty instance of the class.
///
public ExtendedPropertiesDictionary() { }
///
/// Initializes a new instance of the class by shallow-copying entries from another dictionary.
///
public ExtendedPropertiesDictionary(IDictionary source) : base(source) { }
///
/// Extracts and deserializes a value from the dictionary, removing the entry if found.
/// Returns the deserialized value, or default if the key is not present.
///
public T? Extract(string key)
{
if (!TryGetValue(key, out object? raw))
return default;
Remove(key);
if (raw is T typed)
return typed;
if (raw is System.Text.Json.JsonElement element)
return System.Text.Json.JsonSerializer.Deserialize(element.GetRawText());
return default;
}
///
/// Gets and deserializes a value from the dictionary without removing it.
/// Handles values that result from deserialization.
///
public T? Get(string key)
{
if (!TryGetValue(key, out object? raw))
return default;
if (raw is T typed)
return typed;
if (raw is System.Text.Json.JsonElement element)
{
T? deserialized = System.Text.Json.JsonSerializer.Deserialize(element.GetRawText());
this[key] = deserialized;
return deserialized;
}
return default;
}
}
///
/// Represents a core activity object that encapsulates the data and metadata for a bot interaction.
///
///
/// This class provides the foundational structure for bot activities including message exchanges,
/// conversation updates, and other bot-related events. It supports serialization to and from JSON
/// and includes extension properties for channel-specific data.
/// Follows the Activity Protocol Specification: https://github.com/microsoft/Agents/blob/main/specs/activity/protocol-activity.md
///
public class CoreActivity
{
///
/// Gets or sets the type of the activity. See for common values.
///
///
/// Common activity types include "message", "conversationUpdate", "contactRelationUpdate", etc.
///
[JsonPropertyName("type")] public string Type { get; set; }
///
/// Gets or sets the unique identifier for the channel on which this activity is occurring.
///
[JsonPropertyName("channelId")] public string? ChannelId { get; set; }
///
/// Gets or sets the unique identifier for the activity.
///
[JsonPropertyName("id")] public string? Id { get; set; }
///
/// Gets or sets the URL of the service endpoint for this activity.
///
///
/// This URL is used to send responses back to the channel.
///
[JsonPropertyName("serviceUrl")] public Uri? ServiceUrl { get; set; }
///
/// Gets or sets the identifier of the activity this activity is a reply to.
///
[JsonPropertyName("replyToId")] public string? ReplyToId { get; set; }
///
/// Gets or sets the conversation information for this activity.
///
[JsonPropertyName("conversation")] public Conversation? Conversation { get; set; }
///
/// Gets or sets the sender account for this activity.
///
[JsonPropertyName("from")] public ChannelAccount? From { get; set; }
///
/// Gets or sets the recipient account for this activity.
///
[JsonPropertyName("recipient")] public ChannelAccount? Recipient { get; set; }
///
/// Gets the extension data dictionary for storing additional properties not defined in the schema.
///
[JsonExtensionData] public ExtendedPropertiesDictionary Properties { get; set; } = [];
///
/// Gets the JSON serializer options used for reflection-based serialization of extended activity types.
///
///
/// Uses reflection-based serialization to support custom activity types that extend CoreActivity.
/// This is used when serializing/deserializing types not registered in the source-generated context.
///
private static readonly JsonSerializerOptions ReflectionJsonOptions = new()
{
WriteIndented = true,
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
};
///
/// Creates a new instance of the class with the specified activity type.
/// Defaults to .
///
/// The activity type. Defaults to "message".
[JsonConstructor]
public CoreActivity(string type = ActivityType.Message)
{
Type = type;
}
///
/// Creates a new instance of the class by copying properties from another activity.
///
/// The source activity to copy from.
protected CoreActivity(CoreActivity activity)
{
ArgumentNullException.ThrowIfNull(activity);
Id = activity.Id;
ServiceUrl = activity.ServiceUrl;
ChannelId = activity.ChannelId;
Type = activity.Type;
Conversation = activity.Conversation is not null ? new Conversation(activity.Conversation.Id) { Properties = new ExtendedPropertiesDictionary(activity.Conversation.Properties) } : null;
From = activity.From is not null ? CloneChannelAccount(activity.From) : null;
Recipient = activity.Recipient is not null ? CloneChannelAccount(activity.Recipient) : null;
Properties = new ExtendedPropertiesDictionary(activity.Properties);
}
#pragma warning disable ExperimentalTeamsTargeted
private static ChannelAccount CloneChannelAccount(ChannelAccount source) => new()
{
Id = source.Id,
Name = source.Name,
IsTargeted = source.IsTargeted,
AgenticAppId = source.AgenticAppId,
AgenticUserId = source.AgenticUserId,
AgenticAppBlueprintId = source.AgenticAppBlueprintId,
Properties = new ExtendedPropertiesDictionary(source.Properties)
};
#pragma warning restore ExperimentalTeamsTargeted
///
/// Serializes the current activity to a JSON string.
///
/// A JSON string representation of the activity.
public virtual string ToJson()
=> JsonSerializer.Serialize(this, CoreActivityJsonContext.Default.CoreActivity);
///
/// Serializes the current activity to a JSON string using the specified for source-generated serialization.
///
/// The type of the activity to serialize. Must inherit from .
/// The JSON type info that provides serialization metadata for type .
/// A JSON string representation of the activity.
public string ToJson(JsonTypeInfo jsonTypeInfo) where T : CoreActivity
=> JsonSerializer.Serialize(this, jsonTypeInfo);
///
/// Serializes the specified activity instance to a JSON string using reflection-based serialization.
///
/// Uses reflection-based serialization to support custom activity types that extend
/// . The resulting JSON reflects the public properties of the activity instance.
/// The type of the activity to serialize. Must inherit from CoreActivity.
/// The activity instance to serialize. Cannot be null.
/// A JSON string representation of the specified activity instance.
public static string ToJson(T instance) where T : CoreActivity
=> JsonSerializer.Serialize(instance, ReflectionJsonOptions);
///
/// Deserializes a JSON string into a object.
///
/// The JSON string to deserialize.
/// A instance.
public static CoreActivity FromJsonString(string json)
=> JsonSerializer.Deserialize(json, CoreActivityJsonContext.Default.CoreActivity)!;
///
/// Asynchronously deserializes a JSON stream into a object.
///
/// The stream containing JSON data to deserialize.
/// A cancellation token to cancel the operation.
/// A task that represents the asynchronous operation. The task result contains the deserialized instance, or null if deserialization fails.
public static ValueTask FromJsonStreamAsync(Stream stream, CancellationToken cancellationToken = default)
=> JsonSerializer.DeserializeAsync(stream, CoreActivityJsonContext.Default.CoreActivity, cancellationToken);
///
/// Asynchronously deserializes a JSON stream into an instance of type using the specified for source-generated serialization.
///
/// The type of the activity to deserialize. Must inherit from .
/// The stream containing JSON data to deserialize.
/// The JSON type info that provides deserialization metadata for type .
/// A cancellation token to cancel the operation.
/// A representing the asynchronous operation. The result contains the deserialized activity, or null if deserialization fails.
public static ValueTask FromJsonStreamAsync(Stream stream, JsonTypeInfo jsonTypeInfo, CancellationToken cancellationToken = default) where T : CoreActivity
=> JsonSerializer.DeserializeAsync(stream, jsonTypeInfo, cancellationToken);
///
/// Asynchronously deserializes a JSON value from the specified stream into an instance of type T.
///
/// The caller is responsible for managing the lifetime of the provided stream. The method uses
/// default JSON serialization options.
/// The type of the object to deserialize. Must derive from CoreActivity.
/// The stream containing the JSON data to deserialize. The stream must be readable and positioned at the start of
/// the JSON content.
/// A cancellation token that can be used to cancel the asynchronous operation.
/// A ValueTask that represents the asynchronous operation. The result contains an instance of type T if
/// deserialization is successful; otherwise, null.
public static ValueTask FromJsonStreamAsync(Stream stream, CancellationToken cancellationToken = default) where T : CoreActivity
=> JsonSerializer.DeserializeAsync(stream, ReflectionJsonOptions, cancellationToken);
///
/// Creates a new instance of the to construct activity instances.
///
/// A new instance.
public static CoreActivityBuilder CreateBuilder() => new();
}