2025-04-23 14:07:22 +02:00
using System.Text ;
2026-07-05 15:20:29 +02:00
using AIStudio.Settings ;
using AIStudio.Settings.DataModel ;
using AIStudio.Tools.PluginSystem.Assistants ;
2025-04-23 14:07:22 +02:00
namespace AIStudio.Tools.PluginSystem ;
public static partial class PluginFactory
{
private static readonly List < PluginBase > RUNNING_PLUGINS = [];
/// <summary>
/// A list of all running plugins.
/// </summary>
public static IReadOnlyCollection < PluginBase > RunningPlugins => RUNNING_PLUGINS ;
2025-08-18 20:40:52 +02:00
private static async Task < List < PluginConfigurationObject >> RestartAllPlugins ( CancellationToken cancellationToken = default )
2025-04-23 14:07:22 +02:00
{
LOG . LogInformation ( "Try to start or restart all plugins." );
2025-08-18 20:40:52 +02:00
var configObjects = new List < PluginConfigurationObject >();
2025-04-23 14:07:22 +02:00
RUNNING_PLUGINS . Clear ();
//
// Get the base language plugin. This is the plugin that will be used to fill in missing keys.
//
var baseLanguagePluginId = InternalPlugin . LANGUAGE_EN_US . MetaData (). Id ;
var baseLanguagePluginMetaData = AVAILABLE_PLUGINS . FirstOrDefault ( p => p . Id == baseLanguagePluginId );
if ( baseLanguagePluginMetaData is null )
LOG . LogError ( $"Was not able to find the base language plugin: Id='{baseLanguagePluginId}'. Please check your installation." );
else
{
2025-06-01 21:14:21 +02:00
try
{
var startedBasePlugin = await Start ( baseLanguagePluginMetaData , cancellationToken );
if ( startedBasePlugin is NoPlugin noPlugin )
LOG . LogError ( $"Was not able to start the base language plugin: Id='{baseLanguagePluginId}'. Reason: {noPlugin.Issues.First()}" );
if ( startedBasePlugin is PluginLanguage languagePlugin )
{
2026-02-19 20:43:47 +01:00
BaseLanguage = languagePlugin ;
2025-06-01 21:14:21 +02:00
RUNNING_PLUGINS . Add ( languagePlugin );
LOG . LogInformation ( $"Successfully started the base language plugin: Id='{languagePlugin.Id}', Type='{languagePlugin.Type}', Name='{languagePlugin.Name}', Version='{languagePlugin.Version}'" );
}
else
LOG . LogError ( $"Was not able to start the base language plugin: Id='{baseLanguagePluginId}'. Reason: {string.Join(" ; ", startedBasePlugin.Issues)}" );
}
catch ( Exception e )
{
LOG . LogError ( e , $"An error occurred while starting the base language plugin: Id='{baseLanguagePluginId}'." );
2026-02-19 20:43:47 +01:00
BaseLanguage = NoPluginLanguage . INSTANCE ;
2025-06-01 21:14:21 +02:00
}
2025-04-23 14:07:22 +02:00
}
2025-06-01 21:14:21 +02:00
2025-04-23 14:07:22 +02:00
//
// Iterate over all available plugins and try to start them.
//
foreach ( var availablePlugin in AVAILABLE_PLUGINS )
{
if ( cancellationToken . IsCancellationRequested )
2025-06-01 21:14:21 +02:00
{
LOG . LogWarning ( "Cancellation requested while starting plugins. Stopping the plugin startup process. Probably due to a timeout." );
2025-04-23 14:07:22 +02:00
break ;
2025-06-01 21:14:21 +02:00
}
2025-04-23 14:07:22 +02:00
if ( availablePlugin . Id == baseLanguagePluginId )
continue ;
2025-06-01 21:14:21 +02:00
try
{
2026-06-10 21:01:27 +02:00
if ( availablePlugin . IsInternal || SettingsManagerAccess . IsPluginEnabled ( availablePlugin ) || availablePlugin . Type == PluginType . CONFIGURATION || availablePlugin . Type == PluginType . ASSISTANT )
2025-06-01 21:14:21 +02:00
if ( await Start ( availablePlugin , cancellationToken ) is { IsValid : true } plugin )
2025-08-18 20:40:52 +02:00
{
if ( plugin is PluginConfiguration configPlugin )
configObjects . AddRange ( configPlugin . ConfigObjects );
2025-06-01 21:14:21 +02:00
RUNNING_PLUGINS . Add ( plugin );
2025-08-18 20:40:52 +02:00
}
2025-06-01 21:14:21 +02:00
}
catch ( Exception e )
{
LOG . LogError ( e , $"An error occurred while starting the plugin: Id='{availablePlugin.Id}', Type='{availablePlugin.Type}', Name='{availablePlugin.Name}', Version='{availablePlugin.Version}'." );
}
2025-04-23 14:07:22 +02:00
}
2026-07-05 15:20:29 +02:00
LogAssistantPluginStartupState ();
2025-06-01 21:14:21 +02:00
// Inform all components that the plugins have been reloaded or started:
await MessageBus . INSTANCE . SendMessage < bool >( null , Event . PLUGINS_RELOADED );
2025-08-18 20:40:52 +02:00
return configObjects ;
2025-04-23 14:07:22 +02:00
}
2026-07-05 15:20:29 +02:00
private static void LogAssistantPluginStartupState ()
{
ManagedConfiguration . TryGet ( x => x . AssistantPluginAudit , x => x . EnterpriseApprovedPlugins , out ConfigMeta < DataAssistantPluginAudit , IList < DataAssistantPluginEnterpriseApproval >> configMeta );
var approvedByConfigPluginId = configMeta is { IsLocked : true } ? configMeta . LockedByConfigPluginId : Guid . Empty ;
var approvedByConfigPluginName = approvedByConfigPluginId == Guid . Empty
? string . Empty
: AVAILABLE_PLUGINS . FirstOrDefault ( x => x . Id == approvedByConfigPluginId )?. Name ?? string . Empty ;
foreach ( var assistantPlugin in RUNNING_PLUGINS . OfType < PluginAssistants >())
{
var securityState = PluginAssistantSecurityResolver . Resolve ( SettingsManagerAccess , assistantPlugin );
if ( securityState . IsEnterpriseApproved )
{
LOG . LogInformation (
$"Successfully started assistant plugin: Id='{assistantPlugin.Id}', Type='{assistantPlugin.Type}', Name='{assistantPlugin.Name}', Version='{assistantPlugin.Version}', SecuritySource='EnterpriseApproval', ApprovedByConfigPluginId='{approvedByConfigPluginId}', ApprovedByConfigPluginName='{approvedByConfigPluginName}'" );
continue ;
}
LOG . LogInformation (
$"Successfully started assistant plugin: Id='{assistantPlugin.Id}', Type='{assistantPlugin.Type}', Name='{assistantPlugin.Name}', Version='{assistantPlugin.Version}'" );
}
}
2025-04-23 14:07:22 +02:00
private static async Task < PluginBase > Start ( IAvailablePlugin meta , CancellationToken cancellationToken = default )
{
var pluginMainFile = Path . Join ( meta . LocalPath , "plugin.lua" );
if (! File . Exists ( pluginMainFile ))
{
LOG . LogError ( $"Was not able to start plugin: Id='{meta.Id}', Type='{meta.Type}', Name='{meta.Name}', Version='{meta.Version}'. Reason: The plugin file does not exist." );
return new NoPlugin ( $"The plugin file does not exist: {pluginMainFile}" );
}
var code = await File . ReadAllTextAsync ( pluginMainFile , Encoding . UTF8 , cancellationToken );
var plugin = await Load ( meta . LocalPath , code , cancellationToken );
2026-04-09 10:01:24 +02:00
plugin . PluginPath = meta . LocalPath ;
2025-04-23 14:07:22 +02:00
if ( plugin is NoPlugin noPlugin )
{
LOG . LogError ( $"Was not able to start plugin: Id='{meta.Id}', Type='{meta.Type}', Name='{meta.Name}', Version='{meta.Version}'. Reason: {noPlugin.Issues.First()}" );
return noPlugin ;
}
if ( plugin . IsValid )
{
//
// When this is a language plugin, we need to set the base language plugin.
//
2026-02-19 20:43:47 +01:00
if ( plugin is PluginLanguage languagePlugin && BaseLanguage != NoPluginLanguage . INSTANCE )
languagePlugin . SetBaseLanguage ( BaseLanguage );
2025-04-23 14:07:22 +02:00
2025-06-01 21:14:21 +02:00
if ( plugin is PluginConfiguration configPlugin )
2025-08-09 19:29:43 +02:00
await configPlugin . InitializeAsync ( false );
2025-06-01 21:14:21 +02:00
2025-04-23 14:07:22 +02:00
LOG . LogInformation ( $"Successfully started plugin: Id='{plugin.Id}', Type='{plugin.Type}', Name='{plugin.Name}', Version='{plugin.Version}'" );
return plugin ;
}
LOG . LogError ( $"Was not able to start plugin: Id='{meta.Id}', Type='{meta.Type}', Name='{meta.Name}', Version='{meta.Version}'. Reasons: {string.Join(" ; ", plugin.Issues)}" );
return new NoPlugin ( $"Was not able to start plugin: Id='{meta.Id}', Type='{meta.Type}', Name='{meta.Name}', Version='{meta.Version}'. Reasons: {string.Join(" ; ", plugin.Issues)}" );
}
2026-04-09 10:01:24 +02:00
}