Until now the only choice was 272K for every model or Full window for every model. The 272K default stays as it is (e950eb08: OpenAI runs its own 1M models at 272K in Codex, and Chen saw 70–90s to a first token at 550K).
What changes:
- A provider's editor has Compact at beside Context window and Max output, in the same form: "500k" for all of its models, "deepseek-v4-flash=272k" for one, and empty to follow Settings. A size at or above a model's window means the whole window. The values are kept in settings.ModelCompacts.
- Settings → Long conversations takes a size of its own beside Compact at / Full window (settings.CompactAt).
- `magpie model compact` takes a size such as 500k.
Order: the model's own value, then its provider's "*", then Settings. Full window turns off only the Settings value. A group uses the smallest value among its members.
Where it applies:
- Codex's model_catalog is told the threshold as context_window, with the whole window as max_context_window.
- Claude Code's CLAUDE_CODE_AUTO_COMPACT_WINDOW follows the main model. A Claude model now takes its provider's value when one is set.
- The Codex list tag changes when the Settings size or Full window changes.
Tests:
- New: TestModelCompactsSetFromTheEditor, TestSetCompactAt, TestCodexCatalogCompactAt and TestClaudeCompactAt.
- TestSettingsSaveKeepsWhatItDoesNotSend now covers the two new fields.
- compact-at.test.cjs (WebKit, en/zh) checks the editor field (shown, refused, chips, saved) and the Settings size (posted, hidden under Full window, no scroll).
- go vet ./..., go test -tags nogui of the root package, provider, codexcat, agent, gui, gateway, settings and catalog under a temp HOME, and GOOS=linux/windows builds all pass.
113 lines
4.2 KiB
Go
113 lines
4.2 KiB
Go
package provider
|
|
|
|
import (
|
|
"maps"
|
|
"testing"
|
|
|
|
"github.com/yetone/magpie/internal/catalog"
|
|
"github.com/yetone/magpie/internal/settings"
|
|
)
|
|
|
|
// A provider's editor sets where Codex and Claude Code compact on its
|
|
// models (#876, hisiling): "*" for all of them and model=size for one,
|
|
// the model's own first; those it leaves out come off, another
|
|
// provider's stay, a model it doesn't serve is refused, and a save that
|
|
// changes nothing tells the agents nothing. A group takes the smallest
|
|
// its models have.
|
|
func TestModelCompactsSetFromTheEditor(t *testing.T) {
|
|
limitsHome(t)
|
|
for _, id := range []string{"deepseek", "other"} {
|
|
if err := Save(Provider{ID: id, Name: id, Chat: "https://relay.example/v1", Key: "k", Models: []string{"deepseek-v4-flash", "deepseek-v4-pro"}}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
if err := SetModelCompacts("other", map[string]int{"deepseek-v4-pro": 300000}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
told := 0
|
|
catalog.Changed = func() { told++ }
|
|
t.Cleanup(func() { catalog.Changed = nil })
|
|
|
|
if err := SetModelCompacts("deepseek", map[string]int{"*": 500000, "deepseek-v4-flash": 272000}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if told != 1 {
|
|
t.Errorf("told the agents %d times; want once", told)
|
|
}
|
|
want := map[string]int{"deepseek/*": 500000, "deepseek/deepseek-v4-flash": 272000, "other/deepseek-v4-pro": 300000}
|
|
if got := settings.Load().ModelCompacts; !maps.Equal(got, want) {
|
|
t.Errorf("compacts %v; want %v", got, want)
|
|
}
|
|
if got := CompactsOf("deepseek"); !maps.Equal(got, map[string]int{"*": 500000, "deepseek-v4-flash": 272000}) {
|
|
t.Errorf("CompactsOf: %v", got)
|
|
}
|
|
for id, n := range map[string]int{
|
|
"deepseek/deepseek-v4-flash": 272000,
|
|
"magpie/deepseek/deepseek-v4-pro": 500000,
|
|
"deepseek/deepseek-v4-pro[1m]": 500000,
|
|
"other/deepseek-v4-flash": 0,
|
|
"other/deepseek-v4-pro": 300000,
|
|
"nobody/deepseek-v4-pro": 0,
|
|
} {
|
|
if got := CompactSet(id); got != n {
|
|
t.Errorf("CompactSet(%s) = %d; want %d", id, got, n)
|
|
}
|
|
}
|
|
group := func(string) (Group, []Member, bool) {
|
|
dp, _ := Find("deepseek")
|
|
ot, _ := Find("other")
|
|
return Group{}, []Member{
|
|
{Path: []string{"deepseek/deepseek-v4-pro"}, Provider: *dp, Model: "deepseek-v4-pro"},
|
|
{Path: []string{"other/deepseek-v4-pro"}, Provider: *ot, Model: "wire-name"},
|
|
{Path: []string{"other/deepseek-v4-flash"}, Provider: *ot, Model: "deepseek-v4-flash"},
|
|
}, true
|
|
}
|
|
if got := compactSet(settings.Load(), GroupPrefix+"mix", group); got != 300000 {
|
|
t.Errorf("a group: %d; want its smallest, 300000", got)
|
|
}
|
|
|
|
told = 0
|
|
if err := SetModelCompacts("deepseek", map[string]int{"*": 500000, "deepseek-v4-flash": 272000}); err != nil || told != 0 {
|
|
t.Errorf("the same again: %v, told %d", err, told)
|
|
}
|
|
if err := SetModelCompacts("deepseek", map[string]int{"typo": 5}); err == nil {
|
|
t.Error("a model deepseek doesn't serve was accepted")
|
|
}
|
|
if got := settings.Load().ModelCompacts; !maps.Equal(got, want) {
|
|
t.Errorf("a refused save changed them: %v", got)
|
|
}
|
|
if err := SetModelCompacts("deepseek", map[string]int{}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if got := settings.Load().ModelCompacts; !maps.Equal(got, map[string]int{"other/deepseek-v4-pro": 300000}) {
|
|
t.Errorf("emptied: %v", got)
|
|
}
|
|
}
|
|
|
|
// The threshold for every model can be any size, not 272K alone (#876):
|
|
// typing one turns Full window off, 272K or nothing is the default again.
|
|
func TestSetCompactAt(t *testing.T) {
|
|
limitsHome(t)
|
|
if err := settings.Save(settings.Settings{FullContext: true}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := SetCompactAt(500000); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if s := settings.Load(); s.FullContext || s.CompactAt != 500000 || s.Compact() != 500000 || s.Working(1000000) != 500000 || s.Working(200000) != 200000 {
|
|
t.Errorf("at 500K: %+v", s)
|
|
}
|
|
if err := SetCompactAt(settings.WorkingWindow); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if s := settings.Load(); s.CompactAt != 0 || s.Compact() != settings.WorkingWindow {
|
|
t.Errorf("at 272K: %+v", s)
|
|
}
|
|
if err := SetCompactAt(-1); err == nil {
|
|
t.Error("a negative size was accepted")
|
|
}
|
|
if s := (settings.Settings{CompactAt: 500000, FullContext: true}); s.Compact() != 0 || s.Working(1000000) != 1000000 {
|
|
t.Errorf("full window: %d", s.Compact())
|
|
}
|
|
}
|