AI-Studio/app/MindWork AI Studio/Tools/FileExtractionResult.cs
Sabrina-devops 902a01a4d0
Added a prompt injection detection (#857)
Co-authored-by: Thorsten Sommer <SommerEngineering@users.noreply.github.com>
2026-08-23 11:09:11 +02:00

77 lines
4.0 KiB
C#

using AIStudio.Tools.Security;
namespace AIStudio.Tools;
/// <summary>
/// The result of reading a file through the Rust runtime.
/// </summary>
/// <remarks>
/// Content and failure travel together on purpose. When reading a file returns a bare string, a
/// failed extraction is indistinguishable from an empty document, and the empty document reaches
/// the AI as if that were the content of the user's file.
/// </remarks>
/// <param name="Outcome">How the extraction ended.</param>
/// <param name="Content">The extracted content. Empty when the extraction failed.</param>
/// <param name="ErrorCode">Why the extraction failed or lost parts of the file.</param>
/// <param name="ErrorMessage">The technical failure description, meant for logs and diagnostics.</param>
/// <param name="FailedPages">The pages which could not be read, when known.</param>
/// <param name="DetectedFormat">The format the runtime identified by looking at the content, when it is worth naming.</param>
public readonly record struct FileExtractionResult(FileExtractionOutcome Outcome, string Content, FileExtractionErrorCode ErrorCode, string? ErrorMessage, IReadOnlyList<int> FailedPages, string? DetectedFormat)
{
private static readonly int[] NO_FAILED_PAGES = [];
private static readonly PromptInjectionFinding[] NO_FINDINGS = [];
private readonly IReadOnlyList<PromptInjectionFinding>? promptInjectionFindings;
/// <summary>
/// The prompt-injection attempts the runtime filtered out of the content, if any.
/// </summary>
/// <remarks>
/// This is a notice, not a failure: the passages were removed and the content around them
/// is intact, which is why it does not affect the outcome. The findings exist so the app
/// can tell the user what was removed from their document.
/// </remarks>
public IReadOnlyList<PromptInjectionFinding> PromptInjectionFindings
{
get => this.promptInjectionFindings ?? NO_FINDINGS;
init => this.promptInjectionFindings = value;
}
/// <summary>
/// How many passages were filtered out. May exceed the number of findings, because the
/// runtime caps how many it reports in detail while it filters every single one.
/// </summary>
public int PromptInjectionRedactedCount { get; init; }
/// <summary>
/// Gets a value indicating whether prompt injections were filtered out of the content.
/// </summary>
public bool HasFilteredPromptInjections => this.PromptInjectionRedactedCount > 0;
public static FileExtractionResult Success(string content, string? detectedFormat = null) => new(FileExtractionOutcome.SUCCESS, content, FileExtractionErrorCode.NONE, null, NO_FAILED_PAGES, detectedFormat);
public static FileExtractionResult Partial(string content, IReadOnlyList<int> failedPages, string? detectedFormat = null) => new(FileExtractionOutcome.PARTIAL, content, FileExtractionErrorCode.PAGE_EXTRACTION_FAILED, null, failedPages, detectedFormat);
public static FileExtractionResult Failed(FileExtractionErrorCode errorCode, string? errorMessage, string? detectedFormat = null) => new(FileExtractionOutcome.FAILED, string.Empty, errorCode, errorMessage, NO_FAILED_PAGES, detectedFormat);
/// <summary>
/// Gets a value indicating whether the whole file was read.
/// </summary>
public bool IsSuccess => this.Outcome is FileExtractionOutcome.SUCCESS;
/// <summary>
/// Gets a value indicating whether the content may be handed to the AI, i.e. the extraction
/// either succeeded or lost only parts of the file.
/// </summary>
public bool HasUsableContent => this.Outcome is FileExtractionOutcome.SUCCESS or FileExtractionOutcome.PARTIAL;
/// <summary>
/// Gets a value indicating whether the file was read, but its content did not match its file
/// extension.
/// </summary>
/// <remarks>
/// On a readable file, only the mismatch notice names a detected format, which is why no
/// separate flag is needed here.
/// </remarks>
public bool HasExtensionMismatch => this.HasUsableContent && this.DetectedFormat is not null;
}