2026-07-05 13:20:29 +00:00
using System.Globalization ;
2025-06-01 19:14:21 +00:00
using AIStudio.Settings ;
2026-04-10 15:11:05 +00:00
using AIStudio.Settings.DataModel ;
2026-02-07 21:59:41 +00:00
using AIStudio.Tools.Services ;
2025-06-01 19:14:21 +00:00
using Lua ;
namespace AIStudio.Tools.PluginSystem ;
public sealed class PluginConfiguration ( bool isInternal , LuaState state , PluginType type ) : PluginBase ( isInternal , state , type )
{
private static string TB ( string fallbackEN ) = > I18N . I . T ( fallbackEN , typeof ( PluginConfiguration ) . Namespace , nameof ( PluginConfiguration ) ) ;
2026-06-10 19:01:27 +00:00
private static SettingsManager SettingsManagerAccess = > Program . SERVICE_PROVIDER . GetRequiredService < SettingsManager > ( ) ;
2026-02-07 21:59:41 +00:00
private static readonly ILogger LOG = Program . LOGGER_FACTORY . CreateLogger ( nameof ( PluginConfiguration ) ) ;
2025-08-26 08:59:56 +00:00
private List < PluginConfigurationObject > configObjects = [ ] ;
2026-04-10 15:11:05 +00:00
private List < DataMandatoryInfo > mandatoryInfos = [ ] ;
2026-06-20 18:28:22 +00:00
private List < DataIntroduction > introductions = [ ] ;
2025-08-18 18:40:52 +00:00
/// <summary>
/// The list of configuration objects. Configuration objects are, e.g., providers or chat templates.
/// </summary>
public IEnumerable < PluginConfigurationObject > ConfigObjects = > this . configObjects ;
2026-02-19 19:43:47 +00:00
2026-04-10 15:11:05 +00:00
/// <summary>
/// The list of mandatory infos provided by this configuration plugin.
2026-06-20 18:28:22 +00:00
/// Mandatory infos are live plugin content and are not persisted to ConfigurationData.
2026-04-10 15:11:05 +00:00
/// </summary>
public IReadOnlyList < DataMandatoryInfo > MandatoryInfos = > this . mandatoryInfos ;
2026-06-20 18:28:22 +00:00
/// <summary>
/// The list of introductions provided by this configuration plugin.
/// Introductions are live plugin content and are not persisted to ConfigurationData.
/// </summary>
public IReadOnlyList < DataIntroduction > Introductions = > this . introductions ;
2026-02-19 19:43:47 +00:00
/// <summary>
/// True/false when explicitly configured in the plugin, otherwise null.
/// </summary>
public bool? DeployedUsingConfigServer { get ; } = ReadDeployedUsingConfigServer ( state ) ;
2025-08-18 18:40:52 +00:00
2025-08-09 17:29:43 +00:00
public async Task InitializeAsync ( bool dryRun )
2025-06-01 19:14:21 +00:00
{
2025-08-09 17:29:43 +00:00
if ( ! this . TryProcessConfiguration ( dryRun , out var issue ) )
2026-04-09 08:08:37 +00:00
this . PluginIssues . Add ( issue ) ;
2025-08-09 17:29:43 +00:00
if ( ! dryRun )
{
2026-02-07 21:59:41 +00:00
// Store any decrypted API keys from enterprise configuration in the OS keyring:
await StoreEnterpriseApiKeysAsync ( ) ;
2026-05-18 14:26:51 +00:00
await StoreEnterpriseSecretsAsync ( ) ;
2026-02-07 21:59:41 +00:00
2026-06-10 19:01:27 +00:00
await SettingsManagerAccess . StoreSettings ( ) ;
2025-08-09 17:29:43 +00:00
await MessageBus . INSTANCE . SendMessage < bool > ( null , Event . CONFIGURATION_CHANGED ) ;
}
2025-06-01 19:14:21 +00:00
}
2025-08-09 17:29:43 +00:00
2026-05-18 14:26:51 +00:00
/// <summary>
/// Stores any pending enterprise secrets in the OS keyring.
/// </summary>
private static async Task StoreEnterpriseSecretsAsync ( )
{
var pendingSecrets = PendingEnterpriseSecrets . GetAndClear ( ) ;
if ( pendingSecrets . Count = = 0 )
return ;
LOG . LogInformation ( $"Storing {pendingSecrets.Count} enterprise secret(s) in the OS keyring." ) ;
var rustService = Program . SERVICE_PROVIDER . GetRequiredService < RustService > ( ) ;
foreach ( var pendingSecret in pendingSecrets )
{
try
{
var secretId = new TemporarySecretId ( pendingSecret . SecretId , pendingSecret . SecretName ) ;
var result = await rustService . SetSecret ( secretId , pendingSecret . SecretData , pendingSecret . StoreType ) ;
if ( result . Success )
LOG . LogDebug ( $"Successfully stored enterprise secret for '{pendingSecret.SecretName}' in the OS keyring." ) ;
else
LOG . LogWarning ( $"Failed to store enterprise secret for '{pendingSecret.SecretName}': {result.Issue}" ) ;
}
catch ( Exception ex )
{
LOG . LogError ( ex , $"Exception while storing enterprise secret for '{pendingSecret.SecretName}'." ) ;
}
}
}
2026-02-07 21:59:41 +00:00
/// <summary>
/// Stores any pending enterprise API keys in the OS keyring.
/// </summary>
private static async Task StoreEnterpriseApiKeysAsync ( )
{
var pendingKeys = PendingEnterpriseApiKeys . GetAndClear ( ) ;
if ( pendingKeys . Count = = 0 )
return ;
LOG . LogInformation ( $"Storing {pendingKeys.Count} enterprise API key(s) in the OS keyring." ) ;
var rustService = Program . SERVICE_PROVIDER . GetRequiredService < RustService > ( ) ;
foreach ( var pendingKey in pendingKeys )
{
try
{
// Create a temporary secret ID object for storing the key:
var secretId = new TemporarySecretId ( pendingKey . SecretId , pendingKey . SecretName ) ;
var result = await rustService . SetAPIKey ( secretId , pendingKey . ApiKey , pendingKey . StoreType ) ;
if ( result . Success )
LOG . LogDebug ( $"Successfully stored enterprise API key for '{pendingKey.SecretName}' in the OS keyring." ) ;
else
LOG . LogWarning ( $"Failed to store enterprise API key for '{pendingKey.SecretName}': {result.Issue}" ) ;
}
catch ( Exception ex )
{
LOG . LogError ( ex , $"Exception while storing enterprise API key for '{pendingKey.SecretName}'." ) ;
}
}
}
/// <summary>
/// Temporary implementation of ISecretId for storing enterprise API keys.
/// </summary>
private sealed record TemporarySecretId ( string SecretId , string SecretName ) : ISecretId ;
2026-02-19 19:43:47 +00:00
private static bool? ReadDeployedUsingConfigServer ( LuaState state )
{
if ( state . Environment [ "DEPLOYED_USING_CONFIG_SERVER" ] . TryRead < bool > ( out var deployedUsingConfigServer ) )
return deployedUsingConfigServer ;
return null ;
}
2025-06-01 19:14:21 +00:00
/// <summary>
/// Tries to initialize the UI text content of the plugin.
/// </summary>
2025-08-18 18:40:52 +00:00
/// <param name="dryRun">When true, the method will not apply any changes but only check if the configuration can be read.</param>
2025-06-01 19:14:21 +00:00
/// <param name="message">The error message, when the UI text content could not be read.</param>
/// <returns>True, when the UI text content could be read successfully.</returns>
2025-08-09 17:29:43 +00:00
private bool TryProcessConfiguration ( bool dryRun , out string message )
2025-06-01 19:14:21 +00:00
{
2025-08-18 18:40:52 +00:00
this . configObjects . Clear ( ) ;
2026-04-10 15:11:05 +00:00
this . mandatoryInfos . Clear ( ) ;
2026-06-20 18:28:22 +00:00
this . introductions . Clear ( ) ;
2025-08-18 18:40:52 +00:00
2025-06-01 19:14:21 +00:00
// Ensure that the main CONFIG table exists and is a valid Lua table:
2026-04-09 08:08:37 +00:00
if ( ! this . State . Environment [ "CONFIG" ] . TryRead < LuaTable > ( out var mainTable ) )
2025-06-01 19:14:21 +00:00
{
message = TB ( "The CONFIG table does not exist or is not a valid table." ) ;
return false ;
}
2025-08-26 08:59:56 +00:00
// Check for the main SETTINGS table:
2025-06-01 19:14:21 +00:00
if ( ! mainTable . TryGetValue ( "SETTINGS" , out var settingsValue ) | | ! settingsValue . TryRead < LuaTable > ( out var settingsTable ) )
{
message = TB ( "The SETTINGS table does not exist or is not a valid table." ) ;
return false ;
}
2025-08-09 17:29:43 +00:00
2025-08-26 08:59:56 +00:00
// Config: check for updates, and if so, how often?
2025-08-26 18:06:13 +00:00
ManagedConfiguration . TryProcessConfiguration ( x = > x . App , x = > x . UpdateInterval , this . Id , settingsTable , dryRun ) ;
// Config: how should updates be installed?
ManagedConfiguration . TryProcessConfiguration ( x = > x . App , x = > x . UpdateInstallation , this . Id , settingsTable , dryRun ) ;
2026-03-21 17:05:06 +00:00
// Config: what should be the start page?
ManagedConfiguration . TryProcessConfiguration ( x = > x . App , x = > x . StartPage , this . Id , settingsTable , dryRun ) ;
2026-06-04 14:24:40 +00:00
2026-06-20 18:28:22 +00:00
// Config: show built-in introduction on the home page?
ManagedConfiguration . TryProcessConfiguration ( x = > x . App , x = > x . ShowIntroduction , this . Id , settingsTable , dryRun ) ;
2026-06-04 14:24:40 +00:00
// Config: show quick start guide on the home page?
ManagedConfiguration . TryProcessConfiguration ( x = > x . App , x = > x . ShowQuickStartGuide , this . Id , settingsTable , dryRun ) ;
2025-08-09 17:29:43 +00:00
2025-08-26 08:59:56 +00:00
// Config: allow the user to add providers?
2025-08-09 17:29:43 +00:00
ManagedConfiguration . TryProcessConfiguration ( x = > x . App , x = > x . AllowUserToAddProvider , this . Id , settingsTable , dryRun ) ;
2026-02-07 21:59:41 +00:00
// Config: show administration settings?
ManagedConfiguration . TryProcessConfiguration ( x = > x . App , x = > x . ShowAdminSettings , this . Id , settingsTable , dryRun ) ;
2025-06-01 19:14:21 +00:00
2025-10-19 09:51:28 +00:00
// Config: preview features visibility
ManagedConfiguration . TryProcessConfiguration ( x = > x . App , x = > x . PreviewVisibility , this . Id , settingsTable , dryRun ) ;
2026-02-20 11:40:38 +00:00
// Config: enabled preview features (plugin contribution; users can enable additional features)
ManagedConfiguration . TryProcessConfigurationWithPluginContribution ( x = > x . App , x = > x . EnabledPreviewFeatures , this . Id , settingsTable , dryRun ) ;
2025-10-19 09:51:28 +00:00
2026-01-12 19:43:45 +00:00
// Config: hide some assistants?
ManagedConfiguration . TryProcessConfiguration ( x = > x . App , x = > x . HiddenAssistants , this . Id , settingsTable , dryRun ) ;
2026-01-24 19:05:34 +00:00
// Config: global voice recording shortcut
ManagedConfiguration . TryProcessConfiguration ( x = > x . App , x = > x . ShortcutVoiceRecording , this . Id , settingsTable , dryRun ) ;
2026-05-21 14:48:34 +00:00
// Config: timeout for external HTTP requests
ManagedConfiguration . TryProcessConfiguration ( x = > x . App , x = > x . HttpClientTimeoutSeconds , this . Id , settingsTable , dryRun ) ;
2026-05-31 16:46:54 +00:00
// Config: custom root certificates for external HTTP requests
ManagedConfiguration . TryProcessConfiguration ( x = > x . App , x = > x . ExternalHttpCustomRootCertificatesEnabled , this . Id , settingsTable , dryRun ) ;
ManagedConfiguration . TryProcessConfiguration ( x = > x . App , x = > x . ExternalHttpCustomRootCertificateBundlePath , this . Id , settingsTable , dryRun ) ;
ManagedConfiguration . TryProcessConfiguration ( x = > x . App , x = > x . ExternalHttpCustomRootCertificateAllowedHosts , this . Id , settingsTable , dryRun ) ;
2026-06-21 09:52:02 +00:00
// Config: provider confidence settings
ManagedConfiguration . TryProcessConfiguration ( x = > x . Confidence , x = > x . EnforceGlobalMinimumConfidence , this . Id , settingsTable , dryRun ) ;
ManagedConfiguration . TryProcessConfiguration ( x = > x . Confidence , x = > x . GlobalMinimumConfidence , this . Id , settingsTable , dryRun ) ;
ManagedConfiguration . TryProcessConfiguration ( x = > x . Confidence , x = > x . ShowProviderConfidence , this . Id , settingsTable , dryRun ) ;
ManagedConfiguration . TryProcessConfiguration ( x = > x . Confidence , x = > x . ConfidenceScheme , this . Id , settingsTable , dryRun ) ;
ManagedConfiguration . TryProcessConfiguration ( x = > x . Confidence , x = > x . CustomConfidenceScheme , this . Id , settingsTable , dryRun ) ;
2026-06-21 13:16:37 +00:00
// Config: data source security settings
ManagedConfiguration . TryProcessConfiguration ( x = > x . DataSourceSecurity , x = > x . TrustedProviderIds , this . Id , settingsTable , dryRun ) ;
2026-07-05 10:59:03 +00:00
2026-07-05 12:46:31 +00:00
// Config: data source selection agent settings
ManagedConfiguration . TryProcessConfiguration ( x = > x . AgentDataSourceSelection , x = > x . PreselectAgentOptions , this . Id , settingsTable , dryRun ) ;
ManagedConfiguration . TryProcessConfiguration ( x = > x . AgentDataSourceSelection , x = > x . PreselectedAgentProvider , Guid . Empty , this . Id , settingsTable , dryRun ) ;
// Config: retrieval context validation agent settings
ManagedConfiguration . TryProcessConfiguration ( x = > x . AgentRetrievalContextValidation , x = > x . EnableRetrievalContextValidation , this . Id , settingsTable , dryRun ) ;
ManagedConfiguration . TryProcessConfiguration ( x = > x . AgentRetrievalContextValidation , x = > x . PreselectAgentOptions , this . Id , settingsTable , dryRun ) ;
ManagedConfiguration . TryProcessConfiguration ( x = > x . AgentRetrievalContextValidation , x = > x . PreselectedAgentProvider , Guid . Empty , this . Id , settingsTable , dryRun ) ;
ManagedConfiguration . TryProcessConfiguration ( x = > x . AgentRetrievalContextValidation , x = > x . NumParallelValidations , this . Id , settingsTable , dryRun ) ;
2026-07-05 10:59:03 +00:00
// Config: assistant plugin audit settings
ManagedConfiguration . TryProcessConfiguration ( x = > x . AssistantPluginAudit , x = > x . RequireAuditBeforeActivation , this . Id , settingsTable , dryRun ) ;
ManagedConfiguration . TryProcessConfiguration ( x = > x . AssistantPluginAudit , x = > x . PreselectedAgentProvider , Guid . Empty , this . Id , settingsTable , dryRun ) ;
ManagedConfiguration . TryProcessConfiguration ( x = > x . AssistantPluginAudit , x = > x . MinimumLevel , this . Id , settingsTable , dryRun ) ;
ManagedConfiguration . TryProcessConfiguration ( x = > x . AssistantPluginAudit , x = > x . BlockActivationBelowMinimum , this . Id , settingsTable , dryRun ) ;
ManagedConfiguration . TryProcessConfiguration ( x = > x . AssistantPluginAudit , x = > x . AutomaticallyAuditAssistants , this . Id , settingsTable , dryRun ) ;
2026-07-05 13:20:29 +00:00
// Config: enterprise-managed approvals for assistant plugins
this . TryProcessEnterpriseApprovedAssistantPlugins ( settingsTable , dryRun ) ;
2026-01-24 19:05:34 +00:00
2025-08-26 08:59:56 +00:00
// Handle configured LLM providers:
2025-09-03 20:41:28 +00:00
PluginConfigurationObject . TryParse ( PluginConfigurationObjectType . LLM_PROVIDER , x = > x . Providers , x = > x . NextProviderNum , mainTable , this . Id , ref this . configObjects , dryRun ) ;
2026-01-09 14:41:54 +00:00
2026-01-09 14:49:44 +00:00
// Handle configured transcription providers:
PluginConfigurationObject . TryParse ( PluginConfigurationObjectType . TRANSCRIPTION_PROVIDER , x = > x . TranscriptionProviders , x = > x . NextTranscriptionNum , mainTable , this . Id , ref this . configObjects , dryRun ) ;
2026-01-09 14:41:54 +00:00
// Handle configured embedding providers:
PluginConfigurationObject . TryParse ( PluginConfigurationObjectType . EMBEDDING_PROVIDER , x = > x . EmbeddingProviders , x = > x . NextEmbeddingNum , mainTable , this . Id , ref this . configObjects , dryRun ) ;
2025-08-26 08:59:56 +00:00
// Handle configured chat templates:
2026-05-22 13:46:03 +00:00
PluginConfigurationObject . TryParse ( PluginConfigurationObjectType . CHAT_TEMPLATE , x = > x . ChatTemplates , x = > x . NextChatTemplateNum , mainTable , this . Id , ref this . configObjects , dryRun , this . PluginPath ) ;
2026-05-18 14:26:51 +00:00
// Handle configured data sources:
PluginConfigurationObject . TryParseDataSources ( mainTable , this . Id , ref this . configObjects , dryRun ) ;
2025-08-18 18:40:52 +00:00
2025-11-14 11:04:01 +00:00
// Handle configured profiles:
PluginConfigurationObject . TryParse ( PluginConfigurationObjectType . PROFILE , x = > x . Profiles , x = > x . NextProfileNum , mainTable , this . Id , ref this . configObjects , dryRun ) ;
2026-02-01 13:50:19 +00:00
// Handle configured document analysis policies:
PluginConfigurationObject . TryParse ( PluginConfigurationObjectType . DOCUMENT_ANALYSIS_POLICY , x = > x . DocumentAnalysis . Policies , x = > x . NextDocumentAnalysisPolicyNum , mainTable , this . Id , ref this . configObjects , dryRun ) ;
2026-04-10 15:11:05 +00:00
// Handle configured mandatory infos:
this . TryReadMandatoryInfos ( mainTable ) ;
2026-06-20 18:28:22 +00:00
// Handle configured introductions:
this . TryReadIntroductions ( mainTable ) ;
2026-02-01 13:50:19 +00:00
2026-02-20 09:08:06 +00:00
// Config: preselected provider?
ManagedConfiguration . TryProcessConfiguration ( x = > x . App , x = > x . PreselectedProvider , Guid . Empty , this . Id , settingsTable , dryRun ) ;
2025-11-14 11:04:01 +00:00
// Config: preselected profile?
2025-12-04 14:37:13 +00:00
ManagedConfiguration . TryProcessConfiguration ( x = > x . App , x = > x . PreselectedProfile , Guid . Empty , this . Id , settingsTable , dryRun ) ;
2026-01-09 14:49:44 +00:00
2026-06-21 13:59:23 +00:00
// Config: preselected chat options?
ManagedConfiguration . TryProcessConfiguration ( x = > x . Chat , x = > x . PreselectOptions , this . Id , settingsTable , dryRun ) ;
ManagedConfiguration . TryProcessConfiguration ( x = > x . Chat , x = > x . PreselectedProvider , Guid . Empty , this . Id , settingsTable , dryRun ) ;
ManagedConfiguration . TryProcessConfiguration ( x = > x . Chat , x = > x . PreselectedProfile , this . Id , settingsTable , dryRun ) ;
ManagedConfiguration . TryProcessConfiguration ( x = > x . Chat , x = > x . PreselectedChatTemplate , this . Id , settingsTable , dryRun ) ;
2026-07-05 12:46:31 +00:00
ManagedConfiguration . TryProcessConfiguration ( x = > x . Chat , x = > x . PreselectedDataSourcesDisabled , this . Id , settingsTable , dryRun ) ;
ManagedConfiguration . TryProcessConfiguration ( x = > x . Chat , x = > x . PreselectedDataSourcesAutomaticSelection , this . Id , settingsTable , dryRun ) ;
ManagedConfiguration . TryProcessConfiguration ( x = > x . Chat , x = > x . PreselectedDataSourcesAutomaticValidation , this . Id , settingsTable , dryRun ) ;
ManagedConfiguration . TryProcessConfiguration ( x = > x . Chat , x = > x . PreselectedDataSourceIds , this . Id , settingsTable , dryRun ) ;
ManagedConfiguration . TryProcessConfiguration ( x = > x . Chat , x = > x . SendToChatDataSourceBehavior , this . Id , settingsTable , dryRun ) ;
2026-06-21 13:59:23 +00:00
2026-01-09 14:49:44 +00:00
// Config: transcription provider?
ManagedConfiguration . TryProcessConfiguration ( x = > x . App , x = > x . UseTranscriptionProvider , Guid . Empty , this . Id , settingsTable , dryRun ) ;
2025-08-26 08:59:56 +00:00
message = string . Empty ;
2025-08-18 18:40:52 +00:00
return true ;
}
2026-04-10 15:11:05 +00:00
2026-07-05 13:20:29 +00:00
private void TryProcessEnterpriseApprovedAssistantPlugins ( LuaTable settingsTable , bool dryRun )
{
if ( ! ManagedConfiguration . TryGet ( x = > x . AssistantPluginAudit , x = > x . EnterpriseApprovedPlugins , out ConfigMeta < DataAssistantPluginAudit , IList < DataAssistantPluginEnterpriseApproval > > configMeta ) )
return ;
var settingName = SettingsManager . ToSettingName < DataAssistantPluginAudit , IList < DataAssistantPluginEnterpriseApproval > > ( x = > x . EnterpriseApprovedPlugins ) ;
var successful = false ;
IList < DataAssistantPluginEnterpriseApproval > configuredApprovals = [ ] ;
if ( settingsTable . TryGetValue ( settingName , out var configuredLuaValue )
& & configuredLuaValue . Type is LuaValueType . Table
& & configuredLuaValue . TryRead < LuaTable > ( out var approvalsTable ) )
{
var approvals = new List < DataAssistantPluginEnterpriseApproval > ( approvalsTable . ArrayLength ) ;
for ( var index = 1 ; index < = approvalsTable . ArrayLength ; index + + )
{
var entryValue = approvalsTable [ index ] ;
if ( entryValue . TryRead < string > ( out var hashText ) )
{
var normalizedHash = NormalizeApprovalHash ( hashText ) ;
if ( ! string . IsNullOrWhiteSpace ( normalizedHash ) )
approvals . Add ( new ( ) { PluginHash = normalizedHash } ) ;
else
LOG . LogWarning ( "The enterprise assistant approval entry at index {Index} contains an empty hash (config plugin id: {ConfigPluginId})." , index , this . Id ) ;
continue ;
}
if ( ! entryValue . TryRead < LuaTable > ( out var entryTable ) )
{
LOG . LogWarning ( "The enterprise assistant approval entry at index {Index} is neither a string nor a table (config plugin id: {ConfigPluginId})." , index , this . Id ) ;
continue ;
}
if ( ! TryParseEnterpriseApprovedAssistantPlugin ( index , entryTable , this . Id , out var approval ) )
continue ;
approvals . Add ( approval ) ;
}
configuredApprovals = approvals ;
successful = true ;
}
if ( dryRun )
return ;
switch ( successful )
{
case true :
configMeta . SetValue ( configuredApprovals ) ;
configMeta . LockConfiguration ( this . Id ) ;
break ;
case false when configMeta . IsLocked & & configMeta . LockedByConfigPluginId = = this . Id :
configMeta . ResetLockedConfiguration ( ) ;
break ;
}
}
private static bool TryParseEnterpriseApprovedAssistantPlugin ( int index , LuaTable table , Guid configPluginId , out DataAssistantPluginEnterpriseApproval approval )
{
approval = new ( ) ;
if ( ! table . TryGetValue ( "PluginHash" , out var pluginHashValue ) | | ! pluginHashValue . TryRead < string > ( out var pluginHash ) )
{
LOG . LogWarning ( "The enterprise assistant approval entry at index {Index} is missing a valid PluginHash (config plugin id: {ConfigPluginId})." , index , configPluginId ) ;
return false ;
}
var normalizedHash = NormalizeApprovalHash ( pluginHash ) ;
if ( string . IsNullOrWhiteSpace ( normalizedHash ) )
{
LOG . LogWarning ( "The enterprise assistant approval entry at index {Index} contains an empty PluginHash (config plugin id: {ConfigPluginId})." , index , configPluginId ) ;
return false ;
}
var displayName = TryReadOptionalString ( table , "DisplayName" ) ;
var comment = TryReadOptionalString ( table , "Comment" ) ;
var approvedBy = TryReadOptionalString ( table , "ApprovedBy" ) ;
var approvedAtUtc = TryReadOptionalDateTimeOffset ( table , "ApprovedAtUtc" , index , configPluginId ) ;
approval = new ( )
{
PluginHash = normalizedHash ,
DisplayName = displayName ,
Comment = comment ,
ApprovedBy = approvedBy ,
ApprovedAtUtc = approvedAtUtc ,
} ;
return true ;
}
private static string TryReadOptionalString ( LuaTable table , string key )
{
return table . TryGetValue ( key , out var value ) & & value . TryRead < string > ( out var text )
? text
: string . Empty ;
}
private static DateTimeOffset ? TryReadOptionalDateTimeOffset ( LuaTable table , string key , int index , Guid configPluginId )
{
if ( ! table . TryGetValue ( key , out var value ) )
return null ;
if ( value . TryRead < string > ( out var text ) & & DateTimeOffset . TryParse ( text , CultureInfo . InvariantCulture , DateTimeStyles . AssumeUniversal , out var parsed ) )
return parsed . ToUniversalTime ( ) ;
LOG . LogWarning ( "The enterprise assistant approval entry at index {Index} contains an invalid {Key} value (config plugin id: {ConfigPluginId})." , index , key , configPluginId ) ;
return null ;
}
private static string NormalizeApprovalHash ( string hash ) = > string . IsNullOrWhiteSpace ( hash ) ? string . Empty : hash . Trim ( ) . ToUpperInvariant ( ) ;
2026-04-10 15:11:05 +00:00
private void TryReadMandatoryInfos ( LuaTable mainTable )
{
if ( ! mainTable . TryGetValue ( "MANDATORY_INFOS" , out var mandatoryInfosValue ) | | ! mandatoryInfosValue . TryRead < LuaTable > ( out var mandatoryInfosTable ) )
return ;
for ( var i = 1 ; i < = mandatoryInfosTable . ArrayLength ; i + + )
{
var luaMandatoryInfoValue = mandatoryInfosTable [ i ] ;
if ( ! luaMandatoryInfoValue . TryRead < LuaTable > ( out var luaMandatoryInfoTable ) )
{
LOG . LogWarning ( "The table 'MANDATORY_INFOS' entry at index {Index} is not a valid table (config plugin id: {ConfigPluginId})." , i , this . Id ) ;
continue ;
}
if ( DataMandatoryInfo . TryParseConfiguration ( i , luaMandatoryInfoTable , this . Id , out var mandatoryInfo ) )
this . mandatoryInfos . Add ( mandatoryInfo ) ;
else
LOG . LogWarning ( "The table 'MANDATORY_INFOS' entry at index {Index} does not contain a valid mandatory info (config plugin id: {ConfigPluginId})." , i , this . Id ) ;
}
}
2026-06-20 18:28:22 +00:00
private void TryReadIntroductions ( LuaTable mainTable )
{
if ( ! mainTable . TryGetValue ( "INTRODUCTIONS" , out var introductionsValue ) | | ! introductionsValue . TryRead < LuaTable > ( out var introductionsTable ) )
return ;
for ( var i = 1 ; i < = introductionsTable . ArrayLength ; i + + )
{
var luaIntroductionValue = introductionsTable [ i ] ;
if ( ! luaIntroductionValue . TryRead < LuaTable > ( out var luaIntroductionTable ) )
{
LOG . LogWarning ( "The table 'INTRODUCTIONS' entry at index {Index} is not a valid table (config plugin id: {ConfigPluginId})." , i , this . Id ) ;
continue ;
}
if ( DataIntroduction . TryParseConfiguration ( i , luaIntroductionTable , this . Id , out var introduction ) )
this . introductions . Add ( introduction ) ;
else
LOG . LogWarning ( "The table 'INTRODUCTIONS' entry at index {Index} does not contain a valid introduction (config plugin id: {ConfigPluginId})." , i , this . Id ) ;
}
}
2026-02-07 21:59:41 +00:00
}