// Copyright (c) Microsoft. All rights reserved.
using System;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Shared.Diagnostics;
namespace Microsoft.Agents.AI.Hosting;
///
/// Defines the contract for storing and retrieving agent conversation threads.
///
///
///
/// Implementations of this interface enable persistent storage of conversation threads,
/// allowing conversations to be resumed across HTTP requests, application restarts,
/// or different service instances in hosted scenarios.
///
///
/// Trust model. The conversationId passed to
/// and typically originates
/// from the wire (for example, an AG-UI RunAgentInput.ThreadId or an A2A
/// contextId). It is a chain-resume identifier, not an authorization
/// token, and the (agent, conversationId) tuple carries no principal/owner
/// dimension. Hosts that serve more than one user from the same registered store must
/// therefore compose a principal dimension into the lookup key, otherwise any caller
/// who knows or guesses another caller's conversationId can resume
/// that other caller's persisted thread. The framework provides
/// as a decorator that rewrites
/// conversationId to include an isolation key resolved from a
/// (for example, the ASP.NET Core
/// ClaimsIdentitySessionIsolationKeyProvider wired up via
/// UseClaimsBasedSessionIsolation(...)). When no provider is registered, the
/// store behaves as a single-namespace persistence layer — appropriate for
/// single-user / first-run / prototyping scenarios but unsafe for multi-user hosts.
///
///
/// Implementer guidance. Implementations should treat
/// conversationId as opaque: do not parse it, do not impose length
/// or character-set constraints on it, and do not assume it round-trips to the value
/// the caller originally supplied (decorators such as
/// may rewrite it before forwarding).
/// Be aware that any logging, telemetry, or audit sink that surfaces
/// conversationId will also surface the isolation prefix when a
/// scoping decorator is in the chain.
///
///
public abstract class AgentSessionStore
{
///
/// Saves a serialized agent session to persistent storage.
///
/// The agent that owns this session.
/// The unique identifier for the conversation/session.
/// The session to save.
/// The to monitor for cancellation requests.
/// A task that represents the asynchronous save operation.
public abstract ValueTask SaveSessionAsync(
AIAgent agent,
string conversationId,
AgentSession session,
CancellationToken cancellationToken = default);
///
/// Retrieves a serialized agent session from persistent storage.
///
/// The agent that owns this session.
/// The unique identifier for the conversation/session to retrieve.
/// The to monitor for cancellation requests.
///
/// A task that represents the asynchronous retrieval operation.
/// The task result contains the serialized session state, or if not found.
///
public abstract ValueTask GetSessionAsync(
AIAgent agent,
string conversationId,
CancellationToken cancellationToken = default);
/// Asks the for an object of the specified type .
/// The type of object being requested.
/// An optional key that can be used to help identify the target service.
/// The found object, otherwise .
/// is .
///
/// The purpose of this method is to allow for the retrieval of strongly-typed services that might be provided by the ,
/// including itself or any services it might be wrapping. This is particularly useful for inspecting delegation chains
/// to verify that specific store implementations are present.
///
public virtual object? GetService(Type serviceType, object? serviceKey = null)
{
_ = Throw.IfNull(serviceType);
return serviceKey is null && serviceType.IsInstanceOfType(this)
? this
: null;
}
/// Asks the for an object of type .
/// The type of the object to be retrieved.
/// An optional key that can be used to help identify the target service.
/// The found object, otherwise .
///
/// The purpose of this method is to allow for the retrieval of strongly typed services that may be provided by the ,
/// including itself or any services it might be wrapping. This is particularly useful for inspecting delegation chains
/// to verify that specific store implementations are present.
///
public TService? GetService(object? serviceKey = null)
=> this.GetService(typeof(TService), serviceKey) is TService service ? service : default;
}