Skip to content
Introducing AI Conversations: Natural Language Interaction for Your Apps! Learn More

Message Store

The IMessageStore interface provides optional chat history persistence. When configured, every user message and AI response is automatically stored.

public interface IMessageStore
{
Task Store(AiChatMessage chatMessage, CancellationToken cancellationToken);
Task Clear(DateTimeOffset? beforeDate = null);
Task<IReadOnlyList<AiChatMessage>> Query(
string? messageContains = null,
DateTimeOffset? fromDate = null,
DateTimeOffset? toDate = null,
int? limit = null,
CancellationToken cancellationToken = default
);
}
builder.Services.AddShinyAiConversation(opts =>
{
opts.SetChatClientProvider<MyChatClientProvider>();
opts.SetMessageStore<MyMessageStore>(addAiLookupTool: true);
});

The addAiLookupTool parameter (default true) registers a ChatLookupAITool that allows the AI to search its own conversation history when answering questions like “What did we talk about yesterday?”

public record AiChatMessage(
string Id,
string Message,
DateTimeOffset Timestamp,
ChatMessageDirection Direction // User or AI
);

The service exposes history through IAiConversationService:

// Get recent messages
var recent = await aiService.GetChatHistory(limit: 50);
// Search by content
var results = await aiService.GetChatHistory(messageContains: "weather");
// Filter by date range
var today = await aiService.GetChatHistory(
startDate: DateTimeOffset.Now.Date,
endDate: DateTimeOffset.Now
);
// Clear all history
await aiService.ClearChatHistory();
// Clear history older than 30 days
await aiService.ClearChatHistory(beforeDate: DateTimeOffset.Now.AddDays(-30));
public class InMemoryMessageStore : IMessageStore
{
readonly List<AiChatMessage> messages = [];
readonly object sync = new();
public Task Store(AiChatMessage chatMessage, CancellationToken cancellationToken)
{
lock (sync)
messages.Add(chatMessage);
return Task.CompletedTask;
}
public Task Clear(DateTimeOffset? beforeDate = null)
{
lock (sync)
{
if (beforeDate.HasValue)
messages.RemoveAll(m => m.Timestamp <= beforeDate.Value);
else
messages.Clear();
}
return Task.CompletedTask;
}
public Task<IReadOnlyList<AiChatMessage>> Query(
string? messageContains = null,
DateTimeOffset? fromDate = null,
DateTimeOffset? toDate = null,
int? limit = null,
CancellationToken cancellationToken = default)
{
lock (sync)
{
IEnumerable<AiChatMessage> query = messages.OrderBy(m => m.Timestamp);
if (!String.IsNullOrWhiteSpace(messageContains))
query = query.Where(m => m.Message.Contains(messageContains, StringComparison.OrdinalIgnoreCase));
if (fromDate.HasValue)
query = query.Where(m => m.Timestamp >= fromDate.Value);
if (toDate.HasValue)
query = query.Where(m => m.Timestamp <= toDate.Value);
if (limit.HasValue)
query = query.Take(limit.Value);
return Task.FromResult<IReadOnlyList<AiChatMessage>>(query.ToList().AsReadOnly());
}
}
}

The MAUI sample uses Shiny.DocumentDb for SQLite-backed persistence:

public class DocumentDbMessageStore(IDocumentStore store) : IMessageStore
{
public Task Store(AiChatMessage chatMessage, CancellationToken cancellationToken)
=> store.Insert(chatMessage, cancellationToken: cancellationToken);
public Task Clear(DateTimeOffset? beforeDate = null)
{
var query = store.Query<AiChatMessage>();
if (beforeDate.HasValue)
query = query.Where(x => x.Timestamp <= beforeDate.Value);
return query.ExecuteDelete();
}
public Task<IReadOnlyList<AiChatMessage>> Query(
string? messageContains = null,
DateTimeOffset? fromDate = null,
DateTimeOffset? toDate = null,
int? limit = null,
CancellationToken cancellationToken = default)
{
var query = store.Query<AiChatMessage>().OrderBy(x => x.Timestamp);
if (!String.IsNullOrWhiteSpace(messageContains))
query = query.Where(x => x.Message.Contains(messageContains));
if (fromDate.HasValue)
query = query.Where(x => x.Timestamp >= fromDate.Value);
if (toDate.HasValue)
query = query.Where(x => x.Timestamp <= toDate.Value);
if (limit.HasValue)
query = query.Paginate(0, limit.Value);
return query.ToList(cancellationToken);
}
}

You could also implement IMessageStore with Entity Framework Core, Cosmos DB, or any other storage backend.