agent: magpie's default compaction window gives way to one the user set in Claude Code (#876, hisiling)
On a non-Claude model magpie wrote CLAUDE_CODE_AUTO_COMPACT_WINDOW=272000 into ~/.claude/settings.json's env even when the user had set autoCompactWindow there, or modelSettings.<model>.autoCompactWindow (where /autocompact saves it). Claude Code takes the env first, so the user's own window was overridden: in a sandbox, autoCompactWindow 900000 showed 900k in /context, and 272k once the env was added. magpie now leaves the env out when the user set either one for the main model. A size the user typed in magpie, for every model or for a model or its provider, is still written, because the user chose it there.
Codex is unaffected. Codex's model_info.rs applies model_context_window and model_auto_compact_token_limit from config.toml over the catalog, and magpie never writes or removes either key.
TestClaudeCompactYieldsToOwnSetting fails without the change ("autoCompactWindow: "272000"; want """).
This commit is contained in:
1 parent
9139b19224
commit
602d4ec6cc
2 files changed
+95
-2
No files matched your search
@@ -2,6 +2,7 @@ package agent
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"cmp"
|
"cmp"
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
@@ -427,8 +428,14 @@ func claudeIn(at place) *Agent {
|
|||||||
// model (settings.Compact), none under Full window
|
// model (settings.Compact), none under Full window
|
||||||
main := mainModel()
|
main := mainModel()
|
||||||
n := provider.CompactSet(main)
|
n := provider.CompactSet(main)
|
||||||
if n == 0 && !claudeModel(main) {
|
if s := settings.Load(); n == 0 && !claudeModel(main) {
|
||||||
n = settings.Load().Compact()
|
n = s.Compact()
|
||||||
|
// magpie's default gives way to an autoCompactWindow the user
|
||||||
|
// set in Claude Code (/autocompact, its settings), which the env
|
||||||
|
// would take precedence over; one typed in magpie doesn't (#876)
|
||||||
|
if s.CompactAt == 0 && claudeOwnCompact(path, main) {
|
||||||
|
n = 0
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if n == 0 {
|
if n == 0 {
|
||||||
return dropCompact()
|
return dropCompact()
|
||||||
@@ -1290,6 +1297,33 @@ func claudeModel(ref string) bool {
|
|||||||
return strings.HasPrefix(strings.ToLower(m), "claude")
|
return strings.HasPrefix(strings.ToLower(m), "claude")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// claudeOwnCompact says the settings.json at path has an auto-compact
|
||||||
|
// window of the user's for model: autoCompactWindow, or one under
|
||||||
|
// modelSettings for it (as /autocompact saves it), a number or "auto".
|
||||||
|
// Claude Code takes CLAUDE_CODE_AUTO_COMPACT_WINDOW before either.
|
||||||
|
func claudeOwnCompact(path, model string) bool {
|
||||||
|
if _, ok := edit.GetJSON(path, "autoCompactWindow"); ok {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
raw, ok := edit.GetJSON(path, "modelSettings")
|
||||||
|
if !ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
var per map[string]struct {
|
||||||
|
Window any `json:"autoCompactWindow"`
|
||||||
|
}
|
||||||
|
if json.Unmarshal([]byte(raw), &per) != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
model = strings.ToLower(strings.TrimSuffix(strings.TrimPrefix(model, "magpie/"), "[1m]"))
|
||||||
|
for k, v := range per {
|
||||||
|
if v.Window != nil && strings.ToLower(strings.TrimSuffix(k, "[1m]")) == model {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
// claudeWindow is the context window to tell Claude Code for the models it
|
// claudeWindow is the context window to tell Claude Code for the models it
|
||||||
// runs on, 0 when magpie doesn't know it. One value serves every model not
|
// runs on, 0 when magpie doesn't know it. One value serves every model not
|
||||||
// marked [1m]: the main model's, or when that one is marked, the smallest of
|
// marked [1m]: the main model's, or when that one is marked, the smallest of
|
||||||
|
|||||||
@@ -138,3 +138,62 @@ func TestClaudeCompactAt(t *testing.T) {
|
|||||||
must(a.Field("model").Set("v/big"))
|
must(a.Field("model").Set("v/big"))
|
||||||
expect("full window", "")
|
expect("full window", "")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// An auto-compact window the user set in Claude Code (autoCompactWindow,
|
||||||
|
// or modelSettings.<model>.autoCompactWindow as /autocompact saves it) is
|
||||||
|
// theirs: magpie's default doesn't write the env that would take
|
||||||
|
// precedence over it; a size typed in magpie, or on the model or its
|
||||||
|
// provider, still does (#876, hisiling).
|
||||||
|
func TestClaudeCompactYieldsToOwnSetting(t *testing.T) {
|
||||||
|
home := t.TempDir()
|
||||||
|
t.Setenv("HOME", home)
|
||||||
|
t.Setenv("USERPROFILE", home)
|
||||||
|
t.Setenv("XDG_CONFIG_HOME", filepath.Join(home, ".config"))
|
||||||
|
t.Setenv("XDG_CACHE_HOME", filepath.Join(home, ".cache"))
|
||||||
|
if err := provider.Save(provider.Provider{ID: "v", Name: "V", Chat: "https://example.test/v1", Key: "k",
|
||||||
|
Models: []string{"big", "flash"}}); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := catalog.SaveLive("v", "https://example.test/v1", []catalog.Model{{ID: "big", Context: 1000000}, {ID: "flash", Context: 1000000}}); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
path := filepath.Join(home, ".claude", "settings.json")
|
||||||
|
writeFile(t, path, `{"autoCompactWindow": 900000}`)
|
||||||
|
a := claude(home)
|
||||||
|
compact := func() string { v, _ := edit.GetJSON(path, "env."+claudeCompactEnv); return v }
|
||||||
|
must := func(err error) {
|
||||||
|
t.Helper()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
expect := func(what, want string) {
|
||||||
|
t.Helper()
|
||||||
|
if got := compact(); got != want {
|
||||||
|
t.Fatalf("%s: %q; want %q", what, got, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
must(a.Field("model").Set("v/big[1m]"))
|
||||||
|
expect("autoCompactWindow", "")
|
||||||
|
if v, _ := edit.GetJSON(path, "autoCompactWindow"); v != "900000" {
|
||||||
|
t.Fatalf("autoCompactWindow changed: %q", v)
|
||||||
|
}
|
||||||
|
must(edit.DelJSON(path, "autoCompactWindow"))
|
||||||
|
must(a.Sync())
|
||||||
|
expect("none set", "272000")
|
||||||
|
// one for this model in modelSettings, not another's
|
||||||
|
must(edit.SetJSON(path, edit.KV{Path: "modelSettings", Value: map[string]any{"v/flash": map[string]any{"autoCompactWindow": 800000}}}))
|
||||||
|
must(a.Sync())
|
||||||
|
expect("another model's", "272000")
|
||||||
|
must(a.Field("model").Set("v/flash[1m]"))
|
||||||
|
expect("this model's", "")
|
||||||
|
// a size typed in magpie, or the model's own there, comes first
|
||||||
|
must(provider.SetModelCompacts("v", map[string]int{"flash": 200000}))
|
||||||
|
must(a.Sync())
|
||||||
|
expect("the model's own in magpie", "200000")
|
||||||
|
must(provider.SetModelCompacts("v", nil))
|
||||||
|
must(provider.SetCompactAt(400000))
|
||||||
|
must(a.Sync())
|
||||||
|
expect("typed in magpie", "400000")
|
||||||
|
}
|
||||||
Reference in new issue
Block a user