From 9912431bd0e7b504548b666fe8b9af0e37468614 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 12 Mar 2026 23:26:58 +0000 Subject: [PATCH 1/7] Initial plan From 576eb385e59a494907e5f88296f54f0ef4231add Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 12 Mar 2026 23:31:38 +0000 Subject: [PATCH 2/7] Add ChatStrategyExtensions.cs with AsChatReducer() extension method and tests Co-authored-by: crickman <66376200+crickman@users.noreply.github.com> --- .../Compaction/ChatStrategyExtensions.cs | 59 +++++++ .../Compaction/ChatStrategyExtensionsTests.cs | 159 ++++++++++++++++++ 2 files changed, 218 insertions(+) create mode 100644 dotnet/src/Microsoft.Agents.AI/Compaction/ChatStrategyExtensions.cs create mode 100644 dotnet/tests/Microsoft.Agents.AI.UnitTests/Compaction/ChatStrategyExtensionsTests.cs diff --git a/dotnet/src/Microsoft.Agents.AI/Compaction/ChatStrategyExtensions.cs b/dotnet/src/Microsoft.Agents.AI/Compaction/ChatStrategyExtensions.cs new file mode 100644 index 0000000000..dde8d270a0 --- /dev/null +++ b/dotnet/src/Microsoft.Agents.AI/Compaction/ChatStrategyExtensions.cs @@ -0,0 +1,59 @@ +// Copyright (c) Microsoft. All rights reserved. + +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.AI; +using Microsoft.Shared.DiagnosticIds; +using Microsoft.Shared.Diagnostics; + +namespace Microsoft.Agents.AI.Compaction; + +/// +/// Provides extension methods for . +/// +[Experimental(DiagnosticIds.Experiments.AgentsAIExperiments)] +public static class ChatStrategyExtensions +{ + /// + /// Returns an that applies this to reduce a list of messages. + /// + /// The compaction strategy to wrap as an . + /// + /// An that, on each call to , builds a + /// from the supplied messages and applies the strategy's compaction logic, + /// returning the resulting included messages. + /// + /// + /// This allows any to be used wherever an is expected, + /// bridging the compaction pipeline into systems bound to the Microsoft.Extensions.AI contract. + /// + public static IChatReducer AsChatReducer(this CompactionStrategy strategy) + { + Throw.IfNull(strategy); + return new CompactionStrategyChatReducer(strategy); + } + + /// + /// An adapter that delegates to a . + /// + private sealed class CompactionStrategyChatReducer : IChatReducer + { + private readonly CompactionStrategy _strategy; + + public CompactionStrategyChatReducer(CompactionStrategy strategy) + { + this._strategy = strategy; + } + + /// + public async Task> ReduceAsync(IEnumerable messages, CancellationToken cancellationToken = default) + { + List messageList = [.. messages]; + CompactionMessageIndex index = CompactionMessageIndex.Create(messageList); + await this._strategy.CompactAsync(index, cancellationToken: cancellationToken).ConfigureAwait(false); + return index.GetIncludedMessages(); + } + } +} diff --git a/dotnet/tests/Microsoft.Agents.AI.UnitTests/Compaction/ChatStrategyExtensionsTests.cs b/dotnet/tests/Microsoft.Agents.AI.UnitTests/Compaction/ChatStrategyExtensionsTests.cs new file mode 100644 index 0000000000..170c8d5acd --- /dev/null +++ b/dotnet/tests/Microsoft.Agents.AI.UnitTests/Compaction/ChatStrategyExtensionsTests.cs @@ -0,0 +1,159 @@ +// Copyright (c) Microsoft. All rights reserved. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Agents.AI.Compaction; +using Microsoft.Extensions.AI; + +namespace Microsoft.Agents.AI.UnitTests.Compaction; + +/// +/// Contains tests for the class. +/// +public class ChatStrategyExtensionsTests +{ + [Fact] + public void AsChatReducerNullStrategyThrows() + { + // Act & Assert + Assert.Throws(() => ((CompactionStrategy)null!).AsChatReducer()); + } + + [Fact] + public async Task AsChatReducerReturnsIChatReducerAsync() + { + // Arrange + ChatReducerCompactionStrategy strategy = new(new IdentityReducer(), CompactionTriggers.Always); + + // Act + IChatReducer reducer = strategy.AsChatReducer(); + + // Assert + Assert.NotNull(reducer); + await Task.CompletedTask; + } + + [Fact] + public async Task ReduceAsyncReturnsAllMessagesWhenStrategyDoesNotCompactAsync() + { + // Arrange — trigger never fires, so no compaction occurs + ChatReducerCompactionStrategy strategy = new(new IdentityReducer(), CompactionTriggers.Never); + IChatReducer reducer = strategy.AsChatReducer(); + + List messages = + [ + new(ChatRole.User, "Hello"), + new(ChatRole.Assistant, "Hi!"), + ]; + + // Act + IEnumerable result = await reducer.ReduceAsync(messages, CancellationToken.None); + + // Assert + Assert.Equal(messages, result); + } + + [Fact] + public async Task ReduceAsyncCompactsMessagesWhenStrategyFiresAsync() + { + // Arrange — reducer keeps only the last message + ChatReducerCompactionStrategy strategy = new( + new TakeLastReducer(1), + CompactionTriggers.Always); + IChatReducer reducer = strategy.AsChatReducer(); + + List messages = + [ + new(ChatRole.User, "First"), + new(ChatRole.Assistant, "Response 1"), + new(ChatRole.User, "Second"), + ]; + + // Act + IEnumerable result = await reducer.ReduceAsync(messages, CancellationToken.None); + + // Assert + List resultList = [.. result]; + Assert.Single(resultList); + Assert.Equal("Second", resultList[0].Text); + } + + [Fact] + public async Task ReduceAsyncPassesCancellationTokenToStrategyAsync() + { + // Arrange + using CancellationTokenSource cts = new(); + CancellationToken capturedToken = default; + + CapturingReducer capturingReducer = new(token => capturedToken = token); + ChatReducerCompactionStrategy strategy = new(capturingReducer, CompactionTriggers.Always); + IChatReducer reducer = strategy.AsChatReducer(); + + List messages = + [ + new(ChatRole.User, "Hello"), + new(ChatRole.User, "World"), + ]; + + // Act + await reducer.ReduceAsync(messages, cts.Token); + + // Assert + Assert.Equal(cts.Token, capturedToken); + } + + [Fact] + public async Task ReduceAsyncEmptyMessagesReturnsEmptyAsync() + { + // Arrange + ChatReducerCompactionStrategy strategy = new(new IdentityReducer(), CompactionTriggers.Always); + IChatReducer reducer = strategy.AsChatReducer(); + + // Act + IEnumerable result = await reducer.ReduceAsync([], CancellationToken.None); + + // Assert + Assert.Empty(result); + } + + /// + /// An that returns messages unchanged. + /// + private sealed class IdentityReducer : IChatReducer + { + public Task> ReduceAsync(IEnumerable messages, CancellationToken cancellationToken = default) + => Task.FromResult(messages); + } + + /// + /// An that keeps only the last n messages. + /// + private sealed class TakeLastReducer : IChatReducer + { + private readonly int _count; + + public TakeLastReducer(int count) => this._count = count; + + public Task> ReduceAsync(IEnumerable messages, CancellationToken cancellationToken = default) + => Task.FromResult>(messages.TakeLast(this._count).ToList()); + } + + /// + /// An that captures the passed to . + /// + private sealed class CapturingReducer : IChatReducer + { + private readonly Action _capture; + + public CapturingReducer(Action capture) => this._capture = capture; + + public Task> ReduceAsync(IEnumerable messages, CancellationToken cancellationToken = default) + { + this._capture(cancellationToken); + return Task.FromResult(messages.TakeLast(1)); + } + } +} From 865fb9e7296944d52077b04abb70748fc7fc49e3 Mon Sep 17 00:00:00 2001 From: Chris <66376200+crickman@users.noreply.github.com> Date: Thu, 12 Mar 2026 17:04:46 -0700 Subject: [PATCH 3/7] Refactor message list creation in ReduceAsync method --- .../Microsoft.Agents.AI/Compaction/ChatStrategyExtensions.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/dotnet/src/Microsoft.Agents.AI/Compaction/ChatStrategyExtensions.cs b/dotnet/src/Microsoft.Agents.AI/Compaction/ChatStrategyExtensions.cs index dde8d270a0..82d1aea76a 100644 --- a/dotnet/src/Microsoft.Agents.AI/Compaction/ChatStrategyExtensions.cs +++ b/dotnet/src/Microsoft.Agents.AI/Compaction/ChatStrategyExtensions.cs @@ -1,6 +1,5 @@ // Copyright (c) Microsoft. All rights reserved. -using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Threading; using System.Threading.Tasks; @@ -32,6 +31,7 @@ public static class ChatStrategyExtensions public static IChatReducer AsChatReducer(this CompactionStrategy strategy) { Throw.IfNull(strategy); + return new CompactionStrategyChatReducer(strategy); } @@ -50,8 +50,7 @@ public CompactionStrategyChatReducer(CompactionStrategy strategy) /// public async Task> ReduceAsync(IEnumerable messages, CancellationToken cancellationToken = default) { - List messageList = [.. messages]; - CompactionMessageIndex index = CompactionMessageIndex.Create(messageList); + CompactionMessageIndex index = CompactionMessageIndex.Create([.. messages]); await this._strategy.CompactAsync(index, cancellationToken: cancellationToken).ConfigureAwait(false); return index.GetIncludedMessages(); } From c6f4c1eb2b93e810b591cf848f160a8f5f272a8e Mon Sep 17 00:00:00 2001 From: Chris <66376200+crickman@users.noreply.github.com> Date: Thu, 12 Mar 2026 17:07:58 -0700 Subject: [PATCH 4/7] Remove unnecessary blank line in AsChatReducer method --- .../Microsoft.Agents.AI/Compaction/ChatStrategyExtensions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dotnet/src/Microsoft.Agents.AI/Compaction/ChatStrategyExtensions.cs b/dotnet/src/Microsoft.Agents.AI/Compaction/ChatStrategyExtensions.cs index 82d1aea76a..5632128d31 100644 --- a/dotnet/src/Microsoft.Agents.AI/Compaction/ChatStrategyExtensions.cs +++ b/dotnet/src/Microsoft.Agents.AI/Compaction/ChatStrategyExtensions.cs @@ -31,7 +31,7 @@ public static class ChatStrategyExtensions public static IChatReducer AsChatReducer(this CompactionStrategy strategy) { Throw.IfNull(strategy); - + return new CompactionStrategyChatReducer(strategy); } From 995def9bd56b731684c528e8e99262a5f0699acf Mon Sep 17 00:00:00 2001 From: Chris <66376200+crickman@users.noreply.github.com> Date: Fri, 13 Mar 2026 09:45:34 -0700 Subject: [PATCH 5/7] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- .../src/Microsoft.Agents.AI/Compaction/ChatStrategyExtensions.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/dotnet/src/Microsoft.Agents.AI/Compaction/ChatStrategyExtensions.cs b/dotnet/src/Microsoft.Agents.AI/Compaction/ChatStrategyExtensions.cs index 5632128d31..b7f224d751 100644 --- a/dotnet/src/Microsoft.Agents.AI/Compaction/ChatStrategyExtensions.cs +++ b/dotnet/src/Microsoft.Agents.AI/Compaction/ChatStrategyExtensions.cs @@ -1,5 +1,6 @@ // Copyright (c) Microsoft. All rights reserved. +using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Threading; using System.Threading.Tasks; From 087359518bc2db0cb97a147d746ef77fbef98975 Mon Sep 17 00:00:00 2001 From: Chris <66376200+crickman@users.noreply.github.com> Date: Fri, 13 Mar 2026 09:46:10 -0700 Subject: [PATCH 6/7] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- .../Compaction/ChatStrategyExtensionsTests.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/dotnet/tests/Microsoft.Agents.AI.UnitTests/Compaction/ChatStrategyExtensionsTests.cs b/dotnet/tests/Microsoft.Agents.AI.UnitTests/Compaction/ChatStrategyExtensionsTests.cs index 170c8d5acd..e0d2c0e039 100644 --- a/dotnet/tests/Microsoft.Agents.AI.UnitTests/Compaction/ChatStrategyExtensionsTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.UnitTests/Compaction/ChatStrategyExtensionsTests.cs @@ -23,7 +23,7 @@ public void AsChatReducerNullStrategyThrows() } [Fact] - public async Task AsChatReducerReturnsIChatReducerAsync() + public void AsChatReducerReturnsIChatReducer() { // Arrange ChatReducerCompactionStrategy strategy = new(new IdentityReducer(), CompactionTriggers.Always); @@ -33,7 +33,6 @@ public async Task AsChatReducerReturnsIChatReducerAsync() // Assert Assert.NotNull(reducer); - await Task.CompletedTask; } [Fact] From 2f31f6d0570e5c7c1a7c8a7384e02d9704e04181 Mon Sep 17 00:00:00 2001 From: Chris Rickman Date: Fri, 13 Mar 2026 10:34:58 -0700 Subject: [PATCH 7/7] Fix test --- .../Compaction/ChatStrategyExtensionsTests.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/dotnet/tests/Microsoft.Agents.AI.UnitTests/Compaction/ChatStrategyExtensionsTests.cs b/dotnet/tests/Microsoft.Agents.AI.UnitTests/Compaction/ChatStrategyExtensionsTests.cs index e0d2c0e039..195d5756e5 100644 --- a/dotnet/tests/Microsoft.Agents.AI.UnitTests/Compaction/ChatStrategyExtensionsTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.UnitTests/Compaction/ChatStrategyExtensionsTests.cs @@ -137,7 +137,7 @@ private sealed class TakeLastReducer : IChatReducer public TakeLastReducer(int count) => this._count = count; public Task> ReduceAsync(IEnumerable messages, CancellationToken cancellationToken = default) - => Task.FromResult>(messages.TakeLast(this._count).ToList()); + => Task.FromResult(messages.Reverse().Take(this._count)); } /// @@ -152,7 +152,8 @@ private sealed class CapturingReducer : IChatReducer public Task> ReduceAsync(IEnumerable messages, CancellationToken cancellationToken = default) { this._capture(cancellationToken); - return Task.FromResult(messages.TakeLast(1)); + IEnumerable reducedMessages = [messages.Reverse().First()]; + return Task.FromResult(reducedMessages); } } }