// Copyright (c) Microsoft. All rights reserved. using System; using System.Diagnostics.CodeAnalysis; using Microsoft.Shared.DiagnosticIds; namespace Microsoft.Agents.AI.Foundry.Hosting; /// /// Built-in stateInitializer factories that derive the /// from the per-session /// applied by the Foundry hosting layer. /// /// /// Pass the result of any of these helpers as the stateInitializer argument when constructing /// : /// /// new FoundryMemoryProvider(client, "my-store", /// stateInitializer: HostedFoundryMemoryProviderScopes.PerUser()); /// /// All helpers throw when /// returns . /// That happens when the agent runs outside the Foundry hosting layer (e.g., a console app); in /// that case write a custom stateInitializer instead of using these helpers. /// [Experimental(DiagnosticIds.Experiments.AIOpenAIResponses)] public static class HostedFoundryMemoryProviderScopes { /// /// Returns a stateInitializer that scopes memories per end user, using /// as the partition key. /// /// A delegate suitable for the stateInitializer argument of . public static Func PerUser() => session => new FoundryMemoryProvider.State(new FoundryMemoryProviderScope(GetRequiredHostedContext(session).UserId)); /// /// Returns a stateInitializer that scopes memories per conversation, using /// as the partition key. Use this when memories should /// be visible to every participant in a shared conversation (for example, a Teams group chat). /// /// A delegate suitable for the stateInitializer argument of . public static Func PerChat() => session => new FoundryMemoryProvider.State(new FoundryMemoryProviderScope(GetRequiredHostedContext(session).ChatId)); /// /// Returns a stateInitializer that scopes memories per (user, chat) pair, composing /// and into a /// single delimiter-safe partition key. Use this when memories should be visible only to the same /// user within the same conversation. /// /// /// Both identity values are opaque strings that may contain any characters, including the : /// delimiter. To keep the composite key injective (so two distinct (user, chat) pairs can never /// collide), each part is escaped (\ becomes \\, then : becomes \:) before /// being joined with a :: separator. /// /// A delegate suitable for the stateInitializer argument of . public static Func PerUserAndChat() => session => { var ctx = GetRequiredHostedContext(session); return new FoundryMemoryProvider.State( new FoundryMemoryProviderScope($"{EscapeScopePart(ctx.UserId)}::{EscapeScopePart(ctx.ChatId)}")); }; /// /// Escapes special characters in a scope part so that distinct (user, chat) pairs produce distinct /// composite scope keys. Backslashes are escaped first (\ becomes \\), then colons /// (: becomes \:), ensuring the {user}::{chat} format is unambiguous. /// private static string EscapeScopePart(string part) => part.Replace("\\", "\\\\").Replace(":", "\\:"); private static HostedSessionContext GetRequiredHostedContext(AgentSession? session) => session?.GetHostedContext() ?? throw new InvalidOperationException( $"{nameof(HostedSessionContext)} was not provided by the hosting layer. " + $"The {nameof(HostedFoundryMemoryProviderScopes)} helpers require the agent to be hosted via the Foundry hosting layer. " + "If running outside a hosted Foundry container, supply a custom stateInitializer to FoundryMemoryProvider instead."); }