From 33f7a6eba0b9518d0123ec3cba0b247000814e66 Mon Sep 17 00:00:00 2001 From: Javier Calvarro Nelson Date: Tue, 9 Jun 2026 13:19:40 +0200 Subject: [PATCH 01/10] Add ToolApprovalRequestContent.IsInvokerRequested Lets consumers distinguish approval requests raised because the underlying function is an ApprovalRequiredAIFunction (IsInvokerRequested = false, the default) from requests that FunctionInvokingChatClient added as collateral because a peer call in the same batch required approval (IsInvokerRequested = true). Both FICC wrap sites set the flag using the existing FindTool + GetService() pattern so additional DelegatingAIFunction wrappers still classify correctly. The streaming helper now takes the tool lists symmetrically with the non-streaming path. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Contents/ToolApprovalRequestContent.cs | 23 ++++ .../Microsoft.Extensions.AI.Abstractions.json | 6 +- .../FunctionInvokingChatClient.cs | 27 +++-- .../AssertExtensions.cs | 1 + .../ToolApprovalRequestContentTests.cs | 24 ++++ ...unctionInvokingChatClientApprovalsTests.cs | 111 +++++++++++++++++- 6 files changed, 181 insertions(+), 11 deletions(-) diff --git a/src/Libraries/Microsoft.Extensions.AI.Abstractions/Contents/ToolApprovalRequestContent.cs b/src/Libraries/Microsoft.Extensions.AI.Abstractions/Contents/ToolApprovalRequestContent.cs index da5e75d49a8..3d744ee59c9 100644 --- a/src/Libraries/Microsoft.Extensions.AI.Abstractions/Contents/ToolApprovalRequestContent.cs +++ b/src/Libraries/Microsoft.Extensions.AI.Abstractions/Contents/ToolApprovalRequestContent.cs @@ -2,7 +2,9 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; +using System.Diagnostics.CodeAnalysis; using System.Text.Json.Serialization; +using Microsoft.Shared.DiagnosticIds; using Microsoft.Shared.Diagnostics; namespace Microsoft.Extensions.AI; @@ -32,6 +34,27 @@ public ToolApprovalRequestContent(string requestId, ToolCallContent toolCall) /// public ToolCallContent ToolCall { get; } + /// + /// Gets or sets a value indicating whether this approval request was added by the invoker + /// of the tool call rather than because the underlying tool itself was declared as requiring approval. + /// + /// + /// + /// Defaults to , indicating that the underlying tool itself required + /// approval (for example, the targeted function was an ). + /// + /// + /// Some invokers convert every concurrent function call in a response into a + /// whenever any one of them targets an + /// , so that approvals and rejections stay coherent + /// across the batch. When set to , this property indicates that the + /// request was added for that reason and the underlying tool did not itself require approval; + /// consumers may use this to, for example, auto-approve such requests. + /// + /// + [Experimental(DiagnosticIds.Experiments.AIFunctionApprovals, UrlFormat = DiagnosticIds.UrlFormat)] + public bool IsInvokerRequested { get; set; } + /// /// Creates a indicating whether the tool call is approved or rejected. /// diff --git a/src/Libraries/Microsoft.Extensions.AI.Abstractions/Microsoft.Extensions.AI.Abstractions.json b/src/Libraries/Microsoft.Extensions.AI.Abstractions/Microsoft.Extensions.AI.Abstractions.json index f48b3048be7..cd12db2a012 100644 --- a/src/Libraries/Microsoft.Extensions.AI.Abstractions/Microsoft.Extensions.AI.Abstractions.json +++ b/src/Libraries/Microsoft.Extensions.AI.Abstractions/Microsoft.Extensions.AI.Abstractions.json @@ -1,5 +1,5 @@ { - "Name": "Microsoft.Extensions.AI.Abstractions, Version=10.6.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35", + "Name": "Microsoft.Extensions.AI.Abstractions, Version=10.8.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35", "Types": [ { "Type": "sealed class Microsoft.Extensions.AI.AdditionalPropertiesDictionary : Microsoft.Extensions.AI.AdditionalPropertiesDictionary", @@ -4604,6 +4604,10 @@ } ], "Properties": [ + { + "Member": "bool Microsoft.Extensions.AI.ToolApprovalRequestContent.IsInvokerRequested { get; set; }", + "Stage": "Experimental" + }, { "Member": "Microsoft.Extensions.AI.ToolCallContent Microsoft.Extensions.AI.ToolApprovalRequestContent.ToolCall { get; }", "Stage": "Stable" diff --git a/src/Libraries/Microsoft.Extensions.AI/ChatCompletion/FunctionInvokingChatClient.cs b/src/Libraries/Microsoft.Extensions.AI/ChatCompletion/FunctionInvokingChatClient.cs index e0ff736a68d..1ac64a3a4bb 100644 --- a/src/Libraries/Microsoft.Extensions.AI/ChatCompletion/FunctionInvokingChatClient.cs +++ b/src/Libraries/Microsoft.Extensions.AI/ChatCompletion/FunctionInvokingChatClient.cs @@ -613,7 +613,7 @@ public override async IAsyncEnumerable GetStreamingResponseA for (; lastYieldedUpdateIndex < updates.Count; lastYieldedUpdateIndex++) { var updateToYield = updates[lastYieldedUpdateIndex]; - if (TryReplaceFunctionCallsWithApprovalRequests(updateToYield.Contents, out var updatedContents)) + if (TryReplaceFunctionCallsWithApprovalRequests(updateToYield.Contents, out var updatedContents, options?.Tools, AdditionalTools)) { updateToYield.Contents = updatedContents; } @@ -1618,7 +1618,10 @@ private static (bool hasApprovalRequiringFcc, int lastApprovalCheckedFCCIndex) C /// Replaces all with and ouputs a new list if any of them were replaced. /// /// true if any was replaced, false otherwise. - private static bool TryReplaceFunctionCallsWithApprovalRequests(IList content, out List? updatedContent) + private static bool TryReplaceFunctionCallsWithApprovalRequests( + IList content, + out List? updatedContent, + params ReadOnlySpan?> toolLists) { updatedContent = null; @@ -1629,7 +1632,11 @@ private static bool TryReplaceFunctionCallsWithApprovalRequests(IList if (content[i] is FunctionCallContent fcc && !fcc.InformationalOnly) { updatedContent ??= [.. content]; // Clone the list if we haven't already - updatedContent[i] = new ToolApprovalRequestContent(ComposeApprovalRequestId(fcc.CallId), fcc); + bool requiredByFunction = FindTool(fcc.Name, toolLists)?.GetService() is not null; + updatedContent[i] = new ToolApprovalRequestContent(ComposeApprovalRequestId(fcc.CallId), fcc) + { + IsInvokerRequested = !requiredByFunction, + }; } } } @@ -1648,7 +1655,7 @@ private IList ReplaceFunctionCallsWithApprovalRequests( var outputMessages = messages; bool anyApprovalRequired = false; - List<(int, int)>? allFunctionCallContentIndices = null; + List<(int MessageIndex, int ContentIndex, bool IsInvokerRequested)>? allFunctionCallContentIndices = null; // Build a list of the indices of all FunctionCallContent items. // Also check if any of them require approval. @@ -1659,9 +1666,10 @@ private IList ReplaceFunctionCallsWithApprovalRequests( { if (content[j] is FunctionCallContent functionCall && !functionCall.InformationalOnly) { - (allFunctionCallContentIndices ??= []).Add((i, j)); + bool requiredByFunction = FindTool(functionCall.Name, toolLists)?.GetService() is not null; + (allFunctionCallContentIndices ??= []).Add((i, j, !requiredByFunction)); - anyApprovalRequired |= FindTool(functionCall.Name, toolLists)?.GetService() is not null; + anyApprovalRequired |= requiredByFunction; } } } @@ -1676,7 +1684,7 @@ private IList ReplaceFunctionCallsWithApprovalRequests( outputMessages = [.. messages]; int lastMessageIndex = -1; - foreach (var (messageIndex, contentIndex) in allFunctionCallContentIndices!) + foreach (var (messageIndex, contentIndex, isInvokerRequested) in allFunctionCallContentIndices!) { // Clone the message if we didn't already clone it in a previous iteration. var message = lastMessageIndex != messageIndex ? outputMessages[messageIndex].Clone() : outputMessages[messageIndex]; @@ -1684,7 +1692,10 @@ private IList ReplaceFunctionCallsWithApprovalRequests( var functionCall = (FunctionCallContent)message.Contents[contentIndex]; LogFunctionRequiresApproval(functionCall.Name); - message.Contents[contentIndex] = new ToolApprovalRequestContent(ComposeApprovalRequestId(functionCall.CallId), functionCall); + message.Contents[contentIndex] = new ToolApprovalRequestContent(ComposeApprovalRequestId(functionCall.CallId), functionCall) + { + IsInvokerRequested = isInvokerRequested, + }; outputMessages[messageIndex] = message; lastMessageIndex = messageIndex; diff --git a/test/Libraries/Microsoft.Extensions.AI.Abstractions.Tests/AssertExtensions.cs b/test/Libraries/Microsoft.Extensions.AI.Abstractions.Tests/AssertExtensions.cs index 8111bf80e94..e909ec00186 100644 --- a/test/Libraries/Microsoft.Extensions.AI.Abstractions.Tests/AssertExtensions.cs +++ b/test/Libraries/Microsoft.Extensions.AI.Abstractions.Tests/AssertExtensions.cs @@ -50,6 +50,7 @@ public static void EqualMessageLists(List expectedMessages, List(json, AIJsonUtilities.DefaultOptions); + + Assert.NotNull(deserialized); + Assert.Equal(value, deserialized!.IsInvokerRequested); + } } diff --git a/test/Libraries/Microsoft.Extensions.AI.Tests/ChatCompletion/FunctionInvokingChatClientApprovalsTests.cs b/test/Libraries/Microsoft.Extensions.AI.Tests/ChatCompletion/FunctionInvokingChatClientApprovalsTests.cs index 1fcf22fa292..4dd932cca20 100644 --- a/test/Libraries/Microsoft.Extensions.AI.Tests/ChatCompletion/FunctionInvokingChatClientApprovalsTests.cs +++ b/test/Libraries/Microsoft.Extensions.AI.Tests/ChatCompletion/FunctionInvokingChatClientApprovalsTests.cs @@ -83,6 +83,9 @@ public async Task AllFunctionCallsReplacedWithApprovalsWhenAnyRequireApprovalAsy [ new ToolApprovalRequestContent("ficc_callId1", new FunctionCallContent("callId1", "Func1")), new ToolApprovalRequestContent("ficc_callId2", new FunctionCallContent("callId2", "Func2", arguments: new Dictionary { { "i", 42 } })) + { + IsInvokerRequested = true, + } ]) ]; @@ -121,12 +124,20 @@ public async Task AllFunctionCallsReplacedWithApprovalsWhenAnyRequestOrAdditiona new ChatMessage(ChatRole.Assistant, [new FunctionCallContent("callId1", "Func1"), new FunctionCallContent("callId2", "Func2", arguments: new Dictionary { { "i", 42 } })]), ]; + // When additionalToolsRequireApproval is true: Func1 (additional tools) requires approval and Func2 (options.Tools) is collateral. + // When false: Func2 (options.Tools) requires approval and Func1 (additional tools) is collateral. List expectedOutput = [ new ChatMessage(ChatRole.Assistant, [ - new ToolApprovalRequestContent("ficc_callId1", new FunctionCallContent("callId1", "Func1")), + new ToolApprovalRequestContent("ficc_callId1", new FunctionCallContent("callId1", "Func1")) + { + IsInvokerRequested = !additionalToolsRequireApproval, + }, new ToolApprovalRequestContent("ficc_callId2", new FunctionCallContent("callId2", "Func2", arguments: new Dictionary { { "i", 42 } })) + { + IsInvokerRequested = additionalToolsRequireApproval, + } ]) ]; @@ -135,6 +146,99 @@ public async Task AllFunctionCallsReplacedWithApprovalsWhenAnyRequestOrAdditiona await InvokeAndAssertStreamingAsync(options, input, downstreamClientOutput, expectedOutput, additionalTools: additionalTools); } + private sealed class PassThroughDelegatingAIFunction(AIFunction inner) : DelegatingAIFunction(inner); + + [Fact] + public async Task IsInvokerRequested_IsFalseForApprovalRequiredFunctionNestedInDelegatingWrapperAsync() + { + // Wrap the ApprovalRequiredAIFunction in another DelegatingAIFunction (e.g. a telemetry decorator). + // FICC must still classify the call as "function-required approval" (IsInvokerRequested = false) + // by walking the delegation chain via GetService(). + AITool[] tools = + [ + new PassThroughDelegatingAIFunction( + new ApprovalRequiredAIFunction( + AIFunctionFactory.Create(() => "Result 1", "Func1"))), + AIFunctionFactory.Create((int i) => $"Result 2: {i}", "Func2"), + ]; + + var options = new ChatOptions { Tools = tools }; + + List input = + [ + new ChatMessage(ChatRole.User, "hello"), + ]; + + List downstreamClientOutput = + [ + new ChatMessage(ChatRole.Assistant, [ + new FunctionCallContent("callId1", "Func1"), + new FunctionCallContent("callId2", "Func2", arguments: new Dictionary { { "i", 42 } }) + ]), + ]; + + List expectedOutput = + [ + new ChatMessage(ChatRole.Assistant, + [ + new ToolApprovalRequestContent("ficc_callId1", new FunctionCallContent("callId1", "Func1")), + new ToolApprovalRequestContent("ficc_callId2", new FunctionCallContent("callId2", "Func2", arguments: new Dictionary { { "i", 42 } })) + { + IsInvokerRequested = true, + } + ]) + ]; + + await InvokeAndAssertAsync(options, input, downstreamClientOutput, expectedOutput); + + await InvokeAndAssertStreamingAsync(options, input, downstreamClientOutput, expectedOutput); + } + + [Fact] + public async Task IsInvokerRequested_IsTrueForFunctionCallWithNoMatchingToolWhenPeerRequiresApprovalAsync() + { + // The downstream client emits an FCC referencing a tool name that is not in the tools list. + // Because a peer call (Func1) requires approval, FICC still wraps the unknown call as collateral. + // Since no matching tool is found (and therefore no ApprovalRequiredAIFunction is detected), + // the resulting approval request must carry IsInvokerRequested = true. + var options = new ChatOptions + { + Tools = + [ + new ApprovalRequiredAIFunction(AIFunctionFactory.Create(() => "Result 1", "Func1")), + ] + }; + + List input = + [ + new ChatMessage(ChatRole.User, "hello"), + ]; + + List downstreamClientOutput = + [ + new ChatMessage(ChatRole.Assistant, [ + new FunctionCallContent("callId1", "Func1"), + new FunctionCallContent("callId2", "Unknown"), + ]), + ]; + + List expectedOutput = + [ + new ChatMessage(ChatRole.Assistant, + [ + new ToolApprovalRequestContent("ficc_callId1", new FunctionCallContent("callId1", "Func1")), + new ToolApprovalRequestContent("ficc_callId2", new FunctionCallContent("callId2", "Unknown")) + { + IsInvokerRequested = true, + } + ]) + ]; + + await InvokeAndAssertAsync(options, input, downstreamClientOutput, expectedOutput); + + await InvokeAndAssertStreamingAsync(options, input, downstreamClientOutput, expectedOutput); + } + [Fact] public async Task ApprovedApprovalResponsesAreExecutedAsync() { @@ -1735,7 +1839,10 @@ private static List CloneInput(List input) => InformationalOnly = fcc.InformationalOnly }, ToolApprovalRequestContent tarc => - new ToolApprovalRequestContent(tarc.RequestId, (ToolCallContent)CloneFcc(tarc.ToolCall)), + new ToolApprovalRequestContent(tarc.RequestId, (ToolCallContent)CloneFcc(tarc.ToolCall)) + { + IsInvokerRequested = tarc.IsInvokerRequested, + }, ToolApprovalResponseContent tarc => new ToolApprovalResponseContent(tarc.RequestId, tarc.Approved, (ToolCallContent)CloneFcc(tarc.ToolCall)) { From 042d61469e7e84b4a554262965eadf3ce6f22684 Mon Sep 17 00:00:00 2001 From: Javier Calvarro Nelson Date: Tue, 9 Jun 2026 23:02:54 +0200 Subject: [PATCH 02/10] Suppress MEAI001 in Evaluation.Reporting projects STJ source generator emits read/write code for ToolApprovalRequestContent.IsInvokerRequested, which is [Experimental(MEAI001)]. The generated code can't carry pragma suppressions, so suppress at the project level. Matches the existing pattern used by Microsoft.Extensions.AI.Abstractions and Microsoft.Extensions.AI. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Microsoft.Extensions.AI.Evaluation.Reporting.Azure.csproj | 1 + .../CSharp/Microsoft.Extensions.AI.Evaluation.Reporting.csproj | 1 + 2 files changed, 2 insertions(+) diff --git a/src/Libraries/Microsoft.Extensions.AI.Evaluation.Reporting.Azure/Microsoft.Extensions.AI.Evaluation.Reporting.Azure.csproj b/src/Libraries/Microsoft.Extensions.AI.Evaluation.Reporting.Azure/Microsoft.Extensions.AI.Evaluation.Reporting.Azure.csproj index ddc986f187a..c7e88cbb236 100644 --- a/src/Libraries/Microsoft.Extensions.AI.Evaluation.Reporting.Azure/Microsoft.Extensions.AI.Evaluation.Reporting.Azure.csproj +++ b/src/Libraries/Microsoft.Extensions.AI.Evaluation.Reporting.Azure/Microsoft.Extensions.AI.Evaluation.Reporting.Azure.csproj @@ -12,6 +12,7 @@ true n/a n/a + $(NoWarn);MEAI001 diff --git a/src/Libraries/Microsoft.Extensions.AI.Evaluation.Reporting/CSharp/Microsoft.Extensions.AI.Evaluation.Reporting.csproj b/src/Libraries/Microsoft.Extensions.AI.Evaluation.Reporting/CSharp/Microsoft.Extensions.AI.Evaluation.Reporting.csproj index 8a960fc4df1..331e7466a15 100644 --- a/src/Libraries/Microsoft.Extensions.AI.Evaluation.Reporting/CSharp/Microsoft.Extensions.AI.Evaluation.Reporting.csproj +++ b/src/Libraries/Microsoft.Extensions.AI.Evaluation.Reporting/CSharp/Microsoft.Extensions.AI.Evaluation.Reporting.csproj @@ -19,6 +19,7 @@ true n/a n/a + $(NoWarn);MEAI001 From 1cc9936a587b1d88119535318c76c889bc73cf90 Mon Sep 17 00:00:00 2001 From: Javier Calvarro Nelson Date: Wed, 10 Jun 2026 10:08:32 +0200 Subject: [PATCH 03/10] Rename IsInvokerRequested to RequiresConfirmation and flip polarity Renames the property added in #7549 to use Abstractions-neutral vocabulary (per @jozkee feedback on #7550) and flips the polarity so the default (true) is correct for any producer that constructs a ToolApprovalRequestContent because the underlying tool genuinely requires approval. FICC now explicitly sets RequiresConfirmation = false for requests it wraps as a no-op because a peer in the same response requires approval. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Contents/ToolApprovalRequestContent.cs | 19 ++++++++------ .../Microsoft.Extensions.AI.Abstractions.json | 2 +- .../FunctionInvokingChatClient.cs | 10 +++---- .../AssertExtensions.cs | 2 +- .../ToolApprovalRequestContentTests.cs | 10 +++---- ...unctionInvokingChatClientApprovalsTests.cs | 26 +++++++++---------- 6 files changed, 36 insertions(+), 33 deletions(-) diff --git a/src/Libraries/Microsoft.Extensions.AI.Abstractions/Contents/ToolApprovalRequestContent.cs b/src/Libraries/Microsoft.Extensions.AI.Abstractions/Contents/ToolApprovalRequestContent.cs index 3d744ee59c9..bf80ffbc208 100644 --- a/src/Libraries/Microsoft.Extensions.AI.Abstractions/Contents/ToolApprovalRequestContent.cs +++ b/src/Libraries/Microsoft.Extensions.AI.Abstractions/Contents/ToolApprovalRequestContent.cs @@ -35,25 +35,28 @@ public ToolApprovalRequestContent(string requestId, ToolCallContent toolCall) public ToolCallContent ToolCall { get; } /// - /// Gets or sets a value indicating whether this approval request was added by the invoker - /// of the tool call rather than because the underlying tool itself was declared as requiring approval. + /// Gets or sets a value indicating whether this approval request needs a confirmation + /// from the consumer before the underlying tool call is invoked. /// /// /// - /// Defaults to , indicating that the underlying tool itself required - /// approval (for example, the targeted function was an ). + /// Defaults to , indicating that the underlying tool genuinely + /// requires approval (for example, the targeted function is an ) + /// and the consumer must obtain a confirmation (for instance, a user prompt, a policy decision, + /// or a governance gate) before the call is invoked. /// /// /// Some invokers convert every concurrent function call in a response into a /// whenever any one of them targets an /// , so that approvals and rejections stay coherent - /// across the batch. When set to , this property indicates that the - /// request was added for that reason and the underlying tool did not itself require approval; - /// consumers may use this to, for example, auto-approve such requests. + /// across the response. When set to , this property indicates that + /// the underlying tool did not itself require approval and that the request exists only to + /// satisfy that constraint; consumers may auto-approve such requests without seeking a + /// confirmation. /// /// [Experimental(DiagnosticIds.Experiments.AIFunctionApprovals, UrlFormat = DiagnosticIds.UrlFormat)] - public bool IsInvokerRequested { get; set; } + public bool RequiresConfirmation { get; set; } = true; /// /// Creates a indicating whether the tool call is approved or rejected. diff --git a/src/Libraries/Microsoft.Extensions.AI.Abstractions/Microsoft.Extensions.AI.Abstractions.json b/src/Libraries/Microsoft.Extensions.AI.Abstractions/Microsoft.Extensions.AI.Abstractions.json index cd12db2a012..2d105bc2a32 100644 --- a/src/Libraries/Microsoft.Extensions.AI.Abstractions/Microsoft.Extensions.AI.Abstractions.json +++ b/src/Libraries/Microsoft.Extensions.AI.Abstractions/Microsoft.Extensions.AI.Abstractions.json @@ -4605,7 +4605,7 @@ ], "Properties": [ { - "Member": "bool Microsoft.Extensions.AI.ToolApprovalRequestContent.IsInvokerRequested { get; set; }", + "Member": "bool Microsoft.Extensions.AI.ToolApprovalRequestContent.RequiresConfirmation { get; set; }", "Stage": "Experimental" }, { diff --git a/src/Libraries/Microsoft.Extensions.AI/ChatCompletion/FunctionInvokingChatClient.cs b/src/Libraries/Microsoft.Extensions.AI/ChatCompletion/FunctionInvokingChatClient.cs index 1ac64a3a4bb..e1d27c7850c 100644 --- a/src/Libraries/Microsoft.Extensions.AI/ChatCompletion/FunctionInvokingChatClient.cs +++ b/src/Libraries/Microsoft.Extensions.AI/ChatCompletion/FunctionInvokingChatClient.cs @@ -1635,7 +1635,7 @@ private static bool TryReplaceFunctionCallsWithApprovalRequests( bool requiredByFunction = FindTool(fcc.Name, toolLists)?.GetService() is not null; updatedContent[i] = new ToolApprovalRequestContent(ComposeApprovalRequestId(fcc.CallId), fcc) { - IsInvokerRequested = !requiredByFunction, + RequiresConfirmation = requiredByFunction, }; } } @@ -1655,7 +1655,7 @@ private IList ReplaceFunctionCallsWithApprovalRequests( var outputMessages = messages; bool anyApprovalRequired = false; - List<(int MessageIndex, int ContentIndex, bool IsInvokerRequested)>? allFunctionCallContentIndices = null; + List<(int MessageIndex, int ContentIndex, bool RequiresConfirmation)>? allFunctionCallContentIndices = null; // Build a list of the indices of all FunctionCallContent items. // Also check if any of them require approval. @@ -1667,7 +1667,7 @@ private IList ReplaceFunctionCallsWithApprovalRequests( if (content[j] is FunctionCallContent functionCall && !functionCall.InformationalOnly) { bool requiredByFunction = FindTool(functionCall.Name, toolLists)?.GetService() is not null; - (allFunctionCallContentIndices ??= []).Add((i, j, !requiredByFunction)); + (allFunctionCallContentIndices ??= []).Add((i, j, requiredByFunction)); anyApprovalRequired |= requiredByFunction; } @@ -1684,7 +1684,7 @@ private IList ReplaceFunctionCallsWithApprovalRequests( outputMessages = [.. messages]; int lastMessageIndex = -1; - foreach (var (messageIndex, contentIndex, isInvokerRequested) in allFunctionCallContentIndices!) + foreach (var (messageIndex, contentIndex, requiresConfirmation) in allFunctionCallContentIndices!) { // Clone the message if we didn't already clone it in a previous iteration. var message = lastMessageIndex != messageIndex ? outputMessages[messageIndex].Clone() : outputMessages[messageIndex]; @@ -1694,7 +1694,7 @@ private IList ReplaceFunctionCallsWithApprovalRequests( LogFunctionRequiresApproval(functionCall.Name); message.Contents[contentIndex] = new ToolApprovalRequestContent(ComposeApprovalRequestId(functionCall.CallId), functionCall) { - IsInvokerRequested = isInvokerRequested, + RequiresConfirmation = requiresConfirmation, }; outputMessages[messageIndex] = message; diff --git a/test/Libraries/Microsoft.Extensions.AI.Abstractions.Tests/AssertExtensions.cs b/test/Libraries/Microsoft.Extensions.AI.Abstractions.Tests/AssertExtensions.cs index e909ec00186..bab1f9f4f68 100644 --- a/test/Libraries/Microsoft.Extensions.AI.Abstractions.Tests/AssertExtensions.cs +++ b/test/Libraries/Microsoft.Extensions.AI.Abstractions.Tests/AssertExtensions.cs @@ -50,7 +50,7 @@ public static void EqualMessageLists(List expectedMessages, List(json, AIJsonUtilities.DefaultOptions); Assert.NotNull(deserialized); - Assert.Equal(value, deserialized!.IsInvokerRequested); + Assert.Equal(value, deserialized!.RequiresConfirmation); } } diff --git a/test/Libraries/Microsoft.Extensions.AI.Tests/ChatCompletion/FunctionInvokingChatClientApprovalsTests.cs b/test/Libraries/Microsoft.Extensions.AI.Tests/ChatCompletion/FunctionInvokingChatClientApprovalsTests.cs index 4dd932cca20..bcdc55d6b7c 100644 --- a/test/Libraries/Microsoft.Extensions.AI.Tests/ChatCompletion/FunctionInvokingChatClientApprovalsTests.cs +++ b/test/Libraries/Microsoft.Extensions.AI.Tests/ChatCompletion/FunctionInvokingChatClientApprovalsTests.cs @@ -84,7 +84,7 @@ public async Task AllFunctionCallsReplacedWithApprovalsWhenAnyRequireApprovalAsy new ToolApprovalRequestContent("ficc_callId1", new FunctionCallContent("callId1", "Func1")), new ToolApprovalRequestContent("ficc_callId2", new FunctionCallContent("callId2", "Func2", arguments: new Dictionary { { "i", 42 } })) { - IsInvokerRequested = true, + RequiresConfirmation = false, } ]) ]; @@ -124,19 +124,19 @@ public async Task AllFunctionCallsReplacedWithApprovalsWhenAnyRequestOrAdditiona new ChatMessage(ChatRole.Assistant, [new FunctionCallContent("callId1", "Func1"), new FunctionCallContent("callId2", "Func2", arguments: new Dictionary { { "i", 42 } })]), ]; - // When additionalToolsRequireApproval is true: Func1 (additional tools) requires approval and Func2 (options.Tools) is collateral. - // When false: Func2 (options.Tools) requires approval and Func1 (additional tools) is collateral. + // When additionalToolsRequireApproval is true: Func1 (additional tools) requires approval and Func2 (options.Tools) does not. + // When false: Func2 (options.Tools) requires approval and Func1 (additional tools) does not. List expectedOutput = [ new ChatMessage(ChatRole.Assistant, [ new ToolApprovalRequestContent("ficc_callId1", new FunctionCallContent("callId1", "Func1")) { - IsInvokerRequested = !additionalToolsRequireApproval, + RequiresConfirmation = additionalToolsRequireApproval, }, new ToolApprovalRequestContent("ficc_callId2", new FunctionCallContent("callId2", "Func2", arguments: new Dictionary { { "i", 42 } })) { - IsInvokerRequested = additionalToolsRequireApproval, + RequiresConfirmation = !additionalToolsRequireApproval, } ]) ]; @@ -149,10 +149,10 @@ public async Task AllFunctionCallsReplacedWithApprovalsWhenAnyRequestOrAdditiona private sealed class PassThroughDelegatingAIFunction(AIFunction inner) : DelegatingAIFunction(inner); [Fact] - public async Task IsInvokerRequested_IsFalseForApprovalRequiredFunctionNestedInDelegatingWrapperAsync() + public async Task RequiresConfirmation_IsTrueForApprovalRequiredFunctionNestedInDelegatingWrapperAsync() { // Wrap the ApprovalRequiredAIFunction in another DelegatingAIFunction (e.g. a telemetry decorator). - // FICC must still classify the call as "function-required approval" (IsInvokerRequested = false) + // FICC must still classify the call as approval-required (RequiresConfirmation = true, the default) // by walking the delegation chain via GetService(). AITool[] tools = [ @@ -184,7 +184,7 @@ public async Task IsInvokerRequested_IsFalseForApprovalRequiredFunctionNestedInD new ToolApprovalRequestContent("ficc_callId1", new FunctionCallContent("callId1", "Func1")), new ToolApprovalRequestContent("ficc_callId2", new FunctionCallContent("callId2", "Func2", arguments: new Dictionary { { "i", 42 } })) { - IsInvokerRequested = true, + RequiresConfirmation = false, } ]) ]; @@ -195,12 +195,12 @@ public async Task IsInvokerRequested_IsFalseForApprovalRequiredFunctionNestedInD } [Fact] - public async Task IsInvokerRequested_IsTrueForFunctionCallWithNoMatchingToolWhenPeerRequiresApprovalAsync() + public async Task RequiresConfirmation_IsFalseForFunctionCallWithNoMatchingToolWhenPeerRequiresApprovalAsync() { // The downstream client emits an FCC referencing a tool name that is not in the tools list. - // Because a peer call (Func1) requires approval, FICC still wraps the unknown call as collateral. + // Because a peer call (Func1) requires approval, FICC still wraps the unknown call. // Since no matching tool is found (and therefore no ApprovalRequiredAIFunction is detected), - // the resulting approval request must carry IsInvokerRequested = true. + // the resulting approval request must carry RequiresConfirmation = false. var options = new ChatOptions { Tools = @@ -229,7 +229,7 @@ public async Task IsInvokerRequested_IsTrueForFunctionCallWithNoMatchingToolWhen new ToolApprovalRequestContent("ficc_callId1", new FunctionCallContent("callId1", "Func1")), new ToolApprovalRequestContent("ficc_callId2", new FunctionCallContent("callId2", "Unknown")) { - IsInvokerRequested = true, + RequiresConfirmation = false, } ]) ]; @@ -1841,7 +1841,7 @@ private static List CloneInput(List input) => ToolApprovalRequestContent tarc => new ToolApprovalRequestContent(tarc.RequestId, (ToolCallContent)CloneFcc(tarc.ToolCall)) { - IsInvokerRequested = tarc.IsInvokerRequested, + RequiresConfirmation = tarc.RequiresConfirmation, }, ToolApprovalResponseContent tarc => new ToolApprovalResponseContent(tarc.RequestId, tarc.Approved, (ToolCallContent)CloneFcc(tarc.ToolCall)) From 3f6c7604325bb9effe79acbe8fdbb78341deb0a0 Mon Sep 17 00:00:00 2001 From: Javier Calvarro Nelson Date: Wed, 10 Jun 2026 10:48:12 +0200 Subject: [PATCH 04/10] Address review feedback on #7549 1. Drop [Experimental(MEAI001)] from RequiresConfirmation; the rest of the approval surface is no longer experimental. 2. Streaming TryReplaceFunctionCallsWithApprovalRequests now takes the pre-computed approvalRequiredFunctions array instead of the raw tool lists, avoiding a second FindTool + GetService scan per FCC. Non-streaming path is unchanged. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Contents/ToolApprovalRequestContent.cs | 3 --- .../Microsoft.Extensions.AI.Abstractions.json | 2 +- .../FunctionInvokingChatClient.cs | 20 ++++++++++++++----- 3 files changed, 16 insertions(+), 9 deletions(-) diff --git a/src/Libraries/Microsoft.Extensions.AI.Abstractions/Contents/ToolApprovalRequestContent.cs b/src/Libraries/Microsoft.Extensions.AI.Abstractions/Contents/ToolApprovalRequestContent.cs index bf80ffbc208..674762b6e6e 100644 --- a/src/Libraries/Microsoft.Extensions.AI.Abstractions/Contents/ToolApprovalRequestContent.cs +++ b/src/Libraries/Microsoft.Extensions.AI.Abstractions/Contents/ToolApprovalRequestContent.cs @@ -2,9 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; -using System.Diagnostics.CodeAnalysis; using System.Text.Json.Serialization; -using Microsoft.Shared.DiagnosticIds; using Microsoft.Shared.Diagnostics; namespace Microsoft.Extensions.AI; @@ -55,7 +53,6 @@ public ToolApprovalRequestContent(string requestId, ToolCallContent toolCall) /// confirmation. /// /// - [Experimental(DiagnosticIds.Experiments.AIFunctionApprovals, UrlFormat = DiagnosticIds.UrlFormat)] public bool RequiresConfirmation { get; set; } = true; /// diff --git a/src/Libraries/Microsoft.Extensions.AI.Abstractions/Microsoft.Extensions.AI.Abstractions.json b/src/Libraries/Microsoft.Extensions.AI.Abstractions/Microsoft.Extensions.AI.Abstractions.json index 2d105bc2a32..c50bc108143 100644 --- a/src/Libraries/Microsoft.Extensions.AI.Abstractions/Microsoft.Extensions.AI.Abstractions.json +++ b/src/Libraries/Microsoft.Extensions.AI.Abstractions/Microsoft.Extensions.AI.Abstractions.json @@ -4606,7 +4606,7 @@ "Properties": [ { "Member": "bool Microsoft.Extensions.AI.ToolApprovalRequestContent.RequiresConfirmation { get; set; }", - "Stage": "Experimental" + "Stage": "Stable" }, { "Member": "Microsoft.Extensions.AI.ToolCallContent Microsoft.Extensions.AI.ToolApprovalRequestContent.ToolCall { get; }", diff --git a/src/Libraries/Microsoft.Extensions.AI/ChatCompletion/FunctionInvokingChatClient.cs b/src/Libraries/Microsoft.Extensions.AI/ChatCompletion/FunctionInvokingChatClient.cs index e1d27c7850c..4f4a7f581fb 100644 --- a/src/Libraries/Microsoft.Extensions.AI/ChatCompletion/FunctionInvokingChatClient.cs +++ b/src/Libraries/Microsoft.Extensions.AI/ChatCompletion/FunctionInvokingChatClient.cs @@ -613,7 +613,7 @@ public override async IAsyncEnumerable GetStreamingResponseA for (; lastYieldedUpdateIndex < updates.Count; lastYieldedUpdateIndex++) { var updateToYield = updates[lastYieldedUpdateIndex]; - if (TryReplaceFunctionCallsWithApprovalRequests(updateToYield.Contents, out var updatedContents, options?.Tools, AdditionalTools)) + if (TryReplaceFunctionCallsWithApprovalRequests(updateToYield.Contents, approvalRequiredFunctions, out var updatedContents)) { updateToYield.Contents = updatedContents; } @@ -1620,8 +1620,8 @@ private static (bool hasApprovalRequiringFcc, int lastApprovalCheckedFCCIndex) C /// true if any was replaced, false otherwise. private static bool TryReplaceFunctionCallsWithApprovalRequests( IList content, - out List? updatedContent, - params ReadOnlySpan?> toolLists) + AITool[] approvalRequiredFunctions, + out List? updatedContent) { updatedContent = null; @@ -1632,10 +1632,20 @@ private static bool TryReplaceFunctionCallsWithApprovalRequests( if (content[i] is FunctionCallContent fcc && !fcc.InformationalOnly) { updatedContent ??= [.. content]; // Clone the list if we haven't already - bool requiredByFunction = FindTool(fcc.Name, toolLists)?.GetService() is not null; + + bool requiresConfirmation = false; + for (int j = 0; j < approvalRequiredFunctions.Length; j++) + { + if (string.Equals(approvalRequiredFunctions[j].Name, fcc.Name, StringComparison.Ordinal)) + { + requiresConfirmation = true; + break; + } + } + updatedContent[i] = new ToolApprovalRequestContent(ComposeApprovalRequestId(fcc.CallId), fcc) { - RequiresConfirmation = requiredByFunction, + RequiresConfirmation = requiresConfirmation, }; } } From 6af2ba9dd360d4068af32d75b3bce6766269edc7 Mon Sep 17 00:00:00 2001 From: Javier Calvarro Nelson Date: Wed, 10 Jun 2026 12:52:51 +0200 Subject: [PATCH 05/10] Drop MEAI001 NoWarn from Evaluation.Reporting projects Now that RequiresConfirmation no longer carries [Experimental(MEAI001)], the STJ source-generated JsonContext for ToolApprovalRequestContent does not touch any experimental member, so the suppression added in 736f11263a is no longer needed. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Microsoft.Extensions.AI.Evaluation.Reporting.Azure.csproj | 1 - .../CSharp/Microsoft.Extensions.AI.Evaluation.Reporting.csproj | 1 - 2 files changed, 2 deletions(-) diff --git a/src/Libraries/Microsoft.Extensions.AI.Evaluation.Reporting.Azure/Microsoft.Extensions.AI.Evaluation.Reporting.Azure.csproj b/src/Libraries/Microsoft.Extensions.AI.Evaluation.Reporting.Azure/Microsoft.Extensions.AI.Evaluation.Reporting.Azure.csproj index c7e88cbb236..ddc986f187a 100644 --- a/src/Libraries/Microsoft.Extensions.AI.Evaluation.Reporting.Azure/Microsoft.Extensions.AI.Evaluation.Reporting.Azure.csproj +++ b/src/Libraries/Microsoft.Extensions.AI.Evaluation.Reporting.Azure/Microsoft.Extensions.AI.Evaluation.Reporting.Azure.csproj @@ -12,7 +12,6 @@ true n/a n/a - $(NoWarn);MEAI001 diff --git a/src/Libraries/Microsoft.Extensions.AI.Evaluation.Reporting/CSharp/Microsoft.Extensions.AI.Evaluation.Reporting.csproj b/src/Libraries/Microsoft.Extensions.AI.Evaluation.Reporting/CSharp/Microsoft.Extensions.AI.Evaluation.Reporting.csproj index 331e7466a15..8a960fc4df1 100644 --- a/src/Libraries/Microsoft.Extensions.AI.Evaluation.Reporting/CSharp/Microsoft.Extensions.AI.Evaluation.Reporting.csproj +++ b/src/Libraries/Microsoft.Extensions.AI.Evaluation.Reporting/CSharp/Microsoft.Extensions.AI.Evaluation.Reporting.csproj @@ -19,7 +19,6 @@ true n/a n/a - $(NoWarn);MEAI001 From a8b1a45ad894d7d7343681cc453a8a18baa1a379 Mon Sep 17 00:00:00 2001 From: Javier Calvarro Nelson Date: Wed, 10 Jun 2026 12:55:45 +0200 Subject: [PATCH 06/10] Tighten RequiresConfirmation XML doc Frames the property in consumer terms and removes invoker/wrap-site detail that does not belong in the Abstractions surface. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Contents/ToolApprovalRequestContent.cs | 23 +++++-------------- 1 file changed, 6 insertions(+), 17 deletions(-) diff --git a/src/Libraries/Microsoft.Extensions.AI.Abstractions/Contents/ToolApprovalRequestContent.cs b/src/Libraries/Microsoft.Extensions.AI.Abstractions/Contents/ToolApprovalRequestContent.cs index 674762b6e6e..0f7deef394a 100644 --- a/src/Libraries/Microsoft.Extensions.AI.Abstractions/Contents/ToolApprovalRequestContent.cs +++ b/src/Libraries/Microsoft.Extensions.AI.Abstractions/Contents/ToolApprovalRequestContent.cs @@ -33,25 +33,14 @@ public ToolApprovalRequestContent(string requestId, ToolCallContent toolCall) public ToolCallContent ToolCall { get; } /// - /// Gets or sets a value indicating whether this approval request needs a confirmation - /// from the consumer before the underlying tool call is invoked. + /// Gets or sets a value indicating whether the underlying tool call must be confirmed + /// before it is invoked. /// /// - /// - /// Defaults to , indicating that the underlying tool genuinely - /// requires approval (for example, the targeted function is an ) - /// and the consumer must obtain a confirmation (for instance, a user prompt, a policy decision, - /// or a governance gate) before the call is invoked. - /// - /// - /// Some invokers convert every concurrent function call in a response into a - /// whenever any one of them targets an - /// , so that approvals and rejections stay coherent - /// across the response. When set to , this property indicates that - /// the underlying tool did not itself require approval and that the request exists only to - /// satisfy that constraint; consumers may auto-approve such requests without seeking a - /// confirmation. - /// + /// Defaults to . When , the underlying tool + /// requires a confirmation (such as a user prompt, a policy decision, or any other approver) + /// before it can be invoked. When , the underlying tool does not + /// require a confirmation and the consumer may proceed without prompting. /// public bool RequiresConfirmation { get; set; } = true; From 3bfa59cdc2adeaf625663b2c783d45e89fa25bf8 Mon Sep 17 00:00:00 2001 From: Javier Calvarro Nelson Date: Wed, 10 Jun 2026 15:02:56 +0200 Subject: [PATCH 07/10] Align ReplaceFunctionCallsWithApprovalRequests with streaming path Non-streaming wrap site now takes the precomputed approvalRequiredFunctions array and uses a name-set check, matching what the streaming wrap site does. Removes the small streaming/non-streaming divergence in RequiresConfirmation classification introduced earlier in this PR. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../FunctionInvokingChatClient.cs | 27 ++++++++++++++----- 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/src/Libraries/Microsoft.Extensions.AI/ChatCompletion/FunctionInvokingChatClient.cs b/src/Libraries/Microsoft.Extensions.AI/ChatCompletion/FunctionInvokingChatClient.cs index 4f4a7f581fb..da702dcf32a 100644 --- a/src/Libraries/Microsoft.Extensions.AI/ChatCompletion/FunctionInvokingChatClient.cs +++ b/src/Libraries/Microsoft.Extensions.AI/ChatCompletion/FunctionInvokingChatClient.cs @@ -355,7 +355,13 @@ public override async Task GetResponseAsync( anyToolsRequireApproval = AnyToolsRequireApproval(options?.Tools, AdditionalTools); if (anyToolsRequireApproval) { - response.Messages = ReplaceFunctionCallsWithApprovalRequests(response.Messages, options?.Tools, AdditionalTools); + var approvalRequiredFunctions = + (options?.Tools ?? Enumerable.Empty()) + .Concat(AdditionalTools ?? Enumerable.Empty()) + .Where(t => t.GetService() is not null) + .ToArray(); + + response.Messages = ReplaceFunctionCallsWithApprovalRequests(response.Messages, approvalRequiredFunctions); } // Any function call work to do? If yes, ensure we're tracking that work in functionCallContents. @@ -1660,7 +1666,7 @@ private static bool TryReplaceFunctionCallsWithApprovalRequests( /// private IList ReplaceFunctionCallsWithApprovalRequests( IList messages, - params ReadOnlySpan?> toolLists) + AITool[] approvalRequiredFunctions) { var outputMessages = messages; @@ -1668,7 +1674,7 @@ private IList ReplaceFunctionCallsWithApprovalRequests( List<(int MessageIndex, int ContentIndex, bool RequiresConfirmation)>? allFunctionCallContentIndices = null; // Build a list of the indices of all FunctionCallContent items. - // Also check if any of them require approval. + // Also check whether each call's target name matches an approval-required function. for (int i = 0; i < messages.Count; i++) { var content = messages[i].Contents; @@ -1676,10 +1682,19 @@ private IList ReplaceFunctionCallsWithApprovalRequests( { if (content[j] is FunctionCallContent functionCall && !functionCall.InformationalOnly) { - bool requiredByFunction = FindTool(functionCall.Name, toolLists)?.GetService() is not null; - (allFunctionCallContentIndices ??= []).Add((i, j, requiredByFunction)); + bool requiresConfirmation = false; + for (int k = 0; k < approvalRequiredFunctions.Length; k++) + { + if (string.Equals(approvalRequiredFunctions[k].Name, functionCall.Name, StringComparison.Ordinal)) + { + requiresConfirmation = true; + break; + } + } + + (allFunctionCallContentIndices ??= []).Add((i, j, requiresConfirmation)); - anyApprovalRequired |= requiredByFunction; + anyApprovalRequired |= requiresConfirmation; } } } From d4f152bd29409900d2a96c5c9d7cd347c6e4705e Mon Sep 17 00:00:00 2001 From: Javier Calvarro Nelson Date: Wed, 10 Jun 2026 15:05:48 +0200 Subject: [PATCH 08/10] Clarify RequiresConfirmation doc when false Spell out that a ToolApprovalResponseContent is still required even when RequiresConfirmation is false, so the underlying tool call can be invoked. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Contents/ToolApprovalRequestContent.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Libraries/Microsoft.Extensions.AI.Abstractions/Contents/ToolApprovalRequestContent.cs b/src/Libraries/Microsoft.Extensions.AI.Abstractions/Contents/ToolApprovalRequestContent.cs index 0f7deef394a..7d858791462 100644 --- a/src/Libraries/Microsoft.Extensions.AI.Abstractions/Contents/ToolApprovalRequestContent.cs +++ b/src/Libraries/Microsoft.Extensions.AI.Abstractions/Contents/ToolApprovalRequestContent.cs @@ -40,7 +40,9 @@ public ToolApprovalRequestContent(string requestId, ToolCallContent toolCall) /// Defaults to . When , the underlying tool /// requires a confirmation (such as a user prompt, a policy decision, or any other approver) /// before it can be invoked. When , the underlying tool does not - /// require a confirmation and the consumer may proceed without prompting. + /// require a confirmation and the consumer may proceed without prompting; a corresponding + /// still has to be supplied so the originating + /// tool call can be invoked. /// public bool RequiresConfirmation { get; set; } = true; From 30f5198c697b02900db2c5b870e598dc3fa129a7 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 10 Jun 2026 15:21:14 +0000 Subject: [PATCH 09/10] Mark RequiresConfirmation as experimental API --- .../Contents/ToolApprovalRequestContent.cs | 3 +++ .../Microsoft.Extensions.AI.Abstractions.json | 2 +- src/Shared/DiagnosticIds/DiagnosticIds.cs | 1 + 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/Libraries/Microsoft.Extensions.AI.Abstractions/Contents/ToolApprovalRequestContent.cs b/src/Libraries/Microsoft.Extensions.AI.Abstractions/Contents/ToolApprovalRequestContent.cs index 7d858791462..7295a04b09c 100644 --- a/src/Libraries/Microsoft.Extensions.AI.Abstractions/Contents/ToolApprovalRequestContent.cs +++ b/src/Libraries/Microsoft.Extensions.AI.Abstractions/Contents/ToolApprovalRequestContent.cs @@ -2,7 +2,9 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; +using System.Diagnostics.CodeAnalysis; using System.Text.Json.Serialization; +using Microsoft.Shared.DiagnosticIds; using Microsoft.Shared.Diagnostics; namespace Microsoft.Extensions.AI; @@ -44,6 +46,7 @@ public ToolApprovalRequestContent(string requestId, ToolCallContent toolCall) /// still has to be supplied so the originating /// tool call can be invoked. /// + [Experimental(DiagnosticIds.Experiments.AIApprovalsInvocationRequired, UrlFormat = DiagnosticIds.UrlFormat)] public bool RequiresConfirmation { get; set; } = true; /// diff --git a/src/Libraries/Microsoft.Extensions.AI.Abstractions/Microsoft.Extensions.AI.Abstractions.json b/src/Libraries/Microsoft.Extensions.AI.Abstractions/Microsoft.Extensions.AI.Abstractions.json index c50bc108143..2d105bc2a32 100644 --- a/src/Libraries/Microsoft.Extensions.AI.Abstractions/Microsoft.Extensions.AI.Abstractions.json +++ b/src/Libraries/Microsoft.Extensions.AI.Abstractions/Microsoft.Extensions.AI.Abstractions.json @@ -4606,7 +4606,7 @@ "Properties": [ { "Member": "bool Microsoft.Extensions.AI.ToolApprovalRequestContent.RequiresConfirmation { get; set; }", - "Stage": "Stable" + "Stage": "Experimental" }, { "Member": "Microsoft.Extensions.AI.ToolCallContent Microsoft.Extensions.AI.ToolApprovalRequestContent.ToolCall { get; }", diff --git a/src/Shared/DiagnosticIds/DiagnosticIds.cs b/src/Shared/DiagnosticIds/DiagnosticIds.cs index e2615f0a0bb..4b4925850a4 100644 --- a/src/Shared/DiagnosticIds/DiagnosticIds.cs +++ b/src/Shared/DiagnosticIds/DiagnosticIds.cs @@ -53,6 +53,7 @@ internal static class Experiments internal const string AITextToSpeech = AIExperiments; internal const string AIMcpServers = AIExperiments; internal const string AIFunctionApprovals = AIExperiments; + internal const string AIApprovalsInvocationRequired = AIExperiments; internal const string AIChatReduction = AIExperiments; internal const string AIToolSearch = AIExperiments; From 2f565561dfc595b8aa7a6038c1f967f824446c84 Mon Sep 17 00:00:00 2001 From: Javier Calvarro Nelson Date: Wed, 10 Jun 2026 18:13:37 +0200 Subject: [PATCH 10/10] Re-add MEAI001 NoWarn to Evaluation.Reporting projects RequiresConfirmation is again [Experimental(MEAI001)] after 566be62e89, so the STJ source-generated JsonContext for ToolApprovalRequestContent reaches an experimental member and the Reporting projects fail to build at Release without the suppression. Restores the same NoWarn that was in 736f11263a. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Microsoft.Extensions.AI.Evaluation.Reporting.Azure.csproj | 1 + .../CSharp/Microsoft.Extensions.AI.Evaluation.Reporting.csproj | 1 + 2 files changed, 2 insertions(+) diff --git a/src/Libraries/Microsoft.Extensions.AI.Evaluation.Reporting.Azure/Microsoft.Extensions.AI.Evaluation.Reporting.Azure.csproj b/src/Libraries/Microsoft.Extensions.AI.Evaluation.Reporting.Azure/Microsoft.Extensions.AI.Evaluation.Reporting.Azure.csproj index ddc986f187a..c7e88cbb236 100644 --- a/src/Libraries/Microsoft.Extensions.AI.Evaluation.Reporting.Azure/Microsoft.Extensions.AI.Evaluation.Reporting.Azure.csproj +++ b/src/Libraries/Microsoft.Extensions.AI.Evaluation.Reporting.Azure/Microsoft.Extensions.AI.Evaluation.Reporting.Azure.csproj @@ -12,6 +12,7 @@ true n/a n/a + $(NoWarn);MEAI001 diff --git a/src/Libraries/Microsoft.Extensions.AI.Evaluation.Reporting/CSharp/Microsoft.Extensions.AI.Evaluation.Reporting.csproj b/src/Libraries/Microsoft.Extensions.AI.Evaluation.Reporting/CSharp/Microsoft.Extensions.AI.Evaluation.Reporting.csproj index 8a960fc4df1..331e7466a15 100644 --- a/src/Libraries/Microsoft.Extensions.AI.Evaluation.Reporting/CSharp/Microsoft.Extensions.AI.Evaluation.Reporting.csproj +++ b/src/Libraries/Microsoft.Extensions.AI.Evaluation.Reporting/CSharp/Microsoft.Extensions.AI.Evaluation.Reporting.csproj @@ -19,6 +19,7 @@ true n/a n/a + $(NoWarn);MEAI001