Убрать неиспользуемые using по код-стайлу

Прогон dotnet format (IDE0005) по 4 решениям: удалены лишние using,
оставшиеся после миграции namespace (676 файлов).
This commit is contained in:
Rustam Khalimov
2026-09-11 13:30:57 +03:00
parent 5bfa92a4ac
commit e8b9fab860
675 changed files with 47248 additions and 49911 deletions
@@ -1,173 +1,166 @@
using System.Text.Json.Nodes;
using Deal.Grpc.Ai;
using Deal.Modules.Settings.Application.Abstractions;
using Deal.Modules.Settings.Application.Models;
using Deal.Modules.Settings.Application.Registrars;
using Deal.Modules.Settings.Application.Services;
using Deal.Infrastructure.Integrations.Abstractions;
using Deal.Infrastructure.Integrations.Exceptions;
using Deal.Infrastructure.Integrations.Extensions;
using Deal.Infrastructure.Integrations.Models;
using Deal.Infrastructure.Integrations.Options;
namespace Deal.Infrastructure.Integrations.Services;
/// <summary>
/// Собирает конфиг активного ИИ-провайдера для запросов ai-service (Ruling 5: ядро расшифровывает
/// aiConfigs и передаёт ProviderConfig в теле каждого запроса; сервис настроек тенанта не знает).
/// </summary>
/// <remarks>
/// Эффективный конфиг 1:1 с python <c>ai.py _cfg()</c> L25–33 и формой ProviderConfig (ai.proto L70–85):
/// активный провайдер — настройка <c>aiProvider</c> (дефолт «deepseek»), каталог — <see cref="AiProviders"/>
/// (fallback на первый — deepseek, как python L28); из переопределения <c>aiConfigs</c> берутся
/// apiKey/baseUrl/model, отсутствующие поля дополняются дефолтами каталога (base провайдера, первая модель);
/// apiKey расшифровывается (значения <c>enc:</c>+… через <see cref="ISecretCipher"/>; незашифрованные ранних
/// версий — как есть, python crypto.decrypt_text L52–61); api_style провайдера — из каталога (Anthropic —
/// «anthropic», остальные — пусто = OpenAI-совместимый). Scoped: читает KV-настройки тенанта (ISettingsStore →
/// scoped TenantDbContext запроса), как LocalAiClassifier/GrpcMlClient.
/// </remarks>
public sealed class AiProviderConfigBuilder
{
// Ключ aiConfigs: поле apiKey переопределения провайдера.
private const string ApiKeyField = "apiKey";
// Ключ aiConfigs: поле baseUrl переопределения провайдера.
private const string BaseUrlField = "baseUrl";
// Ключ aiConfigs: поле model переопределения провайдера.
private const string ModelField = "model";
// Префикс зашифрованного значения apiKey (crypto.py L49: enc: + Base64(nonce‖ct‖tag)).
private const string EncryptedPrefix = "enc:";
private readonly ISettingsStore _store;
private readonly ISecretCipher _secretCipher;
/// <summary>
/// Создаёт сборщик конфига провайдера.
/// </summary>
/// <param name="store">KV-хранилище настроек тенанта (aiProvider/aiConfigs).</param>
/// <param name="secretCipher">Расшифровка секрета aiConfigs.apiKey (AES-GCM, Ruling 2).</param>
public AiProviderConfigBuilder(ISettingsStore store, ISecretCipher secretCipher)
{
ArgumentNullException.ThrowIfNull(store);
ArgumentNullException.ThrowIfNull(secretCipher);
_store = store;
_secretCipher = secretCipher;
}
/// <summary>
/// Собирает ProviderConfig активного провайдера для тела запроса ai-service.
/// </summary>
/// <param name="ct">Токен отмены.</param>
/// <returns>Конфиг: provider_id/base/model/api_key (расшифрованный)/api_style (см. ai.proto).</returns>
public async Task<ProviderConfig> BuildAsync(CancellationToken ct)
{
string providerId = await ReadProviderIdAsync(ct);
AiProviderDefinition meta = AiProviders.All.FirstOrDefault(provider => provider.Id == providerId)
?? AiProviders.All[0]; // неизвестный id — дефолтный провайдер (python L28)
JsonObject? overrides = await ReadAiConfigsOverrideAsync(ct);
JsonObject? raw = overrides?[meta.Id] as JsonObject;
string apiKey = ReadField(raw, ApiKeyField);
if (apiKey.StartsWith(EncryptedPrefix, StringComparison.Ordinal))
{
apiKey = _secretCipher.Decrypt(apiKey);
}
string baseUrl = ReadField(raw, BaseUrlField);
if (baseUrl.Length == 0)
{
baseUrl = meta.Base; // python L90: cfg.baseUrl or meta.base
}
string model = ReadField(raw, ModelField);
if (model.Length == 0)
{
model = meta.Models.FirstOrDefault() ?? string.Empty; // python L91: cfg.model or models[0]
}
var config = new ProviderConfig
{
ProviderId = meta.Id,
BaseUrl = baseUrl,
Model = model,
};
if (apiKey.Length > 0)
{
config.ApiKey = apiKey;
}
if (meta.ApiStyle is { Length: > 0 } apiStyle)
{
config.ApiStyle = apiStyle;
}
return config;
}
// Активный провайдер из настройки aiProvider (дефолт «deepseek», python L26).
// ct: Токен отмены.
// Возвращает: Id провайдера (каталога AiProviders).
private async Task<string> ReadProviderIdAsync(CancellationToken ct)
{
SettingValue? row = await _store.GetAsync(SettingsKeys.AiProvider, ct);
if (row is null)
{
return SettingsDefaults.AiProvider;
}
try
{
JsonNode? value = JsonNode.Parse(row.ValueJson);
if (value is JsonValue scalar && scalar.TryGetValue<string>(out string? providerId)
&& !string.IsNullOrWhiteSpace(providerId))
{
return providerId;
}
}
catch (System.Text.Json.JsonException)
{
// Повреждённая строка — дефолт (мягкая семантика, как в SettingsService).
}
return SettingsDefaults.AiProvider;
}
// Переопределение aiConfigs тенанта (JSON-объект «id провайдера → конфиг»); null — дефолты.
// ct: Токен отмены.
// Возвращает: Объект переопределения или null.
private async Task<JsonObject?> ReadAiConfigsOverrideAsync(CancellationToken ct)
{
SettingValue? row = await _store.GetAsync(SettingsKeys.AiConfigs, ct);
if (row is null)
{
return null;
}
try
{
return JsonNode.Parse(row.ValueJson) as JsonObject;
}
catch (System.Text.Json.JsonException)
{
// Повреждённая строка — дефолты (мягкая семантика, как в SettingsService).
return null;
}
}
// Строковое поле конфига провайдера (отсутствие/null/не-строка → пустая строка).
// config: Объект конфига провайдера (может быть null — дефолты).
// field: Имя поля (apiKey/baseUrl/model).
// Возвращает: Значение строкой или пустая строка.
private static string ReadField(JsonObject? config, string field)
{
if (config is null || !config.TryGetPropertyValue(field, out JsonNode? node) || node is not JsonValue value)
{
return string.Empty;
}
return value.TryGetValue<string>(out string? text) ? text ?? string.Empty : string.Empty;
}
}
using System.Text.Json.Nodes;
using Deal.Grpc.Ai;
using Deal.Modules.Settings.Application.Abstractions;
using Deal.Modules.Settings.Application.Models;
namespace Deal.Infrastructure.Integrations.Services;
/// <summary>
/// Собирает конфиг активного ИИ-провайдера для запросов ai-service (Ruling 5: ядро расшифровывает
/// aiConfigs и передаёт ProviderConfig в теле каждого запроса; сервис настроек тенанта не знает).
/// </summary>
/// <remarks>
/// Эффективный конфиг 1:1 с python <c>ai.py _cfg()</c> L25–33 и формой ProviderConfig (ai.proto L70–85):
/// активный провайдер — настройка <c>aiProvider</c> (дефолт «deepseek»), каталог — <see cref="AiProviders"/>
/// (fallback на первый — deepseek, как python L28); из переопределения <c>aiConfigs</c> берутся
/// apiKey/baseUrl/model, отсутствующие поля дополняются дефолтами каталога (base провайдера, первая модель);
/// apiKey расшифровывается (значения <c>enc:</c>+… через <see cref="ISecretCipher"/>; незашифрованные ранних
/// версий — как есть, python crypto.decrypt_text L52–61); api_style провайдера — из каталога (Anthropic —
/// «anthropic», остальные — пусто = OpenAI-совместимый). Scoped: читает KV-настройки тенанта (ISettingsStore →
/// scoped TenantDbContext запроса), как LocalAiClassifier/GrpcMlClient.
/// </remarks>
public sealed class AiProviderConfigBuilder
{
// Ключ aiConfigs: поле apiKey переопределения провайдера.
private const string ApiKeyField = "apiKey";
// Ключ aiConfigs: поле baseUrl переопределения провайдера.
private const string BaseUrlField = "baseUrl";
// Ключ aiConfigs: поле model переопределения провайдера.
private const string ModelField = "model";
// Префикс зашифрованного значения apiKey (crypto.py L49: enc: + Base64(nonce‖ct‖tag)).
private const string EncryptedPrefix = "enc:";
private readonly ISettingsStore _store;
private readonly ISecretCipher _secretCipher;
/// <summary>
/// Создаёт сборщик конфига провайдера.
/// </summary>
/// <param name="store">KV-хранилище настроек тенанта (aiProvider/aiConfigs).</param>
/// <param name="secretCipher">Расшифровка секрета aiConfigs.apiKey (AES-GCM, Ruling 2).</param>
public AiProviderConfigBuilder(ISettingsStore store, ISecretCipher secretCipher)
{
ArgumentNullException.ThrowIfNull(store);
ArgumentNullException.ThrowIfNull(secretCipher);
_store = store;
_secretCipher = secretCipher;
}
/// <summary>
/// Собирает ProviderConfig активного провайдера для тела запроса ai-service.
/// </summary>
/// <param name="ct">Токен отмены.</param>
/// <returns>Конфиг: provider_id/base/model/api_key (расшифрованный)/api_style (см. ai.proto).</returns>
public async Task<ProviderConfig> BuildAsync(CancellationToken ct)
{
string providerId = await ReadProviderIdAsync(ct);
AiProviderDefinition meta = AiProviders.All.FirstOrDefault(provider => provider.Id == providerId)
?? AiProviders.All[0]; // неизвестный id — дефолтный провайдер (python L28)
JsonObject? overrides = await ReadAiConfigsOverrideAsync(ct);
JsonObject? raw = overrides?[meta.Id] as JsonObject;
string apiKey = ReadField(raw, ApiKeyField);
if (apiKey.StartsWith(EncryptedPrefix, StringComparison.Ordinal))
{
apiKey = _secretCipher.Decrypt(apiKey);
}
string baseUrl = ReadField(raw, BaseUrlField);
if (baseUrl.Length == 0)
{
baseUrl = meta.Base; // python L90: cfg.baseUrl or meta.base
}
string model = ReadField(raw, ModelField);
if (model.Length == 0)
{
model = meta.Models.FirstOrDefault() ?? string.Empty; // python L91: cfg.model or models[0]
}
var config = new ProviderConfig
{
ProviderId = meta.Id,
BaseUrl = baseUrl,
Model = model,
};
if (apiKey.Length > 0)
{
config.ApiKey = apiKey;
}
if (meta.ApiStyle is { Length: > 0 } apiStyle)
{
config.ApiStyle = apiStyle;
}
return config;
}
// Активный провайдер из настройки aiProvider (дефолт «deepseek», python L26).
// ct: Токен отмены.
// Возвращает: Id провайдера (каталога AiProviders).
private async Task<string> ReadProviderIdAsync(CancellationToken ct)
{
SettingValue? row = await _store.GetAsync(SettingsKeys.AiProvider, ct);
if (row is null)
{
return SettingsDefaults.AiProvider;
}
try
{
JsonNode? value = JsonNode.Parse(row.ValueJson);
if (value is JsonValue scalar && scalar.TryGetValue<string>(out string? providerId)
&& !string.IsNullOrWhiteSpace(providerId))
{
return providerId;
}
}
catch (System.Text.Json.JsonException)
{
// Повреждённая строка — дефолт (мягкая семантика, как в SettingsService).
}
return SettingsDefaults.AiProvider;
}
// Переопределение aiConfigs тенанта (JSON-объект «id провайдера → конфиг»); null — дефолты.
// ct: Токен отмены.
// Возвращает: Объект переопределения или null.
private async Task<JsonObject?> ReadAiConfigsOverrideAsync(CancellationToken ct)
{
SettingValue? row = await _store.GetAsync(SettingsKeys.AiConfigs, ct);
if (row is null)
{
return null;
}
try
{
return JsonNode.Parse(row.ValueJson) as JsonObject;
}
catch (System.Text.Json.JsonException)
{
// Повреждённая строка — дефолты (мягкая семантика, как в SettingsService).
return null;
}
}
// Строковое поле конфига провайдера (отсутствие/null/не-строка → пустая строка).
// config: Объект конфига провайдера (может быть null — дефолты).
// field: Имя поля (apiKey/baseUrl/model).
// Возвращает: Значение строкой или пустая строка.
private static string ReadField(JsonObject? config, string field)
{
if (config is null || !config.TryGetPropertyValue(field, out JsonNode? node) || node is not JsonValue value)
{
return string.Empty;
}
return value.TryGetValue<string>(out string? text) ? text ?? string.Empty : string.Empty;
}
}