From 18828fbe854e1cadb4f117653ce2b1a46ef02369 Mon Sep 17 00:00:00 2001 From: Thorsten Sommer Date: Mon, 18 Aug 2025 20:34:04 +0200 Subject: [PATCH] Add configuration object management for providers and chat templates --- .../Tools/PluginSystem/PluginConfiguration.cs | 105 +++++++++++------- .../PluginSystem/PluginConfigurationObject.cs | 23 ++++ .../PluginConfigurationObjectType.cs | 13 +++ .../PluginSystem/PluginFactory.Loading.cs | 53 ++++----- .../PluginSystem/PluginFactory.Starting.cs | 9 +- 5 files changed, 131 insertions(+), 72 deletions(-) create mode 100644 app/MindWork AI Studio/Tools/PluginSystem/PluginConfigurationObject.cs create mode 100644 app/MindWork AI Studio/Tools/PluginSystem/PluginConfigurationObjectType.cs diff --git a/app/MindWork AI Studio/Tools/PluginSystem/PluginConfiguration.cs b/app/MindWork AI Studio/Tools/PluginSystem/PluginConfiguration.cs index 70799c56..9e309b10 100644 --- a/app/MindWork AI Studio/Tools/PluginSystem/PluginConfiguration.cs +++ b/app/MindWork AI Studio/Tools/PluginSystem/PluginConfiguration.cs @@ -14,7 +14,14 @@ public sealed class PluginConfiguration(bool isInternal, LuaState state, PluginT private static string TB(string fallbackEN) => I18N.I.T(fallbackEN, typeof(PluginConfiguration).Namespace, nameof(PluginConfiguration)); private static readonly ILogger LOGGER = Program.LOGGER_FACTORY.CreateLogger(); private static readonly SettingsManager SETTINGS_MANAGER = Program.SERVICE_PROVIDER.GetRequiredService(); - + + private readonly List configObjects = []; + + /// + /// The list of configuration objects. Configuration objects are, e.g., providers or chat templates. + /// + public IEnumerable ConfigObjects => this.configObjects; + public async Task InitializeAsync(bool dryRun) { if(!this.TryProcessConfiguration(dryRun, out var issue)) @@ -30,11 +37,13 @@ public sealed class PluginConfiguration(bool isInternal, LuaState state, PluginT /// /// Tries to initialize the UI text content of the plugin. /// - /// When true, the method will not apply any changes, but only check if the configuration can be read. + /// When true, the method will not apply any changes but only check if the configuration can be read. /// The error message, when the UI text content could not be read. /// True, when the UI text content could be read successfully. private bool TryProcessConfiguration(bool dryRun, out string message) { + this.configObjects.Clear(); + // Ensure that the main CONFIG table exists and is a valid Lua table: if (!this.state.Environment["CONFIG"].TryRead(out var mainTable)) { @@ -90,29 +99,38 @@ public sealed class PluginConfiguration(bool isInternal, LuaState state, PluginT // Apply the configured providers to the system settings: // #pragma warning disable MWAIS0001 - if (!dryRun) + foreach (var configuredProvider in configuredProviders) { - foreach (var configuredProvider in configuredProviders) - { - // The iterating variable is immutable, so we need to create a local copy: - var provider = configuredProvider; + // The iterating variable is immutable, so we need to create a local copy: + var provider = configuredProvider; - var providerIndex = SETTINGS_MANAGER.ConfigurationData.Providers.FindIndex(p => p.Id == provider.Id); - if (providerIndex > -1) - { - // Case: The provider already exists, we update it: - var existingProvider = SETTINGS_MANAGER.ConfigurationData.Providers[providerIndex]; - provider = provider with { Num = existingProvider.Num }; // Keep the original number - SETTINGS_MANAGER.ConfigurationData.Providers[providerIndex] = provider; - } - else - { - // Case: The provider does not exist, we add it: - provider = provider with { Num = SETTINGS_MANAGER.ConfigurationData.NextProviderNum++ }; - SETTINGS_MANAGER.ConfigurationData.Providers.Add(provider); - } + // Store this provider in the config object list: + this.configObjects.Add(new() + { + ConfigPluginId = this.Id, + Id = Guid.Parse(provider.Id), + Type = PluginConfigurationObjectType.LLM_PROVIDER, + }); + + if (dryRun) + continue; + + var providerIndex = SETTINGS_MANAGER.ConfigurationData.Providers.FindIndex(p => p.Id == provider.Id); + if (providerIndex > -1) + { + // Case: The provider already exists, we update it: + var existingProvider = SETTINGS_MANAGER.ConfigurationData.Providers[providerIndex]; + provider = provider with { Num = existingProvider.Num }; // Keep the original number + SETTINGS_MANAGER.ConfigurationData.Providers[providerIndex] = provider; + } + else + { + // Case: The provider does not exist, we add it: + provider = provider with { Num = SETTINGS_MANAGER.ConfigurationData.NextProviderNum++ }; + SETTINGS_MANAGER.ConfigurationData.Providers.Add(provider); } } + #pragma warning restore MWAIS0001 // @@ -138,26 +156,35 @@ public sealed class PluginConfiguration(bool isInternal, LuaState state, PluginT } // Apply configured chat templates to the system settings: - if (!dryRun) + foreach (var configuredTemplate in configuredTemplates) { - foreach (var configuredTemplate in configuredTemplates) + // The iterating variable is immutable, so we need to create a local copy: + var template = configuredTemplate; + + // Store this provider in the config object list: + this.configObjects.Add(new() { - // The iterating variable is immutable, so we need to create a local copy: - var template = configuredTemplate; - var tplIndex = SETTINGS_MANAGER.ConfigurationData.ChatTemplates.FindIndex(t => t.Id == template.Id); - if (tplIndex > -1) - { - // Case: The template already exists, we update it: - var existingTemplate = SETTINGS_MANAGER.ConfigurationData.ChatTemplates[tplIndex]; - template = template with { Num = existingTemplate.Num }; - SETTINGS_MANAGER.ConfigurationData.ChatTemplates[tplIndex] = template; - } - else - { - // Case: The template does not exist, we add it: - template = template with { Num = SETTINGS_MANAGER.ConfigurationData.NextChatTemplateNum++ }; - SETTINGS_MANAGER.ConfigurationData.ChatTemplates.Add(template); - } + ConfigPluginId = this.Id, + Id = Guid.Parse(template.Id), + Type = PluginConfigurationObjectType.CHAT_TEMPLATE, + }); + + if (dryRun) + continue; + + var tplIndex = SETTINGS_MANAGER.ConfigurationData.ChatTemplates.FindIndex(t => t.Id == template.Id); + if (tplIndex > -1) + { + // Case: The template already exists, we update it: + var existingTemplate = SETTINGS_MANAGER.ConfigurationData.ChatTemplates[tplIndex]; + template = template with { Num = existingTemplate.Num }; + SETTINGS_MANAGER.ConfigurationData.ChatTemplates[tplIndex] = template; + } + else + { + // Case: The template does not exist, we add it: + template = template with { Num = SETTINGS_MANAGER.ConfigurationData.NextChatTemplateNum++ }; + SETTINGS_MANAGER.ConfigurationData.ChatTemplates.Add(template); } } } diff --git a/app/MindWork AI Studio/Tools/PluginSystem/PluginConfigurationObject.cs b/app/MindWork AI Studio/Tools/PluginSystem/PluginConfigurationObject.cs new file mode 100644 index 00000000..258e6c3c --- /dev/null +++ b/app/MindWork AI Studio/Tools/PluginSystem/PluginConfigurationObject.cs @@ -0,0 +1,23 @@ +namespace AIStudio.Tools.PluginSystem; + +/// +/// Represents metadata for a configuration object from a configuration plugin. These are +/// complex objects such as configured LLM providers, chat templates, etc. +/// +public sealed record PluginConfigurationObject +{ + /// + /// The id of the configuration plugin to which this configuration object belongs. + /// + public required Guid ConfigPluginId { get; init; } = Guid.NewGuid(); + + /// + /// The id of the configuration object, e.g., the id of a chat template. + /// + public required Guid Id { get; init; } = Guid.NewGuid(); + + /// + /// The type of the configuration object. + /// + public required PluginConfigurationObjectType Type { get; init; } = PluginConfigurationObjectType.NONE; +} \ No newline at end of file diff --git a/app/MindWork AI Studio/Tools/PluginSystem/PluginConfigurationObjectType.cs b/app/MindWork AI Studio/Tools/PluginSystem/PluginConfigurationObjectType.cs new file mode 100644 index 00000000..1cb4f604 --- /dev/null +++ b/app/MindWork AI Studio/Tools/PluginSystem/PluginConfigurationObjectType.cs @@ -0,0 +1,13 @@ +namespace AIStudio.Tools.PluginSystem; + +public enum PluginConfigurationObjectType +{ + NONE, + UNKNOWN, + + PROFILE, + DATA_SOURCE, + LLM_PROVIDER, + CHAT_TEMPLATE, + EMBEDDING_PROVIDER, +} \ No newline at end of file diff --git a/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.Loading.cs b/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.Loading.cs index d9ac6fde..5972b3a4 100644 --- a/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.Loading.cs +++ b/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.Loading.cs @@ -40,6 +40,8 @@ public static partial class PluginFactory if (!await PLUGIN_LOAD_SEMAPHORE.WaitAsync(0, cancellationToken)) return; + var configObjectList = new List(); + try { LOG.LogInformation("Start loading plugins."); @@ -112,7 +114,8 @@ public static partial class PluginFactory } // Start or restart all plugins: - await RestartAllPlugins(cancellationToken); + var configObjects = await RestartAllPlugins(cancellationToken); + configObjectList.AddRange(configObjects); } finally { @@ -149,6 +152,16 @@ public static partial class PluginFactory SETTINGS_MANAGER.ConfigurationData.Providers.Remove(configuredProvider); wasConfigurationChanged = true; } + + if(!configObjectList.Any(configObject => + configObject.Type is PluginConfigurationObjectType.LLM_PROVIDER && + configObject.ConfigPluginId == providerSourcePluginId && + configObject.Id.ToString() == configuredProvider.Id)) + { + LOG.LogWarning($"The configured LLM provider '{configuredProvider.InstanceName}' (id={configuredProvider.Id}) is not present in the configuration plugin anymore. Removing the provider from the settings."); + SETTINGS_MANAGER.ConfigurationData.Providers.Remove(configuredProvider); + wasConfigurationChanged = true; + } } #pragma warning restore MWAIS0001 @@ -172,41 +185,17 @@ public static partial class PluginFactory SETTINGS_MANAGER.ConfigurationData.ChatTemplates.Remove(configuredTemplate); wasConfigurationChanged = true; } - } - - // - // Before checking simple settings, validate that still-present configuration plugins haven't removed individual - // providers or chat templates they previously managed. If so, remove those items from our settings as well: - // - #pragma warning disable MWAIS0001 - foreach (var runningPlugin in RUNNING_PLUGINS.OfType()) - { - var (providerIds, templateIds) = runningPlugin.GetManagedObjectIds(); - var cfgPluginId = runningPlugin.Id; - - // Providers managed by this plugin but no longer present in plugin config - var providersToRemove = SETTINGS_MANAGER.ConfigurationData.Providers - .Where(p => p.IsEnterpriseConfiguration && p.EnterpriseConfigurationPluginId == cfgPluginId && !providerIds.Contains(p.Id)) - .ToList(); - foreach (var p in providersToRemove) + + if(!configObjectList.Any(configObject => + configObject.Type is PluginConfigurationObjectType.CHAT_TEMPLATE && + configObject.ConfigPluginId == templateSourcePluginId && + configObject.Id.ToString() == configuredTemplate.Id)) { - LOG.LogWarning($"The configured LLM provider '{p.InstanceName}' (id={p.Id}) was removed from its configuration plugin (id={cfgPluginId}). Removing the provider from the settings."); - SETTINGS_MANAGER.ConfigurationData.Providers.Remove(p); - wasConfigurationChanged = true; - } - - // Chat templates managed by this plugin but no longer present in plugin config - var templatesToRemove = SETTINGS_MANAGER.ConfigurationData.ChatTemplates - .Where(t => t.IsEnterpriseConfiguration && t.EnterpriseConfigurationPluginId == cfgPluginId && !templateIds.Contains(t.Id)) - .ToList(); - foreach (var t in templatesToRemove) - { - LOG.LogWarning($"The configured chat template '{t.Name}' (id={t.Id}) was removed from its configuration plugin (id={cfgPluginId}). Removing the chat template from the settings."); - SETTINGS_MANAGER.ConfigurationData.ChatTemplates.Remove(t); + LOG.LogWarning($"The configured chat template '{configuredTemplate.Name}' (id={configuredTemplate.Id}) is not present in the configuration plugin anymore. Removing the chat template from the settings."); + SETTINGS_MANAGER.ConfigurationData.ChatTemplates.Remove(configuredTemplate); wasConfigurationChanged = true; } } - #pragma warning restore MWAIS0001 // // ========================================================== diff --git a/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.Starting.cs b/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.Starting.cs index 0943a48e..5d734b06 100644 --- a/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.Starting.cs +++ b/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.Starting.cs @@ -11,9 +11,10 @@ public static partial class PluginFactory /// public static IReadOnlyCollection RunningPlugins => RUNNING_PLUGINS; - private static async Task RestartAllPlugins(CancellationToken cancellationToken = default) + private static async Task> RestartAllPlugins(CancellationToken cancellationToken = default) { LOG.LogInformation("Try to start or restart all plugins."); + var configObjects = new List(); RUNNING_PLUGINS.Clear(); // @@ -65,7 +66,12 @@ public static partial class PluginFactory { if (availablePlugin.IsInternal || SETTINGS_MANAGER.IsPluginEnabled(availablePlugin) || availablePlugin.Type == PluginType.CONFIGURATION) if(await Start(availablePlugin, cancellationToken) is { IsValid: true } plugin) + { + if (plugin is PluginConfiguration configPlugin) + configObjects.AddRange(configPlugin.ConfigObjects); + RUNNING_PLUGINS.Add(plugin); + } } catch (Exception e) { @@ -75,6 +81,7 @@ public static partial class PluginFactory // Inform all components that the plugins have been reloaded or started: await MessageBus.INSTANCE.SendMessage(null, Event.PLUGINS_RELOADED); + return configObjects; } private static async Task Start(IAvailablePlugin meta, CancellationToken cancellationToken = default)