2025-04-23 12:07:22 +00:00
using System.Text ;
2026-07-05 13:20:29 +00:00
using AIStudio.Settings ;
using AIStudio.Settings.DataModel ;
using AIStudio.Tools.PluginSystem.Assistants ;
2025-04-23 12:07:22 +00: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 18:40:52 +00:00
private static async Task < List < PluginConfigurationObject > > RestartAllPlugins ( CancellationToken cancellationToken = default )
2025-04-23 12:07:22 +00:00
{
LOG . LogInformation ( "Try to start or restart all plugins." ) ;
2025-08-18 18:40:52 +00:00
var configObjects = new List < PluginConfigurationObject > ( ) ;
2025-04-23 12:07:22 +00: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 19:14:21 +00: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 19:43:47 +00:00
BaseLanguage = languagePlugin ;
2025-06-01 19:14:21 +00: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 19:43:47 +00:00
BaseLanguage = NoPluginLanguage . INSTANCE ;
2025-06-01 19:14:21 +00:00
}
2025-04-23 12:07:22 +00:00
}
2025-06-01 19:14:21 +00:00
2025-04-23 12:07:22 +00:00
//
// Iterate over all available plugins and try to start them.
//
foreach ( var availablePlugin in AVAILABLE_PLUGINS )
{
if ( cancellationToken . IsCancellationRequested )
2025-06-01 19:14:21 +00:00
{
LOG . LogWarning ( "Cancellation requested while starting plugins. Stopping the plugin startup process. Probably due to a timeout." ) ;
2025-04-23 12:07:22 +00:00
break ;
2025-06-01 19:14:21 +00:00
}
2025-04-23 12:07:22 +00:00
if ( availablePlugin . Id = = baseLanguagePluginId )
continue ;
2025-06-01 19:14:21 +00:00
try
{
2026-06-10 19:01:27 +00:00
if ( availablePlugin . IsInternal | | SettingsManagerAccess . IsPluginEnabled ( availablePlugin ) | | availablePlugin . Type = = PluginType . CONFIGURATION | | availablePlugin . Type = = PluginType . ASSISTANT )
2025-06-01 19:14:21 +00:00
if ( await Start ( availablePlugin , cancellationToken ) is { IsValid : true } plugin )
2025-08-18 18:40:52 +00:00
{
if ( plugin is PluginConfiguration configPlugin )
configObjects . AddRange ( configPlugin . ConfigObjects ) ;
2025-06-01 19:14:21 +00:00
RUNNING_PLUGINS . Add ( plugin ) ;
2025-08-18 18:40:52 +00:00
}
2025-06-01 19:14:21 +00: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 12:07:22 +00:00
}
2026-07-05 13:20:29 +00:00
LogAssistantPluginStartupState ( ) ;
2025-06-01 19:14:21 +00:00
// Inform all components that the plugins have been reloaded or started:
await MessageBus . INSTANCE . SendMessage < bool > ( null , Event . PLUGINS_RELOADED ) ;
2025-08-18 18:40:52 +00:00
return configObjects ;
2025-04-23 12:07:22 +00:00
}
2026-07-05 13:20:29 +00: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 12:07:22 +00: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 08:01:24 +00:00
plugin . PluginPath = meta . LocalPath ;
2025-04-23 12:07:22 +00: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 19:43:47 +00:00
if ( plugin is PluginLanguage languagePlugin & & BaseLanguage ! = NoPluginLanguage . INSTANCE )
languagePlugin . SetBaseLanguage ( BaseLanguage ) ;
2025-04-23 12:07:22 +00:00
2025-06-01 19:14:21 +00:00
if ( plugin is PluginConfiguration configPlugin )
2025-08-09 17:29:43 +00:00
await configPlugin . InitializeAsync ( false ) ;
2025-06-01 19:14:21 +00:00
2025-04-23 12:07:22 +00: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 08:01:24 +00:00
}