using Deal.Grpc.Hosting.Interceptors; using Deal.Grpc.Hosting.Models; using Deal.Grpc.Hosting.Options; using Deal.Grpc.Hosting.Services; namespace Deal.Ai; /// /// Собирает WebApplication gRPC-хоста ai-service. /// public static class AiServiceHost { /// /// Создаёт (не запускает) хост /// /// TCP-порт Kestrel. /// Аргументы командной строки (Program.cs); в тестах не нужны. /// Опциональный хук DI для тестов (подмена LLM-фасада фейками: регистрация, добавленная здесь, побеждает — DI резолвит последнюю; см. AiServiceHostTests/AiRpcTests). /// Опциональный хук конфигурации билдера для production-точки входа (Program.cs): Serilog. Тесты хост поднимают БЕЗ этого хука — логирование файлов/консоли тестам не нужно. /// Собранный хост; запуск — StartAsync/RunAsync у вызывающего. public static WebApplication Create( int grpcPort, string[]? args = null, Action? configureServices = null, Action? configureBuilder = null) { WebApplicationBuilder builder = WebApplication.CreateBuilder(args ?? []); // Общая серверная обвязка (Deal.Grpc.Hosting, C31): mTLS env DEAL_MTLS_* — загрузка // сертификатов сразу с fail-fast (compose-prod монтирует deploy/certs, scripts/mtls-certs.sh); MtlsCertificates? mtlsCertificates = GrpcServer.LoadMtlsCertificates(builder); GrpcServer.ConfigureKestrelHttp2Endpoint(builder, grpcPort, mtlsCertificates); builder.Services.AddDealGrpcServer(); builder.Services.AddReadyHealthCheck("хост ai-service готов"); // попытки управляется внутри — 90 с OpenAI / 60 с Anthropic; клиент без общего таймаута) и // оркестратор ретраев/JSON/usage поверх него. Ключи API — в конфиге запроса, не в DI/логах. builder.Services.AddHttpClient(static httpClient => httpClient.Timeout = Timeout.InfiniteTimeSpan); builder.Services.AddSingleton( static sp => sp.GetRequiredService()); builder.Services.AddSingleton>(DefaultRetryDelayAsync); builder.Services.AddSingleton(); configureServices?.Invoke(builder.Services); configureBuilder?.Invoke(builder); WebApplication app = builder.Build(); app.MapGrpcService(); app.MapGrpcHealthChecksService(); return app; } // Пауза между ретраями по умолчанию (Task.Delay; тесты подменяют через DI). // delay: Длительность паузы. // cancellationToken: Токен отмены. private static Task DefaultRetryDelayAsync(TimeSpan delay, CancellationToken cancellationToken) => Task.Delay(delay, cancellationToken); }