Files
TryWorldandTryWorld2026 116ca957cc agent: a provider's Compact at reaches Codex's on-disk catalog (#876) (#924)
Where Codex compacts a long conversation reached the gateway's /models
(provider.CodexListed sets catalog.Model.Compact) and Claude Code
(claude.go calls provider.CompactSet), but not the list magpie writes for
Codex on disk: magpieModels left Compact unset, so codexcat fell back to
the threshold for every model. A threshold the user set on a model or on
its provider did nothing while Codex read that list — the case where Codex
is not signed in to ChatGPT and model_catalog_json is what it reads.

magpieModels now carries it, as the gateway's list and Claude Code already
do. provider.CompactSetIn is compactSet for a caller holding the settings
and the group finder already, as EffectivePriceIn is for prices: this loop
runs for hundreds of models at magpie's start.

TestCodexCatalogCompactAtReachesDisk fails without the change (204800, want
100000) and passes with it: relay's "*" and then relay/glm-4.6's own
threshold reach the file, with the model's window kept as the maximum, and
the whole window comes back when they are cleared.

Co-authored-by: TryWorld2026 <tryworld@users.noreply.github.com>
2026-10-05 23:45:52 +08:00

157 lines
4.7 KiB
Go

package provider
import (
"strings"
"github.com/yetone/magpie/internal/catalog"
"github.com/yetone/magpie/internal/settings"
)
// Where Codex and Claude Code compact a long conversation (#876): the
// window they are told for a model whose own is longer. A model's own
// (settings.ModelCompacts, "provider/model"), else its provider's
// ("provider/*"), else the one for every model (settings.Compact: 272K, a
// number the user gave, or none under Full window).
// compactOf is the user's threshold for a provider's model: its own, else
// its provider's "*"; 0 when neither is set.
func compactOf(s settings.Settings, providerID, model string) int {
if n := s.ModelCompacts[providerID+"/"+model]; n > 0 {
return n
}
return s.ModelCompacts[providerID+"/*"]
}
// CompactSet is the threshold the user set on a model, by its catalog id
// ("provider/model", magpie/ and a [1m] mark taken off), or on its
// provider; for a group, the smallest its models have, so none of them
// outgrows its own. 0 when none is set: settings.Compact then answers.
func CompactSet(id string) int {
return compactSet(settings.Load(), id, GroupFinder())
}
// CompactSetIn is CompactSet for a caller that already holds the settings
// and the group finder: an agent's list is built for hundreds of models at
// magpie's start, and reading both again for each is what EffectivePriceIn
// saves the same loop.
func CompactSetIn(s settings.Settings, id string, find func(string) (Group, []Member, bool)) int {
return compactSet(s, id, find)
}
func compactSet(s settings.Settings, id string, find func(string) (Group, []Member, bool)) int {
if len(s.ModelCompacts) == 0 {
return 0
}
id = strings.TrimSuffix(strings.TrimPrefix(strings.TrimSpace(id), "magpie/"), "[1m]")
if strings.HasPrefix(id, GroupPrefix) {
_, ms, _ := find(id)
least := 0
for _, m := range ms {
if n := compactOf(s, m.Provider.ID, entryModel(m)); n > 0 && (least == 0 || n < least) {
least = n
}
}
return least
}
pid, model, ok := strings.Cut(id, "/")
if !ok {
return 0
}
return compactOf(s, pid, model)
}
// entryModel is the model id a group's member is listed under: the last
// of its path, the provider's id taken off, which is not always what the
// vendor is asked for (a wire name).
func entryModel(m Member) string {
if n := len(m.Path); n > 0 {
if model, ok := strings.CutPrefix(m.Path[n-1], m.Provider.ID+"/"); ok {
return model
}
}
return m.Model
}
// CompactsOf is the thresholds the user set on a provider's models, by
// model id, "*" for all of them: what its editor's Compact at shows.
func CompactsOf(providerID string) map[string]int {
out := map[string]int{}
for k, n := range settings.Load().ModelCompacts {
if model, ok := strings.CutPrefix(k, providerID+"/"); ok && model != "" && n > 0 {
out[model] = n
}
}
return out
}
// SetModelCompacts makes a provider's thresholds cs, by model id, "*" for
// all of them, as its editor's Compact at says: those it leaves out are
// taken away, and every one set has to be a model the provider serves, as
// for a reply limit (SetModelOutputs). Codex's and Claude Code's lists are
// written again when anything changed.
func SetModelCompacts(providerID string, cs map[string]int) error {
p, err := Find(providerID)
if err != nil {
return err
}
for model, n := range cs {
if n < 0 {
return errorf("a compaction threshold is a number of tokens, not %d", n)
}
if err := settings.CheckModelKey("a compaction threshold", p.ID+"/"+model); err != nil {
return err
}
if model != "*" && n > 0 && !p.serves(model) {
return errorf("%s has no model %s (magpie provider %s lists them)", p.ID, model, p.ID)
}
}
s := settings.Load()
changed := false
for k := range s.ModelCompacts {
if model, ok := strings.CutPrefix(k, p.ID+"/"); ok && cs[model] <= 0 {
delete(s.ModelCompacts, k)
changed = true
}
}
for model, n := range cs {
if n <= 0 || s.ModelCompacts[p.ID+"/"+model] == n {
continue
}
if s.ModelCompacts == nil {
s.ModelCompacts = map[string]int{}
}
s.ModelCompacts[p.ID+"/"+model] = n
changed = true
}
if !changed {
return nil
}
if err := settings.Save(s); err != nil {
return err
}
catalog.Touched()
return nil
}
// SetCompactAt has every model without a threshold of its own compacted at
// n tokens, Full window turned off; 0 is the working window again
// (settings.WorkingWindow).
func SetCompactAt(n int) error {
if n < 0 {
return errorf("a compaction threshold is a number of tokens, not %d", n)
}
s := settings.Load()
if n == settings.WorkingWindow {
n = 0
}
if s.CompactAt == n && !s.FullContext {
return nil
}
s.CompactAt, s.FullContext = n, false
if err := settings.Save(s); err != nil {
return err
}
catalog.Touched()
return nil
}