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);
}
}
}