2024-12-03 15:24:40 +01:00
using System.Text.Json.Serialization ;
using AIStudio.Provider ;
2026-01-09 15:41:54 +01:00
using AIStudio.Tools.PluginSystem ;
2026-05-22 15:46:03 +02:00
using SharedTools ;
2024-12-03 15:24:40 +01:00
using Host = AIStudio . Provider . SelfHosted . Host ;
2026-05-22 15:46:03 +02:00
using LuaTable = Lua . LuaTable ;
2024-12-03 15:24:40 +01:00
namespace AIStudio.Settings ;
2026-01-09 15:41:54 +01:00
public sealed record EmbeddingProvider (
2024-12-03 15:24:40 +01:00
uint Num ,
string Id ,
string Name ,
LLMProviders UsedLLMProvider ,
Model Model ,
bool IsSelfHosted = false ,
2026-01-09 15:41:54 +01:00
bool IsEnterpriseConfiguration = false ,
Guid EnterpriseConfigurationPluginId = default ,
2024-12-03 15:24:40 +01:00
string Hostname = "http://localhost:1234" ,
2026-01-09 15:41:54 +01:00
Host Host = Host . NONE ) : ConfigurationBaseObject , ISecretId
2024-12-03 15:24:40 +01:00
{
2026-01-09 15:41:54 +01:00
private static readonly ILogger < EmbeddingProvider > LOGGER = Program . LOGGER_FACTORY . CreateLogger < EmbeddingProvider >();
public static readonly EmbeddingProvider NONE = new ();
public EmbeddingProvider () : this (
0 ,
Guid . Empty . ToString (),
string . Empty ,
LLMProviders . NONE ,
default ,
false ,
false ,
Guid . Empty )
{
}
2024-12-03 15:24:40 +01:00
public override string ToString () => this . Name ;
2026-01-09 15:41:54 +01:00
2024-12-03 15:24:40 +01:00
#region Implementation of ISecretId
2026-01-09 15:41:54 +01:00
2024-12-03 15:24:40 +01:00
/// <inheritdoc />
[JsonIgnore]
2026-06-20 15:55:09 +02:00
public string SecretId => this . IsEnterpriseConfiguration ? $"{ISecretId.ENTERPRISE_KEY_PREFIX}::{this.UsedLLMProvider.ToSecretId()}" : this . UsedLLMProvider . ToSecretId ();
2026-01-09 15:41:54 +01:00
2024-12-03 15:24:40 +01:00
/// <inheritdoc />
[JsonIgnore]
public string SecretName => this . Name ;
2026-01-09 15:41:54 +01:00
2024-12-03 15:24:40 +01:00
#endregion
2026-01-09 15:41:54 +01:00
public static bool TryParseEmbeddingProviderTable ( int idx , LuaTable table , Guid configPluginId , out ConfigurationBaseObject provider )
{
provider = NONE ;
if (! table . TryGetValue ( "Id" , out var idValue ) || ! idValue . TryRead < string >( out var idText ) || ! Guid . TryParse ( idText , out var id ))
{
2026-02-19 20:43:47 +01:00
LOGGER . LogWarning ( $"The configured embedding provider {idx} does not contain a valid ID. The ID must be a valid GUID. (Plugin ID: {configPluginId})" );
2026-01-09 15:41:54 +01:00
return false ;
}
if (! table . TryGetValue ( "Name" , out var nameValue ) || ! nameValue . TryRead < string >( out var name ))
{
2026-02-19 20:43:47 +01:00
LOGGER . LogWarning ( $"The configured embedding provider {idx} does not contain a valid name. (Plugin ID: {configPluginId})" );
2026-01-09 15:41:54 +01:00
return false ;
}
if (! table . TryGetValue ( "UsedLLMProvider" , out var usedLLMProviderValue ) || ! usedLLMProviderValue . TryRead < string >( out var usedLLMProviderText ) || ! Enum . TryParse < LLMProviders >( usedLLMProviderText , true , out var usedLLMProvider ))
{
2026-02-19 20:43:47 +01:00
LOGGER . LogWarning ( $"The configured embedding provider {idx} does not contain a valid LLM provider enum value. (Plugin ID: {configPluginId})" );
2026-01-09 15:41:54 +01:00
return false ;
}
if (! table . TryGetValue ( "Host" , out var hostValue ) || ! hostValue . TryRead < string >( out var hostText ) || ! Enum . TryParse < Host >( hostText , true , out var host ))
{
2026-02-19 20:43:47 +01:00
LOGGER . LogWarning ( $"The configured embedding provider {idx} does not contain a valid host enum value. (Plugin ID: {configPluginId})" );
2026-01-09 15:41:54 +01:00
return false ;
}
if (! table . TryGetValue ( "Hostname" , out var hostnameValue ) || ! hostnameValue . TryRead < string >( out var hostname ))
{
2026-02-19 20:43:47 +01:00
LOGGER . LogWarning ( $"The configured embedding provider {idx} does not contain a valid hostname. (Plugin ID: {configPluginId})" );
2026-01-09 15:41:54 +01:00
return false ;
}
if (! table . TryGetValue ( "Model" , out var modelValue ) || ! modelValue . TryRead < LuaTable >( out var modelTable ))
{
2026-02-19 20:43:47 +01:00
LOGGER . LogWarning ( $"The configured embedding provider {idx} does not contain a valid model table. (Plugin ID: {configPluginId})" );
2026-01-09 15:41:54 +01:00
return false ;
}
2026-02-19 20:43:47 +01:00
if (! TryReadModelTable ( idx , modelTable , configPluginId , out var model ))
2026-01-09 15:41:54 +01:00
{
2026-02-19 20:43:47 +01:00
LOGGER . LogWarning ( $"The configured embedding provider {idx} does not contain a valid model configuration. (Plugin ID: {configPluginId})" );
2026-01-09 15:41:54 +01:00
return false ;
}
provider = new EmbeddingProvider
{
2026-02-01 14:50:19 +01:00
Num = 0 , // will be set later by the PluginConfigurationObject
2026-01-09 15:41:54 +01:00
Id = id . ToString (),
Name = name ,
UsedLLMProvider = usedLLMProvider ,
Model = model ,
IsSelfHosted = usedLLMProvider is LLMProviders . SELF_HOSTED ,
IsEnterpriseConfiguration = true ,
EnterpriseConfigurationPluginId = configPluginId ,
Hostname = hostname ,
Host = host ,
};
2026-02-07 22:59:41 +01:00
// Handle encrypted API key if present:
if ( table . TryGetValue ( "APIKey" , out var apiKeyValue ) && apiKeyValue . TryRead < string >( out var apiKeyText ) && ! string . IsNullOrWhiteSpace ( apiKeyText ))
{
if (! EnterpriseEncryption . IsEncrypted ( apiKeyText ))
2026-02-19 20:43:47 +01:00
LOGGER . LogWarning ( $"The configured embedding provider {idx} contains a plaintext API key. Only encrypted API keys (starting with 'ENC:v1:') are supported. (Plugin ID: {configPluginId})" );
2026-02-07 22:59:41 +01:00
else
{
var encryption = PluginFactory . EnterpriseEncryption ;
if ( encryption ?. IsAvailable == true )
{
if ( encryption . TryDecrypt ( apiKeyText , out var decryptedApiKey ))
{
// Queue the API key for storage in the OS keyring:
PendingEnterpriseApiKeys . Add ( new (
2026-06-20 15:55:09 +02:00
$"{ISecretId.ENTERPRISE_KEY_PREFIX}::{usedLLMProvider.ToSecretId()}" ,
2026-02-07 22:59:41 +01:00
name ,
decryptedApiKey ,
SecretStoreType . EMBEDDING_PROVIDER ));
2026-02-19 20:43:47 +01:00
LOGGER . LogDebug ( $"Successfully decrypted API key for embedding provider {idx}. It will be stored in the OS keyring. (Plugin ID: {configPluginId})" );
2026-02-07 22:59:41 +01:00
}
else
2026-02-19 20:43:47 +01:00
LOGGER . LogWarning ( $"Failed to decrypt API key for embedding provider {idx}. The encryption secret may be incorrect. (Plugin ID: {configPluginId})" );
2026-02-07 22:59:41 +01:00
}
else
2026-02-19 20:43:47 +01:00
LOGGER . LogWarning ( $"The configured embedding provider {idx} contains an encrypted API key, but no encryption secret is configured. (Plugin ID: {configPluginId})" );
2026-02-07 22:59:41 +01:00
}
}
2026-01-09 15:41:54 +01:00
return true ;
}
2026-02-19 20:43:47 +01:00
private static bool TryReadModelTable ( int idx , LuaTable table , Guid configPluginId , out Model model )
2026-01-09 15:41:54 +01:00
{
model = default ;
if (! table . TryGetValue ( "Id" , out var idValue ) || ! idValue . TryRead < string >( out var id ))
{
2026-02-19 20:43:47 +01:00
LOGGER . LogWarning ( $"The configured embedding provider {idx} does not contain a valid model ID. (Plugin ID: {configPluginId})" );
2026-01-09 15:41:54 +01:00
return false ;
}
if (! table . TryGetValue ( "DisplayName" , out var displayNameValue ) || ! displayNameValue . TryRead < string >( out var displayName ))
{
2026-02-19 20:43:47 +01:00
LOGGER . LogWarning ( $"The configured embedding provider {idx} does not contain a valid model display name. (Plugin ID: {configPluginId})" );
2026-01-09 15:41:54 +01:00
return false ;
}
model = new ( id , displayName );
return true ;
}
2026-02-07 22:59:41 +01:00
/// <summary>
/// Exports the embedding provider configuration as a Lua configuration section.
/// </summary>
/// <param name="encryptedApiKey">Optional encrypted API key to include in the export.</param>
/// <returns>A Lua configuration section string.</returns>
public string ExportAsConfigurationSection ( string? encryptedApiKey = null )
{
var apiKeyLine = string . Empty ;
if (! string . IsNullOrWhiteSpace ( encryptedApiKey ))
{
apiKeyLine = $"""
[" APIKey "] = " { LuaTools . EscapeLuaString ( encryptedApiKey )} ",
""";
}
return $ $"""
CONFIG[" EMBEDDING_PROVIDERS "][#CONFIG[" EMBEDDING_PROVIDERS "]+1] = {
["Id"] = "{{Guid.NewGuid().ToString()}}" ,
["Name"] = "{{LuaTools.EscapeLuaString(this.Name)}}" ,
["UsedLLMProvider"] = "{{this.UsedLLMProvider}}" ,
["Host"] = "{{this.Host}}" ,
["Hostname"] = "{{LuaTools.EscapeLuaString(this.Hostname)}}" ,
{{ apiKeyLine }}
["Model"] = {
["Id"] = "{{LuaTools.EscapeLuaString(this.Model.Id)}}" ,
["DisplayName"] = "{{LuaTools.EscapeLuaString(this.Model.DisplayName ?? string.Empty)}}" ,
},
}
""";
}
}