diff --git a/dotnet/src/Microsoft.Agents.AI.Hosting.A2A.AspNetCore/Microsoft.Agents.AI.Hosting.A2A.AspNetCore.csproj b/dotnet/src/Microsoft.Agents.AI.Hosting.A2A.AspNetCore/Microsoft.Agents.AI.Hosting.A2A.AspNetCore.csproj index 200aa29ccc..b91ea40baa 100644 --- a/dotnet/src/Microsoft.Agents.AI.Hosting.A2A.AspNetCore/Microsoft.Agents.AI.Hosting.A2A.AspNetCore.csproj +++ b/dotnet/src/Microsoft.Agents.AI.Hosting.A2A.AspNetCore/Microsoft.Agents.AI.Hosting.A2A.AspNetCore.csproj @@ -27,6 +27,7 @@ + diff --git a/dotnet/src/Microsoft.Agents.AI.Hosting.AspNetCore/ServiceCollectionExtensions.cs b/dotnet/src/Microsoft.Agents.AI.Hosting.AspNetCore/ServiceCollectionExtensions.cs new file mode 100644 index 0000000000..1ba2a16db8 --- /dev/null +++ b/dotnet/src/Microsoft.Agents.AI.Hosting.AspNetCore/ServiceCollectionExtensions.cs @@ -0,0 +1,42 @@ +// Copyright (c) Microsoft. All rights reserved. + +using System; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.DependencyInjection; + +namespace Microsoft.Agents.AI.Hosting; + +/// +/// Extension methods for configuring AI hosting services in an . +/// +public static class ServiceCollectionExtensions +{ + /// + /// Registers a that uses claims from the current user's identity + /// to generate session isolation keys. + /// + /// The to add services to. + /// Optional configuration for the claims-based session isolation key provider. + /// The so that additional calls can be chained. + /// + /// This method requires to be registered in the service collection. + /// Ensure that services.AddHttpContextAccessor() has been called before using this method. + /// + public static IServiceCollection UseClaimsBasedSessionIsolation( + this IServiceCollection services, + ClaimsIdentitySessionIsolationKeyProviderOptions? options = null) + { + options ??= new(); + ServiceDescriptor descriptor = new(typeof(SessionIsolationKeyProvider), CreateIsolationKeyProvider, ServiceLifetime.Scoped); + services.Add(descriptor); + + return services; + + object CreateIsolationKeyProvider(IServiceProvider serviceProvider) + { + IHttpContextAccessor contextAccessor = serviceProvider.GetRequiredService(); + + return new ClaimsIdentitySessionIsolationKeyProvider(contextAccessor, options); + } + } +} diff --git a/dotnet/src/Microsoft.Agents.AI.Hosting/HostedAgentBuilderExtensions.cs b/dotnet/src/Microsoft.Agents.AI.Hosting/HostedAgentBuilderExtensions.cs index d1397fcda4..ed11840f5e 100644 --- a/dotnet/src/Microsoft.Agents.AI.Hosting/HostedAgentBuilderExtensions.cs +++ b/dotnet/src/Microsoft.Agents.AI.Hosting/HostedAgentBuilderExtensions.cs @@ -3,6 +3,7 @@ using System; using Microsoft.Extensions.AI; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; using Microsoft.Shared.Diagnostics; namespace Microsoft.Agents.AI.Hosting; @@ -16,12 +17,11 @@ public static class HostedAgentBuilderExtensions /// Configures the host agent builder to use an in-memory session store for agent session management. /// /// The host agent builder to configure with the in-memory session store. + /// When , wraps the session store with an + /// to provide isolation-key-based scoping for sessions. Defaults to . /// The same instance, configured to use an in-memory session store. - public static IHostedAgentBuilder WithInMemorySessionStore(this IHostedAgentBuilder builder) - { - builder.ServiceCollection.AddKeyedSingleton(builder.Name, new InMemoryAgentSessionStore()); - return builder; - } + public static IHostedAgentBuilder WithInMemorySessionStore(this IHostedAgentBuilder builder, bool withIsolation = true) + => builder.WithSessionStore(new InMemoryAgentSessionStore(), withIsolation); /// /// Registers the specified agent session store with the host agent builder, enabling session-specific storage for @@ -29,12 +29,11 @@ public static class HostedAgentBuilderExtensions /// /// The host agent builder to configure with the session store. Cannot be null. /// The agent session store instance to register. Cannot be null. + /// When , wraps the session store with an + /// to provide isolation-key-based scoping for sessions. Defaults to . /// The same host agent builder instance, allowing for method chaining. - public static IHostedAgentBuilder WithSessionStore(this IHostedAgentBuilder builder, AgentSessionStore store) - { - builder.ServiceCollection.AddKeyedSingleton(builder.Name, store); - return builder; - } + public static IHostedAgentBuilder WithSessionStore(this IHostedAgentBuilder builder, AgentSessionStore store, bool withIsolation = true) + => builder.WithSessionStore((sp, key) => store, ServiceLifetime.Singleton, withIsolation); /// /// Configures the host agent builder to use a custom session store implementation for agent sessions. @@ -44,16 +43,36 @@ public static class HostedAgentBuilderExtensions /// name. /// The DI service lifetime for the session store registration. Defaults to /// because session stores persist conversation state across requests and are consumed independently of the agent's lifetime. + /// When , wraps the session store with an + /// to provide isolation-key-based scoping for sessions. Defaults to . /// The same host agent builder instance, enabling further configuration. - public static IHostedAgentBuilder WithSessionStore(this IHostedAgentBuilder builder, Func createAgentSessionStore, ServiceLifetime lifetime = ServiceLifetime.Singleton) + public static IHostedAgentBuilder WithSessionStore(this IHostedAgentBuilder builder, Func createAgentSessionStore, ServiceLifetime lifetime = ServiceLifetime.Singleton, bool withIsolation = true) { builder.ServiceCollection.AddKeyedService(builder.Name, (sp, key) => { Throw.IfNull(key); var keyString = key as string; Throw.IfNullOrEmpty(keyString); - return createAgentSessionStore(sp, keyString) ?? + + AgentSessionStore store = createAgentSessionStore(sp, keyString) ?? throw new InvalidOperationException($"The agent session store factory did not return a valid {nameof(AgentSessionStore)} instance for key '{keyString}'."); + + if (withIsolation && store.GetService() is null) + { + var isolationKeyProvider = sp.GetService(); + + // Best efforts options getting + IsolationKeyScopedAgentSessionStoreOptions? options = sp.GetService(); + if (options is null) + { + var optionsProvider = sp.GetService>(); + options = optionsProvider?.Value; + } + + store = new IsolationKeyScopedAgentSessionStore(store, isolationKeyProvider, options ?? new()); + } + + return store; }, lifetime); return builder; }