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
+143 -153

No files matched your search

@@ -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,115 +19,115 @@ 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.
/// </summary>
/// <remarks>
/// Blazor cannot issue JS interop calls once the browser connection of a circuit is gone. That happens
/// during every reload and while a component gets disposed, so the failure is expected rather than
/// exceptional. Discarding such a call is not an option, though: the discarded task keeps the fault
/// 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)
{
try
/// <summary>
/// Calls a JavaScript function which returns nothing, and tolerates a circuit which is already gone.
/// </summary>
/// <remarks>
/// Blazor cannot issue JS interop calls once the browser connection of a circuit is gone. That happens
/// during every reload and while a component gets disposed, so the failure is expected rather than
/// exceptional. Discarding such a call is not an option, though: the discarded task keeps the fault
/// 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="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 async ValueTask<bool> TryInvokeVoidAsync(string identifier, params object?[]? args)
{
await jsRuntime.InvokeVoidAsync(identifier, args);
return true;
try
{
await jsRuntime.InvokeVoidAsync(identifier, args);
return true;
}
catch (Exception exception)
{
LogInvocationFailure(exception, identifier);
return false;
}
}
catch (Exception exception)
/// <summary>
/// Calls a JavaScript function which returns nothing, unless the circuit is known to be disconnected.
/// </summary>
/// <remarks>
/// Prefer this over the variant without a circuit state wherever the caller knows its circuit. While a
/// browser connection is gone, every single call would otherwise throw, which is needless work for
/// 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="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 async ValueTask<bool> TryInvokeVoidAsync(CircuitStateService circuitState, string identifier, params object?[]? args)
{
LogInvocationFailure(exception, identifier);
return false;
if (!circuitState.IsConnected)
{
LOGGER.LogDebug("The JS call '{Identifier}' was skipped because the browser connection of the circuit '{CircuitId}' is down.", identifier, circuitState.CircuitId);
return false;
}
return await jsRuntime.TryInvokeVoidAsync(identifier, args);
}
/// <summary>
/// Calls a JavaScript function which returns a value, unless the circuit is known to be disconnected.
/// </summary>
/// <remarks>
/// The two parts of the result answer two different questions, and callers must keep them apart.
/// Whether the browser ran the function at all comes first: a call which never arrived says nothing
/// about the page, so nobody may act on an answer they did not get. What the function returned is the
/// second question, and there a null is a legitimate answer -- it means the browser looked and found
/// nothing.
/// </remarks>
/// <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 async ValueTask<(bool WasInvoked, TValue? Value)> TryInvokeAsync<TValue>(CircuitStateService circuitState, string identifier, params object?[]? args)
{
if (!circuitState.IsConnected)
{
LOGGER.LogDebug("The JS call '{Identifier}' was skipped because the browser connection of the circuit '{CircuitId}' is down.", identifier, circuitState.CircuitId);
return (false, default);
}
try
{
return (true, await jsRuntime.InvokeAsync<TValue>(identifier, args));
}
catch (Exception exception)
{
LogInvocationFailure(exception, identifier);
return (false, default);
}
}
}
/// <summary>
/// Calls a JavaScript function which returns nothing, unless the circuit is known to be disconnected.
/// </summary>
/// <remarks>
/// Prefer this over the variant without a circuit state wherever the caller knows its circuit. While a
/// browser connection is gone, every single call would otherwise throw, which is needless work for
/// 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)
{
if (!circuitState.IsConnected)
{
LOGGER.LogDebug("The JS call '{Identifier}' was skipped because the browser connection of the circuit '{CircuitId}' is down.", identifier, circuitState.CircuitId);
return false;
}
return await jsRuntime.TryInvokeVoidAsync(identifier, args);
}
/// <summary>
/// Calls a JavaScript function which returns a value, unless the circuit is known to be disconnected.
/// </summary>
/// <remarks>
/// The two parts of the result answer two different questions, and callers must keep them apart.
/// Whether the browser ran the function at all comes first: a call which never arrived says nothing
/// about the page, so nobody may act on an answer they did not get. What the function returned is the
/// 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)
{
if (!circuitState.IsConnected)
{
LOGGER.LogDebug("The JS call '{Identifier}' was skipped because the browser connection of the circuit '{CircuitId}' is down.", identifier, circuitState.CircuitId);
return (false, default);
}
try
{
return (true, await jsRuntime.InvokeAsync<TValue>(identifier, args));
}
catch (Exception exception)
{
LogInvocationFailure(exception, identifier);
return (false, default);
}
}
/// <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)
extension(IJSObjectReference module)
{
try
/// <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="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 async ValueTask<bool> TryInvokeVoidAsync(string identifier, params object?[]? args)
{
await module.InvokeVoidAsync(identifier, args);
return true;
}
catch (Exception exception)
{
LogInvocationFailure(exception, identifier);
return false;
try
{
await module.InvokeVoidAsync(identifier, args);
return true;
}
catch (Exception exception)
{
LogInvocationFailure(exception, identifier);
return false;
}
}
}
@@ -49,32 +49,29 @@ internal static class MailToolArguments
private static readonly string[] SPECIAL_FOLDER_VALUES = [SPECIAL_FOLDER_SENT, SPECIAL_FOLDER_DRAFTS];
/// <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
.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.")
.OptionalString(AFTER_ARGUMENT, "Optional: only mails received at this point in time or later. A date such as 2026-09-01 stands for the beginning of that day in the time zone of the user. A date with a time of day such as 2026-09-01T14:30 is read in that time zone as well, unless it ends with an offset such as +02:00 or with Z.")
.OptionalString(BEFORE_ARGUMENT, "Optional: only mails received before this point in time, in the same forms as the argument after. A date stands for the beginning of that day, so before 2026-09-30 leaves that day out.")
.OptionalBoolean(IS_UNREAD_ARGUMENT, "Optional: true for unread mails only, false for read ones only.")
.OptionalBoolean(IS_FLAGGED_ARGUMENT, "Optional: true for flagged mails only, false for unflagged ones only.")
.OptionalBoolean(IS_ENCRYPTED_ARGUMENT, "Optional: true for encrypted mails only, false for unencrypted ones only. AI Studio cannot read the content of encrypted mails, only their header.")
.OptionalEnum(IMPORTANCE_ARGUMENT, "Optional importance the sender marked the mails with. Mails without such a mark count as normal.", IMPORTANCE_VALUES)
.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);
extension(ToolParameterSchemaBuilder builder)
{
/// <summary>
/// Adds the conditions to the arguments a mail tool describes.
/// </summary>
/// <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>
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.")
.OptionalString(AFTER_ARGUMENT, "Optional: only mails received at this point in time or later. A date such as 2026-09-01 stands for the beginning of that day in the time zone of the user. A date with a time of day such as 2026-09-01T14:30 is read in that time zone as well, unless it ends with an offset such as +02:00 or with Z.")
.OptionalString(BEFORE_ARGUMENT, "Optional: only mails received before this point in time, in the same forms as the argument after. A date stands for the beginning of that day, so before 2026-09-30 leaves that day out.")
.OptionalBoolean(IS_UNREAD_ARGUMENT, "Optional: true for unread mails only, false for read ones only.")
.OptionalBoolean(IS_FLAGGED_ARGUMENT, "Optional: true for flagged mails only, false for unflagged ones only.")
.OptionalBoolean(IS_ENCRYPTED_ARGUMENT, "Optional: true for encrypted mails only, false for unencrypted ones only. AI Studio cannot read the content of encrypted mails, only their header.")
.OptionalEnum(IMPORTANCE_ARGUMENT, "Optional importance the sender marked the mails with. Mails without such a mark count as normal.", IMPORTANCE_VALUES)
.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.