Adopted further C# 14 and .NET 10 features (#1158)

This commit is contained in:
Thorsten Sommer authored and GitHub committed 2026-11-04 14:04:10 +01:00
1 parent f649febf88
commit f0fa1411c8
40 files changed
+545 -56

No files matched your search

@@ -281,7 +281,7 @@ public abstract partial class AssistantBase<TSettings> : AssistantLowerBase wher
this.formChangeTimer.Elapsed += (_, _) =>
{
this.formChangeTimer.Stop();
this.InvokeAsync(this.OnFormChange).Observe($"{nameof(AssistantBase<TSettings>)}: handling a form change");
this.InvokeAsync(this.OnFormChange).Observe($"{nameof(AssistantBase<>)}: handling a form change");
};
this.MightPreselectValues();
@@ -321,7 +321,7 @@ public abstract partial class AssistantBase<TSettings> : AssistantLowerBase wher
#endregion
private string TB(string fallbackEN) => this.T(fallbackEN, typeof(AssistantBase<TSettings>).Namespace, nameof(AssistantBase<TSettings>));
private string TB(string fallbackEN) => this.T(fallbackEN, typeof(AssistantBase<>).Namespace, nameof(AssistantBase<>));
private string SubmitButtonStyle => this.SettingsManager.ConfigurationData.Confidence.ShowProviderConfidence ? this.ProviderSettings.UsedLLMProvider.GetConfidence(this.SettingsManager).StyleBorder(this.SettingsManager) : string.Empty;
@@ -470,7 +470,7 @@ public abstract partial class AssistantBase<TSettings> : AssistantLowerBase wher
Array.Resize(ref this.InputIssues, this.InputIssues.Length + 1);
this.InputIssues[^1] = issue;
this.InputIsValid = false;
this.RefreshAssistantUIAsync().Observe($"{nameof(AssistantBase<TSettings>)}: rendering an added input issue");
this.RefreshAssistantUIAsync().Observe($"{nameof(AssistantBase<>)}: rendering an added input issue");
}
/// <summary>
@@ -480,7 +480,7 @@ public abstract partial class AssistantBase<TSettings> : AssistantLowerBase wher
{
this.InputIssues = [];
this.InputIsValid = true;
this.RefreshAssistantUIAsync().Observe($"{nameof(AssistantBase<TSettings>)}: rendering cleared input issues");
this.RefreshAssistantUIAsync().Observe($"{nameof(AssistantBase<>)}: rendering cleared input issues");
}
protected void CreateChatThread()
@@ -1009,7 +1009,7 @@ public abstract partial class AssistantBase<TSettings> : AssistantLowerBase wher
{
await this.ConsumeMediaOutcomeAsync();
this.StateHasChanged();
}).Observe($"{nameof(AssistantBase<TSettings>)}: consuming a media import outcome");
}).Observe($"{nameof(AssistantBase<>)}: consuming a media import outcome");
}
/// <summary>Consumes a terminal media notification when this assistant is visible.</summary>
@@ -14,12 +14,20 @@ internal sealed class LauncherTextsResponse
{
public const string SCHEMA_VERSION_VALUE = "assistant_builder_launcher_texts_v1";
/// <summary>
/// How the answer of the model is read.
/// </summary>
/// <remarks>
/// An answer which names a field twice leaves open which of the values the model meant, so it is
/// refused like any other invalid JSON.
/// </remarks>
internal static readonly JsonSerializerOptions JSON_OPTIONS = new JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower,
AllowTrailingCommas = false,
ReadCommentHandling = JsonCommentHandling.Disallow,
MaxDepth = 8,
AllowDuplicateProperties = false,
TypeInfoResolver = LauncherTextsResponseJsonContext.Default,
}.AsReadOnly();
@@ -4,12 +4,20 @@ namespace AIStudio.Assistants.Builder;
internal sealed partial class LuaResponse
{
/// <summary>
/// How the answer of the model is read.
/// </summary>
/// <remarks>
/// An answer which names a field twice leaves open which of the values the model meant, the Lua code
/// included, so it is refused like any other invalid JSON.
/// </remarks>
internal static readonly JsonSerializerOptions JSON_OPTIONS = new JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower,
AllowTrailingCommas = false,
ReadCommentHandling = JsonCommentHandling.Disallow,
MaxDepth = 32,
AllowDuplicateProperties = false,
TypeInfoResolver = LuaResponseJsonContext.Default,
}.AsReadOnly();
@@ -31,6 +31,9 @@ internal static class VisualBriefingJson
/// here even though the persisted manifest writes their member names. Unlike other options of the app,
/// these are not made read-only right here, because MWAIS0010 wants nothing but the object creation;
/// System.Text.Json locks them with their first use anyway.
///
/// The answers of the model are read with these options, too, which therefore refuse a field named
/// twice. That rule only concerns reading and changes no byte of the output.
/// </remarks>
[CanonicalJsonConfiguration]
internal static JsonSerializerOptions Canonical { get; } = new()
@@ -40,6 +43,7 @@ internal static class VisualBriefingJson
WriteIndented = false,
Encoder = JavaScriptEncoder.Default,
UnmappedMemberHandling = JsonUnmappedMemberHandling.Disallow,
AllowDuplicateProperties = false,
TypeInfoResolver = VisualBriefingCanonicalJsonContext.Default,
};
@@ -40,4 +40,7 @@ public enum VisualBriefingStructuredResponseIssueKind
/// <summary>The parsed response violated a semantic stage contract.</summary>
SEMANTIC_CONTRACT_INVALID,
/// <summary>An object of the response named the same field more than once.</summary>
DUPLICATE_FIELD,
}
@@ -20,7 +20,7 @@ internal static partial class VisualBriefingStructuredResponseProcessor
.DisableHtml()
.Build();
private static readonly NullabilityInfoContext NULLABILITY = new();
private static readonly object CONTRACT_LOCK = new();
private static readonly Lock CONTRACT_LOCK = new();
private static readonly Dictionary<Type, ContractShape> CONTRACTS = [];
/// <summary>
@@ -159,6 +159,18 @@ internal static partial class VisualBriefingStructuredResponseProcessor
"The structured response root must be a JSON object.",
expected: "JSON object");
var duplicateIssue = FindDuplicateField(document.RootElement, "$");
if (duplicateIssue is not null)
return Rejected<T>(
candidate,
duplicateIssue.Kind,
VisualBriefingFailureCode.RESPONSE_JSON_INVALID,
VisualBriefingValidationRule.JSON_INVALID,
ContractIssueMessage(duplicateIssue),
duplicateIssue.Path,
fieldName: duplicateIssue.FieldName,
expected: duplicateIssue.Expected);
var contractIssue = Inspect(
document.RootElement,
GetContract(typeof(T)),
@@ -377,6 +389,58 @@ internal static partial class VisualBriefingStructuredResponseProcessor
private static long? ToResponseLine(ResponseCandidate candidate, long? candidateLine) =>
candidateLine is null ? null : candidate.StartLine + candidateLine;
/// <summary>
/// Finds the first field which an object of the response names a second time.
/// </summary>
/// <remarks>
/// JSON allows a name twice, and readers disagree on which of the values counts: the checks below
/// see one of them, while the browser which runs the finished briefing may see the other. The
/// response is refused instead, and the repair names the field. This looks into every value, also
/// into those the contract leaves open, such as the data of a chart.
/// </remarks>
private static ContractInspectionIssue? FindDuplicateField(JsonElement element, string path)
{
switch (element.ValueKind)
{
case JsonValueKind.Object:
{
var names = new HashSet<string>(StringComparer.Ordinal);
foreach (var property in element.EnumerateObject())
{
var safeField = SafeIdentifier(property.Name);
var fieldPath = string.IsNullOrEmpty(safeField) ? path : $"{path}.{safeField}";
if (!names.Add(property.Name))
return new(
VisualBriefingStructuredResponseIssueKind.DUPLICATE_FIELD,
fieldPath,
safeField,
"every field name only once per object");
var issue = FindDuplicateField(property.Value, fieldPath);
if (issue is not null)
return issue;
}
return null;
}
case JsonValueKind.Array:
{
var index = 0;
foreach (var item in element.EnumerateArray())
{
var issue = FindDuplicateField(item, $"{path}[{index}]");
if (issue is not null)
return issue;
index++;
}
return null;
}
default:
return null;
}
}
private static ContractInspectionIssue? Inspect(
JsonElement element,
ContractShape shape,
@@ -496,6 +560,10 @@ internal static partial class VisualBriefingStructuredResponseProcessor
$"The model response contains the unknown field '{issue.FieldName}' at {issue.Path}.",
VisualBriefingStructuredResponseIssueKind.UNKNOWN_FIELD =>
$"The model response contains an unknown field at {issue.Path}.",
VisualBriefingStructuredResponseIssueKind.DUPLICATE_FIELD when !string.IsNullOrEmpty(issue.FieldName) =>
$"The model response contains the field '{issue.FieldName}' more than once at {issue.Path}.",
VisualBriefingStructuredResponseIssueKind.DUPLICATE_FIELD =>
$"The model response contains a field more than once at {issue.Path}.",
VisualBriefingStructuredResponseIssueKind.REQUIRED_FIELD_MISSING =>
$"The required field '{issue.FieldName}' is missing at {issue.Path}.",
VisualBriefingStructuredResponseIssueKind.ENUM_VALUE_INVALID =>