Moved the build to the .NET SDK v10.0.403 (#1156)

This commit is contained in:
Thorsten Sommer authored and GitHub committed 2026-11-04 11:48:11 +01:00
1 parent c0a75f39ac
commit 0375318b1b
10 files changed
+36 -46

No files matched your search

+1 -1
View File
@@ -793,7 +793,7 @@ jobs:
- name: Read the toolchain versions from the metadata
run: |
# The .NET SDK version. The format is '10.0.114 (commit 0427b9abbe)',
# The .NET SDK version. The format is '10.0.403 (commit 5a2162bea6)',
# so we extract the version number alone:
dotnet_sdk_version=$(sed -n '4p' metadata.txt | sed 's/[^0-9.]*\([0-9.]*\).*/\1/')
+1 -1
View File
@@ -28,7 +28,7 @@ Key structure:
## Important Development Notes
- **Naming conventions** - Constants, enum members, and `static readonly` fields use `UPPER_SNAKE_CASE` such as `MY_CONSTANT`.
- **Extension members** - Declare extension methods in an `extension` block of their static class, one block per receiver, never with a `this` parameter: MWAIS0016 refuses the classic form. The `<param>` of the receiver, and of a generic block its `<typeparam>`, belongs on the block, not on its members. The one exception is a method with a `params` parameter, which stays classic until the SDK in `metadata.txt` carries the fix for dotnet/roslyn#80024: for a member of an extension block, the compiler warns about every argument passed one by one to a `params` parameter (CS8620), see `JsRuntimeExtensions`.
- **Extension members** - Declare extension methods in an `extension` block of their static class, one block per receiver, never with a `this` parameter: MWAIS0016 refuses the classic form. The `<param>` of the receiver, and of a generic block its `<typeparam>`, belongs on the block, not on its members.
- **Message Bus** - Singleton event bus for cross-component communication inside the .NET app
- **Prompt injection reports** - The user hears once per action about what the filter removed, as soon as that action ends. Content which belongs to an action carries its scope from `PromptInjectionGuardService.BeginAction` down to where it is read or filtered; whatever is filtered without a scope is reported right away. Pass the scope as a parameter, never through a field or an `AsyncLocal`: the service serves the whole app, and the message bus delivers to other receivers in the execution context of whoever sends.
- **Paths in stored data** - A stored path to a file which AI Studio keeps below the data directory, such as a transcript or a tokenizer copy, is relative to that directory, so that a backup or a copy of the data directory works on another computer, under another user name, or on another OS. Mark such a property with `[JsonConverter(typeof(DataDirectoryRelativePathConverter))]`, for a positional record parameter with `[property: ...]`; in memory, the path stays absolute. Better still, derive the path from IDs and store none, as `VisualBriefingStore` does. Paths to files of the user stay absolute. Chats stored before v26.10.1 are migrated by a compatibility shim, see `documentation/compatibility-shims/2026-10-relative-data-paths.md`.
@@ -3,16 +3,11 @@ using AIStudio.Tools.Services;
namespace AIStudio.Tools;
//
// The methods with a params parameter stay classic extension methods instead of moving into the
// extension block: for a member of an extension block, the compiler of the .NET 10.0.1xx SDK warns
// about every argument passed one by one to a params parameter (CS8620, dotnet/roslyn#80024). Move
// them once the SDK in metadata.txt carries the fix; MWAIS0016 makes the same exception until then.
//
public static class JsRuntimeExtensions
{
private static readonly ILogger LOGGER = Program.LOGGER_FACTORY.CreateLogger(nameof(JsRuntimeExtensions));
/// <param name="jsRuntime">The JS runtime to call.</param>
extension(IJSRuntime jsRuntime)
{
public async Task GenerateAndShowDiff(string text1, string text2)
@@ -24,7 +19,6 @@ public static class JsRuntimeExtensions
{
await jsRuntime.InvokeVoidAsync("clearDiv", divId);
}
}
/// <summary>
/// Calls a JavaScript function which returns nothing, and tolerates a circuit which is already gone.
@@ -36,12 +30,11 @@ public static class JsRuntimeExtensions
/// until the finalizer reports it as an unobserved task exception, without any hint at its origin.
/// This method is the one place which knows how to await such a call and what to do with its failure.
/// </remarks>
/// <param name="jsRuntime">The JS runtime to call.</param>
/// <param name="identifier">The name of the JavaScript function.</param>
/// <param name="args">The arguments for the JavaScript function.</param>
/// <returns>True when the browser ran the function. Callers which remember what they told the browser
/// must check this: a call which never arrived leaves the browser in its previous state.</returns>
public static async ValueTask<bool> TryInvokeVoidAsync(this IJSRuntime jsRuntime, string identifier, params object?[]? args)
public async ValueTask<bool> TryInvokeVoidAsync(string identifier, params object?[]? args)
{
try
{
@@ -64,12 +57,11 @@ public static class JsRuntimeExtensions
/// something we already know cannot succeed — a component of a disconnected circuit which keeps
/// rendering would produce one such exception per render.
/// </remarks>
/// <param name="jsRuntime">The JS runtime to call.</param>
/// <param name="circuitState">The circuit of the caller.</param>
/// <param name="identifier">The name of the JavaScript function.</param>
/// <param name="args">The arguments for the JavaScript function.</param>
/// <returns>True when the browser ran the function, false when it was skipped or failed.</returns>
public static async ValueTask<bool> TryInvokeVoidAsync(this IJSRuntime jsRuntime, CircuitStateService circuitState, string identifier, params object?[]? args)
public async ValueTask<bool> TryInvokeVoidAsync(CircuitStateService circuitState, string identifier, params object?[]? args)
{
if (!circuitState.IsConnected)
{
@@ -90,12 +82,11 @@ public static class JsRuntimeExtensions
/// second question, and there a null is a legitimate answer -- it means the browser looked and found
/// nothing.
/// </remarks>
/// <param name="jsRuntime">The JS runtime to call.</param>
/// <param name="circuitState">The circuit of the caller.</param>
/// <param name="identifier">The name of the JavaScript function.</param>
/// <param name="args">The arguments for the JavaScript function.</param>
/// <returns>Whether the browser ran the function, and what it returned.</returns>
public static async ValueTask<(bool WasInvoked, TValue? Value)> TryInvokeAsync<TValue>(this IJSRuntime jsRuntime, CircuitStateService circuitState, string identifier, params object?[]? args)
public async ValueTask<(bool WasInvoked, TValue? Value)> TryInvokeAsync<TValue>(CircuitStateService circuitState, string identifier, params object?[]? args)
{
if (!circuitState.IsConnected)
{
@@ -113,16 +104,19 @@ public static class JsRuntimeExtensions
return (false, default);
}
}
}
/// <param name="module">The JavaScript module to call.</param>
extension(IJSObjectReference module)
{
/// <summary>
/// Calls a function of a JavaScript module which returns nothing, and tolerates a circuit which is
/// already gone. See the remarks on the JS runtime variant of this method.
/// </summary>
/// <param name="module">The JavaScript module to call.</param>
/// <param name="identifier">The name of the function inside the module.</param>
/// <param name="args">The arguments for the function.</param>
/// <returns>True when the browser ran the function, false when it failed.</returns>
public static async ValueTask<bool> TryInvokeVoidAsync(this IJSObjectReference module, string identifier, params object?[]? args)
public async ValueTask<bool> TryInvokeVoidAsync(string identifier, params object?[]? args)
{
try
{
@@ -135,6 +129,7 @@ public static class JsRuntimeExtensions
return false;
}
}
}
private static void LogInvocationFailure(Exception exception, string identifier)
{
@@ -49,20 +49,16 @@ internal static class MailToolArguments
private static readonly string[] SPECIAL_FOLDER_VALUES = [SPECIAL_FOLDER_SENT, SPECIAL_FOLDER_DRAFTS];
/// <param name="builder">The schema of the tool.</param>
extension(ToolParameterSchemaBuilder builder)
{
/// <summary>
/// Adds the conditions to the arguments a mail tool describes.
/// </summary>
/// <param name="builder">The schema of the tool.</param>
/// <param name="toolAction">What the tool does with the mails, e.g., "search", completing "the mailboxes to ...".</param>
/// <param name="mailboxIds">The ids of the mailboxes the tool offers, or none while no mailboxes are known.</param>
/// <returns>The schema, for further arguments.</returns>
/// <remarks>
/// A classic extension method instead of a member of an extension block: for those, the compiler of
/// the .NET 10.0.1xx SDK warns about every mailbox id passed one by one to the params parameter
/// (CS8620, dotnet/roslyn#80024). Move it once the SDK in metadata.txt carries the fix; MWAIS0016 makes
/// the same exception until then.
/// </remarks>
public static ToolParameterSchemaBuilder AddMailConditions(this ToolParameterSchemaBuilder builder, string toolAction, params string[] mailboxIds) => builder
public ToolParameterSchemaBuilder AddMailConditions(string toolAction, params string[] mailboxIds) => builder
.OptionalStringArray(MAILBOX_IDS_ARGUMENT, $"Optional IDs of the mailboxes to {toolAction}, out of those listed in the description of this tool. Leave it out to {toolAction} all of them.", mailboxIds)
.OptionalString(FROM_ARGUMENT, $"Optional part of the address or the name of the sender, such as 'alice@example.org', 'example.org', or 'Alice'. At most {MAX_ADDRESS_CHARACTERS} characters.")
.OptionalString(TO_ARGUMENT, $"Optional part of the address or the name of a recipient in To, Cc, or Bcc. At most {MAX_ADDRESS_CHARACTERS} characters.")
@@ -75,6 +71,7 @@ internal static class MailToolArguments
.OptionalBoolean(HAS_ATTACHMENTS_ARGUMENT, "Optional: true for mails with attachments only, false for mails without any.")
.OptionalString(FOLDER_ARGUMENT, $"Optional full path of the folder the mails lie in, exactly as results show it, such as 'INBOX' or 'Archive/2026'. Subfolders are not included. For the sent mails or the drafts, use {SPECIAL_FOLDER_ARGUMENT} instead.")
.OptionalEnum(SPECIAL_FOLDER_ARGUMENT, $"Optional: only the mails the user sent, or only the drafts of the user, however the server names these folders. Cannot be combined with {FOLDER_ARGUMENT}.", SPECIAL_FOLDER_VALUES);
}
/// <summary>
/// The value of the importance argument which stands for the given importance.
@@ -20,10 +20,12 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<!--
Never above the Roslyn of the .NET SDK we build with (see metadata.txt): the 10.0.1xx SDKs ship
Roslyn 5.0. Built against a newer one, the compiler skips these analyzers with CS9057. Raise
both only together with the SDK feature band, with SourceGeneratedMappings, and with the Tests
project.
Never above the Roslyn of the oldest .NET SDK which may build AI Studio: the 10.0.1xx SDKs ship
Roslyn 5.0. Built against a newer one, the compiler skips these analyzers with CS9057. The
pipeline builds with the SDK in metadata.txt, a 10.0.4xx with Roslyn 5.9, but the .NET packages
of Linux distributions bring a 10.0.1xx, and so may the computer of a contributor. Raise both
only together with SourceGeneratedMappings and the Tests project, and only once a 10.0.1xx SDK
no longer has to build AI Studio.
-->
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="5.0.0" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="5.0.0" />
@@ -13,10 +13,7 @@ namespace SourceCodeRules.StyleAnalyzers;
/// </summary>
/// <remarks>
/// AI Studio declares its extension members in extension blocks, one per receiver, so that all members of a
/// receiver read alike and extension properties can join them. A method with a params parameter stays a
/// classic one for now: for a member of an extension block, the compiler of the .NET 10.0.1xx SDK warns about
/// every argument passed one by one to a params parameter (CS8620, dotnet/roslyn#80024). Drop that exception
/// once the SDK in metadata.txt carries the fix.
/// receiver read alike and extension properties can join them.
/// </remarks>
#pragma warning disable RS1038
[DiagnosticAnalyzer(LanguageNames.CSharp)]
@@ -29,7 +26,7 @@ public sealed class ClassicExtensionMethodAnalyzer : DiagnosticAnalyzer
private const string MESSAGE_FORMAT = "Declare '{0}' as a member of an extension block for its receiver instead of with a this parameter";
private const string DESCRIPTION = "AI Studio declares its extension members in extension blocks, one per receiver. Only a method with a params parameter stays a classic extension method, until the SDK fixes dotnet/roslyn#80024.";
private const string DESCRIPTION = "AI Studio declares its extension members in extension blocks, one per receiver.";
private const string CATEGORY = "Style";
@@ -55,9 +52,6 @@ public sealed class ClassicExtensionMethodAnalyzer : DiagnosticAnalyzer
if (thisKeyword.IsKind(SyntaxKind.None))
return;
if (parameters.Any(parameter => parameter.Modifiers.Any(SyntaxKind.ParamsKeyword)))
return;
context.ReportDiagnostic(Diagnostic.Create(RULE, thisKeyword.GetLocation(), method.Identifier.Text));
}
}
@@ -21,10 +21,13 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<!--
Never above the Roslyn of the .NET SDK we build with (see metadata.txt): the 10.0.1xx SDKs ship
Never above the Roslyn of the oldest .NET SDK which may build AI Studio: the 10.0.1xx SDKs ship
Roslyn 5.0. Built against a newer one, the compiler skips this generator with CS9057, and the
generated namespaces such as AIStudio.Tools.PluginSystem.Assistants.Icons go missing. Raise it
only together with the SDK feature band, with SourceCodeRules, and with the Tests project.
generated namespaces such as AIStudio.Tools.PluginSystem.Assistants.Icons go missing. The
pipeline builds with the SDK in metadata.txt, a 10.0.4xx with Roslyn 5.9, but the .NET packages
of Linux distributions bring a 10.0.1xx, and so may the computer of a contributor. Raise it only
together with SourceCodeRules and the Tests project, and only once a 10.0.1xx SDK no longer has
to build AI Studio.
-->
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="5.0.0" />
</ItemGroup>
@@ -11,9 +11,7 @@ namespace AIStudio.Tests.Analyzers;
/// </summary>
/// <remarks>
/// AI Studio declares all its extension members in extension blocks, one per receiver. MWAIS0016 refuses the
/// classic form while compiling, except for a method with a params parameter: for a member of an extension
/// block, the compiler of the .NET 10.0.1xx SDK warns about every argument passed one by one to a params
/// parameter (CS8620, dotnet/roslyn#80024).
/// classic form while compiling.
/// </remarks>
[TestFixture]
public sealed class ClassicExtensionMethodAnalyzerTests
@@ -24,6 +22,7 @@ public sealed class ClassicExtensionMethodAnalyzerTests
[TestCase("public static T Echo<T>(this T value) where T : class => value;", "Echo", TestName = "A generic extension method")]
[TestCase("public static void Bump(this ref int value) => value++;", "Bump", TestName = "An extension method with a ref receiver")]
[TestCase("public static System.Threading.Tasks.Task<string> LabelAsync(this Meeting meeting) => System.Threading.Tasks.Task.FromResult(meeting.Name);", "LabelAsync", TestName = "An asynchronous extension method")]
[TestCase("public static string Label(this Meeting meeting, params string[] prefixes) => string.Concat(prefixes) + meeting.Name;", "Label", TestName = "An extension method with a params parameter")]
public async Task AClassicExtensionMethodIsRefused(string member, string name)
{
var reported = await AnalyzeAsync(member);
@@ -39,7 +38,6 @@ public sealed class ClassicExtensionMethodAnalyzerTests
[TestCase("extension(Meeting meeting) { public string Label() => meeting.Name; }", TestName = "A member of an extension block")]
[TestCase("extension<T>(T value) where T : class { public T Echo() => value; }", TestName = "A member of a generic extension block")]
[TestCase("public static string Label(Meeting meeting) => meeting.Name;", TestName = "A static method without a this parameter")]
[TestCase("public static string Label(this Meeting meeting, params string[] prefixes) => string.Concat(prefixes) + meeting.Name;", TestName = "An extension method with a params parameter")]
public async Task AnythingElseIsLeftAlone(string member)
{
var reported = await AnalyzeAsync(member);
+2 -1
View File
@@ -14,7 +14,8 @@
<!--
Held at the version the two analyzer projects build against, so that a test compiling a
snippet uses the same Roslyn the analyzers themselves were written for. Raise all three
together, and never above the Roslyn of the .NET SDK in metadata.txt (5.0 for 10.0.1xx).
together, and never above the Roslyn of the oldest .NET SDK which may build AI Studio (5.0 for
10.0.1xx), even though the SDK in metadata.txt ships a newer one; SourceCodeRules explains why.
-->
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="5.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.11.0" />
+1 -1
View File
@@ -1,7 +1,7 @@
26.10.1
2026-10-29 07:39:07 UTC
259
10.0.114 (commit 0427b9abbe)
10.0.403 (commit 5a2162bea6)
10.0.14 (commit 0427b9abbe)
1.99.1 (commit 58cf8f9c3)
9.12.0