diff --git a/dotnet/src/Microsoft.Agents.AI/ChatClient/MessageInjectingChatClient.cs b/dotnet/src/Microsoft.Agents.AI/ChatClient/MessageInjectingChatClient.cs index cbf5a18626..4d9fa56880 100644 --- a/dotnet/src/Microsoft.Agents.AI/ChatClient/MessageInjectingChatClient.cs +++ b/dotnet/src/Microsoft.Agents.AI/ChatClient/MessageInjectingChatClient.cs @@ -263,8 +263,7 @@ private static AgentSession GetRequiredSession() } /// - /// Drains all pending injected messages from the queue and returns a new list combining - /// the original messages with the drained messages. The original list is never modified. + /// Drains all pending injected messages from the queue and returns the combined messages. /// private static IList DrainInjectedMessages(List queue, IList newMessages) { @@ -275,6 +274,14 @@ private static IList DrainInjectedMessages(List queue, return newMessages; } + if (newMessages is List mutableMessages) + { + // Keep the outer function loop's history list in sync with what was sent to the model. + mutableMessages.AddRange(queue); + queue.Clear(); + return mutableMessages; + } + var combined = new List(newMessages); combined.AddRange(queue); queue.Clear(); diff --git a/dotnet/tests/Microsoft.Agents.AI.UnitTests/ChatClient/MessageInjectingChatClientTests.cs b/dotnet/tests/Microsoft.Agents.AI.UnitTests/ChatClient/MessageInjectingChatClientTests.cs index 93f6b02b03..bcff9521d5 100644 --- a/dotnet/tests/Microsoft.Agents.AI.UnitTests/ChatClient/MessageInjectingChatClientTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.UnitTests/ChatClient/MessageInjectingChatClientTests.cs @@ -407,6 +407,67 @@ public async Task RunAsync_PropagatesConversationId_AcrossInternalLoopIterations Assert.Equal("conv-123", capturedConversationIds[1]); // Second call: propagated from first response } + [Fact] + public async Task RunAsync_KeepsInjectedMessagesInFunctionLoopHistoryAsync() + { + // Arrange + int serviceCallCount = 0; + int toolCallCount = 0; + List> capturedMessageTexts = []; + MessageInjectingChatClient? injectorRef = null; + ChatClientAgentSession? sessionRef = null; + + Mock mockService = new(); + mockService.Setup( + s => s.GetResponseAsync( + It.IsAny>(), + It.IsAny(), + It.IsAny())) + .Returns((IEnumerable msgs, ChatOptions? _, CancellationToken _) => + { + serviceCallCount++; + capturedMessageTexts.Add(msgs.Select(m => m.Text).ToList()); + + return Task.FromResult(serviceCallCount switch + { + 1 => new ChatResponse([new(ChatRole.Assistant, + [new FunctionCallContent("call1", "myTool", new Dictionary())])]), + 2 => new ChatResponse([new(ChatRole.Assistant, + [new FunctionCallContent("call2", "myTool", new Dictionary())])]), + _ => new ChatResponse([new(ChatRole.Assistant, "final")]), + }); + }); + + var tool = AIFunctionFactory.Create(() => + { + toolCallCount++; + if (toolCallCount == 1) + { + injectorRef!.EnqueueMessages(sessionRef!, [new ChatMessage(ChatRole.User, "injected correction")]); + } + + return "tool result"; + }, "myTool", "A test tool"); + + ChatClientAgent agent = new(mockService.Object, options: new() + { + ChatOptions = new() { Tools = [tool] }, + EnableMessageInjection = true, + }, services: new ServiceCollection().BuildServiceProvider()); + + injectorRef = agent.ChatClient.GetService()!; + + // Act + var session = await agent.CreateSessionAsync() as ChatClientAgentSession; + sessionRef = session; + await agent.RunAsync([new(ChatRole.User, "original")], session); + + // Assert + Assert.Equal(3, serviceCallCount); + Assert.Contains(capturedMessageTexts[1], text => text == "injected correction"); + Assert.Contains(capturedMessageTexts[2], text => text == "injected correction"); + } + /// /// Verifies that a session with pending injected messages can be serialized and deserialized, /// and that the deserialized session correctly delivers the injected messages on the next run.