Files
magpie/internal/agent/codex_halfwired_test.go
yetone 528d6c12dc agent, gui: Codex left on model_provider = "magpie" with no [model_providers.magpie] loads its config again, and connecting with nothing in magpie says what to do, in the reader's language (Tystem on Discord) — the Codex app said "无法加载 config.toml … Model provider magpie not found", magpie showed Codex as not connected on "deepseek-flash", and its switch answered only "Codex can't be connected to magpie", in English. Codex looks model_provider up in [model_providers.*] as it loads config.toml (codex-rs: "Model provider {id} not found"), so the key without the table keeps it from loading any config, a new thread too, not just an old one. magpie never writes one without the other: it takes model_provider out whenever it steps out as the provider (disconnect, Unwire, a model of Codex's own, Join) and keeps the table so the threads started on magpie still open. That is unchanged, and TestCodexSteppingOutLeavesNoHalfState now covers it. Another tool or a hand edit had taken the table away. Sync, at magpie's start and whenever the catalog changes, now writes the table back whenever the config names magpie as its provider, at the top or in a profile, and has no table, whether Codex is connected or not. Check reports that state too. A config magpie can't parse is still only reported, never written to. A Connect that finds none of magpie's models because no provider or subscription has been added answers agent.NoModelsError, "Add a provider or subscription in magpie first, then connect Codex". /api answers it with code no_models and the agent's name, which app.js says through t() (en/zh/ja/de). docs/reference.md no longer says the table goes with a native model. TestCodexProviderWithoutTable (top and profile: Check says so, Sync writes the table) and TestConnectWithNoModels in internal/agent, TestAgentConnectNoModels in internal/gui, and agent-connect-no-models.test.cjs (WebKit, en/zh/ja/de: the toast in the reader's language, the switch back off, no scroll, no native dialog) are new and fail without the change; the latter's en case passes either way, as the server's message is English. Chromium wouldn't launch on this machine for this test or the existing agent-connect.test.cjs. TestCodexSyncAddsBaseURLToProvider now expects the table added to a hand-made provider that had none. go vet, go build (darwin, GOOS=linux nogui, GOOS=windows) and go test -tags nogui ./... under a temp HOME pass. In the first full run, internal/gui's TestProvidersAnswerWhileListsComeIn and internal/proc's TestUserPath failed under load; both passed alone and in the second full run.
2026-10-05 21:09:13 +08:00

86 lines
3.0 KiB
Go

package agent
import (
"errors"
"strings"
"testing"
"github.com/yetone/magpie/internal/edit"
"github.com/yetone/magpie/internal/provider"
)
// Tystem on Discord: Codex wouldn't load its config ("Model provider
// `magpie` not found") with model_provider = "magpie" and model =
// "deepseek-flash" left in config.toml and no [model_providers.magpie],
// and magpie showed Codex as not connected. Codex refuses the whole config
// so, not just an old thread: Check says so, and Sync writes the table
// back, at the top and for a profile alike.
func TestCodexProviderWithoutTable(t *testing.T) {
for _, tc := range []struct{ name, config string }{
{"top", "model_provider = \"magpie\"\nmodel = \"deepseek-flash\"\n"},
{"profile", "profile = \"ds\"\n\n[profiles.ds]\nmodel_provider = \"magpie\"\nmodel = \"deepseek-flash\"\n"},
} {
t.Run(tc.name, func(t *testing.T) {
home, read := codexHome(t, "", tc.config)
cx := codex(home)
if msg := cx.Check(); !strings.Contains(msg, "[model_providers.magpie]") {
t.Fatalf("check: %q", msg)
}
if err := cx.Sync(); err != nil {
t.Fatal(err)
}
tb, err := edit.GetTOMLTable(cx.Path, "model_providers.magpie")
if err != nil || tb["base_url"] != here(home).v1() || tb["wire_api"] != "responses" || tb["experimental_bearer_token"] == "" {
t.Fatalf("table %v %v:\n%s", tb, err, read())
}
if msg := cx.Check(); strings.Contains(msg, "[model_providers.magpie]") {
t.Fatalf("check after sync: %q", msg)
}
})
}
}
// Every way magpie steps out as Codex's provider takes model_provider out
// with it and leaves the table: never the half state above.
func TestCodexSteppingOutLeavesNoHalfState(t *testing.T) {
for _, tc := range []struct {
name string
out func(a *Agent) error
}{
{"disconnect", func(a *Agent) error { return a.Fields[0].Set("") }},
{"unwire", func(a *Agent) error { return a.Unwire() }},
{"own model", func(a *Agent) error { return a.Fields[0].Set("gpt-5.5") }},
} {
t.Run(tc.name, func(t *testing.T) {
home, read := codexHome(t, "", "model = \"gpt-5.5\"\n")
cx := codex(home)
if err := cx.Fields[0].Set("fake/m1"); err != nil {
t.Fatal(err)
}
if err := tc.out(cx); err != nil {
t.Fatal(err)
}
if p, _ := edit.GetTOMLTop(cx.Path, "model_provider"); p == magpieID {
t.Fatalf("model_provider left:\n%s", read())
}
if tb, _ := edit.GetTOMLTable(cx.Path, "model_providers.magpie"); tb == nil {
t.Fatalf("table gone, for the threads started on magpie:\n%s", read())
}
})
}
}
// Connecting with no provider or subscription in magpie says what to do,
// with a code the GUI says it by in the reader's language.
func TestConnectWithNoModels(t *testing.T) {
home, _ := codexHome(t, "", "model = \"gpt-5.5\"\n")
if err := provider.Delete("fake"); err != nil {
t.Fatal(err)
}
_, err := codex(home).ConnectHow()
var nm *NoModelsError
if !errors.As(err, &nm) || nm.Agent != "Codex" || !strings.Contains(err.Error(), "Add a provider or subscription in magpie first, then connect Codex") {
t.Fatalf("%v", err)
}
}