Files
magpie/internal/agent/codex_api_own_test.go
yetone 5cf4d1dae1 codex: on magpie API, Codex's own model as the default keeps magpie as its provider and magpie's models in its picker (#701, nullburn: Codex 选回原生默认模型后,第三方模型目录消失(已选择 magpie API)) — with Codex's sign-in set to magpie API, picking one of Codex's own models (gpt-6-luna) as its default in the Agents row ran unroute(): model_provider, model_catalog_json, openai_base_url and magpie-models.json all went, so every third-party model left Codex's picker while the row still said magpie API; and choosing magpie API while on Codex's own model wired nothing (login.Set only re-ran set for a magpie model). Now, on magpie API, Codex's own model goes as magpie serves it on the ChatGPT account there (codexOwnViaMagpie: gpt-6-luna -> codex/gpt-6-luna, the entries shown to Codex first), so set() takes the magpie branch and only the default changes; the picker's OpenAI group offers those ids under their own names (Label gpt-6-luna) so the value matches; one magpie doesn't serve (the ChatGPT subscription off in magpie) is refused with a message naming both ways out, the config untouched, rather than quietly unwired; choosing magpie API on Codex's own model wires it at once; Sync wires a config an older magpie left so (#701's: model = "gpt-6-luna", sign-in api). Back on ChatGPT, a codex/… default turns into Codex's own model again, straight to OpenAI as before (codexOwnOf); the ChatGPT sign-in's paths are otherwise unchanged. TestCodexAPIKeepsMagpieOnOwnModel (api on an own model, magpie model then own model again, picker values, refusal, back to ChatGPT) and TestCodexAPISyncWiresOwnModel fail on the old code and pass; TestCodexProviderAlsoSetsBaseURL's api case now expects magpie to stay wired on Codex's own model. End to end in a temp HOME with a copy of a real models_cache.json: api, fake/m1, then gpt-6-luna leaves model = "codex/gpt-6-luna" with model_provider, model_catalog_json and openai_base_url, and codex-cli 0.160.0's codex debug models with CODEX_HOME on that sandbox (gateway ports pointed at :1) lists fake/m1 beside codex/gpt-6-luna and the other codex/ models. go vet, builds (darwin, GOOS=linux, GOOS=windows) and go test -tags nogui -count=1 ./... under a temp HOME pass. Not tried in the Codex app or with a turn sent through a real ChatGPT account.
2026-10-03 23:40:06 +08:00

109 lines
3.8 KiB
Go

package agent
import (
"os"
"path/filepath"
"strings"
"testing"
)
// On magpie API, one of Codex's own models picked as its default keeps
// magpie as Codex's provider, magpie's models in its picker: the model goes
// as magpie serves it on the ChatGPT account. Picking it used to take magpie
// out, and every model of magpie's left Codex's picker with it (#701). Back
// on ChatGPT, the model is Codex's own again, straight to OpenAI.
func TestCodexAPIKeepsMagpieOnOwnModel(t *testing.T) {
home, read := codexHome(t, `{"tokens":{"access_token":"x","id_token":"x.e30.x"}}`, "model = \"gpt-a\"\n")
dir := filepath.Join(home, ".codex")
os.WriteFile(filepath.Join(dir, "models_cache.json"), []byte(`{"models":[
{"slug":"gpt-a","display_name":"A","priority":1},
{"slug":"gpt-b","display_name":"B","priority":2}]}`), 0o644)
catalogPath := filepath.Join(dir, "magpie-models.json")
wired := func(model string) {
t.Helper()
cfg := read()
if !strings.Contains(cfg, `model_provider = "magpie"`) || !strings.Contains(cfg, "model_catalog_json") ||
!strings.Contains(cfg, "openai_base_url") || !strings.Contains(cfg, `model = "`+model+`"`) {
t.Fatalf("not wired on %s:\n%s", model, cfg)
}
b, err := os.ReadFile(catalogPath)
if err != nil || !strings.Contains(string(b), `"fake/m1"`) {
t.Fatalf("magpie's models not in Codex's catalog (%v):\n%s", err, b)
}
}
cx := codex(home)
login, model := cx.Field("login"), cx.Fields[0]
// magpie API chosen on Codex's own model wires magpie in at once
if err := login.Set("api"); err != nil {
t.Fatal(err)
}
wired("codex/gpt-a")
// a magpie model, then Codex's own again: magpie stays
if err := model.Set("fake/m1"); err != nil {
t.Fatal(err)
}
wired("fake/m1")
if err := model.Set("gpt-b"); err != nil {
t.Fatal(err)
}
wired("codex/gpt-b")
// the picker offers Codex's own as they are set, under their names
found := false
for _, o := range model.Options(nil) {
if o.Group == "OpenAI" && o.Value == "codex/gpt-b" && o.Label == "gpt-b" {
found = true
}
}
if !found {
t.Fatalf("Codex's own models in the picker: %+v", model.Options(nil))
}
if got := model.Get(); got != "codex/gpt-b" {
t.Fatalf("model = %q", got)
}
// one magpie doesn't serve is refused, the config left as it was
before := read()
if err := model.Set("gpt-zzz"); err == nil {
t.Fatal("a model magpie doesn't serve was taken on magpie API")
}
if read() != before {
t.Fatalf("a refused model changed the config:\n%s", read())
}
// back on ChatGPT: Codex's own model, straight to OpenAI
if err := login.Set(""); err != nil {
t.Fatal(err)
}
cfg := read()
if !strings.Contains(cfg, `model = "gpt-b"`) || strings.Contains(cfg, "model_provider =") ||
strings.Contains(cfg, "openai_base_url") || strings.Contains(cfg, "model_catalog_json") {
t.Fatalf("back on ChatGPT:\n%s", cfg)
}
if err := model.Set("gpt-a"); err != nil {
t.Fatal(err)
}
if cfg := read(); !strings.Contains(cfg, `model = "gpt-a"`) || strings.Contains(cfg, "openai_base_url") {
t.Fatalf("own model on ChatGPT:\n%s", cfg)
}
}
// A Codex an older magpie left on its own model, unwired, while its sign-in
// is magpie API (#701's config) is wired again at the next sync.
func TestCodexAPISyncWiresOwnModel(t *testing.T) {
home, read := codexHome(t, `{"tokens":{"access_token":"x","id_token":"x.e30.x"}}`, "model = \"gpt-a\"\n")
dir := filepath.Join(home, ".codex")
os.WriteFile(filepath.Join(dir, "models_cache.json"), []byte(`{"models":[{"slug":"gpt-a","display_name":"A","priority":1}]}`), 0o644)
cx := codex(home)
// the choice alone, as the old code kept it on Codex's own model
stash(map[string]string{here(home).key("codex.login"): "api"})
if err := cx.Sync(); err != nil {
t.Fatal(err)
}
if cfg := read(); !strings.Contains(cfg, `model_provider = "magpie"`) || !strings.Contains(cfg, `model = "codex/gpt-a"`) {
t.Fatalf("after sync:\n%s", cfg)
}
}