From 0cc25f1e8c2a476a4980ce0c7de2e3a8b08662cd Mon Sep 17 00:00:00 2001 From: oniisanlabs <1436535+oniisanlabs@users.noreply.github.com> Date: Tue, 27 Oct 2026 16:32:14 +0100 Subject: [PATCH 1/3] fix(chatgpt): refresh subscription defaults and compatibility version Replace retired preset suggestions with vendor-documented Sol, Terra, Astra, and Luna IDs. Update the Codex discovery compatibility floor for Terra and verify that saved model selections are retained. Refs Twigpine/zero#1177 (defaults portion) --- internal/config/chatgpt_defaults_test.go | 51 +++++++++++++++++++ internal/providercatalog/catalog.go | 4 +- internal/providermodelcatalog/catalog.go | 10 ++-- internal/providermodelcatalog/catalog_test.go | 4 +- .../chatgpt_defaults_test.go | 26 ++++++++++ internal/providermodeldiscovery/discovery.go | 5 +- internal/tui/provider_wizard_oauth_test.go | 6 +-- 7 files changed, 94 insertions(+), 12 deletions(-) create mode 100644 internal/config/chatgpt_defaults_test.go create mode 100644 internal/providermodeldiscovery/chatgpt_defaults_test.go diff --git a/internal/config/chatgpt_defaults_test.go b/internal/config/chatgpt_defaults_test.go new file mode 100644 index 000000000..473c00ef0 --- /dev/null +++ b/internal/config/chatgpt_defaults_test.go @@ -0,0 +1,51 @@ +package config + +import ( + "encoding/json" + "os" + "path/filepath" + "testing" +) + +func TestEnsureChatGPTDefaultPreservesSavedSelection(t *testing.T) { + for _, saved := range []string{"", "gpt-explicit-saved"} { + name := "new profile" + if saved != "" { + name = "saved selection" + } + t.Run(name, func(t *testing.T) { + path := filepath.Join(t.TempDir(), "config.json") + if saved != "" { + body, err := json.Marshal(FileConfig{ActiveProvider: "codex", Providers: []ProviderProfile{{Name: "codex", CatalogID: "chatgpt", Model: saved}}}) + if err != nil { + t.Fatal(err) + } + if err := os.WriteFile(path, body, 0600); err != nil { + t.Fatal(err) + } + } + ensured, err := EnsureCatalogProvider(path, "chatgpt") + if err != nil { + t.Fatal(err) + } + body, err := os.ReadFile(path) + if err != nil { + t.Fatal(err) + } + var cfg FileConfig + if err := json.Unmarshal(body, &cfg); err != nil { + t.Fatal(err) + } + want := "gpt-6.1-sol" + if saved != "" { + want = saved + } + if len(cfg.Providers) != 1 || cfg.Providers[0].Model != want { + t.Fatalf("ChatGPT default/selection = %#v, want model %q", cfg.Providers, want) + } + if saved != "" && (ensured.Name != "codex" || ensured.Created) { + t.Fatalf("saved subscription profile was replaced: %#v", ensured) + } + }) + } +} diff --git a/internal/providercatalog/catalog.go b/internal/providercatalog/catalog.go index 6cd1c21a8..ba67212b1 100644 --- a/internal/providercatalog/catalog.go +++ b/internal/providercatalog/catalog.go @@ -132,7 +132,9 @@ var descriptors = []Descriptor{ // way in; RequiresAuth is forced true so catalog consumers know the provider // needs an interactive login before a request will succeed. func() Descriptor { - d := openAICompat("chatgpt", "ChatGPT", "https://chatgpt.com/backend-api/codex", "gpt-5.5", nil) + // New ChatGPT profiles follow the vendor's GPT-5.5 replacement guidance: + // https://learn.chatgpt.com/docs/models#gpt-55-retirement + d := openAICompat("chatgpt", "ChatGPT", "https://chatgpt.com/backend-api/codex", "gpt-6.1-sol", nil) d.RequiresAuth = true return oauthProvider(d, false, false) }(), diff --git a/internal/providermodelcatalog/catalog.go b/internal/providermodelcatalog/catalog.go index 5bf5e27d4..b1de7f04c 100644 --- a/internal/providermodelcatalog/catalog.go +++ b/internal/providermodelcatalog/catalog.go @@ -113,10 +113,12 @@ var curatedModels = map[string][]Model{ {ID: "deepseek/deepseek-chat", Description: "coding model"}, }, "chatgpt": { - {ID: "gpt-5.5", Description: "recommended Codex model"}, - {ID: "gpt-5.4", Description: "strong Codex model"}, - {ID: "gpt-5.4-mini", Description: "fast Codex model"}, - {ID: "gpt-5.3-codex-spark", Description: "research preview fast Codex model"}, + // Suggestions only: availability comes from the authenticated Codex catalog. + // https://learn.chatgpt.com/docs/models + {ID: "gpt-6.1-sol", Description: "balanced Codex model"}, + {ID: "gpt-6.1-terra", Description: "fast visual and coding model"}, + {ID: "gpt-6-astra", Description: "advanced reasoning model"}, + {ID: "gpt-6-luna", Description: "efficient coding model"}, }, "deepseek": { {ID: "deepseek-chat", Description: "catalog default"}, diff --git a/internal/providermodelcatalog/catalog_test.go b/internal/providermodelcatalog/catalog_test.go index f952db52f..3a5a86ba0 100644 --- a/internal/providermodelcatalog/catalog_test.go +++ b/internal/providermodelcatalog/catalog_test.go @@ -35,8 +35,8 @@ func TestModelsAreProviderScoped(t *testing.T) { }, { provider: "chatgpt", - want: []string{"gpt-5.5", "gpt-5.4", "gpt-5.4-mini", "gpt-5.3-codex-spark"}, - notWant: []string{"gpt-5", "gpt-4.1", "openai/gpt-4.1"}, + want: []string{"gpt-6.1-sol", "gpt-6.1-terra", "gpt-6-astra", "gpt-6-luna"}, + notWant: []string{"gpt-5.5", "gpt-5.4", "gpt-5.4-mini", "gpt-5.3-codex-spark", "openai/gpt-4.1"}, }, { provider: "aimlapi", diff --git a/internal/providermodeldiscovery/chatgpt_defaults_test.go b/internal/providermodeldiscovery/chatgpt_defaults_test.go new file mode 100644 index 000000000..95c101033 --- /dev/null +++ b/internal/providermodeldiscovery/chatgpt_defaults_test.go @@ -0,0 +1,26 @@ +package providermodeldiscovery + +import ( + "context" + "github.com/Twigpine/zero/internal/config" + "net/http" + "net/http/httptest" + "testing" +) + +func TestChatGPTDiscoveryUsesCurrentCompatibilityVersion(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if got := r.URL.Query().Get("client_version"); got != "0.159.0" { + t.Errorf("client_version = %q, want documented GPT-6.1 Terra compatibility floor 0.159.0", got) + } + _, _ = w.Write([]byte(`{"models":[{"slug":"gpt-future-coder"}]}`)) + })) + defer server.Close() + profile := config.ProviderProfile{ + CatalogID: "chatgpt", ProviderKind: config.ProviderKindOpenAICompatible, + BaseURL: server.URL + "/backend-api/codex", APIKey: "test-bearer", + } + if _, err := Discover(context.Background(), profile, Options{HTTPClient: server.Client()}); err != nil { + t.Fatal(err) + } +} diff --git a/internal/providermodeldiscovery/discovery.go b/internal/providermodeldiscovery/discovery.go index 3a78121ac..3e1dbd250 100644 --- a/internal/providermodeldiscovery/discovery.go +++ b/internal/providermodeldiscovery/discovery.go @@ -20,8 +20,9 @@ import ( ) const ( - anthropicVersion = "2023-06-01" - chatGPTModelsProtocolVersion = "0.146.0" + anthropicVersion = "2023-06-01" + // GPT-6.1 Terra's documented Codex client floor: https://learn.chatgpt.com/docs/models. + chatGPTModelsProtocolVersion = "0.159.0" ) type Model struct { diff --git a/internal/tui/provider_wizard_oauth_test.go b/internal/tui/provider_wizard_oauth_test.go index 56bcb002b..c9777f833 100644 --- a/internal/tui/provider_wizard_oauth_test.go +++ b/internal/tui/provider_wizard_oauth_test.go @@ -258,7 +258,7 @@ func TestProviderWizardChatGPTOAuthModelsUseCodexSet(t *testing.T) { for _, model := range models { got[model.ID] = true } - for _, want := range []string{"gpt-5.5", "gpt-5.4", "gpt-5.4-mini", "gpt-5.3-codex-spark"} { + for _, want := range []string{"gpt-6.1-sol", "gpt-6.1-terra", "gpt-6-astra", "gpt-6-luna"} { if !got[want] { t.Fatalf("ChatGPT OAuth models missing %q; got %#v", want, providerWizardModelIDs(models)) } @@ -266,8 +266,8 @@ func TestProviderWizardChatGPTOAuthModelsUseCodexSet(t *testing.T) { if got["gpt-5"] { t.Fatalf("ChatGPT OAuth models should not include stale gpt-5 fallback; got %#v", providerWizardModelIDs(models)) } - if models[0].ID != "gpt-5.5" { - t.Fatalf("default ChatGPT OAuth model = %q, want gpt-5.5", models[0].ID) + if models[0].ID != "gpt-6.1-sol" { + t.Fatalf("default ChatGPT OAuth model = %q, want gpt-6.1-sol", models[0].ID) } } From 9bda172a5ddb98c2c98d9f3647c8f224d7f412e6 Mon Sep 17 00:00:00 2001 From: oniisanlabs <1436535+oniisanlabs@users.noreply.github.com> Date: Tue, 27 Oct 2026 16:56:55 +0100 Subject: [PATCH 2/3] test(chatgpt): assert discovered model in compatibility probe Verify the live response contains exactly gpt-future-coder in addition to checking the Codex compatibility query value. Refs Twigpine/zero#1177 --- internal/providermodeldiscovery/chatgpt_defaults_test.go | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/internal/providermodeldiscovery/chatgpt_defaults_test.go b/internal/providermodeldiscovery/chatgpt_defaults_test.go index 95c101033..23ddced73 100644 --- a/internal/providermodeldiscovery/chatgpt_defaults_test.go +++ b/internal/providermodeldiscovery/chatgpt_defaults_test.go @@ -2,10 +2,11 @@ package providermodeldiscovery import ( "context" - "github.com/Twigpine/zero/internal/config" "net/http" "net/http/httptest" "testing" + + "github.com/Twigpine/zero/internal/config" ) func TestChatGPTDiscoveryUsesCurrentCompatibilityVersion(t *testing.T) { @@ -20,7 +21,11 @@ func TestChatGPTDiscoveryUsesCurrentCompatibilityVersion(t *testing.T) { CatalogID: "chatgpt", ProviderKind: config.ProviderKindOpenAICompatible, BaseURL: server.URL + "/backend-api/codex", APIKey: "test-bearer", } - if _, err := Discover(context.Background(), profile, Options{HTTPClient: server.Client()}); err != nil { + models, err := Discover(context.Background(), profile, Options{HTTPClient: server.Client()}) + if err != nil { t.Fatal(err) } + if len(models) != 1 || models[0].ID != "gpt-future-coder" { + t.Fatalf("models = %#v, want only gpt-future-coder", models) + } } From 955be0328dc01a3bcb0872150ad6d1b0c258f11c Mon Sep 17 00:00:00 2001 From: oniisanlabs <1436535+oniisanlabs@users.noreply.github.com> Date: Thu, 29 Oct 2026 10:20:19 +0100 Subject: [PATCH 3/3] docs(chatgpt): align Terra description with vendor guidance --- internal/providermodelcatalog/catalog.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/providermodelcatalog/catalog.go b/internal/providermodelcatalog/catalog.go index b1de7f04c..984f56de9 100644 --- a/internal/providermodelcatalog/catalog.go +++ b/internal/providermodelcatalog/catalog.go @@ -116,7 +116,7 @@ var curatedModels = map[string][]Model{ // Suggestions only: availability comes from the authenticated Codex catalog. // https://learn.chatgpt.com/docs/models {ID: "gpt-6.1-sol", Description: "balanced Codex model"}, - {ID: "gpt-6.1-terra", Description: "fast visual and coding model"}, + {ID: "gpt-6.1-terra", Description: "fast model for writing, visual understanding, and computer use"}, {ID: "gpt-6-astra", Description: "advanced reasoning model"}, {ID: "gpt-6-luna", Description: "efficient coding model"}, },