codex: a ChatGPT account out of allowance gets magpie as a provider of Codex's, and a routing group is named as one in the Codex app's list — signed in through openai_base_url, the Codex app won't send anything once the account has used its allowance up, a magpie model's request too, so magpie is then wired as it is when signed out (model_provider, as the account's own models can't run anyway); and the gateway's model list for the signed-in app named a group after its first member's provider ("z-ai/glm-5.3 · nvidia"), reading as that provider's own model, where it now says routing group, as magpie's other lists do
This commit is contained in:
5 files changed
+84
-3
No files matched your search
+17
-2
@@ -1,11 +1,13 @@
|
||||
package agent
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/yetone/magpie/internal/catalog"
|
||||
"github.com/yetone/magpie/internal/codexcat"
|
||||
@@ -22,7 +24,9 @@ import (
|
||||
// answers the rest — the model list included, so magpie's models join
|
||||
// OpenAI's in /model. Not signed in, the built-in provider can't run, and
|
||||
// magpie is a provider of its own: a [model_providers.magpie] table,
|
||||
// `model_provider = "magpie"`, and a model catalog file for /model.
|
||||
// `model_provider = "magpie"`, and a model catalog file for /model. So it
|
||||
// is too for a ChatGPT account that has used its allowance up, which the
|
||||
// Codex app won't send anything for, whoever serves the model.
|
||||
|
||||
func codex(home string) *Agent {
|
||||
dir := filepath.Join(home, ".codex")
|
||||
@@ -91,7 +95,10 @@ func codex(home string) *Agent {
|
||||
stash(map[string]string{"codex.model": get("model"), "codex.effort": get("model_reasoning_effort"),
|
||||
"codex.provider": get("model_provider"), "codex.catalog": get("model_catalog_json")})
|
||||
}
|
||||
if codexSignedIn(dir) {
|
||||
// a ChatGPT account out of allowance keeps the Codex app from
|
||||
// sending at all, a magpie model's request too; as a provider
|
||||
// of Codex's own, magpie is past that
|
||||
if codexSignedIn(dir) && !codexUsedUp() {
|
||||
if err := dropProvider(); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -246,6 +253,14 @@ func isCodexGateway(u string) bool {
|
||||
return strings.HasPrefix(u, "http://127.0.0.1:") && strings.HasSuffix(strings.TrimSuffix(u, "/"), gateway.CodexPath)
|
||||
}
|
||||
|
||||
// codexUsedUp reports whether the ChatGPT account Codex is signed in to
|
||||
// has used up its allowance. A var so tests can say.
|
||||
var codexUsedUp = func() bool {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
return provider.CodexUsedUp(ctx)
|
||||
}
|
||||
|
||||
// codexSignedIn reports whether Codex has a sign-in of its own, a ChatGPT
|
||||
// account or an API key, which its built-in OpenAI provider needs.
|
||||
func codexSignedIn(dir string) bool {
|
||||
|
||||
@@ -16,6 +16,9 @@ func codexHome(t *testing.T, auth, config string) (home string, read func() stri
|
||||
t.Setenv("XDG_CONFIG_HOME", filepath.Join(home, ".config"))
|
||||
t.Setenv("XDG_CACHE_HOME", filepath.Join(home, ".cache"))
|
||||
t.Setenv("CODEX_HOME", "")
|
||||
usedUp := codexUsedUp
|
||||
codexUsedUp = func() bool { return false }
|
||||
t.Cleanup(func() { codexUsedUp = usedUp })
|
||||
dir := filepath.Join(home, ".codex")
|
||||
os.MkdirAll(dir, 0o755)
|
||||
if auth != "" {
|
||||
@@ -94,3 +97,18 @@ func TestCodexSignedOutUsesProvider(t *testing.T) {
|
||||
t.Fatalf("reset:\n%s", cfg)
|
||||
}
|
||||
}
|
||||
|
||||
// A ChatGPT account that has used its allowance up keeps the Codex app from
|
||||
// sending, so magpie is a provider of Codex's then, as when signed out.
|
||||
func TestCodexUsedUpUsesProvider(t *testing.T) {
|
||||
home, read := codexHome(t, `{"tokens":{"access_token":"x","id_token":"x.e30.x"}}`, "")
|
||||
codexUsedUp = func() bool { return true }
|
||||
if err := codex(home).Fields[0].Set("fake/m1"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
cfg := read()
|
||||
if strings.Contains(cfg, "openai_base_url") || !strings.Contains(cfg, `model_provider = "magpie"`) ||
|
||||
!strings.Contains(cfg, "[model_providers.magpie]") || !strings.Contains(cfg, `model = "fake/m1"`) {
|
||||
t.Fatalf("\n%s", cfg)
|
||||
}
|
||||
}
|
||||
@@ -260,7 +260,12 @@ func (s *Server) codexModels(w http.ResponseWriter, r *http.Request) {
|
||||
if e.Provider.Account != nil && e.Provider.Account.Agent == "codex" {
|
||||
continue
|
||||
}
|
||||
ms = append(ms, catalog.Model{ID: e.ID, Name: e.Name + " · " + e.Provider.Name, Efforts: e.Efforts})
|
||||
// a group answers for its first member but is not that provider's
|
||||
by := e.Provider.Name
|
||||
if e.Group != "" {
|
||||
by = "routing group"
|
||||
}
|
||||
ms = append(ms, catalog.Model{ID: e.ID, Name: e.Name + " · " + by, Efforts: e.Efforts})
|
||||
}
|
||||
if etag != "" {
|
||||
w.Header().Set("ETag", etag)
|
||||
|
||||
@@ -199,3 +199,29 @@ func TestCodexWebSocketUpgradeRequired(t *testing.T) {
|
||||
t.Errorf("%d", rec.Code)
|
||||
}
|
||||
}
|
||||
|
||||
// A routing group in Codex's model list is named as one, not as its first
|
||||
// member's provider, which would read as that provider's own model.
|
||||
func TestCodexModelsNameGroups(t *testing.T) {
|
||||
setup(t, provider.Chat, &fake{t: t})
|
||||
chatgpt(t, func(w http.ResponseWriter, r *http.Request) { io.WriteString(w, `{"models":[]}`) })
|
||||
if err := provider.SaveGroup(provider.Group{Name: "G", Members: []string{"fake/m1"}}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
rec := httptest.NewRecorder()
|
||||
New().Handler().ServeHTTP(rec, httptest.NewRequest("GET", CodexPath+"/models", nil))
|
||||
var list struct {
|
||||
Models []struct {
|
||||
Slug string `json:"slug"`
|
||||
Name string `json:"display_name"`
|
||||
} `json:"models"`
|
||||
}
|
||||
json.Unmarshal(rec.Body.Bytes(), &list)
|
||||
names := map[string]string{}
|
||||
for _, m := range list.Models {
|
||||
names[m.Slug] = m.Name
|
||||
}
|
||||
if names["group/g"] != "G · routing group" || !strings.HasSuffix(names["fake/m1"], " · Fake") {
|
||||
t.Fatalf("%v", names)
|
||||
}
|
||||
}
|
||||
@@ -114,3 +114,20 @@ func loginQuota(ctx context.Context, l Login) SubscriptionQuota {
|
||||
}
|
||||
return q
|
||||
}
|
||||
|
||||
// CodexUsedUp reports whether the ChatGPT account Codex is signed in to has
|
||||
// used up its allowance for now; false when that isn't known.
|
||||
func CodexUsedUp(ctx context.Context) bool {
|
||||
for _, l := range Logins("codex") {
|
||||
if !l.Active {
|
||||
continue
|
||||
}
|
||||
q := loginQuota(ctx, l)
|
||||
for _, w := range q.Windows {
|
||||
if !w.Aside && w.Model == "" && w.Used >= 100 {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
Reference in new issue
Block a user