From 456960e48dbde535b22e885d8daed8907c60e41c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 5 Dec 2025 13:23:07 +0000 Subject: [PATCH 1/5] Initial plan From de2a72004962d72e82a563cc250d1eb921509d8c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 5 Dec 2025 13:39:36 +0000 Subject: [PATCH 2/5] Fix message ordering inconsistency when using AIContextProvider Co-authored-by: westey-m <164392973+westey-m@users.noreply.github.com> --- .../ChatClient/ChatClientAgent.cs | 4 +- .../ChatClient/ChatClientAgentTests.cs | 154 +++++++++++++++++- 2 files changed, 152 insertions(+), 6 deletions(-) diff --git a/dotnet/src/Microsoft.Agents.AI/ChatClient/ChatClientAgent.cs b/dotnet/src/Microsoft.Agents.AI/ChatClient/ChatClientAgent.cs index df7477241c..a738aa8ef5 100644 --- a/dotnet/src/Microsoft.Agents.AI/ChatClient/ChatClientAgent.cs +++ b/dotnet/src/Microsoft.Agents.AI/ChatClient/ChatClientAgent.cs @@ -272,7 +272,7 @@ public override async IAsyncEnumerable RunStreamingAsync this.UpdateThreadWithTypeAndConversationId(safeThread, chatResponse.ConversationId); // To avoid inconsistent state we only notify the thread of the input messages if no error occurs after the initial request. - await NotifyMessageStoreOfNewMessagesAsync(safeThread, inputMessages.Concat(aiContextProviderMessages ?? []).Concat(chatResponse.Messages), cancellationToken).ConfigureAwait(false); + await NotifyMessageStoreOfNewMessagesAsync(safeThread, (aiContextProviderMessages ?? []).Concat(inputMessages).Concat(chatResponse.Messages), cancellationToken).ConfigureAwait(false); // Notify the AIContextProvider of all new messages. await NotifyAIContextProviderOfSuccessAsync(safeThread, inputMessages, aiContextProviderMessages, chatResponse.Messages, cancellationToken).ConfigureAwait(false); @@ -415,7 +415,7 @@ private async Task RunCoreAsync(thread!.MessageStore); Assert.Equal(3, messageStore.Count); - Assert.Equal("user message", messageStore[0].Text); - Assert.Equal("context provider message", messageStore[1].Text); + Assert.Equal("context provider message", messageStore[0].Text); + Assert.Equal("user message", messageStore[1].Text); Assert.Equal("response", messageStore[2].Text); mockProvider.Verify(p => p.InvokingAsync(It.IsAny(), It.IsAny()), Times.Once); @@ -2070,8 +2070,8 @@ public async Task RunStreamingAsyncInvokesAIContextProviderAndUsesResultAsync() // Verify that the thread was updated with the input, ai context and response messages var messageStore = Assert.IsType(thread!.MessageStore); Assert.Equal(3, messageStore.Count); - Assert.Equal("user message", messageStore[0].Text); - Assert.Equal("context provider message", messageStore[1].Text); + Assert.Equal("context provider message", messageStore[0].Text); + Assert.Equal("user message", messageStore[1].Text); Assert.Equal("response", messageStore[2].Text); mockProvider.Verify(p => p.InvokingAsync(It.IsAny(), It.IsAny()), Times.Once); @@ -2129,6 +2129,152 @@ await Assert.ThrowsAsync(async () => x.InvokeException is InvalidOperationException), It.IsAny()), Times.Once); } + /// + /// Verify that messages are stored in MessageStore in the same order they are sent to the chat client. + /// Order should be: AIContextProvider messages, Input messages, Response messages. + /// + [Fact] + public async Task VerifyMessageOrderingWithAIContextProviderAsync() + { + // Arrange + var existingMessages = new List + { + new(ChatRole.User, "Message A"), + new(ChatRole.Assistant, "Message B") + }; + + var inputMessage = new ChatMessage(ChatRole.User, "Message C"); + var aiContextProviderMessage = new ChatMessage(ChatRole.System, "Message X"); + var responseMessage = new ChatMessage(ChatRole.Assistant, "Message D"); + + List? messagesToChatClient = null; + Mock mockService = new(); + mockService + .Setup(s => s.GetResponseAsync( + It.IsAny>(), + It.IsAny(), + It.IsAny())) + .Callback, ChatOptions, CancellationToken>((msgs, opts, ct) => messagesToChatClient = msgs.ToList()) + .ReturnsAsync(new ChatResponse([responseMessage])); + + var mockContextProvider = new Mock(); + mockContextProvider + .Setup(p => p.InvokingAsync(It.IsAny(), It.IsAny())) + .ReturnsAsync(new AIContext + { + Messages = [aiContextProviderMessage], + }); + + var messageStore = new InMemoryChatMessageStore(); + await messageStore.AddMessagesAsync(existingMessages); + + ChatClientAgent agent = new(mockService.Object, options: new() + { + ChatOptions = new() { Instructions = "test instructions" }, + AIContextProviderFactory = _ => mockContextProvider.Object + }); + + var thread = new ChatClientAgentThread + { + MessageStore = messageStore, + AIContextProvider = mockContextProvider.Object + }; + + // Act + await agent.RunAsync([inputMessage], thread); + + // Assert - Verify order sent to chat client: [Existing, AIContextProvider, Input] + Assert.NotNull(messagesToChatClient); + Assert.Equal(4, messagesToChatClient.Count); + Assert.Equal("Message A", messagesToChatClient[0].Text); + Assert.Equal("Message B", messagesToChatClient[1].Text); + Assert.Equal("Message X", messagesToChatClient[2].Text); + Assert.Equal("Message C", messagesToChatClient[3].Text); + + // Assert - Verify order stored in MessageStore: [Existing, AIContextProvider, Input, Response] + var storedMessagesList = (await messageStore.GetMessagesAsync()).ToList(); + Assert.Equal(5, storedMessagesList.Count); + Assert.Equal("Message A", storedMessagesList[0].Text); + Assert.Equal("Message B", storedMessagesList[1].Text); + Assert.Equal("Message X", storedMessagesList[2].Text); + Assert.Equal("Message C", storedMessagesList[3].Text); + Assert.Equal("Message D", storedMessagesList[4].Text); + } + + /// + /// Verify that messages are stored in MessageStore in the same order they are sent to the chat client (streaming version). + /// Order should be: AIContextProvider messages, Input messages, Response messages. + /// + [Fact] + public async Task VerifyMessageOrderingWithAIContextProviderStreamingAsync() + { + // Arrange + var existingMessages = new List + { + new(ChatRole.User, "Message A"), + new(ChatRole.Assistant, "Message B") + }; + + var inputMessage = new ChatMessage(ChatRole.User, "Message C"); + var aiContextProviderMessage = new ChatMessage(ChatRole.System, "Message X"); + ChatResponseUpdate responseUpdate = new() { Role = ChatRole.Assistant }; + responseUpdate.Contents.Add(new TextContent("Message D")); + + List? messagesToChatClient = null; + Mock mockService = new(); + mockService + .Setup(s => s.GetStreamingResponseAsync( + It.IsAny>(), + It.IsAny(), + It.IsAny())) + .Callback, ChatOptions, CancellationToken>((msgs, opts, ct) => messagesToChatClient = msgs.ToList()) + .Returns(ToAsyncEnumerableAsync([responseUpdate])); + + var mockContextProvider = new Mock(); + mockContextProvider + .Setup(p => p.InvokingAsync(It.IsAny(), It.IsAny())) + .ReturnsAsync(new AIContext + { + Messages = [aiContextProviderMessage], + }); + + var messageStore = new InMemoryChatMessageStore(); + await messageStore.AddMessagesAsync(existingMessages); + + ChatClientAgent agent = new(mockService.Object, options: new() + { + ChatOptions = new() { Instructions = "test instructions" }, + AIContextProviderFactory = _ => mockContextProvider.Object + }); + + var thread = new ChatClientAgentThread + { + MessageStore = messageStore, + AIContextProvider = mockContextProvider.Object + }; + + // Act + var updates = agent.RunStreamingAsync([inputMessage], thread); + await updates.ToAgentRunResponseAsync(); + + // Assert - Verify order sent to chat client: [Existing, AIContextProvider, Input] + Assert.NotNull(messagesToChatClient); + Assert.Equal(4, messagesToChatClient.Count); + Assert.Equal("Message A", messagesToChatClient[0].Text); + Assert.Equal("Message B", messagesToChatClient[1].Text); + Assert.Equal("Message X", messagesToChatClient[2].Text); + Assert.Equal("Message C", messagesToChatClient[3].Text); + + // Assert - Verify order stored in MessageStore: [Existing, AIContextProvider, Input, Response] + var storedMessagesList = (await messageStore.GetMessagesAsync()).ToList(); + Assert.Equal(5, storedMessagesList.Count); + Assert.Equal("Message A", storedMessagesList[0].Text); + Assert.Equal("Message B", storedMessagesList[1].Text); + Assert.Equal("Message X", storedMessagesList[2].Text); + Assert.Equal("Message C", storedMessagesList[3].Text); + Assert.Equal("Message D", storedMessagesList[4].Text); + } + #endregion private static async IAsyncEnumerable ToAsyncEnumerableAsync(IEnumerable values) From 308158c52c7651c2ed97b22d4b3fa0c4877bff0c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 5 Dec 2025 14:25:37 +0000 Subject: [PATCH 3/5] Revert to original message ordering: Input, AIContextProvider, Response Co-authored-by: westey-m <164392973+westey-m@users.noreply.github.com> --- .../ChatClient/ChatClientAgent.cs | 4 +-- .../ChatClient/ChatClientAgentTests.cs | 28 +++++++++---------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/dotnet/src/Microsoft.Agents.AI/ChatClient/ChatClientAgent.cs b/dotnet/src/Microsoft.Agents.AI/ChatClient/ChatClientAgent.cs index a738aa8ef5..df7477241c 100644 --- a/dotnet/src/Microsoft.Agents.AI/ChatClient/ChatClientAgent.cs +++ b/dotnet/src/Microsoft.Agents.AI/ChatClient/ChatClientAgent.cs @@ -272,7 +272,7 @@ public override async IAsyncEnumerable RunStreamingAsync this.UpdateThreadWithTypeAndConversationId(safeThread, chatResponse.ConversationId); // To avoid inconsistent state we only notify the thread of the input messages if no error occurs after the initial request. - await NotifyMessageStoreOfNewMessagesAsync(safeThread, (aiContextProviderMessages ?? []).Concat(inputMessages).Concat(chatResponse.Messages), cancellationToken).ConfigureAwait(false); + await NotifyMessageStoreOfNewMessagesAsync(safeThread, inputMessages.Concat(aiContextProviderMessages ?? []).Concat(chatResponse.Messages), cancellationToken).ConfigureAwait(false); // Notify the AIContextProvider of all new messages. await NotifyAIContextProviderOfSuccessAsync(safeThread, inputMessages, aiContextProviderMessages, chatResponse.Messages, cancellationToken).ConfigureAwait(false); @@ -415,7 +415,7 @@ private async Task RunCoreAsync(thread!.MessageStore); Assert.Equal(3, messageStore.Count); - Assert.Equal("context provider message", messageStore[0].Text); - Assert.Equal("user message", messageStore[1].Text); + Assert.Equal("user message", messageStore[0].Text); + Assert.Equal("context provider message", messageStore[1].Text); Assert.Equal("response", messageStore[2].Text); mockProvider.Verify(p => p.InvokingAsync(It.IsAny(), It.IsAny()), Times.Once); @@ -2070,8 +2070,8 @@ public async Task RunStreamingAsyncInvokesAIContextProviderAndUsesResultAsync() // Verify that the thread was updated with the input, ai context and response messages var messageStore = Assert.IsType(thread!.MessageStore); Assert.Equal(3, messageStore.Count); - Assert.Equal("context provider message", messageStore[0].Text); - Assert.Equal("user message", messageStore[1].Text); + Assert.Equal("user message", messageStore[0].Text); + Assert.Equal("context provider message", messageStore[1].Text); Assert.Equal("response", messageStore[2].Text); mockProvider.Verify(p => p.InvokingAsync(It.IsAny(), It.IsAny()), Times.Once); @@ -2130,8 +2130,8 @@ await Assert.ThrowsAsync(async () => } /// - /// Verify that messages are stored in MessageStore in the same order they are sent to the chat client. - /// Order should be: AIContextProvider messages, Input messages, Response messages. + /// Verify that messages are stored in MessageStore with AIContextProvider messages. + /// Order stored should be: Input messages, AIContextProvider messages, Response messages. /// [Fact] public async Task VerifyMessageOrderingWithAIContextProviderAsync() @@ -2191,19 +2191,19 @@ public async Task VerifyMessageOrderingWithAIContextProviderAsync() Assert.Equal("Message X", messagesToChatClient[2].Text); Assert.Equal("Message C", messagesToChatClient[3].Text); - // Assert - Verify order stored in MessageStore: [Existing, AIContextProvider, Input, Response] + // Assert - Verify order stored in MessageStore: [Existing, Input, AIContextProvider, Response] var storedMessagesList = (await messageStore.GetMessagesAsync()).ToList(); Assert.Equal(5, storedMessagesList.Count); Assert.Equal("Message A", storedMessagesList[0].Text); Assert.Equal("Message B", storedMessagesList[1].Text); - Assert.Equal("Message X", storedMessagesList[2].Text); - Assert.Equal("Message C", storedMessagesList[3].Text); + Assert.Equal("Message C", storedMessagesList[2].Text); + Assert.Equal("Message X", storedMessagesList[3].Text); Assert.Equal("Message D", storedMessagesList[4].Text); } /// - /// Verify that messages are stored in MessageStore in the same order they are sent to the chat client (streaming version). - /// Order should be: AIContextProvider messages, Input messages, Response messages. + /// Verify that messages are stored in MessageStore with AIContextProvider messages (streaming version). + /// Order stored should be: Input messages, AIContextProvider messages, Response messages. /// [Fact] public async Task VerifyMessageOrderingWithAIContextProviderStreamingAsync() @@ -2265,13 +2265,13 @@ public async Task VerifyMessageOrderingWithAIContextProviderStreamingAsync() Assert.Equal("Message X", messagesToChatClient[2].Text); Assert.Equal("Message C", messagesToChatClient[3].Text); - // Assert - Verify order stored in MessageStore: [Existing, AIContextProvider, Input, Response] + // Assert - Verify order stored in MessageStore: [Existing, Input, AIContextProvider, Response] var storedMessagesList = (await messageStore.GetMessagesAsync()).ToList(); Assert.Equal(5, storedMessagesList.Count); Assert.Equal("Message A", storedMessagesList[0].Text); Assert.Equal("Message B", storedMessagesList[1].Text); - Assert.Equal("Message X", storedMessagesList[2].Text); - Assert.Equal("Message C", storedMessagesList[3].Text); + Assert.Equal("Message C", storedMessagesList[2].Text); + Assert.Equal("Message X", storedMessagesList[3].Text); Assert.Equal("Message D", storedMessagesList[4].Text); } From a749f19fc4d7cb05294e27c5b6afca86845db767 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 5 Dec 2025 14:35:17 +0000 Subject: [PATCH 4/5] Reorder messages to ChatClient to match MessageStore order: Existing, Input, AIContextProvider Co-authored-by: westey-m <164392973+westey-m@users.noreply.github.com> --- .../ChatClient/ChatClientAgent.cs | 6 ++-- .../ChatClient/ChatClientAgentTests.cs | 32 +++++++++---------- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/dotnet/src/Microsoft.Agents.AI/ChatClient/ChatClientAgent.cs b/dotnet/src/Microsoft.Agents.AI/ChatClient/ChatClientAgent.cs index df7477241c..cdb2daefe7 100644 --- a/dotnet/src/Microsoft.Agents.AI/ChatClient/ChatClientAgent.cs +++ b/dotnet/src/Microsoft.Agents.AI/ChatClient/ChatClientAgent.cs @@ -647,6 +647,9 @@ await thread.AIContextProvider.InvokedAsync(new(inputMessages, aiContextProvider inputMessagesForChatClient.AddRange(await typedThread.MessageStore.GetMessagesAsync(cancellationToken).ConfigureAwait(false)); } + // Add the input messages before getting context from AIContextProvider. + inputMessagesForChatClient.AddRange(inputMessages); + // If we have an AIContextProvider, we should get context from it, and update our // messages and options with the additional context. if (typedThread.AIContextProvider is not null) @@ -675,9 +678,6 @@ await thread.AIContextProvider.InvokedAsync(new(inputMessages, aiContextProvider chatOptions.Instructions = string.IsNullOrWhiteSpace(chatOptions.Instructions) ? aiContext.Instructions : $"{chatOptions.Instructions}\n{aiContext.Instructions}"; } } - - // Add the input messages to the end of thread messages. - inputMessagesForChatClient.AddRange(inputMessages); } // If a user provided two different thread ids, via the thread object and options, we should throw diff --git a/dotnet/tests/Microsoft.Agents.AI.UnitTests/ChatClient/ChatClientAgentTests.cs b/dotnet/tests/Microsoft.Agents.AI.UnitTests/ChatClient/ChatClientAgentTests.cs index 2cac26c358..64442b4b5e 100644 --- a/dotnet/tests/Microsoft.Agents.AI.UnitTests/ChatClient/ChatClientAgentTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.UnitTests/ChatClient/ChatClientAgentTests.cs @@ -599,13 +599,13 @@ public async Task RunAsyncInvokesAIContextProviderAndUsesResultAsync() await agent.RunAsync(requestMessages, thread); // Assert - // Should contain: base instructions, context message, user message, base function, context function + // Should contain: base instructions, user message, context message, base function, context function Assert.Equal(2, capturedMessages.Count); Assert.Equal("base instructions\ncontext provider instructions", capturedInstructions); - Assert.Equal("context provider message", capturedMessages[0].Text); - Assert.Equal(ChatRole.System, capturedMessages[0].Role); - Assert.Equal("user message", capturedMessages[1].Text); - Assert.Equal(ChatRole.User, capturedMessages[1].Role); + Assert.Equal("user message", capturedMessages[0].Text); + Assert.Equal(ChatRole.User, capturedMessages[0].Role); + Assert.Equal("context provider message", capturedMessages[1].Text); + Assert.Equal(ChatRole.System, capturedMessages[1].Role); Assert.Equal(2, capturedTools.Count); Assert.Contains(capturedTools, t => t.Name == "base function"); Assert.Contains(capturedTools, t => t.Name == "context provider function"); @@ -2056,13 +2056,13 @@ public async Task RunStreamingAsyncInvokesAIContextProviderAndUsesResultAsync() _ = await updates.ToAgentRunResponseAsync(); // Assert - // Should contain: base instructions, context message, user message, base function, context function + // Should contain: base instructions, user message, context message, base function, context function Assert.Equal(2, capturedMessages.Count); Assert.Equal("base instructions\ncontext provider instructions", capturedInstructions); - Assert.Equal("context provider message", capturedMessages[0].Text); - Assert.Equal(ChatRole.System, capturedMessages[0].Role); - Assert.Equal("user message", capturedMessages[1].Text); - Assert.Equal(ChatRole.User, capturedMessages[1].Role); + Assert.Equal("user message", capturedMessages[0].Text); + Assert.Equal(ChatRole.User, capturedMessages[0].Role); + Assert.Equal("context provider message", capturedMessages[1].Text); + Assert.Equal(ChatRole.System, capturedMessages[1].Role); Assert.Equal(2, capturedTools.Count); Assert.Contains(capturedTools, t => t.Name == "base function"); Assert.Contains(capturedTools, t => t.Name == "context provider function"); @@ -2183,13 +2183,13 @@ public async Task VerifyMessageOrderingWithAIContextProviderAsync() // Act await agent.RunAsync([inputMessage], thread); - // Assert - Verify order sent to chat client: [Existing, AIContextProvider, Input] + // Assert - Verify order sent to chat client: [Existing, Input, AIContextProvider] Assert.NotNull(messagesToChatClient); Assert.Equal(4, messagesToChatClient.Count); Assert.Equal("Message A", messagesToChatClient[0].Text); Assert.Equal("Message B", messagesToChatClient[1].Text); - Assert.Equal("Message X", messagesToChatClient[2].Text); - Assert.Equal("Message C", messagesToChatClient[3].Text); + Assert.Equal("Message C", messagesToChatClient[2].Text); + Assert.Equal("Message X", messagesToChatClient[3].Text); // Assert - Verify order stored in MessageStore: [Existing, Input, AIContextProvider, Response] var storedMessagesList = (await messageStore.GetMessagesAsync()).ToList(); @@ -2257,13 +2257,13 @@ public async Task VerifyMessageOrderingWithAIContextProviderStreamingAsync() var updates = agent.RunStreamingAsync([inputMessage], thread); await updates.ToAgentRunResponseAsync(); - // Assert - Verify order sent to chat client: [Existing, AIContextProvider, Input] + // Assert - Verify order sent to chat client: [Existing, Input, AIContextProvider] Assert.NotNull(messagesToChatClient); Assert.Equal(4, messagesToChatClient.Count); Assert.Equal("Message A", messagesToChatClient[0].Text); Assert.Equal("Message B", messagesToChatClient[1].Text); - Assert.Equal("Message X", messagesToChatClient[2].Text); - Assert.Equal("Message C", messagesToChatClient[3].Text); + Assert.Equal("Message C", messagesToChatClient[2].Text); + Assert.Equal("Message X", messagesToChatClient[3].Text); // Assert - Verify order stored in MessageStore: [Existing, Input, AIContextProvider, Response] var storedMessagesList = (await messageStore.GetMessagesAsync()).ToList(); From 9f28cfc89d6c8fa4d2fdcb0f2e73ffea8c6d8309 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 5 Dec 2025 15:07:53 +0000 Subject: [PATCH 5/5] Remove redundant test methods as existing tests already verify the behavior Co-authored-by: westey-m <164392973+westey-m@users.noreply.github.com> --- .../ChatClient/ChatClientAgentTests.cs | 146 ------------------ 1 file changed, 146 deletions(-) diff --git a/dotnet/tests/Microsoft.Agents.AI.UnitTests/ChatClient/ChatClientAgentTests.cs b/dotnet/tests/Microsoft.Agents.AI.UnitTests/ChatClient/ChatClientAgentTests.cs index 64442b4b5e..14195eb69c 100644 --- a/dotnet/tests/Microsoft.Agents.AI.UnitTests/ChatClient/ChatClientAgentTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.UnitTests/ChatClient/ChatClientAgentTests.cs @@ -2129,152 +2129,6 @@ await Assert.ThrowsAsync(async () => x.InvokeException is InvalidOperationException), It.IsAny()), Times.Once); } - /// - /// Verify that messages are stored in MessageStore with AIContextProvider messages. - /// Order stored should be: Input messages, AIContextProvider messages, Response messages. - /// - [Fact] - public async Task VerifyMessageOrderingWithAIContextProviderAsync() - { - // Arrange - var existingMessages = new List - { - new(ChatRole.User, "Message A"), - new(ChatRole.Assistant, "Message B") - }; - - var inputMessage = new ChatMessage(ChatRole.User, "Message C"); - var aiContextProviderMessage = new ChatMessage(ChatRole.System, "Message X"); - var responseMessage = new ChatMessage(ChatRole.Assistant, "Message D"); - - List? messagesToChatClient = null; - Mock mockService = new(); - mockService - .Setup(s => s.GetResponseAsync( - It.IsAny>(), - It.IsAny(), - It.IsAny())) - .Callback, ChatOptions, CancellationToken>((msgs, opts, ct) => messagesToChatClient = msgs.ToList()) - .ReturnsAsync(new ChatResponse([responseMessage])); - - var mockContextProvider = new Mock(); - mockContextProvider - .Setup(p => p.InvokingAsync(It.IsAny(), It.IsAny())) - .ReturnsAsync(new AIContext - { - Messages = [aiContextProviderMessage], - }); - - var messageStore = new InMemoryChatMessageStore(); - await messageStore.AddMessagesAsync(existingMessages); - - ChatClientAgent agent = new(mockService.Object, options: new() - { - ChatOptions = new() { Instructions = "test instructions" }, - AIContextProviderFactory = _ => mockContextProvider.Object - }); - - var thread = new ChatClientAgentThread - { - MessageStore = messageStore, - AIContextProvider = mockContextProvider.Object - }; - - // Act - await agent.RunAsync([inputMessage], thread); - - // Assert - Verify order sent to chat client: [Existing, Input, AIContextProvider] - Assert.NotNull(messagesToChatClient); - Assert.Equal(4, messagesToChatClient.Count); - Assert.Equal("Message A", messagesToChatClient[0].Text); - Assert.Equal("Message B", messagesToChatClient[1].Text); - Assert.Equal("Message C", messagesToChatClient[2].Text); - Assert.Equal("Message X", messagesToChatClient[3].Text); - - // Assert - Verify order stored in MessageStore: [Existing, Input, AIContextProvider, Response] - var storedMessagesList = (await messageStore.GetMessagesAsync()).ToList(); - Assert.Equal(5, storedMessagesList.Count); - Assert.Equal("Message A", storedMessagesList[0].Text); - Assert.Equal("Message B", storedMessagesList[1].Text); - Assert.Equal("Message C", storedMessagesList[2].Text); - Assert.Equal("Message X", storedMessagesList[3].Text); - Assert.Equal("Message D", storedMessagesList[4].Text); - } - - /// - /// Verify that messages are stored in MessageStore with AIContextProvider messages (streaming version). - /// Order stored should be: Input messages, AIContextProvider messages, Response messages. - /// - [Fact] - public async Task VerifyMessageOrderingWithAIContextProviderStreamingAsync() - { - // Arrange - var existingMessages = new List - { - new(ChatRole.User, "Message A"), - new(ChatRole.Assistant, "Message B") - }; - - var inputMessage = new ChatMessage(ChatRole.User, "Message C"); - var aiContextProviderMessage = new ChatMessage(ChatRole.System, "Message X"); - ChatResponseUpdate responseUpdate = new() { Role = ChatRole.Assistant }; - responseUpdate.Contents.Add(new TextContent("Message D")); - - List? messagesToChatClient = null; - Mock mockService = new(); - mockService - .Setup(s => s.GetStreamingResponseAsync( - It.IsAny>(), - It.IsAny(), - It.IsAny())) - .Callback, ChatOptions, CancellationToken>((msgs, opts, ct) => messagesToChatClient = msgs.ToList()) - .Returns(ToAsyncEnumerableAsync([responseUpdate])); - - var mockContextProvider = new Mock(); - mockContextProvider - .Setup(p => p.InvokingAsync(It.IsAny(), It.IsAny())) - .ReturnsAsync(new AIContext - { - Messages = [aiContextProviderMessage], - }); - - var messageStore = new InMemoryChatMessageStore(); - await messageStore.AddMessagesAsync(existingMessages); - - ChatClientAgent agent = new(mockService.Object, options: new() - { - ChatOptions = new() { Instructions = "test instructions" }, - AIContextProviderFactory = _ => mockContextProvider.Object - }); - - var thread = new ChatClientAgentThread - { - MessageStore = messageStore, - AIContextProvider = mockContextProvider.Object - }; - - // Act - var updates = agent.RunStreamingAsync([inputMessage], thread); - await updates.ToAgentRunResponseAsync(); - - // Assert - Verify order sent to chat client: [Existing, Input, AIContextProvider] - Assert.NotNull(messagesToChatClient); - Assert.Equal(4, messagesToChatClient.Count); - Assert.Equal("Message A", messagesToChatClient[0].Text); - Assert.Equal("Message B", messagesToChatClient[1].Text); - Assert.Equal("Message C", messagesToChatClient[2].Text); - Assert.Equal("Message X", messagesToChatClient[3].Text); - - // Assert - Verify order stored in MessageStore: [Existing, Input, AIContextProvider, Response] - var storedMessagesList = (await messageStore.GetMessagesAsync()).ToList(); - Assert.Equal(5, storedMessagesList.Count); - Assert.Equal("Message A", storedMessagesList[0].Text); - Assert.Equal("Message B", storedMessagesList[1].Text); - Assert.Equal("Message C", storedMessagesList[2].Text); - Assert.Equal("Message X", storedMessagesList[3].Text); - Assert.Equal("Message D", storedMessagesList[4].Text); - } - #endregion private static async IAsyncEnumerable ToAsyncEnumerableAsync(IEnumerable values)