Added support for organization-managed provider confidence settings (#815)
Build and Release / Determine run mode (push) Waiting to run
Build and Release / Read metadata (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-aarch64-apple-darwin, osx-arm64, macos-latest, aarch64-apple-darwin, dmg,app,updater, dmg) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-aarch64-pc-windows-msvc.exe, win-arm64, windows-latest, aarch64-pc-windows-msvc, nsis,updater, nsis) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-aarch64-unknown-linux-gnu, linux-arm64, ubuntu-22.04-arm, aarch64-unknown-linux-gnu, appimage,updater, appimage) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-x86_64-apple-darwin, osx-x64, macos-latest, x86_64-apple-darwin, dmg,app,updater, dmg) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-x86_64-pc-windows-msvc.exe, win-x64, windows-latest, x86_64-pc-windows-msvc, nsis,updater, nsis) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-x86_64-unknown-linux-gnu, linux-x64, ubuntu-22.04, x86_64-unknown-linux-gnu, appimage,updater, appimage) (push) Blocked by required conditions
Build and Release / Prepare & create release (push) Blocked by required conditions
Build and Release / Publish release (push) Blocked by required conditions

This commit is contained in:
Thorsten Sommer authored and GitHub committed 2026-06-21 11:52:02 +02:00
1 parent 5045da3a91
commit e65110a142
31 files changed
+950 -380

No files matched your search

@@ -271,8 +271,8 @@ public static class ConfigurationSelectDataFactory
public static IEnumerable<ConfigurationSelectData<ConfidenceLevel>> GetConfidenceLevelsData(SettingsManager settingsManager, bool restrictToGlobalMinimum = false)
{
var minimumLevel = ConfidenceLevel.NONE;
if(restrictToGlobalMinimum && settingsManager.ConfigurationData.LLMProviders is { EnforceGlobalMinimumConfidence: true, GlobalMinimumConfidence: not ConfidenceLevel.NONE and not ConfidenceLevel.UNKNOWN })
minimumLevel = settingsManager.ConfigurationData.LLMProviders.GlobalMinimumConfidence;
if(restrictToGlobalMinimum && settingsManager.ConfigurationData.Confidence is { EnforceGlobalMinimumConfidence: true, GlobalMinimumConfidence: not ConfidenceLevel.NONE and not ConfidenceLevel.UNKNOWN })
minimumLevel = settingsManager.ConfigurationData.Confidence.GlobalMinimumConfidence;
foreach (var level in Enum.GetValues<ConfidenceLevel>())
{
@@ -11,7 +11,7 @@ public sealed class Data
/// The version of the settings file. Allows us to upgrade the settings
/// when a new version is available.
/// </summary>
public Version Version { get; init; } = Version.V5;
public Version Version { get; init; } = Version.V6;
/// <summary>
/// List of configured providers.
@@ -19,9 +19,9 @@ public sealed class Data
public List<Provider> Providers { get; init; } = [];
/// <summary>
/// Settings concerning the LLM providers.
/// Settings concerning confidence levels.
/// </summary>
public DataLLMProviders LLMProviders { get; init; } = new();
public DataConfidence Confidence { get; init; } = new(x => x.Confidence);
/// <summary>
/// A collection of embedding providers configured.
@@ -0,0 +1,40 @@
using System.Linq.Expressions;
using AIStudio.Provider;
namespace AIStudio.Settings.DataModel;
public sealed class DataConfidence(Expression<Func<Data, DataConfidence>>? configSelection = null)
{
/// <summary>
/// The default constructor for the JSON deserializer.
/// </summary>
public DataConfidence() : this(null)
{
}
/// <summary>
/// Should we enforce a global minimum confidence level?
/// </summary>
public bool EnforceGlobalMinimumConfidence { get; set; } = ManagedConfiguration.Register(configSelection, n => n.EnforceGlobalMinimumConfidence, false);
/// <summary>
/// The global minimum confidence level to enforce.
/// </summary>
public ConfidenceLevel GlobalMinimumConfidence { get; set; } = ManagedConfiguration.Register(configSelection, n => n.GlobalMinimumConfidence, ConfidenceLevel.NONE);
/// <summary>
/// Should we show the provider confidence level?
/// </summary>
public bool ShowProviderConfidence { get; set; } = ManagedConfiguration.Register(configSelection, n => n.ShowProviderConfidence, true);
/// <summary>
/// Which confidence scheme to use.
/// </summary>
public ConfidenceSchemes ConfidenceScheme { get; set; } = ManagedConfiguration.Register(configSelection, n => n.ConfidenceScheme, ConfidenceSchemes.TRUST_ALL);
/// <summary>
/// Provide custom confidence levels for each provider family.
/// </summary>
public Dictionary<LLMProviders, ConfidenceLevel> CustomConfidenceScheme { get; set; } = ManagedConfiguration.Register(configSelection, n => n.CustomConfidenceScheme, []);
}
@@ -1,8 +1,8 @@
using AIStudio.Provider;
namespace AIStudio.Settings.DataModel;
namespace AIStudio.Settings.DataModel.PreviousModels;
public sealed class DataLLMProviders
public sealed class DataLLMProvidersV5
{
/// <summary>
/// Should we enforce a global minimum confidence level?
@@ -25,7 +25,7 @@ public sealed class DataLLMProviders
public ConfidenceSchemes ConfidenceScheme { get; set; } = ConfidenceSchemes.TRUST_ALL;
/// <summary>
/// Provide custom confidence levels for each LLM provider.
/// Provide custom confidence levels for each provider family.
/// </summary>
public Dictionary<LLMProviders, ConfidenceLevel> CustomConfidenceScheme { get; set; } = new();
}
@@ -16,7 +16,7 @@ public sealed class DataV4
/// <summary>
/// Settings concerning the LLM providers.
/// </summary>
public DataLLMProviders LLMProviders { get; init; } = new();
public DataLLMProvidersV5 LLMProviders { get; init; } = new();
/// <summary>
/// List of configured profiles.
@@ -0,0 +1,149 @@
using AIStudio.Tools.PluginSystem.Assistants;
namespace AIStudio.Settings.DataModel.PreviousModels;
public sealed class DataV5
{
/// <summary>
/// The version of the settings file. Allows us to upgrade the settings
/// when a new version is available.
/// </summary>
public Version Version { get; init; } = Version.V5;
/// <summary>
/// List of configured providers.
/// </summary>
public List<AIStudio.Settings.Provider> Providers { get; init; } = [];
/// <summary>
/// Settings concerning the LLM providers.
/// </summary>
public DataLLMProvidersV5 LLMProviders { get; init; } = new();
/// <summary>
/// A collection of embedding providers configured.
/// </summary>
public List<EmbeddingProvider> EmbeddingProviders { get; init; } = [];
/// <summary>
/// A collection of speech providers configured.
/// </summary>
public List<TranscriptionProvider> TranscriptionProviders { get; init; } = [];
/// <summary>
/// A collection of data sources configured.
/// </summary>
public List<IDataSource> DataSources { get; set; } = [];
/// <summary>
/// List of configured profiles.
/// </summary>
public List<Profile> Profiles { get; init; } = [];
/// <summary>
/// List of configured chat templates.
/// </summary>
public List<ChatTemplate> ChatTemplates { get; init; } = [];
/// <summary>
/// List of enabled plugins.
/// </summary>
public List<Guid> EnabledPlugins { get; set; } = [];
/// <summary>
/// Metadata for managed settings that use a plugin-provided editable default.
/// </summary>
public Dictionary<string, ManagedEditableDefaultState> ManagedEditableDefaults { get; set; } = [];
/// <summary>
/// Cached audit results for assistant plugins.
/// </summary>
public List<PluginAssistantAudit> AssistantPluginAudits { get; set; } = [];
/// <summary>
/// The next provider number to use.
/// </summary>
public uint NextProviderNum { get; set; } = 1;
/// <summary>
/// The next embedding provider number to use.
/// </summary>
public uint NextEmbeddingNum { get; set; } = 1;
/// <summary>
/// The next transcription provider number to use.
/// </summary>
public uint NextTranscriptionNum { get; set; } = 1;
/// <summary>
/// The next data source number to use.
/// </summary>
public uint NextDataSourceNum { get; set; } = 1;
/// <summary>
/// The next profile number to use.
/// </summary>
public uint NextProfileNum { get; set; } = 1;
/// <summary>
/// The next chat template number to use.
/// </summary>
public uint NextChatTemplateNum { get; set; } = 1;
/// <summary>
/// The next document analysis policy number to use.
/// </summary>
public uint NextDocumentAnalysisPolicyNum { get; set; } = 1;
public DataApp App { get; init; } = new(x => x.App);
public DataChat Chat { get; init; } = new();
public DataWorkspace Workspace { get; init; } = new();
public DataIconFinder IconFinder { get; init; } = new();
public DataTranslation Translation { get; init; } = new();
public DataCoding Coding { get; init; } = new();
public DataERI ERI { get; init; } = new();
public DataDocumentAnalysis DocumentAnalysis { get; init; } = new();
public DataMandatoryInformation MandatoryInformation { get; init; } = new();
public DataTextSummarizer TextSummarizer { get; init; } = new();
public DataTextContentCleaner TextContentCleaner { get; init; } = new();
public DataAgentDataSourceSelection AgentDataSourceSelection { get; init; } = new();
public DataAgentRetrievalContextValidation AgentRetrievalContextValidation { get; init; } = new();
public DataAssistantPluginAudit AssistantPluginAudit { get; init; } = new(x => x.AssistantPluginAudit);
public DataAgenda Agenda { get; init; } = new();
public DataGrammarSpelling GrammarSpelling { get; init; } = new();
public DataRewriteImprove RewriteImprove { get; init; } = new();
public DataPromptOptimizer PromptOptimizer { get; init; } = new();
public DataEMail EMail { get; init; } = new();
public DataSlideBuilder SlideBuilder { get; init; } = new();
public DataLegalCheck LegalCheck { get; init; } = new();
public DataSynonyms Synonyms { get; init; } = new();
public DataMyTasks MyTasks { get; init; } = new();
public DataJobPostings JobPostings { get; init; } = new();
public DataBiasOfTheDay BiasOfTheDay { get; init; } = new();
public DataI18N I18N { get; init; } = new();
}
@@ -38,14 +38,14 @@ public static partial class ManagedConfiguration
//
// Handle configured enum values
//
// Check if that configuration was registered:
if(!TryGet(configSelection, propertyExpression, out var configMeta))
return false;
var successful = false;
var configuredValue = configMeta.Default;
// Step 1 -- try to read the Lua value out of the Lua table:
if(settings.TryGetValue(SettingsManager.ToSettingName(propertyExpression), out var configuredEnumValue))
{
@@ -60,7 +60,7 @@ public static partial class ManagedConfiguration
}
}
}
if(dryRun)
return successful;
@@ -98,14 +98,14 @@ public static partial class ManagedConfiguration
//
// Handle configured ISpanParsable values
//
// Check if that configuration was registered:
if(!TryGet(configSelection, propertyExpression, out var configMeta))
return false;
var successful = false;
var configuredValue = configMeta.Default;
// Step 1 -- try to read the Lua value out of the Lua table:
if (settings.TryGetValue(SettingsManager.ToSettingName(propertyExpression), out var configuredLuaValue))
{
@@ -119,7 +119,7 @@ public static partial class ManagedConfiguration
successful = true;
}
}
// Step 2b -- try to read the Lua value:
if(configuredLuaValue.TryRead<TValue>(out var configuredLuaValueInstance))
{
@@ -135,7 +135,7 @@ public static partial class ManagedConfiguration
var managedMode = ReadManagedConfigurationMode(propertyExpression, settings);
return HandleParsedScalarValue(configPluginId, dryRun, successful, configMeta, configuredValue, managedMode, settingName);
}
/// <summary>
/// Attempts to process the configuration settings from a Lua table for string values.
/// </summary>
@@ -189,14 +189,14 @@ public static partial class ManagedConfiguration
//
// Handle configured string values
//
// Check if that configuration was registered:
if(!TryGet(configSelection, propertyExpression, out var configMeta))
return false;
var successful = false;
var configuredValue = configMeta.Default;
// Step 1 -- try to read the Lua value out of the Lua table:
if(settings.TryGetValue(SettingsManager.ToSettingName(propertyExpression), out var configuredTextValue))
{
@@ -210,7 +210,7 @@ public static partial class ManagedConfiguration
successful = Guid.TryParse(configuredText, out var id);
configuredValue = successful ? id.ToString().ToLowerInvariant() : configuredText;
break;
// Case: the read string is just a string:
case string:
configuredValue = configuredText;
@@ -219,7 +219,7 @@ public static partial class ManagedConfiguration
}
}
}
var settingName = SettingName(propertyExpression);
var managedMode = ReadManagedConfigurationMode(propertyExpression, settings);
return HandleParsedScalarValue(configPluginId, dryRun, successful, configMeta, configuredValue, managedMode, settingName);
@@ -273,13 +273,13 @@ public static partial class ManagedConfiguration
// Determine the length of the Lua table and prepare a list to hold the parsed values:
var len = valueTable.ArrayLength;
var list = new List<TValue>(len);
// Iterate over each entry in the Lua table:
for (var index = 1; index <= len; index++)
{
// Retrieve the Lua value at the current index:
var value = valueTable[index];
// Step 2a -- try to read the Lua value as a string:
if (value.Type is LuaValueType.String && value.TryRead<string>(out var configuredLuaValueText))
{
@@ -304,7 +304,7 @@ public static partial class ManagedConfiguration
}
// ReSharper restore MethodOverloadWithOptionalParameter
/// <summary>
/// Attempts to process the configuration settings from a Lua table for enum list types.
/// </summary>
@@ -333,14 +333,14 @@ public static partial class ManagedConfiguration
//
// Handle configured enum lists
//
// Check if that configuration was registered:
if(!TryGet(configSelection, propertyExpression, out var configMeta))
return false;
var successful = false;
var configuredValue = configMeta.Default;
// Step 1 -- try to read the Lua value (we expect a table) out of the Lua table:
if (settings.TryGetValue(SettingsManager.ToSettingName(propertyExpression), out var configuredLuaList) &&
configuredLuaList.Type is LuaValueType.Table &&
@@ -349,13 +349,13 @@ public static partial class ManagedConfiguration
// Determine the length of the Lua table and prepare a list to hold the parsed values:
var len = valueTable.ArrayLength;
var list = new List<TValue>(len);
// Iterate over each entry in the Lua table:
for (var index = 1; index <= len; index++)
{
// Retrieve the Lua value at the current index:
var value = valueTable[index];
// Step 2 -- try to read the Lua value as a string:
if (value.Type is LuaValueType.String && value.TryRead<string>(out var configuredLuaValueText))
{
@@ -364,17 +364,17 @@ public static partial class ManagedConfiguration
list.Add((TValue)configuredEnum);
}
}
configuredValue = list;
successful = true;
}
if(dryRun)
return successful;
return HandleParsedValue(configPluginId, dryRun, successful, configMeta, configuredValue);
}
/// <summary>
/// Attempts to process the configuration settings from a Lua table for string list types.
/// </summary>
@@ -400,14 +400,14 @@ public static partial class ManagedConfiguration
//
// Handle configured string lists
//
// Check if that configuration was registered:
if(!TryGet(configSelection, propertyExpression, out var configMeta))
return false;
var successful = false;
var configuredValue = configMeta.Default;
// Step 1 -- try to read the Lua value (we expect a table) out of the Lua table:
if (settings.TryGetValue(SettingsManager.ToSettingName(propertyExpression), out var configuredLuaList) &&
configuredLuaList.Type is LuaValueType.Table &&
@@ -416,25 +416,25 @@ public static partial class ManagedConfiguration
// Determine the length of the Lua table and prepare a list to hold the parsed values:
var len = valueTable.ArrayLength;
var list = new List<string>(len);
// Iterate over each entry in the Lua table:
for (var index = 1; index <= len; index++)
{
// Retrieve the Lua value at the current index:
var value = valueTable[index];
// Step 2 -- try to read the Lua value as a string:
if (value.Type is LuaValueType.String && value.TryRead<string>(out var configuredLuaValueText))
list.Add(configuredLuaValueText);
}
configuredValue = list;
successful = true;
}
if(dryRun)
return successful;
return HandleParsedValue(configPluginId, dryRun, successful, configMeta, configuredValue);
}
@@ -486,13 +486,13 @@ public static partial class ManagedConfiguration
// Determine the length of the Lua table and prepare a set to hold the parsed values:
var len = valueTable.ArrayLength;
var set = new HashSet<TValue>(len);
// Iterate over each entry in the Lua table:
for (var index = 1; index <= len; index++)
{
// Retrieve the Lua value at the current index:
var value = valueTable[index];
// Step 2a -- try to read the Lua value as a string:
if (value.Type is LuaValueType.String && value.TryRead<string>(out var configuredLuaValueText))
{
@@ -517,7 +517,7 @@ public static partial class ManagedConfiguration
}
// ReSharper restore MethodOverloadWithOptionalParameter
/// <summary>
/// Attempts to process the configuration settings from a Lua table for enum set types.
/// </summary>
@@ -546,14 +546,14 @@ public static partial class ManagedConfiguration
//
// Handle configured enum sets
//
// Check if that configuration was registered:
if(!TryGet(configSelection, propertyExpression, out var configMeta))
return false;
var successful = false;
var configuredValue = configMeta.Default;
// Step 1 -- try to read the Lua value (we expect a table) out of the Lua table:
if (settings.TryGetValue(SettingsManager.ToSettingName(propertyExpression), out var configuredLuaList) &&
configuredLuaList.Type is LuaValueType.Table &&
@@ -562,13 +562,13 @@ public static partial class ManagedConfiguration
// Determine the length of the Lua table and prepare a set to hold the parsed values:
var len = valueTable.ArrayLength;
var set = new HashSet<TValue>(len);
// Iterate over each entry in the Lua table:
for (var index = 1; index <= len; index++)
{
// Retrieve the Lua value at the current index:
var value = valueTable[index];
// Step 2 -- try to read the Lua value as a string:
if (value.Type is LuaValueType.String && value.TryRead<string>(out var configuredLuaValueText))
{
@@ -577,14 +577,14 @@ public static partial class ManagedConfiguration
set.Add((TValue)configuredEnum);
}
}
configuredValue = set;
successful = true;
}
if(dryRun)
return successful;
return HandleParsedValue(configPluginId, dryRun, successful, configMeta, configuredValue);
}
@@ -629,13 +629,13 @@ public static partial class ManagedConfiguration
// Determine the length of the Lua table and prepare a set to hold the parsed values:
var len = valueTable.ArrayLength;
var set = new HashSet<TValue>(len);
// Iterate over each entry in the Lua table:
for (var index = 1; index <= len; index++)
{
// Retrieve the Lua value at the current index:
var value = valueTable[index];
// Step 2 -- try to read the Lua value as a string:
if (value.Type is LuaValueType.String && value.TryRead<string>(out var configuredLuaValueText))
{
@@ -671,7 +671,7 @@ public static partial class ManagedConfiguration
return successful;
}
/// <summary>
/// Attempts to process the configuration settings from a Lua table for string set types.
/// </summary>
@@ -697,14 +697,14 @@ public static partial class ManagedConfiguration
//
// Handle configured string sets
//
// Check if that configuration was registered:
if(!TryGet(configSelection, propertyExpression, out var configMeta))
return false;
var successful = false;
var configuredValue = configMeta.Default;
// Step 1 -- try to read the Lua value (we expect a table) out of the Lua table:
if (settings.TryGetValue(SettingsManager.ToSettingName(propertyExpression), out var configuredLuaList) &&
configuredLuaList.Type is LuaValueType.Table &&
@@ -713,28 +713,28 @@ public static partial class ManagedConfiguration
// Determine the length of the Lua table and prepare a set to hold the parsed values:
var len = valueTable.ArrayLength;
var set = new HashSet<string>(len);
// Iterate over each entry in the Lua table:
for (var index = 1; index <= len; index++)
{
// Retrieve the Lua value at the current index:
var value = valueTable[index];
// Step 2 -- try to read the Lua value as a string:
if (value.Type is LuaValueType.String && value.TryRead<string>(out var configuredLuaValueText))
set.Add(configuredLuaValueText);
}
configuredValue = set;
successful = true;
}
if(dryRun)
return successful;
return HandleParsedValue(configPluginId, dryRun, successful, configMeta, configuredValue);
}
/// <summary>
/// Attempts to process the configuration settings from a Lua table for string dictionary types.
/// </summary>
@@ -760,14 +760,14 @@ public static partial class ManagedConfiguration
//
// Handle configured string dictionaries (both keys and values are strings)
//
// Check if that configuration was registered:
if(!TryGet(configSelection, propertyExpression, out var configMeta))
return false;
var successful = false;
var configuredValue = configMeta.Default;
// Step 1 -- try to read the Lua value (we expect a table) out of the Lua table:
if (settings.TryGetValue(SettingsManager.ToSettingName(propertyExpression), out var configuredLuaList) &&
configuredLuaList.Type is LuaValueType.Table &&
@@ -778,7 +778,7 @@ public static partial class ManagedConfiguration
var len = valueTable.HashMapCount;
if (len > 0)
configuredValue.Clear();
// In order to iterate over all key-value pairs in the Lua table, we have to use TryGetNext.
// Thus, we initialize the previous key variable to Nil and keep calling TryGetNext until
// there are no more pairs:
@@ -787,25 +787,101 @@ public static partial class ManagedConfiguration
{
// Update the previous key for the next iteration:
previousKey = pair.Key;
// Try to read both the key and the value as strings:
var hadKey = pair.Key.TryRead<string>(out var key);
var hadValue = pair.Value.TryRead<string>(out var value);
// If both key and value were read successfully, add them to the dictionary:
if (hadKey && hadValue)
configuredValue[key] = value;
}
successful = true;
}
if(dryRun)
return successful;
return HandleParsedValue(configPluginId, dryRun, successful, configMeta, configuredValue);
}
/// <summary>
/// Attempts to process the configuration settings from a Lua table for enum dictionary types.
/// </summary>
/// <remarks>
/// When the configuration is successfully processed, it updates the configuration metadata with the configured value.
/// Furthermore, it applies the configured managed state to the provided configuration plugin ID.
/// The setting's value is set to the configured value when locked or when the editable default should apply.
/// </remarks>
/// <param name="configPluginId">The ID of the related configuration plugin.</param>
/// <param name="settings">The Lua table containing the settings to process.</param>
/// <param name="configSelection">The expression to select the configuration class.</param>
/// <param name="propertyExpression">The expression to select the property within the configuration class.</param>
/// <param name="dryRun">When true, the method will not apply any changes but only check if the configuration can be read.</param>
/// <typeparam name="TClass">The type of the configuration class.</typeparam>
/// <typeparam name="TKey">The enum type of the dictionary keys.</typeparam>
/// <typeparam name="TValue">The enum type of the dictionary values.</typeparam>
/// <returns>True when the configuration was successfully processed, otherwise false.</returns>
public static bool TryProcessConfiguration<TClass, TKey, TValue>(
Expression<Func<Data, TClass>> configSelection,
Expression<Func<TClass, Dictionary<TKey, TValue>>> propertyExpression,
Guid configPluginId,
LuaTable settings,
bool dryRun)
where TKey : struct, Enum
where TValue : struct, Enum
{
//
// Handle configured enum dictionaries (Lua keys and values are strings)
//
// Check if that configuration was registered:
if(!TryGet(configSelection, propertyExpression, out var configMeta))
return false;
var successful = false;
var configuredValue = new Dictionary<TKey, TValue>(configMeta.Default);
// Step 1 -- try to read the Lua value (we expect a table) out of the Lua table:
if (settings.TryGetValue(SettingsManager.ToSettingName(propertyExpression), out var configuredLuaList) &&
configuredLuaList.Type is LuaValueType.Table &&
configuredLuaList.TryRead<LuaTable>(out var valueTable))
{
configuredValue.Clear();
// In order to iterate over all key-value pairs in the Lua table, we have to use TryGetNext.
// Thus, we initialize the previous key variable to Nil and keep calling TryGetNext until
// there are no more pairs:
var previousKey = LuaValue.Nil;
while(valueTable.TryGetNext(previousKey, out var pair))
{
// Update the previous key for the next iteration:
previousKey = pair.Key;
// Try to read both the key and the value as strings:
var hadKey = pair.Key.TryRead<string>(out var keyText);
var hadValue = pair.Value.TryRead<string>(out var valueText);
// If both key and value were read successfully, parse and add them to the dictionary:
if (hadKey
&& hadValue
&& Enum.TryParse<TKey>(keyText, true, out var key)
&& Enum.TryParse<TValue>(valueText, true, out var value))
configuredValue[key] = value;
}
successful = true;
}
if(dryRun)
return successful;
var settingName = SettingName(propertyExpression);
var managedMode = ReadManagedConfigurationMode(propertyExpression, settings);
return HandleParsedScalarValue(configPluginId, dryRun, successful, configMeta, configuredValue, managedMode, settingName);
}
/// <summary>
/// Handles the parsed configuration value based on whether the parsing was successful and whether it's a dry run.
/// </summary>
@@ -826,14 +902,14 @@ public static partial class ManagedConfiguration
{
if(dryRun)
return successful;
switch (successful)
{
case true:
//
// Case: the setting was configured, and we could read the value successfully.
//
// Set the configured value and lock the configuration:
configMeta.SetValue(configuredValue);
configMeta.LockConfiguration(configPluginId);
@@ -852,7 +928,7 @@ public static partial class ManagedConfiguration
//
configMeta.ResetLockedConfiguration();
break;
case false:
//
// Case: the setting was not configured, or we could not read the value successfully.
@@ -946,8 +1022,12 @@ public static partial class ManagedConfiguration
{
null => string.Empty,
string text => text,
System.Collections.IDictionary dictionary => string.Join(";", dictionary.Keys
.Cast<object>()
.OrderBy(key => key.ToString(), StringComparer.Ordinal)
.Select(key => $"{key}:{dictionary[key]}")),
IFormattable formattable => formattable.ToString(null, CultureInfo.InvariantCulture),
_ => value.ToString() ?? string.Empty,
};
}
@@ -66,13 +66,13 @@ public static partial class ManagedConfiguration
// we ignore the register call and return the default value:
if(configSelection is null)
return defaultValue;
var configPath = Path(configSelection, propertyExpression);
// If the metadata already exists for this configuration path, we return the default value:
if (METADATA.ContainsKey(configPath))
return defaultValue;
// Not registered yet, so we register it now:
METADATA[configPath] = new ConfigMeta<TClass, string>(configSelection, propertyExpression)
{
@@ -104,13 +104,13 @@ public static partial class ManagedConfiguration
// we ignore the register call and return the default value:
if(configSelection is null)
return [defaultValue];
var configPath = Path(configSelection, propertyExpression);
// If the metadata already exists for this configuration path, we return the default value:
if (METADATA.ContainsKey(configPath))
return [defaultValue];
// Not registered yet, so we register it now:
METADATA[configPath] = new ConfigMeta<TClass, IList<TValue>>(configSelection, propertyExpression)
{
@@ -142,13 +142,13 @@ public static partial class ManagedConfiguration
// we ignore the register call and return the default value:
if(configSelection is null)
return [..defaultValues];
var configPath = Path(configSelection, propertyExpression);
// If the metadata already exists for this configuration path, we return the default value:
if (METADATA.ContainsKey(configPath))
return [..defaultValues];
// Not registered yet, so we register it now:
METADATA[configPath] = new ConfigMeta<TClass, IList<TValue>>(configSelection, propertyExpression)
{
@@ -179,13 +179,13 @@ public static partial class ManagedConfiguration
// we ignore the register call and return the default value:
if (configSelection is null)
return [defaultValue];
var configPath = Path(configSelection, propertyExpression);
// If the metadata already exists for this configuration path, we return the default value:
if (METADATA.ContainsKey(configPath))
return [defaultValue];
// Not registered yet, so we register it now:
METADATA[configPath] = new ConfigMeta<TClass, ISet<TValue>>(configSelection, propertyExpression)
{
@@ -217,13 +217,13 @@ public static partial class ManagedConfiguration
// we ignore the register call and return the default value:
if (configSelection is null)
return [..defaultValues];
var configPath = Path(configSelection, propertyExpression);
// If the metadata already exists for this configuration path, we return the default value:
if (METADATA.ContainsKey(configPath))
return [..defaultValues];
// Not registered yet, so we register it now:
METADATA[configPath] = new ConfigMeta<TClass, ISet<TValue>>(configSelection, propertyExpression)
{
@@ -268,7 +268,48 @@ public static partial class ManagedConfiguration
{
Default = defaultValues,
};
return defaultValues;
}
/// <summary>
/// Registers a configuration setting with a default dictionary of enum key-value pairs.
/// </summary>
/// <remarks>
/// When the method is invoked with a null configSelection, the configuration path
/// is ignored, and the specified default values are returned without registration.
/// </remarks>
/// <param name="configSelection">The expression that selects the configuration class from the root Data model.</param>
/// <param name="propertyExpression">The expression to select the property within the configuration class.</param>
/// <param name="defaultValues">The default dictionary of values to use when the setting is not configured.</param>
/// <typeparam name="TClass">The type of the configuration class from which the property is selected.</typeparam>
/// <typeparam name="TKey">The enum type of the dictionary keys.</typeparam>
/// <typeparam name="TValue">The enum type of the dictionary values.</typeparam>
/// <returns>A dictionary containing the default values.</returns>
public static Dictionary<TKey, TValue> Register<TClass, TKey, TValue>(
Expression<Func<Data, TClass>>? configSelection,
Expression<Func<TClass, Dictionary<TKey, TValue>>> propertyExpression,
Dictionary<TKey, TValue> defaultValues)
where TKey : struct, Enum
where TValue : struct, Enum
{
// When called from the JSON deserializer by using the standard constructor,
// we ignore the register call and return the default value:
if (configSelection is null)
return new();
var configPath = Path(configSelection, propertyExpression);
// If the metadata already exists for this configuration path, we return the default value:
if (METADATA.ContainsKey(configPath))
return defaultValues;
// Not registered yet, so we register it now:
METADATA[configPath] = new ConfigMeta<TClass, Dictionary<TKey, TValue>>(configSelection, propertyExpression)
{
Default = defaultValues,
};
return defaultValues;
}
}
@@ -231,6 +231,44 @@ public static partial class ManagedConfiguration
return false;
}
/// <summary>
/// Attempts to retrieve the configuration metadata for an enum dictionary-based setting.
/// </summary>
/// <remarks>
/// When no configuration metadata is found, it returns a NoConfig instance with the default
/// value set to an empty dictionary. This allows the caller to handle the absence of configuration
/// gracefully. In such cases, the return value of the method will be false.
/// </remarks>
/// <param name="configSelection">The expression to select the configuration class.</param>
/// <param name="propertyExpression">The expression to select the property within the
/// configuration class.</param>
/// <param name="configMeta">The output parameter that will hold the configuration metadata
/// if found.</param>
/// <typeparam name="TClass">The type of the configuration class.</typeparam>
/// <typeparam name="TKey">The enum type of the dictionary keys.</typeparam>
/// <typeparam name="TValue">The enum type of the dictionary values.</typeparam>
/// <returns>True if the configuration metadata was found, otherwise false.</returns>
public static bool TryGet<TClass, TKey, TValue>(
Expression<Func<Data, TClass>> configSelection,
Expression<Func<TClass, Dictionary<TKey, TValue>>> propertyExpression,
out ConfigMeta<TClass, Dictionary<TKey, TValue>> configMeta)
where TKey : struct, Enum
where TValue : struct, Enum
{
var configPath = Path(configSelection, propertyExpression);
if (METADATA.TryGetValue(configPath, out var value) && value is ConfigMeta<TClass, Dictionary<TKey, TValue>> meta)
{
configMeta = meta;
return true;
}
configMeta = new NoConfig<TClass, Dictionary<TKey, TValue>>(configSelection, propertyExpression)
{
Default = new Dictionary<TKey, TValue>(),
};
return false;
}
/// <summary>
/// Checks if a configuration setting is left over from a configuration plugin that is no longer available.
/// If the configuration setting is locked and managed by a configuration plugin that is not available,
@@ -399,6 +437,42 @@ public static partial class ManagedConfiguration
return false;
}
public static bool IsConfigurationLeftOver<TClass, TKey, TValue>(
Expression<Func<Data, TClass>> configSelection,
Expression<Func<TClass, Dictionary<TKey, TValue>>> propertyExpression,
IEnumerable<IAvailablePlugin> availablePlugins)
where TKey : struct, Enum
where TValue : struct, Enum
{
if (!TryGet(configSelection, propertyExpression, out var configMeta))
return false;
if (configMeta.ManagedMode is ManagedConfigurationMode.EDITABLE_DEFAULT)
{
var plugin = availablePlugins.FirstOrDefault(x => x.Id == configMeta.EditableDefaultByConfigPluginId);
if (plugin is null)
{
configMeta.ClearEditableDefaultConfiguration();
ClearEditableDefaultState(SettingName(propertyExpression));
return true;
}
return false;
}
if (configMeta.LockedByConfigPluginId == Guid.Empty || !configMeta.IsLocked)
return false;
var lockedPlugin = availablePlugins.FirstOrDefault(x => x.Id == configMeta.LockedByConfigPluginId);
if (lockedPlugin is null)
{
configMeta.ResetLockedConfiguration();
return true;
}
return false;
}
private static string Path<TClass, TValue>(Expression<Func<Data, TClass>> configSelection, Expression<Func<TClass, TValue>> propertyExpression)
{
@@ -165,9 +165,9 @@ public sealed class SettingsManager
public ConfidenceLevel GetMinimumConfidenceLevel(Tools.Components component)
{
var minimumLevel = ConfidenceLevel.NONE;
var enforceGlobalMinimumConfidence = this.ConfigurationData.LLMProviders is { EnforceGlobalMinimumConfidence: true, GlobalMinimumConfidence: not ConfidenceLevel.NONE and not ConfidenceLevel.UNKNOWN };
var enforceGlobalMinimumConfidence = this.ConfigurationData.Confidence is { EnforceGlobalMinimumConfidence: true, GlobalMinimumConfidence: not ConfidenceLevel.NONE and not ConfidenceLevel.UNKNOWN };
if (enforceGlobalMinimumConfidence)
minimumLevel = this.ConfigurationData.LLMProviders.GlobalMinimumConfidence;
minimumLevel = this.ConfigurationData.Confidence.GlobalMinimumConfidence;
var componentMinimumLevel = component.MinimumConfidence(this);
if (componentMinimumLevel > minimumLevel)
@@ -402,7 +402,7 @@ public sealed class SettingsManager
if(llmProvider is LLMProviders.NONE)
return ConfidenceLevel.NONE;
switch (this.ConfigurationData.LLMProviders.ConfidenceScheme)
switch (this.ConfigurationData.Confidence.ConfidenceScheme)
{
case ConfidenceSchemes.TRUST_ALL:
return llmProvider switch
@@ -462,7 +462,7 @@ public sealed class SettingsManager
};
case ConfidenceSchemes.CUSTOM:
return this.ConfigurationData.LLMProviders.CustomConfidenceScheme.GetValueOrDefault(llmProvider, ConfidenceLevel.UNKNOWN);
return this.ConfigurationData.Confidence.CustomConfidenceScheme.GetValueOrDefault(llmProvider, ConfidenceLevel.UNKNOWN);
default:
return ConfidenceLevel.UNKNOWN;
@@ -24,7 +24,8 @@ public static class SettingsMigrations
configV1 = MigrateV1ToV2(logger, configV1);
configV1 = MigrateV2ToV3(logger, configV1);
var configV14 = MigrateV3ToV4(logger, configV1);
return MigrateV4ToV5(logger, configV14);
var configV15 = MigrateV4ToV5(logger, configV14);
return MigrateV5ToV6(logger, configV15);
case Version.V2:
var configV2 = JsonSerializer.Deserialize<DataV1V3>(configData, jsonOptions);
@@ -36,7 +37,8 @@ public static class SettingsMigrations
configV2 = MigrateV2ToV3(logger, configV2);
var configV24 = MigrateV3ToV4(logger, configV2);
return MigrateV4ToV5(logger, configV24);
var configV25 = MigrateV4ToV5(logger, configV24);
return MigrateV5ToV6(logger, configV25);
case Version.V3:
var configV3 = JsonSerializer.Deserialize<DataV1V3>(configData, jsonOptions);
@@ -47,8 +49,9 @@ public static class SettingsMigrations
}
var configV34 = MigrateV3ToV4(logger, configV3);
return MigrateV4ToV5(logger, configV34);
var configV35 = MigrateV4ToV5(logger, configV34);
return MigrateV5ToV6(logger, configV35);
case Version.V4:
var configV4 = JsonSerializer.Deserialize<DataV4>(configData, jsonOptions);
if (configV4 is null)
@@ -57,18 +60,29 @@ public static class SettingsMigrations
return new();
}
return MigrateV4ToV5(logger, configV4);
var configV45 = MigrateV4ToV5(logger, configV4);
return MigrateV5ToV6(logger, configV45);
default:
logger.LogInformation("No configuration migration is needed.");
var configV5 = JsonSerializer.Deserialize<Data>(configData, jsonOptions);
case Version.V5:
var configV5 = JsonSerializer.Deserialize<DataV5>(configData, jsonOptions);
if (configV5 is null)
{
logger.LogError("Failed to parse the v4 configuration. Using default values.");
logger.LogError("Failed to parse the v5 configuration. Using default values.");
return new();
}
return configV5;
return MigrateV5ToV6(logger, configV5);
default:
logger.LogInformation("No configuration migration is needed.");
var configV6 = JsonSerializer.Deserialize<Data>(configData, jsonOptions);
if (configV6 is null)
{
logger.LogError("Failed to parse the v6 configuration. Using default values.");
return new();
}
return configV6;
}
}
@@ -83,9 +97,9 @@ public static class SettingsMigrations
return new()
{
Version = Version.V2,
Providers = previousData.Providers.Select(provider => provider with { IsSelfHosted = false, Hostname = string.Empty }).ToList(),
EnableSpellchecking = previousData.EnableSpellchecking,
IsSavingEnergy = previousData.IsSavingEnergy,
NextProviderNum = previousData.NextProviderNum,
@@ -93,7 +107,7 @@ public static class SettingsMigrations
UpdateInterval = previousData.UpdateInterval,
};
}
private static DataV1V3 MigrateV2ToV3(ILogger<SettingsManager> logger, DataV1V3 previousData)
{
//
@@ -109,7 +123,7 @@ public static class SettingsMigrations
{
if(provider.IsSelfHosted)
return provider with { Host = Host.LM_STUDIO };
return provider with { Host = Host.NONE };
}).ToList(),
@@ -129,14 +143,14 @@ public static class SettingsMigrations
// Summary:
// We grouped the settings into different categories.
//
logger.LogInformation("Migrating from v3 to v4...");
return new()
{
Version = Version.V4,
Providers = previousConfig.Providers,
NextProviderNum = previousConfig.NextProviderNum,
App = new(x => x.App)
{
EnableSpellchecking = previousConfig.EnableSpellchecking,
@@ -144,27 +158,27 @@ public static class SettingsMigrations
UpdateInterval = previousConfig.UpdateInterval,
NavigationBehavior = previousConfig.NavigationBehavior,
},
Chat = new()
{
ShortcutSendBehavior = previousConfig.ShortcutSendBehavior,
PreselectOptions = previousConfig.PreselectChatOptions,
PreselectedProvider = previousConfig.PreselectedChatProvider,
},
Workspace = new()
{
StorageBehavior = previousConfig.WorkspaceStorageBehavior,
StorageTemporaryMaintenancePolicy = previousConfig.WorkspaceStorageTemporaryMaintenancePolicy,
},
IconFinder = new()
{
PreselectOptions = previousConfig.PreselectIconOptions,
PreselectedProvider = previousConfig.PreselectedIconProvider,
PreselectedSource = previousConfig.PreselectedIconSource,
},
Translation = new()
{
PreselectLiveTranslation = previousConfig.PreselectLiveTranslation,
@@ -177,7 +191,7 @@ public static class SettingsMigrations
PreselectContentCleanerAgent = previousConfig.PreselectContentCleanerAgentForTranslation,
PreselectWebContentReader = previousConfig.PreselectWebContentReaderForTranslation,
},
Coding = new()
{
PreselectOptions = previousConfig.PreselectCodingOptions,
@@ -186,7 +200,7 @@ public static class SettingsMigrations
PreselectedOtherProgrammingLanguage = previousConfig.PreselectedCodingOtherLanguage,
PreselectCompilerMessages = previousConfig.PreselectCodingCompilerMessages,
},
TextSummarizer = new()
{
PreselectOptions = previousConfig.PreselectTextSummarizerOptions,
@@ -199,7 +213,7 @@ public static class SettingsMigrations
PreselectContentCleanerAgent = previousConfig.PreselectContentCleanerAgentForTextSummarizer,
PreselectWebContentReader = previousConfig.PreselectWebContentReaderForTextSummarizer,
},
TextContentCleaner = new()
{
PreselectAgentOptions = previousConfig.PreselectAgentTextContentCleanerOptions,
@@ -207,14 +221,14 @@ public static class SettingsMigrations
},
};
}
private static Data MigrateV4ToV5(ILogger<SettingsManager> logger, DataV4 previousConfig)
private static DataV5 MigrateV4ToV5(ILogger<SettingsManager> logger, DataV4 previousConfig)
{
//
// Summary:
// We renamed the LLM provider enum.
//
logger.LogInformation("Migrating from v4 to v5...");
return new()
{
@@ -241,4 +255,68 @@ public static class SettingsMigrations
MyTasks = previousConfig.MyTasks,
};
}
private static Data MigrateV5ToV6(ILogger<SettingsManager> logger, DataV5 previousConfig)
{
//
// Summary:
// We moved confidence settings out of LLM provider settings.
//
logger.LogInformation("Migrating from v5 to v6...");
return new()
{
Version = Version.V6,
Providers = previousConfig.Providers,
Confidence = new(x => x.Confidence)
{
EnforceGlobalMinimumConfidence = previousConfig.LLMProviders.EnforceGlobalMinimumConfidence,
GlobalMinimumConfidence = previousConfig.LLMProviders.GlobalMinimumConfidence,
ShowProviderConfidence = previousConfig.LLMProviders.ShowProviderConfidence,
ConfidenceScheme = previousConfig.LLMProviders.ConfidenceScheme,
CustomConfidenceScheme = previousConfig.LLMProviders.CustomConfidenceScheme,
},
EmbeddingProviders = previousConfig.EmbeddingProviders,
TranscriptionProviders = previousConfig.TranscriptionProviders,
DataSources = previousConfig.DataSources,
Profiles = previousConfig.Profiles,
ChatTemplates = previousConfig.ChatTemplates,
EnabledPlugins = previousConfig.EnabledPlugins,
ManagedEditableDefaults = previousConfig.ManagedEditableDefaults,
AssistantPluginAudits = previousConfig.AssistantPluginAudits,
NextProviderNum = previousConfig.NextProviderNum,
NextEmbeddingNum = previousConfig.NextEmbeddingNum,
NextTranscriptionNum = previousConfig.NextTranscriptionNum,
NextDataSourceNum = previousConfig.NextDataSourceNum,
NextProfileNum = previousConfig.NextProfileNum,
NextChatTemplateNum = previousConfig.NextChatTemplateNum,
NextDocumentAnalysisPolicyNum = previousConfig.NextDocumentAnalysisPolicyNum,
App = previousConfig.App,
Chat = previousConfig.Chat,
Workspace = previousConfig.Workspace,
IconFinder = previousConfig.IconFinder,
Translation = previousConfig.Translation,
Coding = previousConfig.Coding,
ERI = previousConfig.ERI,
DocumentAnalysis = previousConfig.DocumentAnalysis,
MandatoryInformation = previousConfig.MandatoryInformation,
TextSummarizer = previousConfig.TextSummarizer,
TextContentCleaner = previousConfig.TextContentCleaner,
AgentDataSourceSelection = previousConfig.AgentDataSourceSelection,
AgentRetrievalContextValidation = previousConfig.AgentRetrievalContextValidation,
AssistantPluginAudit = previousConfig.AssistantPluginAudit,
Agenda = previousConfig.Agenda,
GrammarSpelling = previousConfig.GrammarSpelling,
RewriteImprove = previousConfig.RewriteImprove,
PromptOptimizer = previousConfig.PromptOptimizer,
EMail = previousConfig.EMail,
SlideBuilder = previousConfig.SlideBuilder,
LegalCheck = previousConfig.LegalCheck,
Synonyms = previousConfig.Synonyms,
MyTasks = previousConfig.MyTasks,
JobPostings = previousConfig.JobPostings,
BiasOfTheDay = previousConfig.BiasOfTheDay,
I18N = previousConfig.I18N,
};
}
}
@@ -13,4 +13,5 @@ public enum Version
V3,
V4,
V5,
V6,
}