// Copyright (c) Microsoft. All rights reserved. using System; using System.Collections.Generic; using System.Linq; using System.Text.Json.Serialization; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.AI; using Microsoft.Shared.Diagnostics; namespace Microsoft.Agents.AI; /// /// Provides an in-memory implementation of with support for message reduction. /// /// /// /// stores chat messages in the , /// providing fast access and manipulation capabilities integrated with session state management. /// /// /// This maintains all messages in memory. For long-running conversations or high-volume scenarios, consider using /// message reduction strategies or alternative storage implementations. /// /// public sealed class InMemoryChatHistoryProvider : ChatHistoryProvider { private readonly ProviderSessionState _sessionState; private IReadOnlyList? _stateKeys; /// /// Initializes a new instance of the class. /// /// /// Optional configuration options that control the provider's behavior, including state initialization, /// message reduction, and serialization settings. If , default settings will be used. /// public InMemoryChatHistoryProvider(InMemoryChatHistoryProviderOptions? options = null) : base( options?.ProvideOutputMessageFilter, options?.StorageInputRequestMessageFilter, options?.StorageInputResponseMessageFilter) { this._sessionState = new ProviderSessionState( options?.StateInitializer ?? (_ => new State()), options?.StateKey ?? this.GetType().Name, options?.JsonSerializerOptions); this.ChatReducer = options?.ChatReducer; this.ReducerTriggerEvent = options?.ReducerTriggerEvent ?? InMemoryChatHistoryProviderOptions.ChatReducerTriggerEvent.BeforeMessagesRetrieval; } /// public override IReadOnlyList StateKeys => this._stateKeys ??= [this._sessionState.StateKey]; /// /// Gets the chat reducer used to process or reduce chat messages. If null, no reduction logic will be applied. /// public IChatReducer? ChatReducer { get; } /// /// Gets the event that triggers the reducer invocation in this provider. /// public InMemoryChatHistoryProviderOptions.ChatReducerTriggerEvent ReducerTriggerEvent { get; } /// /// Gets the chat messages stored for the specified session. /// /// The agent session containing the state. /// A list of chat messages, or an empty list if no state is found. public List GetMessages(AgentSession? session) => this._sessionState.GetOrInitializeState(session).Messages; /// /// Sets the chat messages for the specified session. /// /// The agent session containing the state. /// The messages to store. /// is . public void SetMessages(AgentSession? session, List messages) { Throw.IfNull(messages); State state = this._sessionState.GetOrInitializeState(session); state.Messages = messages; } /// protected override async ValueTask> ProvideChatHistoryAsync(InvokingContext context, CancellationToken cancellationToken = default) { State state = this._sessionState.GetOrInitializeState(context.Session); if (this.ReducerTriggerEvent is InMemoryChatHistoryProviderOptions.ChatReducerTriggerEvent.BeforeMessagesRetrieval && this.ChatReducer is not null) { // Apply pre-retrieval reduction if configured await ReduceMessagesAsync(this.ChatReducer, state, cancellationToken).ConfigureAwait(false); } return state.Messages; } /// protected override async ValueTask StoreChatHistoryAsync(InvokedContext context, CancellationToken cancellationToken = default) { State state = this._sessionState.GetOrInitializeState(context.Session); // Add request and response messages to the provider var allNewMessages = (context.RequestMessages ?? []).Concat(context.ResponseMessages ?? []); state.Messages.AddRange(allNewMessages); if (this.ReducerTriggerEvent is InMemoryChatHistoryProviderOptions.ChatReducerTriggerEvent.AfterMessageAdded && this.ChatReducer is not null) { // Apply pre-write reduction strategy if configured await ReduceMessagesAsync(this.ChatReducer, state, cancellationToken).ConfigureAwait(false); } } private static async Task ReduceMessagesAsync(IChatReducer reducer, State state, CancellationToken cancellationToken = default) { state.Messages = [.. await reducer.ReduceAsync(state.Messages, cancellationToken).ConfigureAwait(false)]; } /// /// Represents the state of a stored in the . /// public sealed class State { /// /// Gets or sets the list of chat messages. /// [JsonPropertyName("messages")] public List Messages { get; set; } = []; } }