Fix streaming path to include chat history in messages sent to chat client (#3798)

RunCoreStreamingAsync was passing inputMessagesForProviders (which lacks
chat history) to GetStreamingResponseAsync instead of
inputMessagesForChatClient (which includes chat history). This caused
streaming runs to lose conversation context on subsequent calls.

The non-streaming path (RunCoreAsync) already correctly used
inputMessagesForChatClient. This aligns the streaming path to match.

Also adds a unit test that validates chat history is included in
messages sent to the chat client during streaming on subsequent calls.

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: lokitoth <6936551+lokitoth@users.noreply.github.com>
This commit is contained in:
Copilot
2026-02-10 10:50:41 -05:00
committed by GitHub
Unverified
parent 8ad66637d8
commit 6c37ce8450
2 changed files with 40 additions and 1 deletions
@@ -226,7 +226,7 @@ public sealed partial class ChatClientAgent : AIAgent
try
{
// Using the enumerator to ensure we consider the case where no updates are returned for notification.
responseUpdatesEnumerator = chatClient.GetStreamingResponseAsync(inputMessagesForProviders, chatOptions, cancellationToken).GetAsyncEnumerator(cancellationToken);
responseUpdatesEnumerator = chatClient.GetStreamingResponseAsync(inputMessagesForChatClient, chatOptions, cancellationToken).GetAsyncEnumerator(cancellationToken);
}
catch (Exception ex)
{
@@ -1320,6 +1320,45 @@ public partial class ChatClientAgentTests
mockFactory.Verify(f => f(It.IsAny<ChatClientAgentOptions.ChatHistoryProviderFactoryContext>(), It.IsAny<CancellationToken>()), Times.Once);
}
/// <summary>
/// Verify that RunStreamingAsync includes chat history in messages sent to the chat client on subsequent calls.
/// </summary>
[Fact]
public async Task RunStreamingAsyncIncludesChatHistoryInMessagesToChatClientAsync()
{
// Arrange
List<IEnumerable<ChatMessage>> capturedMessages = [];
Mock<IChatClient> mockService = new();
ChatResponseUpdate[] returnUpdates =
[
new ChatResponseUpdate(role: ChatRole.Assistant, content: "response"),
];
mockService.Setup(
s => s.GetStreamingResponseAsync(
It.IsAny<IEnumerable<ChatMessage>>(),
It.IsAny<ChatOptions>(),
It.IsAny<CancellationToken>()))
.Returns(ToAsyncEnumerableAsync(returnUpdates))
.Callback<IEnumerable<ChatMessage>, ChatOptions?, CancellationToken>((msgs, _, _) => capturedMessages.Add(msgs.ToList()));
ChatClientAgent agent = new(mockService.Object, options: new()
{
ChatOptions = new() { Instructions = "test instructions" },
});
// Act
ChatClientAgentSession? session = await agent.CreateSessionAsync() as ChatClientAgentSession;
await agent.RunStreamingAsync([new(ChatRole.User, "first")], session).ToListAsync();
await agent.RunStreamingAsync([new(ChatRole.User, "second")], session).ToListAsync();
// Assert - the second call should include chat history (first user message + first response) plus the new message
Assert.Equal(2, capturedMessages.Count);
var secondCallMessages = capturedMessages[1].ToList();
Assert.Equal(3, secondCallMessages.Count);
Assert.Equal("first", secondCallMessages[0].Text);
Assert.Equal("response", secondCallMessages[1].Text);
Assert.Equal("second", secondCallMessages[2].Text);
}
/// <summary>
/// Verify that RunStreamingAsync throws when a <see cref="ChatHistoryProvider"/> factory is provided and the chat client returns a conversation id.
/// </summary>