2025-04-23 14:07:22 +02:00
using System.Text ;
2025-08-09 19:29:43 +02:00
using AIStudio.Settings ;
2026-04-09 10:01:24 +02:00
using AIStudio.Tools.PluginSystem.Assistants ;
2025-04-23 14:07:22 +02:00
using Lua ;
using Lua.Standard ;
namespace AIStudio.Tools.PluginSystem ;
public static partial class PluginFactory
{
private static readonly List < IAvailablePlugin > AVAILABLE_PLUGINS = [];
private static readonly SemaphoreSlim PLUGIN_LOAD_SEMAPHORE = new ( 1 , 1 );
/// <summary>
/// A list of all available plugins.
/// </summary>
public static IReadOnlyCollection < IPluginMetadata > AvailablePlugins => AVAILABLE_PLUGINS ;
/// <summary>
/// Try to load all plugins from the plugins directory.
/// </summary>
/// <remarks>
/// Loading plugins means:<br/>
/// - Parsing and checking the plugin code<br/>
/// - Check for forbidden plugins<br/>
/// - Creating a new instance of the allowed plugin<br/>
/// - Read the plugin metadata<br/>
2025-04-27 09:06:05 +02:00
/// - Start the plugin<br/>
2025-04-23 14:07:22 +02:00
/// </remarks>
public static async Task LoadAll ( CancellationToken cancellationToken = default )
{
2026-02-19 20:43:47 +01:00
if (! IsInitialized )
2025-04-23 14:07:22 +02:00
{
LOG . LogError ( "PluginFactory is not initialized. Please call Setup() before using it." );
return ;
}
2026-07-15 21:00:25 +02:00
// Wait for ongoing reloads instead of silently skipping this request.
// This caller must return only after its reload has run.
await PLUGIN_LOAD_SEMAPHORE . WaitAsync ( cancellationToken );
2025-04-23 14:07:22 +02:00
2025-08-18 20:40:52 +02:00
var configObjectList = new List < PluginConfigurationObject >();
2025-04-23 14:07:22 +02:00
try
{
LOG . LogInformation ( "Start loading plugins." );
if (! Directory . Exists ( PLUGINS_ROOT ))
{
LOG . LogInformation ( "No plugins found." );
return ;
}
AVAILABLE_PLUGINS . Clear ();
//
// The easiest way to load all plugins is to find all `plugin.lua` files and load them.
// By convention, each plugin is enforced to have a `plugin.lua` file.
//
var pluginMainFiles = Directory . EnumerateFiles ( PLUGINS_ROOT , "plugin.lua" , SearchOption . AllDirectories );
foreach ( var pluginMainFile in pluginMainFiles )
{
2025-04-27 16:13:15 +02:00
try
{
if ( cancellationToken . IsCancellationRequested )
2025-06-01 21:14:21 +02:00
{
LOG . LogWarning ( "Was not able to load all plugins, because the operation was cancelled. It seems to be a timeout." );
2025-04-27 16:13:15 +02:00
break ;
2025-06-01 21:14:21 +02:00
}
2025-04-27 16:13:15 +02:00
LOG . LogInformation ( $"Try to load plugin: {pluginMainFile}" );
var fileInfo = new FileInfo ( pluginMainFile );
string code ;
await using ( var fileStream = fileInfo . Open ( FileMode . Open , FileAccess . Read , FileShare . ReadWrite ))
{
using var reader = new StreamReader ( fileStream , Encoding . UTF8 );
code = await reader . ReadToEndAsync ( cancellationToken );
}
var pluginPath = Path . GetDirectoryName ( pluginMainFile )!;
var plugin = await Load ( pluginPath , code , cancellationToken );
2025-04-23 14:07:22 +02:00
2025-04-27 16:13:15 +02:00
switch ( plugin )
{
case NoPlugin noPlugin when noPlugin . Issues . Any ():
LOG . LogError ( $"Was not able to load plugin: '{pluginMainFile}'. Reason: {noPlugin.Issues.First()}" );
continue ;
2025-04-23 14:07:22 +02:00
2025-04-27 16:13:15 +02:00
case NoPlugin :
LOG . LogError ( $"Was not able to load plugin: '{pluginMainFile}'. Reason: Unknown." );
continue ;
2025-04-23 14:07:22 +02:00
2025-04-27 16:13:15 +02:00
case { IsValid : false }:
LOG . LogError ( $"Was not able to load plugin '{pluginMainFile}', because the Lua code is not a valid AI Studio plugin. There are {plugin.Issues.Count()} issues to fix. First issue is: {plugin.Issues.FirstOrDefault()}" );
2025-08-26 10:59:56 +02:00
#if DEBUG
2025-04-27 16:13:15 +02:00
foreach ( var pluginIssue in plugin . Issues )
LOG . LogError ( $"Plugin issue: {pluginIssue}" );
2025-08-26 10:59:56 +02:00
#endif
2025-04-27 16:13:15 +02:00
continue ;
2025-04-23 14:07:22 +02:00
2025-04-27 16:13:15 +02:00
case { IsMaintained : false }:
LOG . LogWarning ( $"The plugin '{pluginMainFile}' is not maintained anymore. Please consider to disable it." );
break ;
}
2025-04-23 14:07:22 +02:00
2025-04-27 16:13:15 +02:00
LOG . LogInformation ( $"Successfully loaded plugin: '{pluginMainFile}' (Id='{plugin.Id}', Type='{plugin.Type}', Name='{plugin.Name}', Version='{plugin.Version}', Authors='{string.Join(" , ", plugin.Authors)}')" );
2026-02-15 18:11:57 +01:00
2026-02-19 20:43:47 +01:00
var isConfigurationPluginInConfigDirectory =
plugin . Type is PluginType . CONFIGURATION &&
pluginPath . StartsWith ( CONFIGURATION_PLUGINS_ROOT , StringComparison . OrdinalIgnoreCase );
var isManagedByConfigServer = false ;
Guid ? managedConfigurationId = null ;
if ( plugin is PluginConfiguration configPlugin )
{
if ( configPlugin . DeployedUsingConfigServer . HasValue )
isManagedByConfigServer = configPlugin . DeployedUsingConfigServer . Value ;
else if ( isConfigurationPluginInConfigDirectory )
{
isManagedByConfigServer = true ;
LOG . LogWarning ( $"The configuration plugin '{plugin.Id}' does not define 'DEPLOYED_USING_CONFIG_SERVER'. Falling back to the plugin path and treating it as managed because it is stored under '{CONFIGURATION_PLUGINS_ROOT}'." );
}
}
2026-07-15 21:00:25 +02:00
else if ( plugin is PluginAssistants assistantPlugin )
isManagedByConfigServer = assistantPlugin . IsManagedByConfigServer ;
2026-02-19 20:43:47 +01:00
2026-02-15 18:11:57 +01:00
// For configuration plugins, validate that the plugin ID matches the enterprise config ID
// (the directory name under which the plugin was downloaded):
2026-02-19 20:43:47 +01:00
if ( isConfigurationPluginInConfigDirectory && isManagedByConfigServer )
2026-02-15 18:11:57 +01:00
{
var directoryName = Path . GetFileName ( pluginPath );
2026-02-19 20:43:47 +01:00
if ( Guid . TryParse ( directoryName , out var enterpriseConfigId ))
{
managedConfigurationId = enterpriseConfigId ;
if ( enterpriseConfigId != plugin . Id )
LOG . LogWarning ( $"The configuration plugin's ID ('{plugin.Id}') does not match the enterprise configuration ID ('{enterpriseConfigId}'). These IDs should be identical. Please update the plugin's ID field to match the enterprise configuration ID." );
}
else
LOG . LogWarning ( $"Could not determine the managed configuration ID for configuration plugin '{plugin.Id}'. The plugin directory '{pluginPath}' does not end with a valid GUID." );
2026-02-15 18:11:57 +01:00
}
2026-02-19 20:43:47 +01:00
AVAILABLE_PLUGINS . Add ( new PluginMetadata ( plugin , pluginPath , isManagedByConfigServer , managedConfigurationId ));
2025-04-27 16:13:15 +02:00
}
catch ( Exception e )
{
LOG . LogError ( $"Was not able to load plugin '{pluginMainFile}'. Issue: {e.Message}" );
LOG . LogDebug ( e . StackTrace );
}
2025-04-23 14:07:22 +02:00
}
// Start or restart all plugins:
2025-08-18 20:40:52 +02:00
var configObjects = await RestartAllPlugins ( cancellationToken );
configObjectList . AddRange ( configObjects );
2025-04-23 14:07:22 +02:00
}
finally
{
PLUGIN_LOAD_SEMAPHORE . Release ();
LOG . LogInformation ( "Finished loading plugins." );
}
2025-06-01 21:14:21 +02:00
//
2025-08-09 19:29:43 +02:00
// =========================================================
2025-08-26 10:59:56 +02:00
// Next, we have to clean up our settings. It is possible
// that a configuration plugin was removed. We have to
// remove the related settings as well:
2025-08-09 19:29:43 +02:00
// =========================================================
2025-06-01 21:14:21 +02:00
//
// Check LLM providers:
2026-02-07 22:59:41 +01:00
var wasConfigurationChanged = await PluginConfigurationObject . CleanLeftOverConfigurationObjects ( PluginConfigurationObjectType . LLM_PROVIDER , x => x . Providers , AVAILABLE_PLUGINS , configObjectList , SecretStoreType . LLM_PROVIDER );
2026-01-09 15:41:54 +01:00
2026-01-09 15:49:44 +01:00
// Check transcription providers:
2026-02-07 22:59:41 +01:00
if ( await PluginConfigurationObject . CleanLeftOverConfigurationObjects ( PluginConfigurationObjectType . TRANSCRIPTION_PROVIDER , x => x . TranscriptionProviders , AVAILABLE_PLUGINS , configObjectList , SecretStoreType . TRANSCRIPTION_PROVIDER ))
2026-01-09 15:49:44 +01:00
wasConfigurationChanged = true ;
2026-01-09 15:41:54 +01:00
// Check embedding providers:
2026-02-07 22:59:41 +01:00
if ( await PluginConfigurationObject . CleanLeftOverConfigurationObjects ( PluginConfigurationObjectType . EMBEDDING_PROVIDER , x => x . EmbeddingProviders , AVAILABLE_PLUGINS , configObjectList , SecretStoreType . EMBEDDING_PROVIDER ))
2026-01-09 15:41:54 +01:00
wasConfigurationChanged = true ;
2026-05-18 16:26:51 +02:00
// Check data sources:
if ( await PluginConfigurationObject . CleanLeftOverConfigurationObjects ( PluginConfigurationObjectType . DATA_SOURCE , x => x . DataSources , AVAILABLE_PLUGINS , configObjectList , SecretStoreType . DATA_SOURCE , deleteSecret : true ))
wasConfigurationChanged = true ;
2025-08-18 20:40:52 +02:00
// Check chat templates:
2026-02-07 22:59:41 +01:00
if ( await PluginConfigurationObject . CleanLeftOverConfigurationObjects ( PluginConfigurationObjectType . CHAT_TEMPLATE , x => x . ChatTemplates , AVAILABLE_PLUGINS , configObjectList ))
2025-08-26 10:59:56 +02:00
wasConfigurationChanged = true ;
2026-02-07 22:59:41 +01:00
2025-11-14 12:04:01 +01:00
// Check profiles:
2026-02-07 22:59:41 +01:00
if ( await PluginConfigurationObject . CleanLeftOverConfigurationObjects ( PluginConfigurationObjectType . PROFILE , x => x . Profiles , AVAILABLE_PLUGINS , configObjectList ))
2025-11-14 12:04:01 +01:00
wasConfigurationChanged = true ;
2026-02-01 14:50:19 +01:00
// Check document analysis policies:
2026-02-07 22:59:41 +01:00
if ( await PluginConfigurationObject . CleanLeftOverConfigurationObjects ( PluginConfigurationObjectType . DOCUMENT_ANALYSIS_POLICY , x => x . DocumentAnalysis . Policies , AVAILABLE_PLUGINS , configObjectList ))
2026-02-01 14:50:19 +01:00
wasConfigurationChanged = true ;
2026-04-10 17:11:05 +02:00
// Check left-over mandatory info acceptances:
2026-06-10 21:01:27 +02:00
if ( SettingsManagerAccess . ConfigurationData . MandatoryInformation . RemoveLeftOverAcceptances ( GetMandatoryInfos ()))
2026-04-10 17:11:05 +02:00
wasConfigurationChanged = true ;
2025-11-14 12:04:01 +01:00
2026-02-20 10:08:06 +01:00
// Check for a preselected provider:
if ( ManagedConfiguration . IsConfigurationLeftOver ( x => x . App , x => x . PreselectedProvider , AVAILABLE_PLUGINS ))
wasConfigurationChanged = true ;
2025-11-14 12:04:01 +01:00
// Check for a preselected profile:
if ( ManagedConfiguration . IsConfigurationLeftOver ( x => x . App , x => x . PreselectedProfile , AVAILABLE_PLUGINS ))
wasConfigurationChanged = true ;
2026-06-21 15:59:23 +02:00
// Check for preselected chat options:
if ( ManagedConfiguration . IsConfigurationLeftOver ( x => x . Chat , x => x . PreselectOptions , AVAILABLE_PLUGINS ))
wasConfigurationChanged = true ;
if ( ManagedConfiguration . IsConfigurationLeftOver ( x => x . Chat , x => x . PreselectedProvider , AVAILABLE_PLUGINS ))
wasConfigurationChanged = true ;
if ( ManagedConfiguration . IsConfigurationLeftOver ( x => x . Chat , x => x . PreselectedProfile , AVAILABLE_PLUGINS ))
wasConfigurationChanged = true ;
if ( ManagedConfiguration . IsConfigurationLeftOver ( x => x . Chat , x => x . PreselectedChatTemplate , AVAILABLE_PLUGINS ))
wasConfigurationChanged = true ;
2026-07-05 14:46:31 +02:00
if ( ManagedConfiguration . IsConfigurationLeftOver ( x => x . Chat , x => x . PreselectedDataSourcesDisabled , AVAILABLE_PLUGINS ))
wasConfigurationChanged = true ;
if ( ManagedConfiguration . IsConfigurationLeftOver ( x => x . Chat , x => x . PreselectedDataSourcesAutomaticSelection , AVAILABLE_PLUGINS ))
wasConfigurationChanged = true ;
if ( ManagedConfiguration . IsConfigurationLeftOver ( x => x . Chat , x => x . PreselectedDataSourcesAutomaticValidation , AVAILABLE_PLUGINS ))
wasConfigurationChanged = true ;
if ( ManagedConfiguration . IsConfigurationLeftOver ( x => x . Chat , x => x . PreselectedDataSourceIds , AVAILABLE_PLUGINS ))
wasConfigurationChanged = true ;
if ( ManagedConfiguration . IsConfigurationLeftOver ( x => x . Chat , x => x . SendToChatDataSourceBehavior , AVAILABLE_PLUGINS ))
wasConfigurationChanged = true ;
2025-11-14 12:04:01 +01:00
2025-10-19 11:51:28 +02:00
// Check for the update interval:
if ( ManagedConfiguration . IsConfigurationLeftOver ( x => x . App , x => x . UpdateInterval , AVAILABLE_PLUGINS ))
2025-08-26 20:06:13 +02:00
wasConfigurationChanged = true ;
2025-10-19 11:51:28 +02:00
// Check for the update installation method:
if ( ManagedConfiguration . IsConfigurationLeftOver ( x => x . App , x => x . UpdateInstallation , AVAILABLE_PLUGINS ))
2025-08-09 19:29:43 +02:00
wasConfigurationChanged = true ;
2026-03-21 18:05:06 +01:00
// Check for the start page:
if ( ManagedConfiguration . IsConfigurationLeftOver ( x => x . App , x => x . StartPage , AVAILABLE_PLUGINS ))
wasConfigurationChanged = true ;
2026-06-20 20:28:22 +02:00
// Check for the built-in introduction visibility:
if ( ManagedConfiguration . IsConfigurationLeftOver ( x => x . App , x => x . ShowIntroduction , AVAILABLE_PLUGINS ))
wasConfigurationChanged = true ;
2026-06-04 16:24:40 +02:00
// Check for the quick start guide visibility:
if ( ManagedConfiguration . IsConfigurationLeftOver ( x => x . App , x => x . ShowQuickStartGuide , AVAILABLE_PLUGINS ))
wasConfigurationChanged = true ;
2026-07-15 13:27:34 +02:00
// Check for the last changelog visibility:
if ( ManagedConfiguration . IsConfigurationLeftOver ( x => x . App , x => x . ShowLastChangelog , AVAILABLE_PLUGINS ))
wasConfigurationChanged = true ;
// Check for the vision panel visibility:
if ( ManagedConfiguration . IsConfigurationLeftOver ( x => x . App , x => x . ShowVision , AVAILABLE_PLUGINS ))
wasConfigurationChanged = true ;
2025-08-26 10:59:56 +02:00
// Check for users allowed to added providers:
2025-10-19 11:51:28 +02:00
if ( ManagedConfiguration . IsConfigurationLeftOver ( x => x . App , x => x . AllowUserToAddProvider , AVAILABLE_PLUGINS ))
wasConfigurationChanged = true ;
2026-02-07 22:59:41 +01:00
// Check for admin settings visibility:
if ( ManagedConfiguration . IsConfigurationLeftOver ( x => x . App , x => x . ShowAdminSettings , AVAILABLE_PLUGINS ))
wasConfigurationChanged = true ;
2025-10-19 11:51:28 +02:00
// Check for preview visibility:
if ( ManagedConfiguration . IsConfigurationLeftOver ( x => x . App , x => x . PreviewVisibility , AVAILABLE_PLUGINS ))
wasConfigurationChanged = true ;
// Check for enabled preview features:
if ( ManagedConfiguration . IsConfigurationLeftOver ( x => x . App , x => x . EnabledPreviewFeatures , AVAILABLE_PLUGINS ))
2025-08-09 19:29:43 +02:00
wasConfigurationChanged = true ;
2025-06-01 21:14:21 +02:00
2026-02-20 12:40:38 +01:00
if ( ManagedConfiguration . IsPluginContributionLeftOver ( x => x . App , x => x . EnabledPreviewFeatures , AVAILABLE_PLUGINS ))
wasConfigurationChanged = true ;
2026-01-09 15:49:44 +01:00
// Check for the transcription provider:
if ( ManagedConfiguration . IsConfigurationLeftOver ( x => x . App , x => x . UseTranscriptionProvider , AVAILABLE_PLUGINS ))
wasConfigurationChanged = true ;
2026-01-12 20:43:45 +01:00
// Check for hidden assistants:
if ( ManagedConfiguration . IsConfigurationLeftOver ( x => x . App , x => x . HiddenAssistants , AVAILABLE_PLUGINS ))
wasConfigurationChanged = true ;
2026-01-24 20:05:34 +01:00
// Check for the voice recording shortcut:
if ( ManagedConfiguration . IsConfigurationLeftOver ( x => x . App , x => x . ShortcutVoiceRecording , AVAILABLE_PLUGINS ))
wasConfigurationChanged = true ;
2026-04-09 10:01:24 +02:00
2026-05-21 16:48:34 +02:00
// Check for the external HTTP client timeout:
if ( ManagedConfiguration . IsConfigurationLeftOver ( x => x . App , x => x . HttpClientTimeoutSeconds , AVAILABLE_PLUGINS ))
wasConfigurationChanged = true ;
2026-05-31 18:46:54 +02:00
// Check for custom root certificates for external HTTP requests:
if ( ManagedConfiguration . IsConfigurationLeftOver ( x => x . App , x => x . ExternalHttpCustomRootCertificatesEnabled , AVAILABLE_PLUGINS ))
wasConfigurationChanged = true ;
if ( ManagedConfiguration . IsConfigurationLeftOver ( x => x . App , x => x . ExternalHttpCustomRootCertificateBundlePath , AVAILABLE_PLUGINS ))
wasConfigurationChanged = true ;
if ( ManagedConfiguration . IsConfigurationLeftOver ( x => x . App , x => x . ExternalHttpCustomRootCertificateAllowedHosts , AVAILABLE_PLUGINS ))
wasConfigurationChanged = true ;
2026-06-21 11:52:02 +02:00
// Check provider confidence settings:
if ( ManagedConfiguration . IsConfigurationLeftOver ( x => x . Confidence , x => x . EnforceGlobalMinimumConfidence , AVAILABLE_PLUGINS ))
wasConfigurationChanged = true ;
if ( ManagedConfiguration . IsConfigurationLeftOver ( x => x . Confidence , x => x . GlobalMinimumConfidence , AVAILABLE_PLUGINS ))
wasConfigurationChanged = true ;
if ( ManagedConfiguration . IsConfigurationLeftOver ( x => x . Confidence , x => x . ShowProviderConfidence , AVAILABLE_PLUGINS ))
wasConfigurationChanged = true ;
if ( ManagedConfiguration . IsConfigurationLeftOver ( x => x . Confidence , x => x . ConfidenceScheme , AVAILABLE_PLUGINS ))
wasConfigurationChanged = true ;
if ( ManagedConfiguration . IsConfigurationLeftOver ( x => x . Confidence , x => x . CustomConfidenceScheme , AVAILABLE_PLUGINS ))
wasConfigurationChanged = true ;
2026-06-21 15:16:37 +02:00
// Check data source security settings:
if ( ManagedConfiguration . IsConfigurationLeftOver ( x => x . DataSourceSecurity , x => x . TrustedProviderIds , AVAILABLE_PLUGINS ))
wasConfigurationChanged = true ;
2026-07-05 14:46:31 +02:00
// Check data source selection agent settings:
if ( ManagedConfiguration . IsConfigurationLeftOver ( x => x . AgentDataSourceSelection , x => x . PreselectAgentOptions , AVAILABLE_PLUGINS ))
wasConfigurationChanged = true ;
if ( ManagedConfiguration . IsConfigurationLeftOver ( x => x . AgentDataSourceSelection , x => x . PreselectedAgentProvider , AVAILABLE_PLUGINS ))
wasConfigurationChanged = true ;
// Check retrieval context validation agent settings:
if ( ManagedConfiguration . IsConfigurationLeftOver ( x => x . AgentRetrievalContextValidation , x => x . EnableRetrievalContextValidation , AVAILABLE_PLUGINS ))
wasConfigurationChanged = true ;
if ( ManagedConfiguration . IsConfigurationLeftOver ( x => x . AgentRetrievalContextValidation , x => x . PreselectAgentOptions , AVAILABLE_PLUGINS ))
wasConfigurationChanged = true ;
if ( ManagedConfiguration . IsConfigurationLeftOver ( x => x . AgentRetrievalContextValidation , x => x . PreselectedAgentProvider , AVAILABLE_PLUGINS ))
wasConfigurationChanged = true ;
if ( ManagedConfiguration . IsConfigurationLeftOver ( x => x . AgentRetrievalContextValidation , x => x . NumParallelValidations , AVAILABLE_PLUGINS ))
wasConfigurationChanged = true ;
2026-04-09 10:01:24 +02:00
// Check if audit is required before it can be activated
if ( ManagedConfiguration . IsConfigurationLeftOver ( x => x . AssistantPluginAudit , x => x . RequireAuditBeforeActivation , AVAILABLE_PLUGINS ))
wasConfigurationChanged = true ;
// Register new preselected provider for the security audit
if ( ManagedConfiguration . IsConfigurationLeftOver ( x => x . AssistantPluginAudit , x => x . PreselectedAgentProvider , AVAILABLE_PLUGINS ))
wasConfigurationChanged = true ;
// Change the minimum required audit level that is required for the allowance of assistants
if ( ManagedConfiguration . IsConfigurationLeftOver ( x => x . AssistantPluginAudit , x => x . MinimumLevel , AVAILABLE_PLUGINS ))
wasConfigurationChanged = true ;
// Check if external plugins are strictly forbidden, when the minimum audit level is fell below
if ( ManagedConfiguration . IsConfigurationLeftOver ( x => x . AssistantPluginAudit , x => x . BlockActivationBelowMinimum , AVAILABLE_PLUGINS ))
wasConfigurationChanged = true ;
// Check if security audits are invoked automatically and transparent for the user
if ( ManagedConfiguration . IsConfigurationLeftOver ( x => x . AssistantPluginAudit , x => x . AutomaticallyAuditAssistants , AVAILABLE_PLUGINS ))
wasConfigurationChanged = true ;
2026-07-05 15:20:29 +02:00
// Check enterprise-managed assistant plugin approvals
if ( ManagedConfiguration . IsConfigurationLeftOver ( x => x . AssistantPluginAudit , x => x . EnterpriseApprovedPlugins , AVAILABLE_PLUGINS ))
wasConfigurationChanged = true ;
2026-01-24 20:05:34 +01:00
2025-06-01 21:14:21 +02:00
if ( wasConfigurationChanged )
{
2026-06-10 21:01:27 +02:00
await SettingsManagerAccess . StoreSettings ();
2025-06-01 21:14:21 +02:00
await MessageBus . INSTANCE . SendMessage < bool >( null , Event . CONFIGURATION_CHANGED );
}
2025-04-23 14:07:22 +02:00
}
2025-04-26 18:55:23 +02:00
public static async Task < PluginBase > Load ( string? pluginPath , string code , CancellationToken cancellationToken = default )
2025-04-23 14:07:22 +02:00
{
if ( ForbiddenPlugins . Check ( code ) is { IsForbidden : true } forbiddenState )
return new NoPlugin ( $"This plugin is forbidden: {forbiddenState.Message}" );
var state = LuaState . Create ();
2025-04-26 18:55:23 +02:00
if (! string . IsNullOrWhiteSpace ( pluginPath ))
{
// Add the module loader so that the plugin can load other Lua modules:
state . ModuleLoader = new PluginLoader ( pluginPath );
}
2025-04-23 14:07:22 +02:00
// Add some useful libraries:
2026-04-09 10:01:24 +02:00
state . OpenBasicLibrary ();
2025-04-23 14:07:22 +02:00
state . OpenModuleLibrary ();
state . OpenStringLibrary ();
state . OpenTableLibrary ();
state . OpenMathLibrary ();
state . OpenBitwiseLibrary ();
state . OpenCoroutineLibrary ();
try
{
await state . DoStringAsync ( code , cancellationToken : cancellationToken );
}
catch ( LuaParseException e )
{
return new NoPlugin ( $"Was not able to parse the plugin: {e.Message}" );
}
catch ( LuaRuntimeException e )
{
return new NoPlugin ( $"Was not able to run the plugin: {e.Message}" );
}
if (! state . Environment [ "TYPE" ]. TryRead < string >( out var typeText ))
return new NoPlugin ( "TYPE does not exist or is not a valid string." );
if (! Enum . TryParse < PluginType >( typeText , out var type ))
return new NoPlugin ( $"TYPE is not a valid plugin type. Valid types are: {CommonTools.GetAllEnumValues<PluginType>()}" );
if ( type is PluginType . NONE )
return new NoPlugin ( $"TYPE is not a valid plugin type. Valid types are: {CommonTools.GetAllEnumValues<PluginType>()}" );
2025-04-26 18:55:23 +02:00
var isInternal = ! string . IsNullOrWhiteSpace ( pluginPath ) && pluginPath . StartsWith ( INTERNAL_PLUGINS_ROOT , StringComparison . OrdinalIgnoreCase );
2025-06-01 21:14:21 +02:00
switch ( type )
2025-04-23 14:07:22 +02:00
{
2025-06-01 21:14:21 +02:00
case PluginType . LANGUAGE :
return new PluginLanguage ( isInternal , state , type );
case PluginType . CONFIGURATION :
2026-05-22 15:46:03 +02:00
var configPlug = new PluginConfiguration ( isInternal , state , type )
{
PluginPath = pluginPath ?? string . Empty
};
2025-08-09 19:29:43 +02:00
await configPlug . InitializeAsync ( true );
2025-06-01 21:14:21 +02:00
return configPlug ;
2025-04-23 14:07:22 +02:00
2026-04-09 10:01:24 +02:00
case PluginType . ASSISTANT :
var assistantPlugin = new PluginAssistants ( isInternal , state , type );
assistantPlugin . TryLoad ();
return assistantPlugin ;
2025-06-01 21:14:21 +02:00
default :
return new NoPlugin ( "This plugin type is not supported yet. Please try again with a future version of AI Studio." );
}
2025-04-23 14:07:22 +02:00
}
2026-02-07 22:59:41 +01:00
}