* provider: a Codex account at a spend cap or past its overage with credits left keeps Codex signed in, as the Codex app's composer still sends for it #966 held such an account (and one whose limit_reached alone was true), following the readiness check Tht in ChatGPT.app 26.930. Tht has one caller, the work-credit-restriction-recovery poller; it isn't the composer's gate. The composer's submit is disabled only by hP (search "n.rate_limit?.allowed!==!1||Jpe(n)||SIe(") and hardBlocked (search "hardBlocked:r.rate_limit?.allowed===!1"): both read rate_limit.allowed alone and let credits (has_credits or unlimited) through before any spend cap or overage. So the app sends for that account, and holding it gains nothing: CodexUsedUp only moves Codex to magpie as provider while Codex is on a magpie model, whose turns already reach magpie through openai_base_url. It only takes the app out of ChatGPT (durable threads, remote control), the loss #966 set out to stop. codexHeld is back to that gate: held when allowed is false, no credits, and not a workspace within its overage (no spend cap, an ordinary reason). Its comment names the build, both gates and why Tht isn't one, each with a string to search for. The spend-cap and overage subcases now expect not held; new subcases pin allowed as the only trigger. Five subcases fail on the merged codexHeld. agent-wiring.md and reference.md say the same. Follow-up to #966. * provider: a Sci, Law, FinServ or Enterprise trial/view-only workspace within its overage keeps Codex signed in codexHeld lets a workspace with no credits through while it is within its overage, as the Codex app's composer does, but the workspace list came from codex-rs's PlanType alone. The app's own plan_type switch for its usage banner (ChatGPT.app 26.930.61225, app-initial) also names sci, law, finserv, enterprise_cbp_trial and enterprise_cbp_view_only, so such an account read held and magpie took Codex off its ChatGPT sign-in while the app still sent. They are added; the comment cites that switch. guest there is a personal plan and stays out, and a plan not listed still counts as no workspace (held). Verification: "Law without credits, within its overage" in TestCodexUsedUpIsWhatTheAppHolds fails without the list change (CodexUsedUp = true, want false) and passes with it. * provider: a held Codex reading with no window at 100% stays held through a failed read A reading kept for when /wham/usage fails dropped Held whenever none of its windows read 100%. That is right for a hold that came with a used-up window which has since started again, but a hold with no window used up (the backend says not allowed, no credits, every window under 100%) has no reset to wait for: the first 503 after it read not held and put Codex back on its ChatGPT sign-in for an account its app sends nothing for. Held is now cleared only when the saved reading had a window used up and none is after the passed resets are applied; any other hold stands till a fresh read. Verification: TestCodexHeldWithNoWindowUpKeptTillAFreshRead (the real Pro reply, credits spent, its window at 40%: a good read, then a 503) fails without the change ("CodexUsedUp = false on the kept held reading, no window used up") and passes with it. TestCodexHeldKeptOnlyTillTheReset still passes; both share their setup now. -race -count=10 passes. * agent, gui, docs: Codex's sign-in notes say magpie steps in while the Codex app blocks the account, not whenever it is used up #981's sign-in notes said magpie becomes Codex's provider "while its account is used up". Since codexHeld, a ChatGPT account at 100% with credits left keeps Codex signed in; magpie steps in only while the Codex app blocks the account (out of allowance, no credits left), and the notes now say that, Always ChatGPT's to match, in English, Chinese, Japanese and German. agent-wiring.md's sign-in line and reference.md's Codex paragraph say when the app holds an account as codexHeld reads it: OpenAI no longer allows it and it has no credits left, a workspace still within its overage excepted; at 100% with credits Codex stays signed in. Verification: codex-signin-always (Chromium and WebKit, en/zh/ja/de, 1100 and 440px) 8/8, i18n-keys 1/1, gui-ja 3/3, gui-de 3/3. git grep finds the old English notes nowhere under internal/gui/tests or internal/gui/assets.
83 lines
3.1 KiB
Go
83 lines
3.1 KiB
Go
package agent
|
|
|
|
import (
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/yetone/magpie/internal/edit"
|
|
)
|
|
|
|
// Codex signed in with ChatGPT, connected on one of magpie's models beside
|
|
// its sign-in, stays connected when Codex writes one of its own models in
|
|
// (its /model, or a Codex app still running on its old pick): its own
|
|
// models are in its list beside magpie's, as when it joined (#940: Codex
|
|
// read as not connected then, its config still on magpie's gateway).
|
|
// Disconnect leaves it on that model. Made magpie's provider (the Codex
|
|
// app blocking its account), it is not beside the sign-in.
|
|
func TestCodexOwnPickAfterMagpieModelStaysConnected(t *testing.T) {
|
|
home, read := codexHome(t, `{"tokens":{"access_token":"x","id_token":"x.e30.x"}}`, "model = \"gpt-5.5\"\n")
|
|
cx := codex(home)
|
|
if err := cx.Apply("model", "fake/m1"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if !cx.Wired() {
|
|
t.Fatalf("not connected on fake/m1:\n%s", read())
|
|
}
|
|
if err := edit.SetTOMLTop(filepath.Join(home, ".codex", "config.toml"), edit.KV{Path: "model", Value: "gpt-6.1-sol"}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if !cx.Wired() {
|
|
t.Fatalf("Codex's own pick disconnected it:\n%s", read())
|
|
}
|
|
// told as a change from outside, the row still connected
|
|
if d := cx.Drift(); d == nil || d.Kind != "replaced" || d.Now != "gpt-6.1-sol" {
|
|
t.Fatalf("drift: %+v", d)
|
|
}
|
|
if err := cx.Disconnect(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if cfg := read(); cx.Wired() || strings.Contains(cfg, "openai_base_url") || !strings.Contains(cfg, `model = "gpt-6.1-sol"`) {
|
|
t.Fatalf("disconnected:\n%s", cfg)
|
|
}
|
|
|
|
// out of its allowance, magpie is its provider: an own model written
|
|
// back is a change from outside, not a pick beside magpie's
|
|
codexUsedUp = func() bool { return true }
|
|
if err := cx.Fields[0].Set("fake/m1"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
path := filepath.Join(home, ".codex", "config.toml")
|
|
if err := edit.SetTOMLTop(path, edit.KV{Path: "model", Value: "gpt-6.1-sol"}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := edit.DelTOMLTop(path, "model_provider"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if cx.Wired() {
|
|
t.Fatalf("still beside the sign-in after magpie became its provider:\n%s", read())
|
|
}
|
|
}
|
|
|
|
// Connected while Codex can't join (the Codex app blocking its ChatGPT
|
|
// account, or a provider of its own in config.toml), Codex stays on the
|
|
// model it was on as magpie serves it on the ChatGPT account, not the first
|
|
// of magpie's other models (#940: it went to Grok 4.7, "magpie doesn't
|
|
// serve the model it was on").
|
|
func TestCodexConnectKeepsItsOwnModelThroughMagpie(t *testing.T) {
|
|
home, read := codexHome(t, `{"tokens":{"access_token":"x","id_token":"x.e30.x"}}`, "model = \"gpt-b\"\n")
|
|
os.WriteFile(filepath.Join(home, ".codex", "models_cache.json"), []byte(`{"models":[
|
|
{"slug":"gpt-a","display_name":"A","priority":1},
|
|
{"slug":"gpt-b","display_name":"B","priority":2}]}`), 0o644)
|
|
codexUsedUp = func() bool { return true }
|
|
cx := codex(home)
|
|
c, err := cx.ConnectHow()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if c.How != "same" || c.Value != "codex/gpt-b" || !strings.Contains(read(), `model = "codex/gpt-b"`) {
|
|
t.Fatalf("connected %+v:\n%s", c, read())
|
|
}
|
|
}
|