// Copyright (c) Microsoft. All rights reserved. using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Text.Json.Serialization; using Microsoft.Extensions.AI; using Microsoft.Shared.DiagnosticIds; namespace Microsoft.Agents.AI.Compaction; /// /// Represents a logical group of instances that must be kept or removed together during compaction. /// /// /// /// Message groups ensure atomic preservation of related messages. For example, an assistant message /// containing tool calls and its corresponding tool result messages form a /// group — removing one without the other would cause LLM API errors. /// /// /// Groups also support exclusion semantics: a group can be marked as excluded (with an optional reason) /// to indicate it should not be included in the messages sent to the model, while still being preserved /// for diagnostics, storage, or later re-inclusion. /// /// /// Each group tracks its , , and /// so that can efficiently aggregate totals across all or only included groups. /// /// [Experimental(DiagnosticIds.Experiments.AgentsAIExperiments)] public sealed class CompactionMessageGroup { /// /// The key used to identify a message as a compaction summary. /// /// /// When this key is present with a value of , the message is classified as /// by . /// public static readonly string SummaryPropertyKey = "_is_summary"; /// /// Initializes a new instance of the class. /// /// The kind of message group. /// The messages in this group. The list is captured as a read-only snapshot. /// The total UTF-8 byte count of the text content in the messages. /// The token count for the messages, computed by a tokenizer or estimated. /// /// The user turn this group belongs to, or for . /// [JsonConstructor] internal CompactionMessageGroup(CompactionGroupKind kind, IReadOnlyList messages, int byteCount, int tokenCount, int? turnIndex = null) { this.Kind = kind; this.Messages = messages; this.MessageCount = messages.Count; this.ByteCount = byteCount; this.TokenCount = tokenCount; this.TurnIndex = turnIndex; } /// /// Gets the kind of this message group. /// public CompactionGroupKind Kind { get; } /// /// Gets the messages in this group. /// public IReadOnlyList Messages { get; } /// /// Gets the number of messages in this group. /// public int MessageCount { get; } /// /// Gets the total UTF-8 byte count of the text content in this group's messages. /// public int ByteCount { get; } /// /// Gets the estimated or actual token count for this group's messages. /// public int TokenCount { get; } /// /// Gets user turn index this group belongs to, or for groups /// that precede the first user message (e.g., system messages). A turn index of 0 /// corresponds with any non-system message that precedes the first user message, /// turn index 1 corresponds with the first user message and its subsequent non-user /// messages, and so on... /// /// /// A turn starts with a group and includes all subsequent /// non-user, non-system groups until the next user group or end of conversation. System messages /// () are always assigned a turn index /// since they never belong to a user turn. /// public int? TurnIndex { get; } /// /// Gets or sets a value indicating whether this group is excluded from the projected message list. /// /// /// Excluded groups are preserved in the collection for diagnostics or storage purposes /// but are not included when calling . /// public bool IsExcluded { get; set; } /// /// Gets or sets an optional reason explaining why this group was excluded. /// public string? ExcludeReason { get; set; } }