using System.Text; using AIStudio.Provider; using AIStudio.Tools.PluginSystem; using SharedTools; namespace AIStudio.Tools.ToolCallingSystem; public sealed partial class ToolSettingsService { private const string LOCKED_SETTINGS = "DataTools.LockedToolSettings"; private const string DEFAULT_SETTINGS = "DataTools.DefaultToolSettings"; private const string MINIMUM_CONFIDENCE = "DataTools.MinimumProviderConfidenceByToolId"; private static string TB(string fallbackEN) => I18N.I.T(fallbackEN, typeof(ToolSettingsService).Namespace, nameof(ToolSettingsService)); /// /// Reads the saved, effective configuration and exports the selected areas. Incomplete tools /// may be exported too: administrators can finish the configuration in their Lua plugin. /// /// /// Uses the same organization overrides and keyring values as tool execution, without saving /// settings or writing to the keyring. The caller provides the admin-only UI and copies a /// successful, nonempty result to the clipboard.

/// Only explicitly selected areas are included. Missing values stay absent, explicitly empty /// non-secret values stay empty, and runtime defaults are not filled in. Secrets require /// opt-in and enterprise encryption, and are always locked, even in a default-value export. /// The optional minimum provider confidence is also always a fixed requirement. ///
public async Task ExportAsync(ToolDefinition definition, IToolImplementation implementation, ToolSettingsExportOptions options) { var areas = implementation.GetExportableSettings(definition); var values = await this.GetSettingsAsync(definition); var confidence = settingsManager.GetMinimumProviderConfidenceForTool(definition.Id, definition.MinimumProviderConfidence); return BuildConfigurationSection(definition, areas, values, options, confidence, PluginFactory.EnterpriseEncryption); } /// /// Resolves selected areas to known fields in schema order. Overlapping areas include a /// field only once; unknown field names are ignored. Form visibility does not limit exports. /// private static IReadOnlyList GetSelectedFieldNames(ToolDefinition definition, IReadOnlyList areas, IReadOnlySet selectedAreaIds) { var selectedIds = new HashSet(selectedAreaIds, StringComparer.Ordinal); var selectedFields = areas.Where(area => selectedIds.Contains(area.Id)) .SelectMany(area => area.FieldNames) .ToHashSet(StringComparer.Ordinal); return definition.SettingsSchema.Properties.Keys.Where(selectedFields.Contains).ToList(); } /// /// Builds a fragment from one snapshot. A failed encryption returns no Lua, even when other /// fields have already been processed, so the caller cannot copy a partial export by accident. /// private static ToolSettingsExportResult BuildConfigurationSection(ToolDefinition definition, IReadOnlyList areas, IReadOnlyDictionary values, ToolSettingsExportOptions options, ConfidenceLevel minimumProviderConfidence, EnterpriseEncryption? encryption) { var lockedValues = new Dictionary(StringComparer.Ordinal); var defaultValues = new Dictionary(StringComparer.Ordinal); foreach (var fieldName in GetSelectedFieldNames(definition, areas, options.SelectedAreaIds)) { if (!values.TryGetValue(fieldName, out var value)) continue; var key = ManagedSettingKey(definition.Id, fieldName); if (definition.SettingsSchema.Properties[fieldName].Secret) { if (!options.IncludeSecrets || string.IsNullOrWhiteSpace(value)) continue; if (encryption?.IsAvailable is not true) return new(ErrorMessage: TB("Cannot export encrypted tool secrets: No enterprise encryption secret is configured.")); if (!encryption.TryEncrypt(value, out var encrypted)) return new(ErrorMessage: TB("The tool secrets could not be encrypted. Nothing was exported.")); lockedValues[key] = encrypted; } else if (options.Mode is ToolSettingsExportMode.LOCKED) lockedValues[key] = value; else defaultValues[key] = value; } if (lockedValues.Count is 0 && defaultValues.Count is 0 && !options.IncludeMinimumProviderConfidence) return new(); if (options.IncludeMinimumProviderConfidence && (!Enum.IsDefined(minimumProviderConfidence) || minimumProviderConfidence is ConfidenceLevel.UNKNOWN)) return new(ErrorMessage: TB("The tool's minimum provider confidence level is invalid.")); var lua = new StringBuilder(); AppendSettings(lua, LOCKED_SETTINGS, lockedValues); AppendSettings(lua, DEFAULT_SETTINGS, defaultValues); if (options.IncludeMinimumProviderConfidence) { AppendSettings(lua, MINIMUM_CONFIDENCE, new Dictionary(StringComparer.Ordinal) { [definition.Id] = minimumProviderConfidence.ToString(), }); // // A managed setting without an AllowUserOverride flag is locked anyway, so writing // "= false" here would only restate the default — and would silently undo an // administrator's own "= true" further up in the same plugin, for the whole // dictionary rather than this tool's entry. A comment says the same thing without // overwriting anything: // lua.AppendLine($"-- The whole table is locked unless you set CONFIG[\"SETTINGS\"][\"{MINIMUM_CONFIDENCE}.AllowUserOverride\"] = true"); } return new(LuaCode: lua.ToString()); } /// /// Adds entries without replacing the table, so administrators can combine export fragments /// in one plugin. Later assignments to the same key win. This does not merge dictionaries /// across separate configuration plugins; those still follow managed-setting precedence. /// private static void AppendSettings(StringBuilder lua, string settingName, IReadOnlyDictionary values) { if (values.Count is 0) return; var table = $"CONFIG[\"SETTINGS\"][\"{settingName}\"]"; if (lua.Length > 0) lua.AppendLine(); lua.AppendLine($"{table} = {table} or {{}}"); foreach (var (key, value) in values) lua.AppendLine($"{table}[\"{LuaTools.EscapeLuaString(key)}\"] = \"{LuaTools.EscapeLuaString(value)}\""); } }