using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Configuration; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.AI; using GeminiDotnet; using GeminiDotnet.Extensions.AI; using NexusReader.Infrastructure.Persistence; using NexusReader.Application.Abstractions.Services; using NexusReader.Infrastructure.Services; using NexusReader.Infrastructure.Configuration; using Polly; using Polly.Retry; namespace NexusReader.Infrastructure; public static class DependencyInjection { public static IServiceCollection AddInfrastructure(this IServiceCollection services, IConfiguration configuration) { var connectionString = configuration.GetConnectionString("SqliteConnection") ?? "Data Source=nexus.db"; services.AddDbContext(options => options.UseSqlite(connectionString)); services.Configure(configuration.GetSection(AiSettings.SectionName)); var aiSettings = configuration.GetSection(AiSettings.SectionName).Get() ?? new AiSettings(); if (string.IsNullOrWhiteSpace(aiSettings.ApiKey) || aiSettings.ApiKey == "PLACEHOLDER") { // We don't throw here to allow the app to start, but services using AI will fail gracefully } services.AddResiliencePipeline("ai-retry", builder => { builder.AddRetry(new RetryStrategyOptions { ShouldHandle = new PredicateBuilder().Handle(ex => ex.Message.Contains("429") || ex.Message.Contains("Too Many Requests") || ex.Message.Contains("quota")), BackoffType = DelayBackoffType.Exponential, UseJitter = true, MaxRetryAttempts = aiSettings.RetryAttempts, Delay = TimeSpan.FromSeconds(2) }); }); services.AddChatClient(new GeminiChatClient(new GeminiClientOptions { ApiKey = aiSettings.ApiKey, ModelId = aiSettings.Model })); services.AddScoped(); services.AddTransient(); services.AddTransient(); return services; } }