Add async API key cleanup for removed providers

This commit is contained in:
Thorsten Sommer 2026-02-07 22:20:25 +01:00
parent 492c0184ec
commit 2805af0da3
Signed by untrusted user who does not match committer: tsommer
GPG Key ID: 371BBA77A02C0108
2 changed files with 26 additions and 10 deletions

View File

@ -2,6 +2,7 @@ using System.Linq.Expressions;
using AIStudio.Settings;
using AIStudio.Settings.DataModel;
using AIStudio.Tools.Services;
using Lua;
@ -13,6 +14,7 @@ namespace AIStudio.Tools.PluginSystem;
/// </summary>
public sealed record PluginConfigurationObject
{
private static readonly RustService RUST_SERVICE = Program.SERVICE_PROVIDER.GetRequiredService<RustService>();
private static readonly SettingsManager SETTINGS_MANAGER = Program.SERVICE_PROVIDER.GetRequiredService<SettingsManager>();
private static readonly ILogger LOG = Program.LOGGER_FACTORY.CreateLogger<PluginConfigurationObject>();
@ -168,12 +170,14 @@ public sealed record PluginConfigurationObject
/// <param name="configObjectSelection">A selection expression to retrieve the configuration objects from the main configuration.</param>
/// <param name="availablePlugins">A list of currently available plugins.</param>
/// <param name="configObjectList">A list of all existing configuration objects.</param>
/// <param name="secretStoreType">An optional parameter specifying the type of secret store to use for deleting associated API keys from the OS keyring, if applicable.</param>
/// <returns>Returns true if the configuration was altered during cleanup; otherwise, false.</returns>
public static bool CleanLeftOverConfigurationObjects<TClass>(
public static async Task<bool> CleanLeftOverConfigurationObjects<TClass>(
PluginConfigurationObjectType configObjectType,
Expression<Func<Data, List<TClass>>> configObjectSelection,
IList<IAvailablePlugin> availablePlugins,
IList<PluginConfigurationObject> configObjectList) where TClass : IConfigurationObject
IList<PluginConfigurationObject> configObjectList,
SecretStoreType? secretStoreType = null) where TClass : IConfigurationObject
{
var configuredObjects = configObjectSelection.Compile()(SETTINGS_MANAGER.ConfigurationData);
var leftOverObjects = new List<TClass>();
@ -206,8 +210,20 @@ public sealed record PluginConfigurationObject
// Remove collected items after enumeration to avoid modifying the collection during iteration:
var wasConfigurationChanged = leftOverObjects.Count > 0;
foreach (var item in leftOverObjects.Distinct())
{
configuredObjects.Remove(item);
// Delete the API key from the OS keyring if the removed object has one:
if(secretStoreType is not null && item is ISecretId secretId)
{
var deleteResult = await RUST_SERVICE.DeleteAPIKey(secretId, secretStoreType.Value);
if (deleteResult.Success)
LOG.LogInformation($"Successfully deleted API key for removed enterprise provider '{item.Name}' from the OS keyring.");
else
LOG.LogWarning($"Failed to delete API key for removed enterprise provider '{item.Name}' from the OS keyring: {deleteResult.Issue}");
}
}
return wasConfigurationChanged;
}
}

View File

@ -131,26 +131,26 @@ public static partial class PluginFactory
//
// Check LLM providers:
var wasConfigurationChanged = PluginConfigurationObject.CleanLeftOverConfigurationObjects(PluginConfigurationObjectType.LLM_PROVIDER, x => x.Providers, AVAILABLE_PLUGINS, configObjectList);
var wasConfigurationChanged = await PluginConfigurationObject.CleanLeftOverConfigurationObjects(PluginConfigurationObjectType.LLM_PROVIDER, x => x.Providers, AVAILABLE_PLUGINS, configObjectList, SecretStoreType.LLM_PROVIDER);
// Check transcription providers:
if(PluginConfigurationObject.CleanLeftOverConfigurationObjects(PluginConfigurationObjectType.TRANSCRIPTION_PROVIDER, x => x.TranscriptionProviders, AVAILABLE_PLUGINS, configObjectList))
if(await PluginConfigurationObject.CleanLeftOverConfigurationObjects(PluginConfigurationObjectType.TRANSCRIPTION_PROVIDER, x => x.TranscriptionProviders, AVAILABLE_PLUGINS, configObjectList, SecretStoreType.TRANSCRIPTION_PROVIDER))
wasConfigurationChanged = true;
// Check embedding providers:
if(PluginConfigurationObject.CleanLeftOverConfigurationObjects(PluginConfigurationObjectType.EMBEDDING_PROVIDER, x => x.EmbeddingProviders, AVAILABLE_PLUGINS, configObjectList))
if(await PluginConfigurationObject.CleanLeftOverConfigurationObjects(PluginConfigurationObjectType.EMBEDDING_PROVIDER, x => x.EmbeddingProviders, AVAILABLE_PLUGINS, configObjectList, SecretStoreType.EMBEDDING_PROVIDER))
wasConfigurationChanged = true;
// Check chat templates:
if(PluginConfigurationObject.CleanLeftOverConfigurationObjects(PluginConfigurationObjectType.CHAT_TEMPLATE, x => x.ChatTemplates, AVAILABLE_PLUGINS, configObjectList))
if(await PluginConfigurationObject.CleanLeftOverConfigurationObjects(PluginConfigurationObjectType.CHAT_TEMPLATE, x => x.ChatTemplates, AVAILABLE_PLUGINS, configObjectList))
wasConfigurationChanged = true;
// Check profiles:
if(PluginConfigurationObject.CleanLeftOverConfigurationObjects(PluginConfigurationObjectType.PROFILE, x => x.Profiles, AVAILABLE_PLUGINS, configObjectList))
if(await PluginConfigurationObject.CleanLeftOverConfigurationObjects(PluginConfigurationObjectType.PROFILE, x => x.Profiles, AVAILABLE_PLUGINS, configObjectList))
wasConfigurationChanged = true;
// Check document analysis policies:
if(PluginConfigurationObject.CleanLeftOverConfigurationObjects(PluginConfigurationObjectType.DOCUMENT_ANALYSIS_POLICY, x => x.DocumentAnalysis.Policies, AVAILABLE_PLUGINS, configObjectList))
if(await PluginConfigurationObject.CleanLeftOverConfigurationObjects(PluginConfigurationObjectType.DOCUMENT_ANALYSIS_POLICY, x => x.DocumentAnalysis.Policies, AVAILABLE_PLUGINS, configObjectList))
wasConfigurationChanged = true;
// Check for a preselected profile: