Fixed an incomplete plugin start with extensive enterprise configurations (#1033)

This commit is contained in:
Thorsten Sommer authored and GitHub committed 2026-10-09 20:11:15 +02:00
1 parent bdf828fa4f
commit 56215aa6dd
16 files changed
+493 -70

No files matched your search

@@ -186,7 +186,7 @@ public readonly record struct DataSourceERI_V1 : IERIDataSource
return null;
}
public static bool TryParseConfiguration(int idx, LuaTable table, Guid configPluginId, out DataSourceERI_V1 dataSource)
public static bool TryParseConfiguration(int idx, LuaTable table, Guid configPluginId, bool dryRun, out DataSourceERI_V1 dataSource)
{
dataSource = default;
if (!table.TryGetValue("Id", out var idValue) || !idValue.TryRead<string>(out var idText) || !Guid.TryParse(idText, out var id))
@@ -301,7 +301,7 @@ public readonly record struct DataSourceERI_V1 : IERIDataSource
EnterpriseConfigurationPluginId = configPluginId,
};
return TryQueueEnterpriseSecret(idx, table, configPluginId, dataSource);
return TryQueueEnterpriseSecret(idx, table, configPluginId, dataSource, dryRun);
}
/// <summary>
@@ -360,7 +360,7 @@ public readonly record struct DataSourceERI_V1 : IERIDataSource
""";
}
private static bool TryQueueEnterpriseSecret(int idx, LuaTable table, Guid configPluginId, DataSourceERI_V1 dataSource)
private static bool TryQueueEnterpriseSecret(int idx, LuaTable table, Guid configPluginId, DataSourceERI_V1 dataSource, bool dryRun)
{
var secretFieldName = dataSource.AuthMethod switch
{
@@ -397,6 +397,13 @@ public readonly record struct DataSourceERI_V1 : IERIDataSource
return false;
}
//
// A dry run only checks the configuration, so it queues nothing: the start which follows it
// would otherwise store the secret once more for every dry run before it:
//
if (dryRun)
return true;
PendingEnterpriseSecrets.Add(new(
$"{ISecretId.ENTERPRISE_KEY_PREFIX}::{dataSource.Id}",
dataSource.Name,
@@ -61,7 +61,7 @@ public sealed record EmbeddingProvider(
#endregion
public static bool TryParseEmbeddingProviderTable(int idx, LuaTable table, Guid configPluginId, string pluginPath, out ConfigurationBaseObject provider)
public static bool TryParseEmbeddingProviderTable(int idx, LuaTable table, Guid configPluginId, string pluginPath, bool dryRun, out ConfigurationBaseObject provider)
{
provider = NONE;
if (!table.TryGetValue("Id", out var idValue) || !idValue.TryRead<string>(out var idText) || !Guid.TryParse(idText, out var id))
@@ -189,13 +189,20 @@ public sealed record EmbeddingProvider(
{
if (encryption.TryDecrypt(apiKeyText, out var decryptedApiKey))
{
// Queue the API key for storage in the OS keyring:
PendingEnterpriseApiKeys.Add(new(
$"{ISecretId.ENTERPRISE_KEY_PREFIX}::{usedLLMProvider.ToSecretId()}",
name,
decryptedApiKey,
SecretStoreType.EMBEDDING_PROVIDER));
LOGGER.LogDebug($"Successfully decrypted API key for embedding provider {idx}. It will be stored in the OS keyring. (Plugin ID: {configPluginId})");
//
// Queue the API key for storage in the OS keyring. A dry run only checks the
// configuration, so it queues nothing: the start which follows it would
// otherwise store the key once more for every dry run before it:
//
if (!dryRun)
{
PendingEnterpriseApiKeys.Add(new(
$"{ISecretId.ENTERPRISE_KEY_PREFIX}::{usedLLMProvider.ToSecretId()}",
name,
decryptedApiKey,
SecretStoreType.EMBEDDING_PROVIDER));
LOGGER.LogDebug($"Successfully decrypted API key for embedding provider {idx}. It will be stored in the OS keyring. (Plugin ID: {configPluginId})");
}
}
else
LOGGER.LogWarning($"Failed to decrypt API key for embedding provider {idx}. The encryption secret may be incorrect. (Plugin ID: {configPluginId})");
+15 -8
View File
@@ -88,7 +88,7 @@ public sealed record Provider(
#endregion
public static bool TryParseProviderTable(int idx, LuaTable table, Guid configPluginId, string pluginPath, out ConfigurationBaseObject provider)
public static bool TryParseProviderTable(int idx, LuaTable table, Guid configPluginId, string pluginPath, bool dryRun, out ConfigurationBaseObject provider)
{
provider = NONE;
if (!table.TryGetValue("Id", out var idValue) || !idValue.TryRead<string>(out var idText) || !Guid.TryParse(idText, out var id))
@@ -210,13 +210,20 @@ public sealed record Provider(
{
if (encryption.TryDecrypt(apiKeyText, out var decryptedApiKey))
{
// Queue the API key for storage in the OS keyring:
PendingEnterpriseApiKeys.Add(new(
$"{ISecretId.ENTERPRISE_KEY_PREFIX}::{usedLLMProvider.ToSecretId()}",
instanceName,
decryptedApiKey,
SecretStoreType.LLM_PROVIDER));
LOGGER.LogDebug($"Successfully decrypted API key for provider {idx}. It will be stored in the OS keyring. (Plugin ID: {configPluginId})");
//
// Queue the API key for storage in the OS keyring. A dry run only checks the
// configuration, so it queues nothing: the start which follows it would
// otherwise store the key once more for every dry run before it:
//
if (!dryRun)
{
PendingEnterpriseApiKeys.Add(new(
$"{ISecretId.ENTERPRISE_KEY_PREFIX}::{usedLLMProvider.ToSecretId()}",
instanceName,
decryptedApiKey,
SecretStoreType.LLM_PROVIDER));
LOGGER.LogDebug($"Successfully decrypted API key for provider {idx}. It will be stored in the OS keyring. (Plugin ID: {configPluginId})");
}
}
else
LOGGER.LogWarning($"Failed to decrypt API key for provider {idx}. The encryption secret may be incorrect. (Plugin ID: {configPluginId})");
@@ -1,4 +1,6 @@
using System.Diagnostics;
using System.Linq.Expressions;
using System.Text;
using System.Text.Json;
using AIStudio.Provider;
@@ -30,6 +32,16 @@ public sealed class SettingsManager
Converters = { new TolerantEnumConverter() },
};
/// <summary>
/// From this duration on, storing the settings is reported as slow.
/// </summary>
/// <remarks>
/// The settings file is a few hundred kilobytes at most, so storing it usually takes some
/// milliseconds. Far beyond that, something is blocking: on one machine, every store took 18
/// seconds, and each one held up the start of AI Studio.
/// </remarks>
private static readonly TimeSpan SLOW_STORE_THRESHOLD = TimeSpan.FromSeconds(1);
private readonly ILogger<SettingsManager> logger;
private readonly RustService rustService;
@@ -337,10 +349,24 @@ public sealed class SettingsManager
return;
}
//
// Serializing and writing are measured on their own, because a slow store has two
// different causes: a property which asks the file system or the network while it is
// serialized, or a file system which is slow to write, e.g., because of a virus scanner
// or a synchronized profile folder. Only the two durations tell them apart:
//
var serializingStartedAt = Stopwatch.GetTimestamp();
var settingsJson = JsonSerializer.Serialize(this.ConfigurationData, JSON_OPTIONS);
var serializingDuration = Stopwatch.GetElapsedTime(serializingStartedAt);
var writingStartedAt = Stopwatch.GetTimestamp();
var settingsPath = Path.Combine(ConfigDirectory!, SETTINGS_FILENAME);
await this.StoreSerializedSettings(settingsJson, settingsPath);
await this.StoreSerializedVersionBackup(this.ConfigurationData.Version, settingsJson);
var writingDuration = Stopwatch.GetElapsedTime(writingStartedAt);
if (serializingDuration + writingDuration >= SLOW_STORE_THRESHOLD)
this.logger.LogWarning($"Storing the settings took {(serializingDuration + writingDuration).TotalMilliseconds:F0} ms, which is unusually long: serializing took {serializingDuration.TotalMilliseconds:F0} ms, writing took {writingDuration.TotalMilliseconds:F0} ms. The settings are {Encoding.UTF8.GetByteCount(settingsJson) / 1024} KB. A slow serialization points to a setting which reaches out to the file system or the network while it is serialized; slow writing points to the file system itself, e.g., a virus scanner or a synchronized profile folder.");
}
finally
{
@@ -56,7 +56,7 @@ public sealed record TranscriptionProvider(
#endregion
public static bool TryParseTranscriptionProviderTable(int idx, LuaTable table, Guid configPluginId, string pluginPath, out ConfigurationBaseObject provider)
public static bool TryParseTranscriptionProviderTable(int idx, LuaTable table, Guid configPluginId, string pluginPath, bool dryRun, out ConfigurationBaseObject provider)
{
provider = NONE;
if (!table.TryGetValue("Id", out var idValue) || !idValue.TryRead<string>(out var idText) || !Guid.TryParse(idText, out var id))
@@ -160,13 +160,20 @@ public sealed record TranscriptionProvider(
{
if (encryption.TryDecrypt(apiKeyText, out var decryptedApiKey))
{
// Queue the API key for storage in the OS keyring:
PendingEnterpriseApiKeys.Add(new(
$"{ISecretId.ENTERPRISE_KEY_PREFIX}::{usedLLMProvider.ToSecretId()}",
name,
decryptedApiKey,
SecretStoreType.TRANSCRIPTION_PROVIDER));
LOGGER.LogDebug($"Successfully decrypted API key for transcription provider {idx}. It will be stored in the OS keyring. (Plugin ID: {configPluginId})");
//
// Queue the API key for storage in the OS keyring. A dry run only checks the
// configuration, so it queues nothing: the start which follows it would
// otherwise store the key once more for every dry run before it:
//
if (!dryRun)
{
PendingEnterpriseApiKeys.Add(new(
$"{ISecretId.ENTERPRISE_KEY_PREFIX}::{usedLLMProvider.ToSecretId()}",
name,
decryptedApiKey,
SecretStoreType.TRANSCRIPTION_PROVIDER));
LOGGER.LogDebug($"Successfully decrypted API key for transcription provider {idx}. It will be stored in the OS keyring. (Plugin ID: {configPluginId})");
}
}
else
LOGGER.LogWarning($"Failed to decrypt API key for transcription provider {idx}. The encryption secret may be incorrect. (Plugin ID: {configPluginId})");