diff --git a/src/Primitives/CrestApps.Core.AI.OpenAI.Azure/Handlers/AzureOpenAIConnectionHandler.cs b/src/Primitives/CrestApps.Core.AI.OpenAI.Azure/Handlers/AzureOpenAIConnectionHandler.cs new file mode 100644 index 00000000..d1092982 --- /dev/null +++ b/src/Primitives/CrestApps.Core.AI.OpenAI.Azure/Handlers/AzureOpenAIConnectionHandler.cs @@ -0,0 +1,39 @@ +using CrestApps.Core.AI.Handlers; +using CrestApps.Core.AI.Models; +using CrestApps.Core.Azure.Models; +using Microsoft.AspNetCore.DataProtection; + +namespace CrestApps.Core.AI.OpenAI.Azure.Handlers; + +internal sealed class AzureOpenAIConnectionHandler : AIProviderConnectionHandlerBase +{ + private const string ConnectionProtectorName = "AIProviderConnection"; + + private readonly IDataProtectionProvider _dataProtectionProvider; + + public AzureOpenAIConnectionHandler(IDataProtectionProvider dataProtectionProvider) + { + _dataProtectionProvider = dataProtectionProvider; + } + + public override void Initializing(InitializingAIProviderConnectionContext context) + { + if (!string.Equals(context.Connection.ClientName, AzureOpenAIConstants.ClientName, StringComparison.Ordinal)) + { + return; + } + + var metadata = context.Connection.GetOrCreate(); + + context.Values["Endpoint"] = metadata.Endpoint?.ToString(); + context.Values["AuthenticationType"] = metadata.AuthenticationType.ToString(); + context.Values["IdentityId"] = metadata.IdentityId; + + if (!string.IsNullOrEmpty(metadata.ApiKey)) + { + var protector = _dataProtectionProvider.CreateProtector(ConnectionProtectorName); + + context.Values["ApiKey"] = protector.Unprotect(metadata.ApiKey); + } + } +} diff --git a/src/Primitives/CrestApps.Core.AI.OpenAI.Azure/ServiceCollectionExtensions.cs b/src/Primitives/CrestApps.Core.AI.OpenAI.Azure/ServiceCollectionExtensions.cs index f1e37ac8..e1018061 100644 --- a/src/Primitives/CrestApps.Core.AI.OpenAI.Azure/ServiceCollectionExtensions.cs +++ b/src/Primitives/CrestApps.Core.AI.OpenAI.Azure/ServiceCollectionExtensions.cs @@ -1,4 +1,6 @@ using CrestApps.Core.AI.Clients; +using CrestApps.Core.AI.Models; +using CrestApps.Core.AI.OpenAI.Azure.Handlers; using CrestApps.Core.AI.OpenAI.Azure.Models; using CrestApps.Core.AI.OpenAI.Azure.Services; using CrestApps.Core.Builders; @@ -22,6 +24,7 @@ public static IServiceCollection AddCoreAIAzureOpenAI(this IServiceCollection se services.TryAddEnumerable(ServiceDescriptor.Singleton, AzureClientOptionsConfiguration>()); services.TryAddEnumerable(ServiceDescriptor.Scoped()); services.TryAddEnumerable(ServiceDescriptor.Scoped()); + services.TryAddEnumerable(ServiceDescriptor.Scoped()); services.AddCoreAIProfile(AzureOpenAIConstants.ClientName, o => { diff --git a/src/Primitives/CrestApps.Core.AI.OpenAI/Handlers/OpenAIConnectionHandler.cs b/src/Primitives/CrestApps.Core.AI.OpenAI/Handlers/OpenAIConnectionHandler.cs new file mode 100644 index 00000000..f2b79253 --- /dev/null +++ b/src/Primitives/CrestApps.Core.AI.OpenAI/Handlers/OpenAIConnectionHandler.cs @@ -0,0 +1,37 @@ +using CrestApps.Core.AI.Handlers; +using CrestApps.Core.AI.Models; +using CrestApps.Core.AI.OpenAI.Models; +using Microsoft.AspNetCore.DataProtection; + +namespace CrestApps.Core.AI.OpenAI.Handlers; + +internal sealed class OpenAIConnectionHandler : AIProviderConnectionHandlerBase +{ + private const string ConnectionProtectorName = "AIProviderConnection"; + + private readonly IDataProtectionProvider _dataProtectionProvider; + + public OpenAIConnectionHandler(IDataProtectionProvider dataProtectionProvider) + { + _dataProtectionProvider = dataProtectionProvider; + } + + public override void Initializing(InitializingAIProviderConnectionContext context) + { + if (!string.Equals(context.Connection.ClientName, OpenAIConstants.ClientName, StringComparison.Ordinal)) + { + return; + } + + var metadata = context.Connection.GetOrCreate(); + + if (!string.IsNullOrEmpty(metadata.ApiKey)) + { + var protector = _dataProtectionProvider.CreateProtector(ConnectionProtectorName); + + context.Values["ApiKey"] = protector.Unprotect(metadata.ApiKey); + } + + context.Values["Endpoint"] = metadata.Endpoint?.ToString(); + } +} diff --git a/src/Primitives/CrestApps.Core.AI.OpenAI/ServiceCollectionExtensions.cs b/src/Primitives/CrestApps.Core.AI.OpenAI/ServiceCollectionExtensions.cs index 05c42b46..30e1b843 100644 --- a/src/Primitives/CrestApps.Core.AI.OpenAI/ServiceCollectionExtensions.cs +++ b/src/Primitives/CrestApps.Core.AI.OpenAI/ServiceCollectionExtensions.cs @@ -1,4 +1,6 @@ using CrestApps.Core.AI.Clients; +using CrestApps.Core.AI.Models; +using CrestApps.Core.AI.OpenAI.Handlers; using CrestApps.Core.AI.OpenAI.Services; using CrestApps.Core.Builders; using Microsoft.Extensions.DependencyInjection; @@ -17,6 +19,7 @@ public static IServiceCollection AddCoreAIOpenAI(this IServiceCollection service ArgumentNullException.ThrowIfNull(services); services.TryAddEnumerable(ServiceDescriptor.Scoped()); + services.TryAddEnumerable(ServiceDescriptor.Scoped()); services.AddCoreAIProfile(OpenAIConstants.ClientName, o => { diff --git a/tests/CrestApps.Core.Tests/Framework/Mvc/AIProviderConnectionOptionsTests.cs b/tests/CrestApps.Core.Tests/Framework/Mvc/AIProviderConnectionOptionsTests.cs index 2fb0a81b..afc8e9b7 100644 --- a/tests/CrestApps.Core.Tests/Framework/Mvc/AIProviderConnectionOptionsTests.cs +++ b/tests/CrestApps.Core.Tests/Framework/Mvc/AIProviderConnectionOptionsTests.cs @@ -1,6 +1,7 @@ using CrestApps.Core.AI; using CrestApps.Core.AI.Deployments; using CrestApps.Core.AI.Models; +using CrestApps.Core.AI.OpenAI; using CrestApps.Core.AI.OpenAI.Azure; using CrestApps.Core.AI.OpenAI.Azure.Models; using CrestApps.Core.AI.Services; @@ -71,6 +72,36 @@ public void AddCoreAIAzureOpenAI_WhenAzureClientSettingsConfigured_ShouldBindAzu Assert.False(options.EnableMessageContentLogging); } + [Fact] + public void AddCoreAIAzureOpenAI_ShouldRegisterAzureConnectionHandler() + { + var services = new ServiceCollection(); + services.AddLogging(); + services.AddDataProtection(); + services.AddCoreAIServices(); + services.AddCoreAIAzureOpenAI(); + using var serviceProvider = services.BuildServiceProvider(); + + var handlers = serviceProvider.GetServices().ToArray(); + + Assert.Contains(handlers, handler => handler.GetType().FullName == "CrestApps.Core.AI.OpenAI.Azure.Handlers.AzureOpenAIConnectionHandler"); + } + + [Fact] + public void AddCoreAIOpenAI_ShouldRegisterOpenAIConnectionHandler() + { + var services = new ServiceCollection(); + services.AddLogging(); + services.AddDataProtection(); + services.AddCoreAIServices(); + services.AddCoreAIOpenAI(); + using var serviceProvider = services.BuildServiceProvider(); + + var handlers = serviceProvider.GetServices().ToArray(); + + Assert.Contains(handlers, handler => handler.GetType().FullName == "CrestApps.Core.AI.OpenAI.Handlers.OpenAIConnectionHandler"); + } + [Fact] public void AddCrestAppsAI_WhenClientNameIsMissing_ShouldIgnoreConnectionEntry() {