/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
*--------------------------------------------------------------------------------------------*/
package com.github.copilot.sdk;
import static org.junit.jupiter.api.Assertions.*;
import java.util.ArrayList;
import java.util.concurrent.TimeUnit;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.Timeout;
import com.github.copilot.sdk.events.AbstractSessionEvent;
import com.github.copilot.sdk.events.AssistantMessageEvent;
import com.github.copilot.sdk.events.SessionCompactionCompleteEvent;
import com.github.copilot.sdk.events.SessionCompactionStartEvent;
import com.github.copilot.sdk.json.InfiniteSessionConfig;
import com.github.copilot.sdk.json.MessageOptions;
import com.github.copilot.sdk.json.PermissionHandler;
import com.github.copilot.sdk.json.SessionConfig;
/**
* Tests for compaction and infinite sessions functionality.
*
*
* These tests verify that sessions can trigger compaction with low thresholds
* and emit appropriate events. Snapshots are stored in
* test/snapshots/compaction/.
*
*/
public class CompactionTest {
private static E2ETestContext ctx;
@BeforeAll
static void setup() throws Exception {
ctx = E2ETestContext.create();
}
@AfterAll
static void teardown() throws Exception {
if (ctx != null) {
ctx.close();
}
}
/**
* Verifies that compaction is triggered with low threshold and emits events.
*
* @see Snapshot:
* compaction/should_trigger_compaction_with_low_threshold_and_emit_events
*/
@Test
@Timeout(value = 120, unit = TimeUnit.SECONDS)
void testShouldTriggerCompactionWithLowThresholdAndEmitEvents() throws Exception {
ctx.configureForTest("compaction", "should_trigger_compaction_with_low_threshold_and_emit_events");
// Create session with very low compaction thresholds to trigger compaction
// quickly
var infiniteConfig = new InfiniteSessionConfig().setEnabled(true)
// Trigger background compaction at 0.5% context usage (~1000 tokens)
.setBackgroundCompactionThreshold(0.005)
// Block at 1% to ensure compaction runs
.setBufferExhaustionThreshold(0.01);
var config = new SessionConfig().setInfiniteSessions(infiniteConfig)
.setOnPermissionRequest(PermissionHandler.APPROVE_ALL);
var events = new ArrayList();
try (CopilotClient client = ctx.createClient()) {
CopilotSession session = client.createSession(config).get();
session.on(event -> events.add(event));
// Send multiple messages to fill up the context window
// With such low thresholds, even a few messages should trigger compaction
session.sendAndWait(new MessageOptions().setPrompt("Tell me a story about a dragon. Be detailed.")).get(60,
TimeUnit.SECONDS);
session.sendAndWait(
new MessageOptions().setPrompt("Continue the story with more details about the dragon's castle."))
.get(60, TimeUnit.SECONDS);
session.sendAndWait(new MessageOptions().setPrompt("Now describe the dragon's treasure in great detail."))
.get(60, TimeUnit.SECONDS);
// Check for compaction events
long compactionStartCount = events.stream().filter(e -> e instanceof SessionCompactionStartEvent).count();
long compactionCompleteCount = events.stream().filter(e -> e instanceof SessionCompactionCompleteEvent)
.count();
// Should have triggered compaction at least once
assertTrue(compactionStartCount >= 1,
"Should have triggered compaction start at least once, got: " + compactionStartCount);
assertTrue(compactionCompleteCount >= 1,
"Should have triggered compaction complete at least once, got: " + compactionCompleteCount);
// Compaction should have succeeded
SessionCompactionCompleteEvent lastCompactionComplete = events.stream()
.filter(e -> e instanceof SessionCompactionCompleteEvent)
.map(e -> (SessionCompactionCompleteEvent) e).reduce((first, second) -> second).orElse(null);
assertNotNull(lastCompactionComplete);
assertTrue(lastCompactionComplete.getData().success(), "Compaction should have succeeded");
// Verify the session still works after compaction
AssistantMessageEvent answer = session
.sendAndWait(new MessageOptions().setPrompt("What was the story about?")).get(60, TimeUnit.SECONDS);
assertNotNull(answer);
assertNotNull(answer.getData().content());
// Should remember it was about a dragon (context preserved via summary)
assertTrue(answer.getData().content().toLowerCase().contains("dragon"),
"Should remember the story was about a dragon: " + answer.getData().content());
session.close();
}
}
/**
* Verifies that compaction events are not emitted when infinite sessions is
* disabled.
*
* @see Snapshot:
* compaction/should_not_emit_compaction_events_when_infinite_sessions_disabled
*/
@Test
void testShouldNotEmitCompactionEventsWhenInfiniteSessionsDisabled() throws Exception {
ctx.configureForTest("compaction", "should_not_emit_compaction_events_when_infinite_sessions_disabled");
var infiniteConfig = new InfiniteSessionConfig().setEnabled(false);
var config = new SessionConfig().setInfiniteSessions(infiniteConfig)
.setOnPermissionRequest(PermissionHandler.APPROVE_ALL);
var compactionEvents = new ArrayList();
try (CopilotClient client = ctx.createClient()) {
CopilotSession session = client.createSession(config).get();
session.on(event -> {
if (event instanceof SessionCompactionStartEvent || event instanceof SessionCompactionCompleteEvent) {
compactionEvents.add(event);
}
});
session.sendAndWait(new MessageOptions().setPrompt("What is 2+2?")).get(60, TimeUnit.SECONDS);
// Should not have any compaction events when disabled
assertEquals(0, compactionEvents.size(),
"Should not have any compaction events when infinite sessions is disabled");
session.close();
}
}
}