using System.Collections.Concurrent; using AIStudio.Tools; using AIStudio.Tools.Security; using Microsoft.AspNetCore.Components; namespace AIStudio.Tests.Tools.Security; /// /// Collects the alerts about the sources of one test, out of whatever other tests send meanwhile. /// /// How the labels of the sources of the test begin. internal sealed class PromptInjectionAlertCollector(string labelPrefix) : IMessageBusReceiver { private readonly ConcurrentQueue> alerts = new(); /// /// The labels of the sources in each alert, in the order the alerts arrived. /// public IReadOnlyList> Alerts => this.alerts.Select(results => results.Select(result => result.Source.Label).ToList()).ToList(); /// /// Each alert as what it tells the user per source, in the order the alerts arrived. /// public IReadOnlyList> Reports => this.alerts.Select(results => results.Select(result => Describe(result.Source.Label, result.RedactedCount, result.Findings.Select(finding => finding.Snippet))).ToList()).ToList(); public static string Describe(string label, int redactedCount, IEnumerable snippets) => $"{label}: {redactedCount} passage(s), {string.Join(" | ", snippets)}"; /// /// Starts collecting the alerts which the message bus delivers. /// public void Register() { MessageBus.INSTANCE.RegisterComponent(this); MessageBus.INSTANCE.ApplyFilters(this, [], [Event.SHOW_PROMPT_INJECTION_ALERT]); } /// /// Stops collecting. /// public void Unregister() => MessageBus.INSTANCE.Unregister(this); public Task ProcessMessage(ComponentBase? sendingComponent, Event triggeredEvent, T? data) { if (triggeredEvent is Event.SHOW_PROMPT_INJECTION_ALERT && data is PromptInjectionAlertMessage alert && alert.Results.Any(result => result.Source.Label.StartsWith(labelPrefix, StringComparison.Ordinal))) this.alerts.Enqueue(alert.Results); return Task.CompletedTask; } public Task> ProcessMessageWithResult(ComponentBase? sendingComponent, Event triggeredEvent, TPayload? data) => Task.FromResult(MessageBusAnswer.NONE); }