// Copyright (c) Microsoft. All rights reserved.
using System.Collections.Concurrent;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
namespace Microsoft.Agents.AI.Hosting;
///
/// Provides an in-memory implementation of for development and testing scenarios.
///
///
///
/// This implementation stores threads in memory using a concurrent dictionary and is suitable for:
///
/// - Single-instance development scenarios
/// - Testing and prototyping
/// - Scenarios where session persistence across restarts is not required
///
///
///
/// Warning: All stored threads will be lost when the application restarts.
/// For production use with multiple instances or persistence across restarts, use a durable storage implementation
/// such as Redis, SQL Server, or Azure Cosmos DB.
///
///
/// Multi-user warning. This store keys threads by
/// (agent.Id, conversationId) only — it has no principal/owner dimension. When
/// the conversation identifier originates from the wire (for example, an AG-UI
/// RunAgentInput.ThreadId or an A2A contextId), any caller who knows
/// or guesses another caller's identifier can resume that other caller's persisted
/// thread. Multi-user hosts must wrap this store in
/// (typically by calling
/// UseClaimsBasedSessionIsolation(...) from
/// Microsoft.Agents.AI.Hosting.AspNetCore or by registering a custom
/// ) so that the conversation namespace is
/// scoped per principal. See the trust-model remarks on
/// for the full background.
///
///
public sealed class InMemoryAgentSessionStore : AgentSessionStore
{
private readonly ConcurrentDictionary _threads = new();
///
public override async ValueTask SaveSessionAsync(AIAgent agent, string conversationId, AgentSession session, CancellationToken cancellationToken = default)
{
var key = GetKey(conversationId, agent.Id);
this._threads[key] = await agent.SerializeSessionAsync(session, cancellationToken: cancellationToken).ConfigureAwait(false);
}
///
public override async ValueTask GetSessionAsync(AIAgent agent, string conversationId, CancellationToken cancellationToken = default)
{
var key = GetKey(conversationId, agent.Id);
JsonElement? sessionContent = this._threads.TryGetValue(key, out var existingSession) ? existingSession : null;
return sessionContent switch
{
null => await agent.CreateSessionAsync(cancellationToken).ConfigureAwait(false),
_ => await agent.DeserializeSessionAsync(sessionContent.Value, cancellationToken: cancellationToken).ConfigureAwait(false),
};
}
private static string GetKey(string conversationId, string agentId) => $"{agentId}:{conversationId}";
}