-
Notifications
You must be signed in to change notification settings - Fork 506
feat: Infinite Sessions #76
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
100ca24
30b6a6f
24fe4d7
d10701b
8a85bc3
63e2f1a
c3f3366
bfa9b72
128a95b
f823071
21dcf41
8d147eb
a75c1f4
b6fa81c
c334859
b49dcc6
a318f06
5694a85
701e463
e1b198a
0910f53
501fe8d
d4da3dc
e924316
b6a2edd
814a40f
bc261a3
c02ec51
1650316
0771de0
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,110 @@ | ||
| /*--------------------------------------------------------------------------------------------- | ||
| * Copyright (c) Microsoft Corporation. All rights reserved. | ||
| *--------------------------------------------------------------------------------------------*/ | ||
|
|
||
| using System.Runtime.InteropServices; | ||
| using GitHub.Copilot.SDK.Test.Harness; | ||
| using Xunit; | ||
| using Xunit.Abstractions; | ||
|
|
||
| namespace GitHub.Copilot.SDK.Test; | ||
|
|
||
| public class CompactionTests(E2ETestFixture fixture, ITestOutputHelper output) : E2ETestBase(fixture, "compaction", output) | ||
| { | ||
| [Fact] | ||
| public async Task Should_Trigger_Compaction_With_Low_Threshold_And_Emit_Events() | ||
| { | ||
| // Create session with very low compaction thresholds to trigger compaction quickly | ||
| var session = await Client.CreateSessionAsync(new SessionConfig | ||
| { | ||
| InfiniteSessions = new InfiniteSessionConfig | ||
| { | ||
| Enabled = true, | ||
| // Trigger background compaction at 0.5% context usage (~1000 tokens) | ||
| BackgroundCompactionThreshold = 0.005, | ||
| // Block at 1% to ensure compaction runs | ||
| BufferExhaustionThreshold = 0.01 | ||
| } | ||
| }); | ||
|
|
||
| var compactionStartEvents = new List<SessionCompactionStartEvent>(); | ||
| var compactionCompleteEvents = new List<SessionCompactionCompleteEvent>(); | ||
|
|
||
| session.On(evt => | ||
| { | ||
| if (evt is SessionCompactionStartEvent startEvt) | ||
| { | ||
| compactionStartEvents.Add(startEvt); | ||
| } | ||
| if (evt is SessionCompactionCompleteEvent completeEvt) | ||
| { | ||
| compactionCompleteEvents.Add(completeEvt); | ||
| } | ||
| }); | ||
|
|
||
| // Send multiple messages to fill up the context window | ||
| await session.SendAndWaitAsync(new MessageOptions | ||
| { | ||
| Prompt = "Tell me a long story about a dragon. Be very detailed." | ||
| }); | ||
| await session.SendAndWaitAsync(new MessageOptions | ||
| { | ||
| Prompt = "Continue the story with more details about the dragon's castle." | ||
| }); | ||
| await session.SendAndWaitAsync(new MessageOptions | ||
| { | ||
| Prompt = "Now describe the dragon's treasure in great detail." | ||
| }); | ||
|
|
||
| // Should have triggered compaction at least once | ||
| Assert.True(compactionStartEvents.Count >= 1, "Expected at least 1 compaction_start event"); | ||
| Assert.True(compactionCompleteEvents.Count >= 1, "Expected at least 1 compaction_complete event"); | ||
|
|
||
| // Compaction should have succeeded | ||
| var lastComplete = compactionCompleteEvents[^1]; | ||
| Assert.True(lastComplete.Data.Success, "Expected compaction to succeed"); | ||
|
|
||
| // Should have removed some tokens | ||
| if (lastComplete.Data.TokensRemoved.HasValue) | ||
| { | ||
| Assert.True(lastComplete.Data.TokensRemoved > 0, "Expected tokensRemoved > 0"); | ||
| } | ||
|
|
||
| // Verify the session still works after compaction | ||
| var answer = await session.SendAndWaitAsync(new MessageOptions | ||
| { | ||
| Prompt = "What was the story about?" | ||
| }); | ||
| Assert.NotNull(answer); | ||
| Assert.NotNull(answer!.Data.Content); | ||
| // Should remember it was about a dragon (context preserved via summary) | ||
| Assert.Contains("dragon", answer.Data.Content.ToLower()); | ||
| } | ||
|
|
||
| [Fact] | ||
| public async Task Should_Not_Emit_Compaction_Events_When_Infinite_Sessions_Disabled() | ||
| { | ||
| var session = await Client.CreateSessionAsync(new SessionConfig | ||
| { | ||
| InfiniteSessions = new InfiniteSessionConfig | ||
| { | ||
| Enabled = false | ||
| } | ||
| }); | ||
|
|
||
| var compactionEvents = new List<SessionEvent>(); | ||
|
|
||
| session.On(evt => | ||
| { | ||
| if (evt is SessionCompactionStartEvent or SessionCompactionCompleteEvent) | ||
| { | ||
| compactionEvents.Add(evt); | ||
| } | ||
| }); | ||
|
|
||
| await session.SendAndWaitAsync(new MessageOptions { Prompt = "What is 2+2?" }); | ||
|
|
||
| // Should not have any compaction events when disabled | ||
| Assert.Empty(compactionEvents); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -287,6 +287,44 @@ When `Streaming: true`: | |
|
|
||
| Note: `assistant.message` and `assistant.reasoning` (final events) are always sent regardless of streaming setting. | ||
|
|
||
| ## Infinite Sessions | ||
|
|
||
| By default, sessions use **infinite sessions** which automatically manage context window limits through background compaction and persist state to a workspace directory. | ||
|
|
||
| ```go | ||
| // Default: infinite sessions enabled with default thresholds | ||
| session, _ := client.CreateSession(&copilot.SessionConfig{ | ||
| Model: "gpt-5", | ||
| }) | ||
|
|
||
| // Access the workspace path for checkpoints and files | ||
| fmt.Println(session.WorkspacePath()) | ||
| // => ~/.copilot/session-state/{sessionId}/ | ||
|
|
||
| // Custom thresholds | ||
| session, _ := client.CreateSession(&copilot.SessionConfig{ | ||
| Model: "gpt-5", | ||
| InfiniteSessions: &copilot.InfiniteSessionConfig{ | ||
| Enabled: copilot.Bool(true), | ||
| BackgroundCompactionThreshold: copilot.Float64(0.80), // Start compacting at 80% context usage | ||
| BufferExhaustionThreshold: copilot.Float64(0.95), // Block at 95% until compaction completes | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Inconsistency Issue: The The codebase has a Recommended fix: Add a // Float64 returns a pointer to the given float64 value.
// Use for setting threshold values: BackgroundCompactionThreshold: Float64(0.80)
func Float64(v float64) *float64 {
return &v
}Alternatively, update the README examples to show manual pointer creation without the helper function.
|
||
| }, | ||
| }) | ||
|
|
||
| // Disable infinite sessions | ||
| session, _ := client.CreateSession(&copilot.SessionConfig{ | ||
| Model: "gpt-5", | ||
| InfiniteSessions: &copilot.InfiniteSessionConfig{ | ||
| Enabled: copilot.Bool(false), | ||
| }, | ||
| }) | ||
| ``` | ||
|
|
||
| When enabled, sessions emit compaction events: | ||
|
|
||
| - `session.compaction_start` - Background compaction started | ||
| - `session.compaction_complete` - Compaction finished (includes token counts) | ||
|
|
||
| ## Transport Modes | ||
|
|
||
| ### stdio (Default) | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The windows tests were flaking. Bumping up this timeout seems to stabilize things, but I'd love a second set of eyes @SteveSandersonMS