provider: the ChatGPT API subscription lists, beside the models its account catalog lists, those Codex lists for the plan, which the token runs though the catalog leaves them out (#933, knothhe)

knothhe found the ChatGPT API account listing far fewer models than pi, while gpt-6-luna, added by hand, ran. OpenAI's catalog for a Sign in with ChatGPT token (GET api.openai.com/v1/models, its visibility "list" entries, as OpenAI's Models and inference page says to show) leaves out models the token runs: fastagent's run on a real account (fastagent-sh/fastagent#698) found 5 listed, and gpt-6-luna, gpt-6-sol and gpt-6.1-sol running though not in it at all, while pi lists its 44 built-in openai models, 36 of which answer "not supported when using Codex with a ChatGPT account". The token is served as Codex on the plan, so the list now adds to the catalog's models the ones Codex lists for a ChatGPT plan: the list magpie's Codex subscription last fetched, else Codex CLI's models_cache.json. A model the catalog hides stays out, and so do magpie's own in Codex's list.

TestChatGPTAPIListsCodexModels is new and fails without the change (models [gpt-5.5 gpt-5.4-mini], no gpt-6-luna). Tested against fake OpenAI servers; not yet with a real ChatGPT API account. go vet, go build (GOOS=linux nogui, GOOS=windows) and go test -tags nogui ./... under a temp HOME pass.
This commit is contained in:
yetone committed 2026-10-06 02:30:31 +08:00
1 parent ab47042ba8
commit b0607ef5a6
2 files changed
+81 -6

No files matched your search

+34 -6
View File
@@ -492,14 +492,39 @@ func siwcFetchModels(ctx context.Context, user string) ([]catalog.Model, error)
}
return nil, errors.New(msg)
}
ms := parseSIWCModels(b)
ms, hidden := parseSIWCModels(b)
ms = siwcWithCodex(ms, hidden)
if len(ms) == 0 {
return nil, errors.New("ChatGPT lists no models for this account")
}
return ms, nil
}
func parseSIWCModels(b []byte) []catalog.Model {
// siwcWithCodex adds to the account's catalog the models Codex lists for a
// ChatGPT plan: the token is served as Codex on the plan (a model it can't
// run is "not supported when using Codex with a ChatGPT account"), and runs
// Codex's newest models the catalog leaves out (#933: gpt-6-luna,
// gpt-6-sol and gpt-6.1-sol ran, listed by neither). Codex's list is the
// one magpie's Codex subscription last fetched, else Codex CLI's cache; a
// model the catalog hides stays out.
func siwcWithCodex(ms []catalog.Model, hidden []string) []catalog.Model {
codex, _, ok := catalog.Live("codex")
if !ok {
codex = catalog.Codex()
}
for _, m := range codex {
if m.ID == "" || strings.Contains(m.ID, "/") || slices.Contains(hidden, m.ID) || slices.ContainsFunc(ms, func(o catalog.Model) bool { return o.ID == m.ID }) {
continue
}
m.Provider, m.APIs = "openai", []string{string(Responses)}
ms = append(ms, m)
}
return ms
}
// parseSIWCModels gives the catalog's models to list, and the slugs of
// those it hides.
func parseSIWCModels(b []byte) (out []catalog.Model, hidden []string) {
var list struct {
Models []struct {
Slug string `json:"slug"`
@@ -514,11 +539,14 @@ func parseSIWCModels(b []byte) []catalog.Model {
} `json:"models"`
}
if json.Unmarshal(b, &list) != nil {
return nil
return nil, nil
}
var out []catalog.Model
for _, m := range list.Models {
if m.Slug == "" || m.Visibility != "" && m.Visibility != "list" {
if m.Slug == "" {
continue
}
if m.Visibility != "" && m.Visibility != "list" {
hidden = append(hidden, m.Slug)
continue
}
mm := catalog.Model{ID: m.Slug, Name: m.DisplayName, Provider: "openai", Context: m.Context, APIs: []string{string(Responses)}}
@@ -534,7 +562,7 @@ func parseSIWCModels(b []byte) []catalog.Model {
}
out = append(out, mm)
}
return out
return out, hidden
}
// siwcDropped are the Responses fields Sign in with ChatGPT refuses: the
@@ -0,0 +1,47 @@
package provider
import (
"context"
"os"
"path/filepath"
"testing"
)
// #933: a ChatGPT API account lists, beside the models its catalog lists,
// those Codex lists for the plan, which the token runs though the catalog
// leaves them out (gpt-6-luna, added by hand, ran); one the catalog hides,
// and magpie's own in Codex's list, stay out.
func TestChatGPTAPIListsCodexModels(t *testing.T) {
home := claudeHome(t)
codex := filepath.Join(home, ".codex")
os.MkdirAll(codex, 0o755)
t.Setenv("CODEX_HOME", codex)
os.WriteFile(filepath.Join(codex, "models_cache.json"), []byte(`{"models":[
{"slug":"gpt-6-luna","display_name":"GPT-6-Luna","priority":1,"context_window":272000,"supported_reasoning_levels":[{"effort":"low"},{"effort":"xhigh"}]},
{"slug":"gpt-5.5","display_name":"GPT-5.5 (Codex)","priority":2},
{"slug":"gpt-hidden","display_name":"Hidden","priority":3},
{"slug":"deepseek/deepseek-v4","display_name":"DeepSeek V4","description":"DeepSeek V4 via magpie","priority":4}]}`), 0o644)
f := newFakeOpenAI(t)
if st := signInVia(t, f, "oaiapp_1", same); st.State != "done" {
t.Fatalf("sign-in %+v", st)
}
p, ok := siwcAccount()
if !ok {
t.Fatal("no account")
}
ms, err := p.Fetch(context.Background())
if err != nil {
t.Fatal(err)
}
var ids []string
for _, m := range ms {
ids = append(ids, m.ID)
}
if len(ms) != 3 || ids[0] != "gpt-5.5" || ids[1] != "gpt-5.4-mini" || ids[2] != "gpt-6-luna" {
t.Fatalf("models %v", ids)
}
if ms[0].Name != "GPT-5.5" || ms[2].Provider != "openai" || len(ms[2].APIs) != 1 || ms[2].APIs[0] != string(Responses) ||
len(ms[2].Efforts) != 2 {
t.Errorf("models %+v", ms)
}
}