Files
magpie/internal/agent/codex_catalog_kept_test.go
yetone c65d9fb473 codex: magpie-models.json keeps the keys the user added by hand to a model, and says supports_reasoning_summaries itself, so Codex before 0.145 sends the effort and shows thinking (heng91, #1450)
What was wrong: every write of ~/.codex/magpie-models.json rendered the
catalog from magpie's models alone, so a key the user had put into an
entry was gone after the next model switch, catalog sync (Refresh, a
provider added, a gateway start) or a switch to Codex's own default,
which also deleted the file outright. The key heng91 added,
supports_reasoning_summaries, is one Codex before 0.145 needs: its
client.rs build_reasoning sends no `reasoning` (no effort, no summary,
so no thinking shown) for a model whose entry doesn't say true, until
openai/codex#32206 (first in 0.145). 0.144 also refuses to load a
catalog an entry of which lacks the key at all.

What changed:
- codexcat: every entry says supports_reasoning_summaries, true for a
  model with effort levels, false otherwise. Later Codex ignore it.
- codexcat.Keep: before a write, the keys of an entry on disk magpie
  doesn't write are carried over to the entry of the same slug, and a
  true of supports_reasoning_summaries stays over magpie's false. A key
  magpie writes keeps magpie's value; a model magpie no longer serves
  drops out; an unreadable file keeps nothing.
- agent/codex.go: the write when magpie becomes the provider and the
  catalog sync go through Keep; the three places that removed the file
  remove it only when nothing of the user's is in it.

Run:
- New tests TestCodexCatalogKeepsWhatTheUserAdded,
  TestCodexCatalogRemovedWhenNothingAdded (agent, from the file magpie
  writes, edited by hand) and TestCodexCatalogReasoningSummaries,
  TestKeepUserKeys (codexcat). Each fails on origin/main's code.
- Real Codex in a sandbox CODEX_HOME against a capture server:
  0.144.0 fails to load the old catalog ("missing field
  supports_reasoning_summaries"), loads the new one and sends
  reasoning {effort: high, summary: auto}; with the user's keys kept
  it sends summary "detailed". 0.162.0 loads both and sends the same.
- gofmt -l clean; go vet, GOOS=linux and GOOS=windows go vet of
  ./internal/agent ./internal/codexcat; linux and windows builds;
  go test -tags nogui ./... under a temp HOME, all ok.
2026-10-10 09:07:44 +08:00

114 lines
3.4 KiB
Go

package agent
import (
"encoding/json"
"os"
"path/filepath"
"strings"
"testing"
"github.com/yetone/magpie/internal/provider"
)
// What the user adds by hand to the model list magpie writes for Codex
// stays through the next model switch, sync and stepping out (#1450:
// supports_reasoning_summaries, put in for Codex to show a model's
// thinking, was gone after the next change). A key magpie writes, or took
// out on purpose (multi_agent_version with the V1 setting off), is
// magpie's.
func TestCodexCatalogKeepsWhatTheUserAdded(t *testing.T) {
home, _ := codexHome(t, "", "")
list := filepath.Join(home, ".codex", "magpie-models.json")
cx := codex(home)
if err := cx.Fields[0].Set("fake/m1"); err != nil {
t.Fatal(err)
}
// the file as magpie wrote it, edited by hand: two keys on fake/m1's
// entry, and a version magpie no longer writes
var cat map[string]any
if err := json.Unmarshal([]byte(readFile(list)), &cat); err != nil {
t.Fatal(err)
}
e := cat["models"].([]any)[0].(map[string]any)
if e["slug"] != "fake/m1" {
t.Fatalf("first entry: %v", e["slug"])
}
e["supports_reasoning_summaries"] = true
e["default_reasoning_summary"] = "detailed"
e["multi_agent_version"] = "v1"
b, _ := json.MarshalIndent(cat, "", " ")
writeFile(t, list, string(b))
has := func(when string) {
t.Helper()
var got struct {
Models []map[string]any `json:"models"`
}
if err := json.Unmarshal([]byte(readFile(list)), &got); err != nil {
t.Fatalf("%s: %v", when, err)
}
var m1 map[string]any
for _, m := range got.Models {
if m["slug"] == "fake/m1" {
m1 = m
}
}
if m1 == nil || m1["supports_reasoning_summaries"] != true || m1["default_reasoning_summary"] != "detailed" {
t.Errorf("%s: the user's keys are gone: %v %v", when, m1["supports_reasoning_summaries"], m1["default_reasoning_summary"])
}
if _, ok := m1["multi_agent_version"]; ok {
t.Errorf("%s: a version magpie no longer writes stayed", when)
}
if m1["base_instructions"] == nil || m1["supports_parallel_tool_calls"] != true {
t.Errorf("%s: magpie's own keys are gone", when)
}
}
// a provider added: the sync writes the list again
if err := provider.Save(provider.Provider{ID: "added", Name: "Added", Key: "k", Chat: "http://127.0.0.1:1/v1", Models: []string{"m2"}}); err != nil {
t.Fatal(err)
}
SyncCatalog()
if !strings.Contains(readFile(list), `"added/m2"`) {
t.Fatal("sync didn't write the new model")
}
has("after a sync")
if err := cx.Fields[0].Set("added/m2"); err != nil {
t.Fatal(err)
}
has("after a model switch")
// Codex's own default: magpie steps out, and the list the user added
// to stays for when magpie is back
if err := cx.Fields[0].Set(""); err != nil {
t.Fatal(err)
}
if _, err := os.Stat(list); err != nil {
t.Fatalf("the list the user added to was removed: %v", err)
}
if err := cx.Fields[0].Set("fake/m1"); err != nil {
t.Fatal(err)
}
has("after stepping out and back")
}
// A list holding nothing of the user's still goes as magpie steps out.
func TestCodexCatalogRemovedWhenNothingAdded(t *testing.T) {
home, _ := codexHome(t, "", "")
list := filepath.Join(home, ".codex", "magpie-models.json")
cx := codex(home)
if err := cx.Fields[0].Set("fake/m1"); err != nil {
t.Fatal(err)
}
if _, err := os.Stat(list); err != nil {
t.Fatal(err)
}
if err := cx.Fields[0].Set(""); err != nil {
t.Fatal(err)
}
if _, err := os.Stat(list); err == nil {
t.Error("magpie's list stayed after it stepped out")
}
}