Added an analyzer which keeps line breaks LF on every system (#1193)

This commit is contained in:
Thorsten Sommer authored and GitHub committed 2026-11-11 19:30:00 +01:00
1 parent b1c1f13993
commit 65d50de033
96 files changed
+741 -393

No files matched your search

@@ -43,8 +43,8 @@ public static class ContentStreamSseHandler
var spreadSheetResult = new StringBuilder();
if (rowNumber == 0)
{
spreadSheetResult.AppendLine();
spreadSheetResult.AppendLine($"# {sheetName}");
spreadSheetResult.AppendLineLF();
spreadSheetResult.AppendLineLF($"# {sheetName}");
}
spreadSheetResult.Append(sseEvent.Content);
@@ -8,6 +8,11 @@ namespace AIStudio.Tools;
/// </summary>
public static class CsvWriter
{
/// <summary>
/// The end of every row: CR LF, as RFC 4180 asks for, on every system.
/// </summary>
public const string ROW_END = "\r\n";
/// <summary>
/// The separator a spreadsheet expects from a CSV file written for the given language.
/// </summary>
@@ -122,18 +122,18 @@ public static class DataSourceReindexWarning
private static async Task<bool> ConfirmNewLocationAsync(IDialogService dialogService, bool usesCloudEmbedding)
{
var body = new StringBuilder();
body.AppendLine(TB("AI Studio keeps what it has already prepared for every file it finds unchanged at the new location, that is, of the same type and with the same size and modification time. In a folder, such a file also has to keep its name and the subfolder it is in."));
body.AppendLine();
body.AppendLine(TB("Every other file goes to your embedding provider once more, which takes a while with many files. Files missing at the new location are removed from the index, while your files on disk stay as they are."));
body.AppendLineLF(TB("AI Studio keeps what it has already prepared for every file it finds unchanged at the new location, that is, of the same type and with the same size and modification time. In a folder, such a file also has to keep its name and the subfolder it is in."));
body.AppendLineLF();
body.AppendLineLF(TB("Every other file goes to your embedding provider once more, which takes a while with many files. Files missing at the new location are removed from the index, while your files on disk stay as they are."));
if (usesCloudEmbedding)
{
body.AppendLine();
body.AppendLine(TB("Your embedding provider runs in the cloud, so preparing files again costs money."));
body.AppendLineLF();
body.AppendLineLF(TB("Your embedding provider runs in the cloud, so preparing files again costs money."));
}
body.AppendLine();
body.AppendLine(TB("Do you want to apply this change anyway?"));
body.AppendLineLF();
body.AppendLineLF(TB("Do you want to apply this change anyway?"));
var dialogParameters = new DialogParameters<ConfirmDialog>
{
@@ -173,11 +173,11 @@ public static class DataSourceReindexWarning
var body = new StringBuilder();
// Counted rather than put into a plural form: the I18N has no mechanism for one.
body.AppendLine(string.Format(TB("These data sources are set up with this embedding provider ({0}):"), affected.Count.CompactCount()));
body.AppendLine();
body.AppendLine(FormatDataSourceNames(affected));
body.AppendLine();
body.AppendLine(TB("They keep answering keyword searches, but searching them by meaning stops working, and no further documents can be prepared for them. The ones which are already prepared stay tied to this provider as well, so you cannot simply move them to another one."));
body.AppendLineLF(string.Format(TB("These data sources are set up with this embedding provider ({0}):"), affected.Count.CompactCount()));
body.AppendLineLF();
body.AppendLineLF(FormatDataSourceNames(affected));
body.AppendLineLF();
body.AppendLineLF(TB("They keep answering keyword searches, but searching them by meaning stops working, and no further documents can be prepared for them. The ones which are already prepared stay tied to this provider as well, so you cannot simply move them to another one."));
return body.ToString();
}
@@ -211,7 +211,7 @@ public static class DataSourceReindexWarning
if (names.Count > MAX_NAMED_DATA_SOURCES)
lines.Add($"- {string.Format(TB("and {0} more."), (names.Count - MAX_NAMED_DATA_SOURCES).CompactCount())}");
return string.Join(Environment.NewLine, lines);
return string.Join('\n', lines);
}
private static async Task<bool> ConfirmAsync(IDialogService dialogService, IReadOnlyList<IDataSourceBase> affected, bool usesCloudEmbedding)
@@ -222,20 +222,20 @@ public static class DataSourceReindexWarning
var body = new StringBuilder();
// Counted rather than put into a plural form: the I18N has no mechanism for one.
body.AppendLine(string.Format(TB("This change makes the prepared documents of the following data sources unusable ({0}):"), affected.Count.CompactCount()));
body.AppendLine();
body.AppendLine(FormatDataSourceNames(affected));
body.AppendLine();
body.AppendLine(TB("Everything prepared for them is thrown away, and every one of their documents goes to your embedding provider once more. With a large data source, this takes a while."));
body.AppendLineLF(string.Format(TB("This change makes the prepared documents of the following data sources unusable ({0}):"), affected.Count.CompactCount()));
body.AppendLineLF();
body.AppendLineLF(FormatDataSourceNames(affected));
body.AppendLineLF();
body.AppendLineLF(TB("Everything prepared for them is thrown away, and every one of their documents goes to your embedding provider once more. With a large data source, this takes a while."));
if (usesCloudEmbedding)
{
body.AppendLine();
body.AppendLine(TB("Your embedding provider runs in the cloud, so preparing everything again costs money."));
body.AppendLineLF();
body.AppendLineLF(TB("Your embedding provider runs in the cloud, so preparing everything again costs money."));
}
body.AppendLine();
body.AppendLine(TB("Do you want to apply this change anyway?"));
body.AppendLineLF();
body.AppendLineLF(TB("Do you want to apply this change anyway?"));
var dialogParameters = new DialogParameters<ConfirmDialog>
{
@@ -32,8 +32,8 @@ public sealed class DocumentManager
// headings, leaving the AI unable to tell the structure of the document apart from our
// boundaries. The presentation reader marks its slides the same way.
//
this.currentPageContent.AppendLine(metadata.Document?.PageIsExact is true ? $"<!-- Page {pageNumber} -->" : $"<!-- Estimated page {pageNumber} -->");
this.currentPageContent.AppendLine();
this.currentPageContent.AppendLineLF(metadata.Document?.PageIsExact is true ? $"<!-- Page {pageNumber} -->" : $"<!-- Estimated page {pageNumber} -->");
this.currentPageContent.AppendLineLF();
this.currentPageContent.Append(content);
//
@@ -55,8 +55,8 @@ public sealed class DocumentManager
var markdownImage = ContentStreamSseHandler.BuildImageMarkdown(image.Id, image.MediaType);
if (markdownImage is not null)
{
this.currentPageContent.AppendLine();
this.currentPageContent.AppendLine(markdownImage);
this.currentPageContent.AppendLineLF();
this.currentPageContent.AppendLineLF(markdownImage);
//
// The runtime counted the text of this page, not the image we just embedded into it.
@@ -202,12 +202,12 @@ public static class FileExportFormatExtensions
switch (format)
{
case FileExportFormat.HTML or FileExportFormat.MARKDOWN:
var commentText = string.Join(Environment.NewLine, lines).Replace("-->", "-- >").Replace("--!>", "--! >");
comment = $"<!--{Environment.NewLine}{commentText}{Environment.NewLine}-->";
var commentText = string.Join('\n', lines).Replace("-->", "-- >").Replace("--!>", "--! >");
comment = $"<!--\n{commentText}\n-->";
return true;
case FileExportFormat.LATEX:
comment = string.Join(Environment.NewLine, lines.Select(line => line.Length is 0 ? "%" : $"% {line}"));
comment = string.Join('\n', lines.Select(line => line.Length is 0 ? "%" : $"% {line}"));
return true;
default:
+2 -2
View File
@@ -99,7 +99,7 @@ public static class Markdown
if (document.Descendants<FencedCodeBlock>().LastOrDefault() is not { ClosingFencedCharCount: 0 } openFence)
return markdownText;
return $"{markdownText}{Environment.NewLine}{new string(openFence.FencedChar, openFence.OpeningFencedCharCount)}";
return $"{markdownText}\n{new string(openFence.FencedChar, openFence.OpeningFencedCharCount)}";
}
/// <summary>Puts a block of Markdown below a Markdown text, such as its sources or a notice.</summary>
@@ -118,7 +118,7 @@ public static class Markdown
// The blank line is not cosmetic: it ends a paragraph, a list, a table, or a block quote, so
// that the block stands on its own instead of being pulled into the last block of the text.
//
return $"{CloseOpenCodeFence(markdownText)}{Environment.NewLine}{Environment.NewLine}{block}";
return $"{CloseOpenCodeFence(markdownText)}\n\n{block}";
}
public static string RemoveSharedIndentation(string value)
@@ -113,7 +113,7 @@ public static class PlainFileExport
/// Turns a formula into a file of LaTeX, between \[ and \] as a formula of its own, so that it can be
/// pasted into a document as it is.
/// </summary>
private static FoundFile ReadFormula(string formula) => new(string.Empty, FileExportFormat.LATEX, SourceCodeLanguage.NONE, $@"\[{Environment.NewLine}{formula}{Environment.NewLine}\]");
private static FoundFile ReadFormula(string formula) => new(string.Empty, FileExportFormat.LATEX, SourceCodeLanguage.NONE, $"\\[\n{formula}\n\\]");
/// <summary>
/// Turns a Markdown table into a file.
@@ -130,7 +130,7 @@ public static class PlainFileExport
var text = new StringBuilder();
foreach (var fields in rows)
text.AppendLine(CsvWriter.ToRow(separator, fields));
text.Append(CsvWriter.ToRow(separator, fields)).Append(CsvWriter.ROW_END);
return new(rows[0].FirstOrDefault() ?? string.Empty, FileExportFormat.CSV, SourceCodeLanguage.NONE, text.ToString());
}
@@ -113,7 +113,7 @@ internal sealed class AssistantDropdown : StatefulAssistantComponentBase
public override string UserPromptFallback(AssistantState state)
{
if (this.IsMultiselect && state.MultiSelect.TryGetValue(this.Name, out var selections))
return this.BuildAuditPromptBlock(string.Join(Environment.NewLine, selections.OrderBy(static value => value, StringComparer.Ordinal)));
return this.BuildAuditPromptBlock(string.Join('\n', selections.OrderBy(static value => value, StringComparer.Ordinal)));
state.SingleSelect.TryGetValue(this.Name, out var userInput);
return this.BuildAuditPromptBlock(userInput);
@@ -57,7 +57,7 @@ internal sealed class AssistantFileAttachment : StatefulAssistantComponentBase
var builder = new StringBuilder();
foreach (var attachment in fileState.DocumentPaths.OrderBy(static attachment => attachment.FilePath, StringComparer.Ordinal))
builder.AppendLine(attachment.FilePath);
builder.AppendLineLF(attachment.FilePath);
return this.BuildAuditPromptBlock(builder.ToString());
}
@@ -282,14 +282,14 @@ internal static class AssistantLuaConversion
var indent = new string(' ', depth * 2);
var childIndent = new string(' ', (depth + 1) * 2);
var builder = new System.Text.StringBuilder();
builder.AppendLine("{");
builder.AppendLineLF("{");
foreach (var entry in table)
{
builder.Append(childIndent);
builder.Append(FormatLuaValue(entry.Key));
builder.Append(" = ");
builder.AppendLine(FormatLuaValue(entry.Value, depth + 1));
builder.AppendLineLF(FormatLuaValue(entry.Value, depth + 1));
}
builder.Append(indent);
@@ -18,13 +18,13 @@ public abstract class StatefulAssistantComponentBase : NamedAssistantComponentBa
var builder = new StringBuilder();
var fieldName = this.Type.ToString().ToLowerInvariant();
builder.AppendLine($"[{fieldName}]");
builder.Append("name: ").AppendLine(this.Name);
builder.AppendLine("context:");
builder.AppendLine(!string.IsNullOrEmpty(this.UserPrompt) ? this.UserPrompt : "<not provided>");
builder.AppendLine("value:");
builder.AppendLine(!string.IsNullOrEmpty(value) ? value : "<empty>");
builder.Append($"[/{fieldName}]").AppendLine().AppendLine();
builder.AppendLineLF($"[{fieldName}]");
builder.Append("name: ").AppendLineLF(this.Name);
builder.AppendLineLF("context:");
builder.AppendLineLF(!string.IsNullOrEmpty(this.UserPrompt) ? this.UserPrompt : "<not provided>");
builder.AppendLineLF("value:");
builder.AppendLineLF(!string.IsNullOrEmpty(value) ? value : "<empty>");
builder.Append($"[/{fieldName}]").AppendLineLF().AppendLineLF();
return builder.ToString();
}
}
@@ -95,71 +95,71 @@ public static class DirectChatLauncherLuaWriter
{
var builder = new StringBuilder();
builder.AppendLine("--[[");
builder.AppendLine(" This direct chat launcher is maintained by AI Studio: its settings dialog rewrites this");
builder.AppendLine(" file as a whole. Editing it by hand works, but the next change made through the dialog");
builder.AppendLine(" replaces everything below, including comments and formatting.");
builder.AppendLine("]]");
builder.AppendLine();
builder.AppendLineLF("--[[");
builder.AppendLineLF(" This direct chat launcher is maintained by AI Studio: its settings dialog rewrites this");
builder.AppendLineLF(" file as a whole. Editing it by hand works, but the next change made through the dialog");
builder.AppendLineLF(" replaces everything below, including comments and formatting.");
builder.AppendLineLF("]]");
builder.AppendLineLF();
builder.AppendLine("-- The ID for this plugin:");
builder.AppendLine($"ID = \"{plugin.Id}\"");
builder.AppendLine();
builder.AppendLineLF("-- The ID for this plugin:");
builder.AppendLineLF($"ID = \"{plugin.Id}\"");
builder.AppendLineLF();
builder.AppendLine("-- The name of the plugin:");
builder.AppendLine($"NAME = \"{Escape(definition.PluginName)}\"");
builder.AppendLine();
builder.AppendLineLF("-- The name of the plugin:");
builder.AppendLineLF($"NAME = \"{Escape(definition.PluginName)}\"");
builder.AppendLineLF();
builder.AppendLine("-- The description of the plugin:");
builder.AppendLine($"DESCRIPTION = \"{Escape(definition.Description)}\"");
builder.AppendLine();
builder.AppendLineLF("-- The description of the plugin:");
builder.AppendLineLF($"DESCRIPTION = \"{Escape(definition.Description)}\"");
builder.AppendLineLF();
builder.AppendLine("-- The version of the plugin:");
builder.AppendLine($"VERSION = \"{plugin.Version}\"");
builder.AppendLine();
builder.AppendLineLF("-- The version of the plugin:");
builder.AppendLineLF($"VERSION = \"{plugin.Version}\"");
builder.AppendLineLF();
if (plugin.LastChanged is { } lastChanged)
{
builder.AppendLine("-- When this plugin was last changed, in UTC, formatted as YYYY-MM-DDTHH:MM:SSZ. Optional.");
builder.AppendLine($"LAST_CHANGED = \"{PluginBase.FormatLastChanged(lastChanged)}\"");
builder.AppendLine();
builder.AppendLineLF("-- When this plugin was last changed, in UTC, formatted as YYYY-MM-DDTHH:MM:SSZ. Optional.");
builder.AppendLineLF($"LAST_CHANGED = \"{PluginBase.FormatLastChanged(lastChanged)}\"");
builder.AppendLineLF();
}
builder.AppendLine("-- The type of the plugin:");
builder.AppendLine($"TYPE = \"{nameof(PluginType.ASSISTANT)}\"");
builder.AppendLine();
builder.AppendLineLF("-- The type of the plugin:");
builder.AppendLineLF($"TYPE = \"{nameof(PluginType.ASSISTANT)}\"");
builder.AppendLineLF();
builder.AppendLine("-- The authors of the plugin:");
builder.AppendLine($"AUTHORS = {WriteStringList(plugin.Authors, FALLBACK_AUTHOR)}");
builder.AppendLine();
builder.AppendLineLF("-- The authors of the plugin:");
builder.AppendLineLF($"AUTHORS = {WriteStringList(plugin.Authors, FALLBACK_AUTHOR)}");
builder.AppendLineLF();
builder.AppendLine("-- The support contact for the plugin:");
builder.AppendLine($"SUPPORT_CONTACT = \"{Escape(ValueOrFallback(plugin.SupportContact, FALLBACK_SUPPORT_CONTACT))}\"");
builder.AppendLine();
builder.AppendLineLF("-- The support contact for the plugin:");
builder.AppendLineLF($"SUPPORT_CONTACT = \"{Escape(ValueOrFallback(plugin.SupportContact, FALLBACK_SUPPORT_CONTACT))}\"");
builder.AppendLineLF();
builder.AppendLine("-- The source URL for the plugin:");
builder.AppendLine($"SOURCE_URL = \"{Escape(ValueOrFallback(plugin.SourceURL, FALLBACK_SOURCE_URL))}\"");
builder.AppendLine();
builder.AppendLineLF("-- The source URL for the plugin:");
builder.AppendLineLF($"SOURCE_URL = \"{Escape(ValueOrFallback(plugin.SourceURL, FALLBACK_SOURCE_URL))}\"");
builder.AppendLineLF();
builder.AppendLine("-- The categories for the plugin:");
builder.AppendLine($"CATEGORIES = {WriteEnumList(plugin.Categories, FALLBACK_CATEGORY)}");
builder.AppendLine();
builder.AppendLineLF("-- The categories for the plugin:");
builder.AppendLineLF($"CATEGORIES = {WriteEnumList(plugin.Categories, FALLBACK_CATEGORY)}");
builder.AppendLineLF();
builder.AppendLine("-- The target groups for the plugin:");
builder.AppendLine($"TARGET_GROUPS = {WriteEnumList(plugin.TargetGroups, FALLBACK_TARGET_GROUP)}");
builder.AppendLine();
builder.AppendLineLF("-- The target groups for the plugin:");
builder.AppendLineLF($"TARGET_GROUPS = {WriteEnumList(plugin.TargetGroups, FALLBACK_TARGET_GROUP)}");
builder.AppendLineLF();
builder.AppendLine("-- The flag for whether the plugin is maintained:");
builder.AppendLine($"IS_MAINTAINED = {WriteBoolean(plugin.IsMaintained)}");
builder.AppendLine();
builder.AppendLineLF("-- The flag for whether the plugin is maintained:");
builder.AppendLineLF($"IS_MAINTAINED = {WriteBoolean(plugin.IsMaintained)}");
builder.AppendLineLF();
builder.AppendLine("-- When the plugin is deprecated, this message will be shown to users:");
builder.AppendLine($"DEPRECATION_MESSAGE = \"{Escape(plugin.DeprecationMessage)}\"");
builder.AppendLine();
builder.AppendLineLF("-- When the plugin is deprecated, this message will be shown to users:");
builder.AppendLineLF($"DEPRECATION_MESSAGE = \"{Escape(plugin.DeprecationMessage)}\"");
builder.AppendLineLF();
builder.AppendLine("-- Enterprise-managed assistants cannot be revised with AI. Keep false for locally managed plugins:");
builder.AppendLine("DEPLOYED_USING_CONFIG_SERVER = false");
builder.AppendLine();
builder.AppendLineLF("-- Enterprise-managed assistants cannot be revised with AI. Keep false for locally managed plugins:");
builder.AppendLineLF("DEPLOYED_USING_CONFIG_SERVER = false");
builder.AppendLineLF();
//
// This metadata marks assistants the Builder created and must not appear on manually
@@ -167,26 +167,26 @@ public static class DirectChatLauncherLuaWriter
//
if (plugin.IsAssistantBuilderGenerated)
{
builder.AppendLine("-- This assistant was created by the AI Studio Assistant Builder:");
builder.AppendLine("AI_STUDIO_ASSISTANT_BUILDER = {Generated = true, SchemaVersion = 1}");
builder.AppendLine();
builder.AppendLineLF("-- This assistant was created by the AI Studio Assistant Builder:");
builder.AppendLineLF("AI_STUDIO_ASSISTANT_BUILDER = {Generated = true, SchemaVersion = 1}");
builder.AppendLineLF();
}
builder.AppendLine("-- The tile opens a chat directly, hence it needs no system prompt, no submit text, and no UI:");
builder.AppendLine("ASSISTANT = {");
builder.AppendLine($" [\"Title\"] = \"{Escape(definition.Title)}\",");
builder.AppendLine($" [\"Description\"] = \"{Escape(definition.Description)}\",");
builder.AppendLineLF("-- The tile opens a chat directly, hence it needs no system prompt, no submit text, and no UI:");
builder.AppendLineLF("ASSISTANT = {");
builder.AppendLineLF($" [\"Title\"] = \"{Escape(definition.Title)}\",");
builder.AppendLineLF($" [\"Description\"] = \"{Escape(definition.Description)}\",");
//
// The behavior follows from the workspace rather than being tracked next to it. A launcher
// without one has no name to write, and the plugin loader rejects a WorkspaceName there, so
// the field is left out the same way the optional IDs below are:
//
if (definition.Launch.OpensTemporaryChat)
builder.AppendLine($" [\"LaunchBehavior\"] = \"{nameof(AssistantPluginLaunchBehavior.OPEN_TEMPORARY_CHAT)}\",");
builder.AppendLineLF($" [\"LaunchBehavior\"] = \"{nameof(AssistantPluginLaunchBehavior.OPEN_TEMPORARY_CHAT)}\",");
else
{
builder.AppendLine($" [\"LaunchBehavior\"] = \"{nameof(AssistantPluginLaunchBehavior.OPEN_WORKSPACE_CHAT_BY_NAME)}\",");
builder.AppendLine($" [\"WorkspaceName\"] = \"{Escape(definition.Launch.WorkspaceName.Trim())}\",");
builder.AppendLineLF($" [\"LaunchBehavior\"] = \"{nameof(AssistantPluginLaunchBehavior.OPEN_WORKSPACE_CHAT_BY_NAME)}\",");
builder.AppendLineLF($" [\"WorkspaceName\"] = \"{Escape(definition.Launch.WorkspaceName.Trim())}\",");
}
//
@@ -194,30 +194,30 @@ public static class DirectChatLauncherLuaWriter
// profile or no chat template. An empty provider GUID has no such meaning and is invalid:
//
if (definition.Launch.ProviderId is { } providerId && providerId != Guid.Empty)
builder.AppendLine($" [\"ProviderId\"] = \"{providerId}\",");
builder.AppendLineLF($" [\"ProviderId\"] = \"{providerId}\",");
if (definition.Launch.ProfileId is { } profileId)
builder.AppendLine($" [\"ProfileId\"] = \"{profileId}\",");
builder.AppendLineLF($" [\"ProfileId\"] = \"{profileId}\",");
if (definition.Launch.ChatTemplateId is { } chatTemplateId)
builder.AppendLine($" [\"ChatTemplateId\"] = \"{chatTemplateId}\",");
builder.AppendLineLF($" [\"ChatTemplateId\"] = \"{chatTemplateId}\",");
if (definition.Launch.DataSourceIds is { Count: > 0 } dataSourceIds)
{
builder.AppendLine(" [\"DataSourceIds\"] = {");
builder.AppendLineLF(" [\"DataSourceIds\"] = {");
foreach (var dataSourceId in dataSourceIds)
builder.AppendLine($" \"{dataSourceId}\",");
builder.AppendLineLF($" \"{dataSourceId}\",");
builder.AppendLine(" },");
builder.AppendLineLF(" },");
}
if (definition.Launch.ToolIds is { Count: > 0 } toolIds)
{
builder.AppendLine(" [\"ToolIds\"] = {");
builder.AppendLineLF(" [\"ToolIds\"] = {");
foreach (var toolId in toolIds)
builder.AppendLine($" {LuaTools.ToLuaStringLiteral(toolId)},");
builder.AppendLineLF($" {LuaTools.ToLuaStringLiteral(toolId)},");
builder.AppendLine(" },");
builder.AppendLineLF(" },");
}
builder.Append('}');
@@ -574,7 +574,7 @@ public sealed class PluginAssistants(bool isInternal, LuaState state, PluginType
private static string BuildSecureSystemPrompt(string pluginSystemPrompt)
{
var separator = $"{Environment.NewLine}{Environment.NewLine}";
var separator = "\n\n";
return string.IsNullOrWhiteSpace(pluginSystemPrompt) ? $"{SECURITY_SYSTEM_PROMPT_PREAMBLE}{separator}{SECURITY_SYSTEM_PROMPT_POSTAMBLE}" : $"{SECURITY_SYSTEM_PROMPT_PREAMBLE}{separator}{pluginSystemPrompt.Trim()}{separator}{SECURITY_SYSTEM_PROMPT_POSTAMBLE}";
}
@@ -939,7 +939,7 @@ public sealed class PluginAssistants(bool isInternal, LuaState state, PluginType
builder.Append(string.IsNullOrWhiteSpace(stateful.UserPrompt) ? "empty" : "set");
}
builder.AppendLine();
builder.AppendLineLF();
if (component.Children.Count > 0)
AppendComponentSummary(builder, component.Children, depth + 1);
@@ -138,8 +138,8 @@ public sealed class AugmentationOne : IAugmentationProcess
var sb = new StringBuilder();
if (selection.Contexts.Count > 0)
{
sb.AppendLine("The following useful information will help you in processing the user prompt:");
sb.AppendLine();
sb.AppendLineLF("The following useful information will help you in processing the user prompt:");
sb.AppendLineLF();
// Let's convert the retrieval contexts to Markdown:
for (var index = 0; index < selection.Contexts.Count; index++)
@@ -147,7 +147,7 @@ public sealed class AugmentationOne : IAugmentationProcess
}
if (selection.LeftOutCount > 0)
sb.AppendLine(selection.LeftOutCount is 1
sb.AppendLineLF(selection.LeftOutCount is 1
? "Of the passages which the search found in the data sources, 1 did not fit into this request and was left out. An answer based on the data sources may therefore be incomplete."
: $"Of the passages which the search found in the data sources, {selection.LeftOutCount} did not fit into this request and were left out. An answer based on the data sources may therefore be incomplete.");
@@ -29,20 +29,20 @@ public sealed record PreparedRetrievalContext(IRetrievalContext Context, string
switch (index)
{
case > 0 when numTotalRetrievalContexts is null:
sb.AppendLine($"# Retrieval context {index}");
sb.AppendLineLF($"# Retrieval context {index}");
break;
case > 0 when numTotalRetrievalContexts > 0:
sb.AppendLine($"# Retrieval context {index} of {numTotalRetrievalContexts}");
sb.AppendLineLF($"# Retrieval context {index} of {numTotalRetrievalContexts}");
break;
default:
sb.AppendLine("# Retrieval context");
sb.AppendLineLF("# Retrieval context");
break;
}
sb.Append(this.Body);
sb.AppendLine();
sb.AppendLineLF();
return sb.ToString();
}
}
@@ -330,7 +330,7 @@ public sealed class AISrcSelWithRetCtxVal : IRagProcess
var builder = new StringBuilder(augmentedData);
if (builder.Length > 0)
builder.AppendLine();
builder.AppendLineLF();
UnreachableDataSources.AppendTo(builder, unreachableDataSources);
return builder.ToString();
@@ -23,23 +23,23 @@ public static class RetrievalContextExtensions
/// <param name="retrievalContext">The context to describe.</param>
internal static void AppendContextDescription(StringBuilder contextBuilder, IRetrievalContext retrievalContext)
{
contextBuilder.AppendLine($"Data source name: {retrievalContext.DataSourceName}");
contextBuilder.AppendLine($"Content category: {retrievalContext.Category}");
contextBuilder.AppendLineLF($"Data source name: {retrievalContext.DataSourceName}");
contextBuilder.AppendLineLF($"Content category: {retrievalContext.Category}");
if(!string.IsNullOrWhiteSpace(retrievalContext.SourceCategory))
contextBuilder.AppendLine($"Source category: {retrievalContext.SourceCategory.ReplaceLineEndings(" ").Trim()}");
contextBuilder.AppendLineLF($"Source category: {retrievalContext.SourceCategory.ReplaceLineEndings(" ").Trim()}");
contextBuilder.AppendLine($"Content type: {retrievalContext.Type}");
contextBuilder.AppendLine($"Content path: {retrievalContext.Path}");
contextBuilder.AppendLineLF($"Content type: {retrievalContext.Type}");
contextBuilder.AppendLineLF($"Content path: {retrievalContext.Path}");
if(retrievalContext is RetrievalTextContext textContext && DescribeLocation(textContext.PageNumber, textContext.EndPageNumber, textContext.SheetName, textContext.StartRow, textContext.EndRow, textContext.StartLine, textContext.EndLine) is { } location)
contextBuilder.AppendLine($"Content location: {location}");
contextBuilder.AppendLineLF($"Content location: {location}");
if(retrievalContext.Links.Count is 0)
return;
contextBuilder.AppendLine("Additional links:");
contextBuilder.AppendLineLF("Additional links:");
foreach(var link in retrievalContext.Links)
contextBuilder.AppendLine($"- {link}");
contextBuilder.AppendLineLF($"- {link}");
}
/// <summary>
@@ -86,8 +86,8 @@ public static class RetrievalContextExtensions
/// <param name="contextBuilder">The builder to write into, after the description of the context.</param>
internal static void AppendImageMatch(StringBuilder contextBuilder)
{
contextBuilder.AppendLine();
contextBuilder.AppendLine("Matched image content: an image matched here, but it is not shown to you. Do not guess what it shows. When it matters for the answer, tell the user that an image matched, and where to find it, using the path and the links above.");
contextBuilder.AppendLineLF();
contextBuilder.AppendLineLF("Matched image content: an image matched here, but it is not shown to you. Do not guess what it shows. When it matters for the answer, tell the user that an image matched, and where to find it, using the path and the links above.");
}
/// <param name="retrievalContexts">The contexts to prepare.</param>
@@ -295,31 +295,31 @@ public static class RetrievalContextExtensions
/// <param name="surroundingContent">The surrounding content, filtered.</param>
private static void AppendTextMatch(StringBuilder contextBuilder, RetrievalTextContext textContext, string matchedText, IReadOnlyList<string> surroundingContent)
{
contextBuilder.AppendLine();
contextBuilder.AppendLine(textContext.IsMatchedTextShortened ? "Matched text content, shortened to fit the size limit:" : "Matched text content:");
contextBuilder.AppendLine("````");
contextBuilder.AppendLine(matchedText);
contextBuilder.AppendLine("````");
contextBuilder.AppendLineLF();
contextBuilder.AppendLineLF(textContext.IsMatchedTextShortened ? "Matched text content, shortened to fit the size limit:" : "Matched text content:");
contextBuilder.AppendLineLF("````");
contextBuilder.AppendLineLF(matchedText);
contextBuilder.AppendLineLF("````");
if (surroundingContent.Count is 0)
{
if (textContext.IsSurroundingContentShortened)
{
contextBuilder.AppendLine();
contextBuilder.AppendLine("Surrounding text content: left out to fit the size limit.");
contextBuilder.AppendLineLF();
contextBuilder.AppendLineLF("Surrounding text content: left out to fit the size limit.");
}
return;
}
contextBuilder.AppendLine();
contextBuilder.AppendLine(textContext.IsSurroundingContentShortened ? "Surrounding text content, shortened to fit the size limit:" : "Surrounding text content:");
contextBuilder.AppendLineLF();
contextBuilder.AppendLineLF(textContext.IsSurroundingContentShortened ? "Surrounding text content, shortened to fit the size limit:" : "Surrounding text content:");
foreach (var surrounding in surroundingContent)
{
contextBuilder.AppendLine();
contextBuilder.AppendLine("````");
contextBuilder.AppendLine(surrounding);
contextBuilder.AppendLine("````");
contextBuilder.AppendLineLF();
contextBuilder.AppendLineLF("````");
contextBuilder.AppendLineLF(surrounding);
contextBuilder.AppendLineLF("````");
}
}
@@ -26,8 +26,8 @@ public static class UnreachableDataSources
/// <param name="dataSources">The data sources whose server could not be reached, in the order to list them.</param>
public static void AppendTo(StringBuilder builder, IReadOnlyList<IDataSource> dataSources)
{
builder.AppendLine("These external data sources of this chat could not be reached or signed in to right now, so they cannot be searched. Their content is missing rather than empty; tell the user so when a question concerns them:");
builder.AppendLineLF("These external data sources of this chat could not be reached or signed in to right now, so they cannot be searched. Their content is missing rather than empty; tell the user so when a question concerns them:");
foreach (var dataSource in dataSources)
builder.AppendLine($"- name='{dataSource.Name}'");
builder.AppendLineLF($"- name='{dataSource.Name}'");
}
}
@@ -261,24 +261,24 @@ public sealed class AssistantPluginGenerationService(ToolRegistry toolRegistry,
continue;
}
builder.AppendLine($"# {contextFile.Title}");
builder.AppendLine($"Source: {contextFile.RelativePath}");
builder.AppendLine("<context>");
builder.AppendLine(content.Trim());
builder.AppendLine("</context>");
builder.AppendLine();
builder.AppendLineLF($"# {contextFile.Title}");
builder.AppendLineLF($"Source: {contextFile.RelativePath}");
builder.AppendLineLF("<context>");
builder.AppendLineLF(content.Trim());
builder.AppendLineLF("</context>");
builder.AppendLineLF();
}
//
// Unlike the files above, this list is not the same on two installations. It is the only
// place the model learns which tool IDs exist, so an assistant cannot name a tool without it:
//
builder.AppendLine("# Available tools");
builder.AppendLine("Source: the tools installed in this AI Studio");
builder.AppendLine("<context>");
builder.AppendLine(await this.FormatAvailableToolsAsync());
builder.AppendLine("</context>");
builder.AppendLine();
builder.AppendLineLF("# Available tools");
builder.AppendLineLF("Source: the tools installed in this AI Studio");
builder.AppendLineLF("<context>");
builder.AppendLineLF(await this.FormatAvailableToolsAsync());
builder.AppendLineLF("</context>");
builder.AppendLineLF();
return builder.ToString().Trim();
}
@@ -302,7 +302,7 @@ public sealed class AssistantPluginGenerationService(ToolRegistry toolRegistry,
var builder = new StringBuilder();
foreach (var tool in activeTools)
builder.AppendLine($"- {tool.Id}: {tool.DescriptionForLLM}");
builder.AppendLineLF($"- {tool.Id}: {tool.DescriptionForLLM}");
return builder.ToString().TrimEnd();
}
@@ -729,11 +729,11 @@ public sealed class AssistantPluginGenerationService(ToolRegistry toolRegistry,
var builder = new StringBuilder();
foreach (var (relativePath, content) in luaFiles)
{
builder.AppendLine($"# {relativePath}");
builder.AppendLine("```lua");
builder.AppendLine(content.Trim());
builder.AppendLine("```");
builder.AppendLine();
builder.AppendLineLF($"# {relativePath}");
builder.AppendLineLF("```lua");
builder.AppendLineLF(content.Trim());
builder.AppendLineLF("```");
builder.AppendLineLF();
}
return builder.ToString().Trim();
@@ -304,7 +304,7 @@ public sealed partial class RustService
if (content.Length >= maxCharacters)
return string.IsNullOrWhiteSpace(piece);
content.AppendLine(piece);
content.AppendLineLF(piece);
return true;
}
+8 -8
View File
@@ -28,12 +28,12 @@ public sealed class SlideManager
//
var contentBuilder = new StringBuilder();
contentBuilder.AppendLine();
contentBuilder.AppendLine($"# Slide {slideNumber}");
contentBuilder.AppendLineLF();
contentBuilder.AppendLineLF($"# Slide {slideNumber}");
// Add any text content to the slide?
if(!string.IsNullOrWhiteSpace(content))
contentBuilder.AppendLine(content);
contentBuilder.AppendLineLF(content);
//
// Add the text content to the slide:
@@ -78,7 +78,7 @@ public sealed class SlideManager
if (!string.IsNullOrWhiteSpace(content))
{
var textContent = slide.Content.OfType<SlideTextContent>().First();
textContent.Text.AppendLine(content);
textContent.Text.AppendLineLF(content);
slide.TokenCount = ContentStreamPendingContent.AddTokenCounts(slide.TokenCount, tokenCount);
}
@@ -112,14 +112,14 @@ public sealed class SlideManager
foreach (var text in slide.Content.OfType<SlideTextContent>())
{
content.AppendLine(text.Text.ToString());
content.AppendLine();
content.AppendLineLF(text.Text.ToString());
content.AppendLineLF();
}
foreach (var image in slide.Content.OfType<SlideImageContent>())
{
content.AppendLine(image.MarkdownImage);
content.AppendLine();
content.AppendLineLF(image.MarkdownImage);
content.AppendLineLF();
}
}
@@ -226,10 +226,10 @@ public static partial class SourceExtensions
foreach (var group in sources.GroupSources())
{
if (sb.Length > 0)
sb.AppendLine();
sb.AppendLineLF();
sb.Append("## ");
sb.AppendLine(group.Heading);
sb.AppendLineLF(group.Heading);
foreach (var numberedSource in group.Sources)
{
@@ -241,7 +241,7 @@ public static partial class SourceExtensions
else
AppendMarkdownLink(sb, numberedSource.Source.Title, keepPageAnchors ? numberedSource.Source.URL : WithoutPageAnchor(numberedSource.Source.URL));
sb.AppendLine();
sb.AppendLineLF();
}
}
@@ -265,7 +265,7 @@ public static partial class SourceExtensions
if (string.IsNullOrWhiteSpace(sourcesMarkdown))
return string.Empty;
return $"# {TB("Sources")}{Environment.NewLine}{Environment.NewLine}{sourcesMarkdown}";
return $"# {TB("Sources")}\n\n{sourcesMarkdown}";
}
/// <summary>
@@ -89,11 +89,11 @@ public sealed class CountMailsTool(SettingsManager settingsManager, MailboxRetri
internal static ToolFunctionDefinition DescribeMailboxes(ToolFunctionDefinition function, IReadOnlyList<DataSourceMailbox> mailboxes)
{
var description = new StringBuilder(DESCRIPTION);
description.AppendLine();
description.AppendLine();
description.AppendLine($"The mailboxes you may count, by the ID to pass in {MailToolArguments.MAILBOX_IDS_ARGUMENT}:");
description.AppendLineLF();
description.AppendLineLF();
description.AppendLineLF($"The mailboxes you may count, by the ID to pass in {MailToolArguments.MAILBOX_IDS_ARGUMENT}:");
foreach (var mailbox in mailboxes)
description.AppendLine($"- id={mailbox.Id}, name='{mailbox.Name}'");
description.AppendLineLF($"- id={mailbox.Id}, name='{mailbox.Name}'");
return function with
{
@@ -54,9 +54,9 @@ internal static class MailToolResults
if (!string.IsNullOrWhiteSpace(text))
{
builder.AppendLine();
builder.AppendLine("---");
builder.AppendLine();
builder.AppendLineLF();
builder.AppendLineLF("---");
builder.AppendLineLF();
builder.Append(text);
}
@@ -65,7 +65,7 @@ internal static class MailToolResults
void AppendField(string label, string? value)
{
if (!string.IsNullOrWhiteSpace(value))
builder.AppendLine($"- **{label}:** {value}");
builder.AppendLineLF($"- **{label}:** {value}");
}
}
@@ -135,11 +135,11 @@ public sealed class SearchMailsTool(SettingsManager settingsManager, MailboxRetr
internal static ToolFunctionDefinition DescribeMailboxes(ToolFunctionDefinition function, IReadOnlyList<DataSourceMailbox> mailboxes)
{
var description = new StringBuilder(DESCRIPTION);
description.AppendLine();
description.AppendLine();
description.AppendLine($"The mailboxes you may search, by the ID to pass in {MailToolArguments.MAILBOX_IDS_ARGUMENT}:");
description.AppendLineLF();
description.AppendLineLF();
description.AppendLineLF($"The mailboxes you may search, by the ID to pass in {MailToolArguments.MAILBOX_IDS_ARGUMENT}:");
foreach (var mailbox in mailboxes)
description.AppendLine($"- id={mailbox.Id}, name='{mailbox.Name}'");
description.AppendLineLF($"- id={mailbox.Id}, name='{mailbox.Name}'");
return function with
{
@@ -286,21 +286,21 @@ public sealed class SemanticSearchTool(SettingsManager settingsManager, DataSour
internal static ToolFunctionDefinition DescribeDataSources(ToolFunctionDefinition function, IReadOnlyList<(IDataSource DataSource, string Description)> dataSources, IReadOnlyList<IDataSource>? unreachableDataSources = null)
{
var description = new StringBuilder(DESCRIPTION);
description.AppendLine();
description.AppendLine();
description.AppendLine($"The data sources you may search, by the ID to pass in {DATA_SOURCE_IDS_ARGUMENT}:");
description.AppendLineLF();
description.AppendLineLF();
description.AppendLineLF($"The data sources you may search, by the ID to pass in {DATA_SOURCE_IDS_ARGUMENT}:");
foreach (var (dataSource, dataSourceDescription) in dataSources)
{
description.Append($"- id={dataSource.Id}, name='{dataSource.Name}', type={GetKind(dataSource)}");
if (!string.IsNullOrWhiteSpace(dataSourceDescription))
description.Append($", description='{dataSourceDescription.Trim().Shorten(MAX_DESCRIPTION_CHARACTERS)}'");
description.AppendLine();
description.AppendLineLF();
}
if (unreachableDataSources is { Count: > 0 })
{
description.AppendLine();
description.AppendLineLF();
UnreachableDataSources.AppendTo(description, unreachableDataSources);
}
@@ -50,7 +50,7 @@ public static class ToolSelectionRules
public static string BuildToolPolicyPrompt(IEnumerable<ToolDefinition> definitions)
{
var policySections = GetInstructedTools(definitions)
.Select(x => $"## Tool `{x.Function.Name}`{Environment.NewLine}{x.SystemPromptInstructions.Trim()}")
.Select(x => $"## Tool `{x.Function.Name}`\n{x.SystemPromptInstructions.Trim()}")
.Distinct(StringComparer.Ordinal)
.ToList();
if (policySections.Count == 0)
@@ -60,7 +60,7 @@ public static class ToolSelectionRules
# Tool usage instructions:
You have multiple tools available. Each tool has a different purpose and usage policy. Choose wisely and if you are not sure, always ask the user for clarification. You must follow the usage policy of each tool to ensure accurate and reliable results. Here are the usage policies for each tool:
{string.Join(Environment.NewLine+Environment.NewLine, policySections)}
{string.Join("\n\n", policySections)}
""";
return toolPolicyPrompt;
@@ -159,7 +159,7 @@ public sealed partial class ToolSettingsService
// dictionary rather than this tool's entry. A comment says the same thing without
// overwriting anything:
//
lua.AppendLine($"-- The whole table is locked unless you set CONFIG[\"SETTINGS\"][\"{MINIMUM_CONFIDENCE}.AllowUserOverride\"] = true");
lua.AppendLineLF($"-- The whole table is locked unless you set CONFIG[\"SETTINGS\"][\"{MINIMUM_CONFIDENCE}.AllowUserOverride\"] = true");
}
return new(LuaCode: lua.ToString());
@@ -177,9 +177,9 @@ public sealed partial class ToolSettingsService
var table = $"CONFIG[\"SETTINGS\"][\"{settingName}\"]";
if (lua.Length > 0)
lua.AppendLine();
lua.AppendLine($"{table} = {table} or {{}}");
lua.AppendLineLF();
lua.AppendLineLF($"{table} = {table} or {{}}");
foreach (var (key, value) in values)
lua.AppendLine($"{table}[\"{LuaTools.EscapeLuaString(key)}\"] = \"{LuaTools.EscapeLuaString(value)}\"");
lua.AppendLineLF($"{table}[\"{LuaTools.EscapeLuaString(key)}\"] = \"{LuaTools.EscapeLuaString(value)}\"");
}
}