Files
magpie/internal/agent/codex_thread_test.go
T
yetone fd1427de0b codex: with magpie as Codex's provider, openai_base_url points at magpie as well, so a thread started on Codex's built-in provider and reopened sends a magpie model to magpie, not to ChatGPT's backend (#259 isymbo: ChatGPT Desktop, "The 'xiaomi/mimo-v2.6-pro' model is not supported when using Codex with a ChatGPT account")
ChatGPT Desktop 26.924.22138 is the Codex app renamed (bundle com.openai.codex, codex-cli 0.158.0-alpha.2.1): it reads ~/.codex and honours openai_base_url, model_provider and chatgpt_base_url; the error text comes from chatgpt.com. Codex keeps a thread on the provider it was started on, so an old ChatGPT conversation reopened while magpie is Codex's provider (signed out, allowance used up, or the sign-in set to api) stays on the built-in provider, and magpie removed openai_base_url in that mode, so a magpie model picked in it went to chatgpt.com, which refused it. Provider mode now sets openai_base_url to magpie's gateway too (whose /backend-api/codex serves "provider/model" and passes Codex's own models on), unless the user has one of their own; picking one of Codex's own models or a reset still removes magpie's. Sync adds it to configs an older magpie wrote in provider mode, when the catalog file is magpie's. Verified by driving ChatGPT Desktop's bundled `codex app-server` in a sandbox HOME with a fake ChatGPT auth.json and fake servers: before, a thread started on the built-in provider and reopened with model_provider = "magpie" went to the built-in base URL; now it reaches magpie's /backend-api/codex/responses, and a new thread /v1/responses. Tests: codex_thread_test.go (TestCodexProviderAlsoSetsBaseURL signed out and api, TestCodexProviderKeepsOwnBaseURL, TestCodexSyncAddsBaseURLToProvider; the first and third fail without the change), codex_route_test.go updated for the base URL in provider mode. go vet, go build -tags nogui (and GOOS=linux, GOOS=windows) and go test -tags nogui ./... pass. Not tried in the real ChatGPT Desktop window or with a real account; a thread already open in a running app keeps the settings it was loaded with, and cloud threads never read the local config.
2026-09-30 11:16:59 +08:00

113 lines
3.6 KiB
Go

package agent
import (
"os"
"path/filepath"
"strings"
"testing"
)
// #259: magpie as Codex's provider, a thread started on Codex's built-in
// provider is opened on it again (Codex keeps a thread's provider), and a
// magpie model picked there went to the ChatGPT backend, which refused it
// ("… is not supported when using Codex with a ChatGPT account"). The base
// URL is magpie's now in that state too, so the built-in provider's request
// reaches magpie; back on Codex's own model, it goes.
func TestCodexProviderAlsoSetsBaseURL(t *testing.T) {
for _, tc := range []struct {
name, auth string
api bool
}{
{"signed out", "", false},
{"api", `{"tokens":{"access_token":"x","id_token":"x.e30.x"}}`, true},
} {
t.Run(tc.name, func(t *testing.T) {
home, read := codexHome(t, tc.auth, "model = \"gpt-5.5\"\n")
cx := codex(home)
if tc.api {
if err := cx.Field("login").Set("api"); err != nil {
t.Fatal(err)
}
}
if err := cx.Fields[0].Set("fake/m1"); err != nil {
t.Fatal(err)
}
cfg := read()
if !strings.Contains(cfg, `model_provider = "magpie"`) || !strings.Contains(cfg, "model_catalog_json") ||
!strings.Contains(cfg, `openai_base_url = "`+codexGatewayURL()+`"`) {
t.Fatalf("\n%s", cfg)
}
if msg := cx.Check(); msg != "" {
t.Fatalf("check: %s", msg)
}
if err := cx.Fields[0].Set("gpt-5.4"); err != nil {
t.Fatal(err)
}
if cfg = read(); strings.Contains(cfg, "openai_base_url") || strings.Contains(cfg, "model_provider =") ||
!strings.Contains(cfg, `model = "gpt-5.4"`) {
t.Fatalf("own model:\n%s", cfg)
}
})
}
}
// A base URL of the user's own — a proxy of theirs in front of OpenAI — is
// left as it is when magpie becomes the provider, and after.
func TestCodexProviderKeepsOwnBaseURL(t *testing.T) {
const own = `openai_base_url = "https://proxy.example/v1"`
home, read := codexHome(t, "", "model = \"gpt-5.5\"\n"+own+"\n")
cx := codex(home)
if err := cx.Fields[0].Set("fake/m1"); err != nil {
t.Fatal(err)
}
if cfg := read(); !strings.Contains(cfg, own) || strings.Count(cfg, "openai_base_url") != 1 ||
!strings.Contains(cfg, `model_provider = "magpie"`) {
t.Fatalf("\n%s", cfg)
}
if err := cx.Fields[0].Set(""); err != nil {
t.Fatal(err)
}
if cfg := read(); !strings.Contains(cfg, own) || strings.Contains(cfg, "model_provider =") {
t.Fatalf("reset:\n%s", cfg)
}
}
// A Codex that an older magpie made its provider, with no base URL, gets
// magpie's at the next sync; a provider set up by hand with a catalog of
// the user's own is not touched.
func TestCodexSyncAddsBaseURLToProvider(t *testing.T) {
home, read := codexHome(t, "", "")
cx := codex(home)
if err := cx.Fields[0].Set("fake/m1"); err != nil {
t.Fatal(err)
}
// as an older magpie left it
cfg := read()
i := strings.Index(cfg, "openai_base_url")
if i < 0 {
t.Fatalf("\n%s", cfg)
}
j := strings.Index(cfg[i:], "\n")
if err := os.WriteFile(filepath.Join(home, ".codex", "config.toml"), []byte(cfg[:i]+cfg[i+j+1:]), 0o644); err != nil {
t.Fatal(err)
}
if strings.Contains(read(), "openai_base_url") {
t.Fatal("base URL still there")
}
if err := cx.Sync(); err != nil {
t.Fatal(err)
}
if cfg = read(); !strings.Contains(cfg, `openai_base_url = "`+codexGatewayURL()+`"`) || !strings.Contains(cfg, `model_provider = "magpie"`) {
t.Fatalf("sync:\n%s", cfg)
}
const mine = "model = \"fake/m1\"\nmodel_provider = \"magpie\"\nmodel_catalog_json = \"/elsewhere/models.json\"\n"
home, read = codexHome(t, "", mine)
if err := codex(home).Sync(); err != nil {
t.Fatal(err)
}
if cfg = read(); cfg != mine {
t.Fatalf("hand-made provider changed:\n%s", cfg)
}
}