Merge branch 'main' into chunk-data

Resolved 29 conflicting files. The notable decisions:

Confidence: main's tool-calling gate (RequiredProviderConfidence) and this
branch's local-RAG gate (DataConfidenceLevel) turned out to be the same rule
on the same axis, so they are now one field. Both tool results and data
sources raise it through RequireProviderConfidence(). The gate checks the
level strictly and no longer exempts providers trusted by configuration:
TrustedProviderIds is documented as applying to data-source security checks
only, and organizations set confidence through DataConfidence
.CustomConfidenceScheme instead. The security axis (DataSecurity, ERI,
IsTrustedForDataSourceSecurityChecks) is unchanged.

Provider creation: main's CreateProvider signature won (hfEndpointKind,
capabilityOverrides, no model parameter); tokenizerPath was added to it and
is set for every provider, including the new Hetzner, IONOS and LiteLLM.
Provider and EmbeddingProvider combine the record parameters, Lua parsing and
Lua serialization of both sides.

File types: main's hierarchy (ODT leaf, WORD parent, PowerPoint without the
legacy .ppt, TABULAR instead of DELIMITED_TABLE) plus this branch's
SPREADSHEET parent with ODS and the xlsm/xlsb/xla/xlam extensions, which the
runtime already reads. Both sides had added a conflicting HTML filter; the
reading family keeps the name, and the export path uses a narrow
HTML_DOCUMENT, following the existing LATEX/TEX split.

Runtime: main's file_data.rs is the base, including the prompt-injection
sanitizer and the extraction routes. Token counting and chunk segmentation
moved into take_released, so they act on the text the filter has released
rather than on text it is still holding. A failed count is logged and left
out instead of ending the extraction, because the app counts such a segment
itself.

Data sources: the participating-provider checks of this branch are kept, and
main's GetAllowedDataSources overload now builds on them. DirectChatService
resolves the launched chat's data source options before the check, so filter
and chat see the same options.

.NET and Rust both build clean; I18N regenerated to 4060 keys.
This commit is contained in:
Thorsten Sommer committed 2026-09-05 21:20:33 +02:00
commit fe35630eff
640 files changed
+45059 -4952

No files matched your search

+42 -6
View File
@@ -1,4 +1,5 @@
using AIStudio.Tools.PluginSystem;
// ReSharper disable MemberCanBePrivate.Global
namespace AIStudio.Tools.Rust;
@@ -38,6 +39,10 @@ public static class FileTypes
/// Gets the standalone HTML filter used for visual briefing import and export.
/// </summary>
public static readonly FileTypeFilter VISUAL_BRIEFING_HTML = FileTypeFilter.Leaf(TB("Visual briefing"), "html");
// Only the canonical extension, without the legacy ".htm": this is what we write when
// exporting, whereas the HTML family above is what we accept when reading.
public static readonly FileTypeFilter HTML_DOCUMENT = FileTypeFilter.Leaf("HTML", "html");
public static readonly FileTypeFilter APP = FileTypeFilter.Leaf("Swift/Kotlin", "swift", "kt");
public static readonly FileTypeFilter SHELL = FileTypeFilter.Leaf("Shell", "sh", "bash", "zsh");
public static readonly FileTypeFilter LOG = FileTypeFilter.Leaf("Log", "log");
@@ -51,21 +56,32 @@ public static class FileTypes
// Document hierarchy
public static readonly FileTypeFilter PDF = FileTypeFilter.Leaf("PDF", "pdf");
public static readonly FileTypeFilter MARKDOWN = FileTypeFilter.Leaf("Markdown", "md");
public static readonly FileTypeFilter TEXT = FileTypeFilter.Leaf(TB("Text"), "txt", "md", "rtf");
public static readonly FileTypeFilter TABULAR = FileTypeFilter.Leaf(TB("Tabular text"), "csv", "tsv");
public static readonly FileTypeFilter CSV = FileTypeFilter.Leaf("CSV", "csv");
public static readonly FileTypeFilter TSV = FileTypeFilter.Leaf("TSV", "tsv");
public static readonly FileTypeFilter MS_WORD = FileTypeFilter.Leaf("Microsoft Word", "docx");
public static readonly FileTypeFilter WORD = FileTypeFilter.Composite("Word", ["odt"], MS_WORD);
public static readonly FileTypeFilter ODT = FileTypeFilter.Leaf("OpenDocument Text", "odt");
public static readonly FileTypeFilter WORD = FileTypeFilter.Parent("Word", ODT, MS_WORD);
public static readonly FileTypeFilter EXCEL = FileTypeFilter.Leaf("Excel", "xls", "xlsx", "xlsm", "xlsb", "xla", "xlam");
public static readonly FileTypeFilter OPEN_DOCUMENT_SPREADSHEET = FileTypeFilter.Leaf("OpenDocument Spreadsheet", "ods");
public static readonly FileTypeFilter SPREADSHEET = FileTypeFilter.Parent(TB("Spreadsheet"), EXCEL, OPEN_DOCUMENT_SPREADSHEET);
public static readonly FileTypeFilter DELIMITED_TABLE = FileTypeFilter.Leaf(TB("Delimited table"), "csv", "tsv");
public static readonly FileTypeFilter POWER_POINT = FileTypeFilter.Leaf("PowerPoint", "ppt", "pptx", "odp");
public static readonly FileTypeFilter ODS = FileTypeFilter.Leaf("OpenDocument Spreadsheet", "ods");
public static readonly FileTypeFilter SPREADSHEET = FileTypeFilter.Parent(TB("Spreadsheet"), EXCEL, ODS);
// The legacy binary ".ppt" is missing on purpose: AI Studio has no reader for it, so offering
// it would only let users attach a file which cannot be read.
public static readonly FileTypeFilter POWER_POINT = FileTypeFilter.Leaf("PowerPoint", "pptx", "odp");
public static readonly FileTypeFilter MAIL = FileTypeFilter.Leaf(TB("Mail"), "eml", "msg", "mbox");
public static readonly FileTypeFilter LATEX = FileTypeFilter.Leaf("LaTeX", "tex", "bib", "sty", "cls", "log");
// Only the LaTeX document itself, without the auxiliary files of the LaTeX family: this is
// what we write when exporting, whereas the family above is what we accept when reading.
public static readonly FileTypeFilter TEX = FileTypeFilter.Leaf("LaTeX", "tex");
public static readonly FileTypeFilter OFFICE_FILES = FileTypeFilter.Parent(TB("Office Files"),
WORD, SPREADSHEET, POWER_POINT, PDF);
public static readonly FileTypeFilter DOCUMENT = FileTypeFilter.Parent(TB("Document"),
TEXT, OFFICE_FILES, SOURCE_CODE, LATEX, DELIMITED_TABLE);
TEXT, TABULAR, OFFICE_FILES, SOURCE_CODE, LATEX);
// Media hierarchy
public static readonly FileTypeFilter IMAGE = FileTypeFilter.Leaf(TB("Image"),
@@ -87,7 +103,27 @@ public static class FileTypes
public static readonly FileTypeFilter CERTIFICATE_BUNDLE = FileTypeFilter.Leaf(TB("Certificate bundle"), "pem", "crt", "cer");
public static readonly FileTypeFilter EXECUTABLES = FileTypeFilter.Leaf(TB("Executable"), "exe", "app", "bin", "appimage");
public static readonly FileTypeFilter SHORTCUT = FileTypeFilter.Leaf(TB("Shortcut"), "lnk");
public static readonly FileTypeFilter PLUGIN_ARCHIVE = FileTypeFilter.Leaf(TB("Plugin archive"), PluginArchive.PLUGIN_FILE_EXTENSION.TrimStart('.'), "zip");
/// <summary>
/// The file types AI Studio converts using Pandoc.
/// </summary>
/// <remarks>
/// This is not a user-selectable type, it mirrors the formats the Rust runtime hands to
/// Pandoc. Every other document type is read by the runtime itself, so it must never depend
/// on a Pandoc installation. Word and OpenDocument text files (.docx, .odt) used to be listed
/// here as well; the runtime reads them on its own now. The name is not localized because it
/// is never shown.
/// </remarks>
private static readonly FileTypeFilter PANDOC_CONVERTED = FileTypeFilter.Leaf("Pandoc conversion", "html", "htm");
/// <summary>
/// Determines whether reading the given file needs Pandoc.
/// </summary>
/// <param name="filePath">The path of the file to check.</param>
/// <returns>True, when reading the file needs Pandoc.</returns>
public static bool RequiresPandoc(string filePath) => IsAllowedPath(filePath, PANDOC_CONVERTED);
public static FileTypeFilter? AsOneFileType(params FileTypeFilter[]? types)
{
if (types == null || types.Length == 0)
@@ -0,0 +1,31 @@
namespace AIStudio.Tools.Rust;
/// <summary>
/// Tells whether this installation is able to update itself, and if not, why.
/// </summary>
public enum InstallationKind
{
/// <summary>
/// An installation the current user owns and which AI Studio may update itself. This is also
/// the fallback when the runtime reports a kind we do not know yet.
/// </summary>
USER,
/// <summary>
/// An installation someone else deployed and maintains, for example, an IT department. Whoever
/// deployed it distributes new versions instead.
/// </summary>
MANAGED,
/// <summary>
/// An installation the current user owns, but which the updater cannot replace. Its owner has
/// to install a new version themselves.
/// </summary>
UNSUPPORTED_LOCATION,
/// <summary>
/// Not an installation at all, but a development build started from a build directory or an
/// IDE. There is nothing here the updater could replace.
/// </summary>
DEVELOPMENT,
}
@@ -0,0 +1,19 @@
namespace AIStudio.Tools.Rust;
/// <summary>
/// Identifies how the Linux build was packaged.
/// </summary>
public enum LinuxPackageType
{
/// <summary>An unknown or future Linux package type reported by the runtime.</summary>
UNKNOWN,
/// <summary>The app is not running on Linux.</summary>
NOT_APPLICABLE,
/// <summary>An AppImage build.</summary>
APP_IMAGE,
/// <summary>A Flatpak build.</summary>
FLATPAK,
}
@@ -1,3 +1,3 @@
namespace AIStudio.Tools.Rust;
public readonly record struct RuntimeInfoResponse(string WorkingDirectory, string ExecutablePath, string LinuxPackageType);
public readonly record struct RuntimeInfoResponse(string WorkingDirectory, string ExecutablePath, LinuxPackageType LinuxPackageType, InstallationKind InstallationKind);
@@ -0,0 +1,6 @@
using System.Text.Json.Serialization;
namespace AIStudio.Tools.Rust;
/// <param name="Texts">The contents to filter. The runtime answers with one result per entry, in this order.</param>
public readonly record struct SanitizePromptInjectionsBatchRequest([property: JsonPropertyName("texts")] IReadOnlyList<string> Texts);
@@ -0,0 +1,6 @@
using System.Text.Json.Serialization;
namespace AIStudio.Tools.Rust;
/// <param name="Results">One result per requested text, in request order. Callers match results to their texts by index.</param>
public readonly record struct SanitizePromptInjectionsBatchResponse([property: JsonPropertyName("results")] IReadOnlyList<SanitizePromptInjectionsResponse> Results);
@@ -0,0 +1,6 @@
using System.Text.Json.Serialization;
namespace AIStudio.Tools.Rust;
/// <param name="Text">The content to filter.</param>
public readonly record struct SanitizePromptInjectionsRequest([property: JsonPropertyName("text")] string Text);
@@ -0,0 +1,13 @@
using System.Text.Json.Serialization;
using AIStudio.Tools.Security;
namespace AIStudio.Tools.Rust;
/// <param name="SanitizedText">The content with the suspicious passages removed. Usable as it stands.</param>
/// <param name="Findings">The passages that were removed, capped by the runtime.</param>
/// <param name="RedactedCount">How many passages were removed in total, which may exceed the number of findings.</param>
public readonly record struct SanitizePromptInjectionsResponse(
[property: JsonPropertyName("sanitized_text")] string SanitizedText,
[property: JsonPropertyName("findings")] IReadOnlyList<PromptInjectionFinding> Findings,
[property: JsonPropertyName("redacted_count")] int RedactedCount);