< Summary

Information
Class: Elsa.AI.Host.Context.AIContextResolver
Assembly: Elsa.AI.Host
File(s): /home/runner/work/elsa-core/elsa-core/src/modules/Elsa.AI.Host/Context/AIContextResolver.cs
Line coverage
93%
Covered lines: 55
Uncovered lines: 4
Coverable lines: 59
Total lines: 104
Line coverage: 93.2%
Branch coverage
77%
Covered branches: 17
Total branches: 22
Branch coverage: 77.2%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%11100%
.cctor()100%11100%
ResolveAsync()100%44100%
Redact(...)100%11100%
RedactObject(...)100%44100%
RedactNode(...)60%151062.5%
RedactText(...)50%2266.66%
IsSensitiveKey(...)100%11100%
BuildProviders(...)100%11100%
SelectProvider(...)100%22100%

File(s)

/home/runner/work/elsa-core/elsa-core/src/modules/Elsa.AI.Host/Context/AIContextResolver.cs

#LineLine coverage
 1using System.Text.RegularExpressions;
 2using Elsa.AI.Abstractions.Contracts;
 3using Elsa.AI.Abstractions.Models;
 4using Microsoft.Extensions.DependencyInjection;
 5using Microsoft.Extensions.Logging;
 6
 7namespace Elsa.AI.Host.Context;
 8
 479public class AIContextResolver(IServiceScopeFactory scopeFactory, ILogger<AIContextResolver> logger)
 10{
 211    private static readonly string[] SensitiveKeyFragments = ["secret", "token", "password", "apikey", "api_key", "api-k
 212    private static readonly Regex[] SensitiveValuePatterns =
 213    [
 214        new(@"\bBearer\s+[A-Za-z0-9._~+/\-=]+", RegexOptions.IgnoreCase | RegexOptions.CultureInvariant | RegexOptions.C
 215        new(@"\b(?:api[_-]?key|token|secret|password)\s*[:=]\s*['""]?[A-Za-z0-9._~+/\-=]{8,}['""]?", RegexOptions.Ignore
 216    ];
 17    private const string Redacted = "[redacted]";
 18
 19    public async ValueTask<IReadOnlyCollection<AIResolvedContext>> ResolveAsync(AIChatRequest request, CancellationToken
 20    {
 4421        using var scope = scopeFactory.CreateScope();
 4422        var providers = BuildProviders(scope.ServiceProvider.GetServices<IAIContextProvider>());
 4423        var resolved = new List<AIResolvedContext>();
 4424        var attachmentsWithProviders = request.Attachments
 1525            .Select(attachment => new
 1526            {
 1527                Attachment = attachment,
 1528                Provider = providers.GetValueOrDefault(attachment.Kind)
 1529            })
 5930            .Where(x => x.Provider != null);
 31
 11732        foreach (var item in attachmentsWithProviders)
 33        {
 1534            if (item.Provider is IPlaceholderAIContextProvider)
 135                logger.LogWarning(
 136                    "AI context kind {ContextKind} is using placeholder provider {ProviderType}. Replace it before produ
 137                    item.Attachment.Kind,
 138                    item.Provider.GetType().Name);
 39
 1540            var context = await item.Provider!.ResolveAsync(new AIContextResolutionRequest
 1541            {
 1542                Attachment = item.Attachment,
 1543                TenantId = request.TenantId,
 1544                UserId = request.UserId
 1545            }, cancellationToken);
 46
 1447            resolved.Add(Redact(context));
 48        }
 49
 4350        return resolved;
 4351    }
 52
 53    private static AIResolvedContext Redact(AIResolvedContext context) =>
 1454        context with
 1455        {
 1456            Summary = RedactText(context.Summary),
 1457            Data = RedactObject(context.Data),
 1458            Metadata = RedactObject(context.Metadata)
 1459        };
 60
 61    private static JsonObject RedactObject(JsonObject source)
 62    {
 2863        var redacted = new JsonObject();
 64
 8265        foreach (var property in source)
 1366            redacted[property.Key] = IsSensitiveKey(property.Key) ? Redacted : RedactNode(property.Value);
 67
 2868        return redacted;
 69    }
 70
 71    private static JsonNode? RedactNode(JsonNode? node) =>
 1172        node switch
 1173        {
 074            JsonObject jsonObject => RedactObject(jsonObject),
 075            JsonArray jsonArray => new JsonArray(jsonArray.Select(RedactNode).ToArray()),
 2076            JsonValue jsonValue when jsonValue.TryGetValue<string>(out var value) => JsonValue.Create(RedactText(value))
 077            JsonValue jsonValue => jsonValue.DeepClone(),
 178            _ => node?.DeepClone()
 1179        };
 80
 81    private static string RedactText(string text)
 82    {
 2483        if (string.IsNullOrWhiteSpace(text))
 084            return text;
 85
 7286        return SensitiveValuePatterns.Aggregate(text, (current, pattern) => pattern.Replace(current, Redacted));
 87    }
 88
 89    private static bool IsSensitiveKey(string key) =>
 11890        SensitiveKeyFragments.Any(fragment => key.Contains(fragment, StringComparison.OrdinalIgnoreCase));
 91
 92    private static Dictionary<string, IAIContextProvider> BuildProviders(IEnumerable<IAIContextProvider> providers)
 93    {
 4494        return providers
 9295            .GroupBy(x => x.Kind, StringComparer.OrdinalIgnoreCase)
 13496            .ToDictionary(x => x.Key, SelectProvider, StringComparer.OrdinalIgnoreCase);
 97    }
 98
 99    private static IAIContextProvider SelectProvider(IEnumerable<IAIContextProvider> providers)
 100    {
 90101        var providerList = providers.ToList();
 181102        return providerList.LastOrDefault(x => x is not IPlaceholderAIContextProvider) ?? providerList.Last();
 103    }
 104}