Files
magpie/internal/agent/codex_group_alias_test.go
yetone 7cc44b68a9 agent: Codex on a routing group's model name is still connected, not "switched off it outside magpie" (#750, Windows 11, magpie 0.1.812, Codex 0.160.0: Codex set to group/auto-gpt-6-1-sol in magpie, its config.toml then reading model = "gpt-6.1-sol" with openai_base_url still magpie's gateway, and the Agents page kept saying 在 magpie 之外被从 … 切走了 with 重新接入, coming back a while after each reconnect) — the gateway takes a model's name without a provider in it as the group of that model (provider.GroupFor: gpt-6.1-sol is group/auto-gpt-6-1-sol), so Codex's requests still went to the group; but Drift compared the field with what magpie recorded setting (applied.json) by string, and gpt-6.1-sol is neither that nor one of magpie's models by magpieValue (isMagpie wants a provider/model id, and Codex's options list it as its own or not at all), so it read as "replaced". What writes gpt-6.1-sol into config.toml is not magpie (no agent path writes a group's model name; set writes the group id as picked) and wasn't found — the Codex app persisting its thread's model by its own name is the likely one — which is why it came back after each reapply. Drift now passes over a field whose value and magpie's recorded one reach the same routing group, one by the group's id and one by its model's name (sameGroup, groupNamed: a group/… id as it is, "magpie/" taken off, else GroupFor); Codex's internal wired() and Agent.Wired (through a new Agent.Routed hook, Codex's routed(): openai_base_url or magpie as its provider) count the group's model name as magpie's only when magpie set that group on the field and Codex's config still sends its model to the gateway, so a Codex taken off magpie on gpt-6.1-sol is not connected, and a Codex's own gpt-5.5 that happens to have an auto group is no more magpie's than before (failover's base URL is untouched). Disconnect takes the field off the group's model name as it does off the group's id (back to Codex's default), rather than leaving Codex asking OpenAI for gpt-6.1-sol. Another group's model (gpt-6.1) or one no group has (gpt-5.5) is still "replaced". The reporter's ask to say which setting differs: the drift's detail already names it ("model is …, not … as magpie set it", the base URL or provider table for unwired) in the status line's and the Apply again pill's tooltip; that is left as it is. Tests: TestCodexGroupByItsModelName (two providers serving gpt-6.1-sol and gpt-6.1, signed in with ChatGPT and with an API key: group/auto-gpt-6-1-sol set, then config.toml's model rewritten to gpt-6.1-sol — Wired and no drift; no route to magpie — not wired; gpt-6.1 and gpt-5.5 — replaced) and TestCodexGroupByItsModelNameDisconnect fail on the old code (not wired, drift replaced, gpt-6.1-sol and the base URL left after Disconnect) and pass. go vet, builds (darwin, GOOS=linux, GOOS=windows) and go test -tags nogui -count=1 ./... under a temp HOME pass. Not tried on Windows or with the reporter's Codex and groups; not checked: what in Codex rewrites the model to gpt-6.1-sol.
2026-10-04 13:11:23 +08:00

92 lines
3.7 KiB
Go

package agent
import (
"os"
"path/filepath"
"strings"
"testing"
"github.com/yetone/magpie/internal/provider"
)
// #750: Codex put on the group magpie found for gpt-6.1-sol
// (group/auto-gpt-6-1-sol), and its config.toml then naming the model as
// vendors spell it, gpt-6.1-sol, with openai_base_url still magpie's: the
// gateway takes gpt-6.1-sol as that same group, so Codex is connected, not
// "switched off group/auto-gpt-6-1-sol outside magpie". A model of
// another group, or of none, is still drift.
func TestCodexGroupByItsModelName(t *testing.T) {
for _, auth := range []string{`{"tokens":{"access_token":"x","id_token":"x.e30.x"}}`, ""} {
home, read := codexHome(t, auth, "")
for _, p := range []provider.Provider{
{ID: "ra", Name: "RA", Chat: "https://ra.example/v1", Key: "k", Models: []string{"gpt-6.1-sol", "gpt-6.1"}},
{ID: "rb", Name: "RB", Chat: "https://rb.example/v1", Key: "k", Models: []string{"gpt-6.1-sol", "gpt-6.1"}},
} {
if err := provider.Save(p); err != nil {
t.Fatal(err)
}
}
if g, ok := provider.GroupFor("gpt-6.1-sol"); !ok || g != "group/auto-gpt-6-1-sol" {
t.Fatalf("GroupFor: %q %v", g, ok)
}
cx := codex(home)
if err := cx.Apply("model", "group/auto-gpt-6-1-sol"); err != nil {
t.Fatal(err)
}
if d := cx.Drift(); d != nil {
t.Fatalf("drift right after a set: %+v", d)
}
path := filepath.Join(home, ".codex", "config.toml")
cfg := read()
if !strings.Contains(cfg, `model = "group/auto-gpt-6-1-sol"`) {
t.Fatalf("set:\n%s", cfg)
}
// Codex names the group's model by its own name, nothing else changed
os.WriteFile(path, []byte(strings.Replace(cfg, `model = "group/auto-gpt-6-1-sol"`, `model = "gpt-6.1-sol"`, 1)), 0o644)
if !cx.Wired() {
t.Errorf("auth %q: not wired on gpt-6.1-sol:\n%s", auth, read())
}
if d := cx.Drift(); d != nil {
t.Errorf("auth %q: drift on gpt-6.1-sol, the same group: %+v", auth, d)
}
// with nothing sending it to magpie, gpt-6.1-sol goes to OpenAI
os.WriteFile(path, []byte("model = \"gpt-6.1-sol\"\n"), 0o644)
if cx.Wired() {
t.Errorf("auth %q: wired on gpt-6.1-sol with no route to magpie", auth)
}
// another group's model is not what magpie set
os.WriteFile(path, []byte(strings.Replace(cfg, `model = "group/auto-gpt-6-1-sol"`, `model = "gpt-6.1"`, 1)), 0o644)
if d := cx.Drift(); d == nil || d.Kind != "replaced" || d.Now != "gpt-6.1" {
t.Errorf("auth %q: gpt-6.1, another group: %+v", auth, d)
}
// nor is one no group has
os.WriteFile(path, []byte(strings.Replace(cfg, `model = "group/auto-gpt-6-1-sol"`, `model = "gpt-5.5"`, 1)), 0o644)
if d := cx.Drift(); d == nil || d.Kind != "replaced" || d.Now != "gpt-5.5" {
t.Errorf("auth %q: gpt-5.5: %+v", auth, d)
}
}
}
// Disconnected on the group's model name, Codex is taken off it as off the
// group's id: back to its default, not left asking OpenAI for gpt-6.1-sol.
func TestCodexGroupByItsModelNameDisconnect(t *testing.T) {
home, read := codexHome(t, `{"tokens":{"access_token":"x","id_token":"x.e30.x"}}`, "")
for _, id := range []string{"ra", "rb"} {
if err := provider.Save(provider.Provider{ID: id, Name: id, Chat: "https://" + id + ".example/v1", Key: "k", Models: []string{"gpt-6.1-sol"}}); err != nil {
t.Fatal(err)
}
}
cx := codex(home)
if err := cx.Apply("model", "group/auto-gpt-6-1-sol"); err != nil {
t.Fatal(err)
}
path := filepath.Join(home, ".codex", "config.toml")
os.WriteFile(path, []byte(strings.Replace(read(), `model = "group/auto-gpt-6-1-sol"`, `model = "gpt-6.1-sol"`, 1)), 0o644)
if err := cx.Disconnect(); err != nil {
t.Fatal(err)
}
if cfg := read(); strings.Contains(cfg, "gpt-6.1-sol") || strings.Contains(cfg, "openai_base_url") || cx.Wired() {
t.Fatalf("disconnected:\n%s", cfg)
}
}