diff --git a/src/Libraries/Microsoft.Extensions.AI/ChatCompletion/FunctionInvokingChatClient.cs b/src/Libraries/Microsoft.Extensions.AI/ChatCompletion/FunctionInvokingChatClient.cs index cd449248867..2a760cb1d8b 100644 --- a/src/Libraries/Microsoft.Extensions.AI/ChatCompletion/FunctionInvokingChatClient.cs +++ b/src/Libraries/Microsoft.Extensions.AI/ChatCompletion/FunctionInvokingChatClient.cs @@ -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 }); } diff --git a/test/Libraries/Microsoft.Extensions.AI.Tests/ChatCompletion/FunctionInvokingChatClientApprovalsTests.cs b/test/Libraries/Microsoft.Extensions.AI.Tests/ChatCompletion/FunctionInvokingChatClientApprovalsTests.cs index dd77ef370c6..aa465054916 100644 --- a/test/Libraries/Microsoft.Extensions.AI.Tests/ChatCompletion/FunctionInvokingChatClientApprovalsTests.cs +++ b/test/Libraries/Microsoft.Extensions.AI.Tests/ChatCompletion/FunctionInvokingChatClientApprovalsTests.cs @@ -1156,6 +1156,55 @@ public async Task FunctionCallsWithInvocationRequiredFalseAreNotReplacedWithAppr Assert.Equal(0, functionInvokedCount); } + [Fact] + public async Task ApprovalResponsePreservesOriginalRequestMessageMetadata() + { + 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,