From 186ed825fddefe1fd81dca24f54cdeed47c7b344 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 4 Feb 2026 03:40:51 +0000 Subject: [PATCH 1/7] Initial plan From c52ab041f0516ff290117ecdaf015c41e394c605 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 4 Feb 2026 03:43:25 +0000 Subject: [PATCH 2/7] Fix approval request/response correlation and add regression test Co-authored-by: halter73 <54385+halter73@users.noreply.github.com> --- .../FunctionInvokingChatClient.cs | 2 +- ...unctionInvokingChatClientApprovalsTests.cs | 52 +++++++++++++++++++ 2 files changed, 53 insertions(+), 1 deletion(-) diff --git a/src/Libraries/Microsoft.Extensions.AI/ChatCompletion/FunctionInvokingChatClient.cs b/src/Libraries/Microsoft.Extensions.AI/ChatCompletion/FunctionInvokingChatClient.cs index cd449248867..aec78b76c99 100644 --- a/src/Libraries/Microsoft.Extensions.AI/ChatCompletion/FunctionInvokingChatClient.cs +++ b/src/Libraries/Microsoft.Extensions.AI/ChatCompletion/FunctionInvokingChatClient.cs @@ -1467,7 +1467,7 @@ private static bool CurrentActivityIsInvokeAgent case FunctionApprovalRequestContent farc: // Validation: Capture each call id for each approval request to ensure later we have a matching response. _ = (approvalRequestCallIds ??= []).Add(farc.FunctionCall.CallId); - (allApprovalRequestsMessages ??= []).Add(farc.Id, message); + (allApprovalRequestsMessages ??= []).Add(farc.FunctionCall.CallId, message); break; case FunctionApprovalResponseContent farc: diff --git a/test/Libraries/Microsoft.Extensions.AI.Tests/ChatCompletion/FunctionInvokingChatClientApprovalsTests.cs b/test/Libraries/Microsoft.Extensions.AI.Tests/ChatCompletion/FunctionInvokingChatClientApprovalsTests.cs index dd77ef370c6..8b88587ff69 100644 --- a/test/Libraries/Microsoft.Extensions.AI.Tests/ChatCompletion/FunctionInvokingChatClientApprovalsTests.cs +++ b/test/Libraries/Microsoft.Extensions.AI.Tests/ChatCompletion/FunctionInvokingChatClientApprovalsTests.cs @@ -1156,6 +1156,58 @@ public async Task FunctionCallsWithInvocationRequiredFalseAreNotReplacedWithAppr Assert.Equal(0, functionInvokedCount); } + [Fact] + public async Task ApprovalResponsePreservesOriginalRequestMessageMetadata() + { + // Regression test for approval request/response correlation bug: + // Previously, approval request messages were stored in a dictionary keyed by FunctionApprovalRequestContent.Id, + // but looked up using approvalResponse.FunctionCall.CallId, causing mismatches and loss of original message metadata. + var options = new ChatOptions + { + Tools = + [ + new ApprovalRequiredAIFunction(AIFunctionFactory.Create(() => "Result 1", "Func1")), + ] + }; + + const string originalMessageId = "original-message-id"; + + // Create input with approval request containing a known MessageId on the containing message + List input = + [ + new ChatMessage(ChatRole.User, "hello"), + new ChatMessage(ChatRole.Assistant, + [ + new FunctionApprovalRequestContent("approval-request-id", new FunctionCallContent("function-call-id", "Func1")) + ]) { MessageId = originalMessageId }, // This MessageId should be preserved + new ChatMessage(ChatRole.User, + [ + new FunctionApprovalResponseContent("approval-request-id", true, new FunctionCallContent("function-call-id", "Func1")) + ]), + ]; + + List downstreamClientOutput = + [ + new ChatMessage(ChatRole.Assistant, "world"), + ]; + + // The reconstructed function call message should preserve the original MessageId + List expectedOutput = + [ + new ChatMessage(ChatRole.Assistant, [new FunctionCallContent("function-call-id", "Func1")]) { MessageId = originalMessageId }, + new ChatMessage(ChatRole.Tool, [new FunctionResultContent("function-call-id", result: "Result 1")]), + new ChatMessage(ChatRole.Assistant, "world"), + ]; + + var actualOutput = await InvokeAndAssertAsync(options, input, downstreamClientOutput, expectedOutput); + + // Verify that the reconstructed function call message has the original MessageId, not a synthetic one + Assert.Equal(originalMessageId, actualOutput[0].MessageId); + + actualOutput = await InvokeAndAssertStreamingAsync(options, input, downstreamClientOutput, expectedOutput); + Assert.Equal(originalMessageId, actualOutput[0].MessageId); + } + private static Task> InvokeAndAssertAsync( ChatOptions? options, List input, From 6751e27d9112c399830cd5cf3a85b572d31c708d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 4 Feb 2026 03:48:38 +0000 Subject: [PATCH 3/7] Fix naming convention in regression test Co-authored-by: halter73 <54385+halter73@users.noreply.github.com> --- .../FunctionInvokingChatClientApprovalsTests.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/test/Libraries/Microsoft.Extensions.AI.Tests/ChatCompletion/FunctionInvokingChatClientApprovalsTests.cs b/test/Libraries/Microsoft.Extensions.AI.Tests/ChatCompletion/FunctionInvokingChatClientApprovalsTests.cs index 8b88587ff69..19bf47151f7 100644 --- a/test/Libraries/Microsoft.Extensions.AI.Tests/ChatCompletion/FunctionInvokingChatClientApprovalsTests.cs +++ b/test/Libraries/Microsoft.Extensions.AI.Tests/ChatCompletion/FunctionInvokingChatClientApprovalsTests.cs @@ -1170,7 +1170,7 @@ public async Task ApprovalResponsePreservesOriginalRequestMessageMetadata() ] }; - const string originalMessageId = "original-message-id"; + const string OriginalMessageId = "original-message-id"; // Create input with approval request containing a known MessageId on the containing message List input = @@ -1179,7 +1179,7 @@ public async Task ApprovalResponsePreservesOriginalRequestMessageMetadata() new ChatMessage(ChatRole.Assistant, [ new FunctionApprovalRequestContent("approval-request-id", new FunctionCallContent("function-call-id", "Func1")) - ]) { MessageId = originalMessageId }, // This MessageId should be preserved + ]) { MessageId = OriginalMessageId }, // This MessageId should be preserved new ChatMessage(ChatRole.User, [ new FunctionApprovalResponseContent("approval-request-id", true, new FunctionCallContent("function-call-id", "Func1")) @@ -1194,7 +1194,7 @@ public async Task ApprovalResponsePreservesOriginalRequestMessageMetadata() // The reconstructed function call message should preserve the original MessageId List expectedOutput = [ - new ChatMessage(ChatRole.Assistant, [new FunctionCallContent("function-call-id", "Func1")]) { MessageId = originalMessageId }, + new ChatMessage(ChatRole.Assistant, [new FunctionCallContent("function-call-id", "Func1")]) { MessageId = OriginalMessageId }, new ChatMessage(ChatRole.Tool, [new FunctionResultContent("function-call-id", result: "Result 1")]), new ChatMessage(ChatRole.Assistant, "world"), ]; @@ -1202,10 +1202,10 @@ public async Task ApprovalResponsePreservesOriginalRequestMessageMetadata() var actualOutput = await InvokeAndAssertAsync(options, input, downstreamClientOutput, expectedOutput); // Verify that the reconstructed function call message has the original MessageId, not a synthetic one - Assert.Equal(originalMessageId, actualOutput[0].MessageId); + Assert.Equal(OriginalMessageId, actualOutput[0].MessageId); actualOutput = await InvokeAndAssertStreamingAsync(options, input, downstreamClientOutput, expectedOutput); - Assert.Equal(originalMessageId, actualOutput[0].MessageId); + Assert.Equal(OriginalMessageId, actualOutput[0].MessageId); } private static Task> InvokeAndAssertAsync( From 9034f0eb4fd84042faf913088b867b44b80a8cc4 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 4 Feb 2026 04:13:52 +0000 Subject: [PATCH 4/7] Use FunctionApprovalRequestContent.Id and FunctionApprovalResponseContent.Id for correlation Co-authored-by: halter73 <54385+halter73@users.noreply.github.com> --- .../ChatCompletion/FunctionInvokingChatClient.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Libraries/Microsoft.Extensions.AI/ChatCompletion/FunctionInvokingChatClient.cs b/src/Libraries/Microsoft.Extensions.AI/ChatCompletion/FunctionInvokingChatClient.cs index aec78b76c99..2a760cb1d8b 100644 --- a/src/Libraries/Microsoft.Extensions.AI/ChatCompletion/FunctionInvokingChatClient.cs +++ b/src/Libraries/Microsoft.Extensions.AI/ChatCompletion/FunctionInvokingChatClient.cs @@ -1467,7 +1467,7 @@ private static bool CurrentActivityIsInvokeAgent case FunctionApprovalRequestContent farc: // Validation: Capture each call id for each approval request to ensure later we have a matching response. _ = (approvalRequestCallIds ??= []).Add(farc.FunctionCall.CallId); - (allApprovalRequestsMessages ??= []).Add(farc.FunctionCall.CallId, message); + (allApprovalRequestsMessages ??= []).Add(farc.Id, message); break; case FunctionApprovalResponseContent farc: @@ -1542,7 +1542,7 @@ private static bool CurrentActivityIsInvokeAgent ref List? targetList = ref approvalResponse.Approved ? ref approvedFunctionCalls : ref rejectedFunctionCalls; ChatMessage? requestMessage = null; - _ = allApprovalRequestsMessages?.TryGetValue(approvalResponse.FunctionCall.CallId, out requestMessage); + _ = allApprovalRequestsMessages?.TryGetValue(approvalResponse.Id, out requestMessage); (targetList ??= []).Add(new() { Response = approvalResponse, RequestMessage = requestMessage }); } From d191239f9aab135e6dda14386a1cde737377ab26 Mon Sep 17 00:00:00 2001 From: Stephen Toub Date: Wed, 4 Feb 2026 08:32:35 -0500 Subject: [PATCH 5/7] Update test/Libraries/Microsoft.Extensions.AI.Tests/ChatCompletion/FunctionInvokingChatClientApprovalsTests.cs --- .../ChatCompletion/FunctionInvokingChatClientApprovalsTests.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/test/Libraries/Microsoft.Extensions.AI.Tests/ChatCompletion/FunctionInvokingChatClientApprovalsTests.cs b/test/Libraries/Microsoft.Extensions.AI.Tests/ChatCompletion/FunctionInvokingChatClientApprovalsTests.cs index 19bf47151f7..aa465054916 100644 --- a/test/Libraries/Microsoft.Extensions.AI.Tests/ChatCompletion/FunctionInvokingChatClientApprovalsTests.cs +++ b/test/Libraries/Microsoft.Extensions.AI.Tests/ChatCompletion/FunctionInvokingChatClientApprovalsTests.cs @@ -1159,9 +1159,6 @@ public async Task FunctionCallsWithInvocationRequiredFalseAreNotReplacedWithAppr [Fact] public async Task ApprovalResponsePreservesOriginalRequestMessageMetadata() { - // Regression test for approval request/response correlation bug: - // Previously, approval request messages were stored in a dictionary keyed by FunctionApprovalRequestContent.Id, - // but looked up using approvalResponse.FunctionCall.CallId, causing mismatches and loss of original message metadata. var options = new ChatOptions { Tools = From 7a3f4d1af6255ef95559c90193ec05af09b8bd74 Mon Sep 17 00:00:00 2001 From: Stephen Toub Date: Wed, 4 Feb 2026 08:33:45 -0500 Subject: [PATCH 6/7] Update test/Libraries/Microsoft.Extensions.AI.Tests/ChatCompletion/FunctionInvokingChatClientApprovalsTests.cs --- .../ChatCompletion/FunctionInvokingChatClientApprovalsTests.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/test/Libraries/Microsoft.Extensions.AI.Tests/ChatCompletion/FunctionInvokingChatClientApprovalsTests.cs b/test/Libraries/Microsoft.Extensions.AI.Tests/ChatCompletion/FunctionInvokingChatClientApprovalsTests.cs index aa465054916..23412e8d52c 100644 --- a/test/Libraries/Microsoft.Extensions.AI.Tests/ChatCompletion/FunctionInvokingChatClientApprovalsTests.cs +++ b/test/Libraries/Microsoft.Extensions.AI.Tests/ChatCompletion/FunctionInvokingChatClientApprovalsTests.cs @@ -1159,9 +1159,6 @@ public async Task FunctionCallsWithInvocationRequiredFalseAreNotReplacedWithAppr [Fact] public async Task ApprovalResponsePreservesOriginalRequestMessageMetadata() { - var options = new ChatOptions - { - Tools = [ new ApprovalRequiredAIFunction(AIFunctionFactory.Create(() => "Result 1", "Func1")), ] From 4cb711921d1c931d51161c2d5ef6b3885cb9b96b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 4 Feb 2026 19:26:42 +0000 Subject: [PATCH 7/7] Revert accidental deletion of test method variable declaration Co-authored-by: stephentoub <2642209+stephentoub@users.noreply.github.com> --- .../ChatCompletion/FunctionInvokingChatClientApprovalsTests.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/test/Libraries/Microsoft.Extensions.AI.Tests/ChatCompletion/FunctionInvokingChatClientApprovalsTests.cs b/test/Libraries/Microsoft.Extensions.AI.Tests/ChatCompletion/FunctionInvokingChatClientApprovalsTests.cs index 23412e8d52c..aa465054916 100644 --- a/test/Libraries/Microsoft.Extensions.AI.Tests/ChatCompletion/FunctionInvokingChatClientApprovalsTests.cs +++ b/test/Libraries/Microsoft.Extensions.AI.Tests/ChatCompletion/FunctionInvokingChatClientApprovalsTests.cs @@ -1159,6 +1159,9 @@ public async Task FunctionCallsWithInvocationRequiredFalseAreNotReplacedWithAppr [Fact] public async Task ApprovalResponsePreservesOriginalRequestMessageMetadata() { + var options = new ChatOptions + { + Tools = [ new ApprovalRequiredAIFunction(AIFunctionFactory.Create(() => "Result 1", "Func1")), ]