diff --git a/docs/changes/unreleased/1870-codex-model-catalog-version.md b/docs/changes/unreleased/1870-codex-model-catalog-version.md new file mode 100644 index 0000000000..ec93cfe522 --- /dev/null +++ b/docs/changes/unreleased/1870-codex-model-catalog-version.md @@ -0,0 +1,13 @@ +--- +kind: fixed +title: Codex discovery lists newer models available to the signed-in account +pr: 1870 +surface: [chat, engine] +invalidates: [] +--- + +Update the shared catalog client version from 0.144.1 to 0.162.1. The account +backend gates model visibility by client version, so the older version omitted +newer models even for accounts with access. Direct listing and translated +catalog requests use the same version; account permissions and visibility +filtering are unchanged. diff --git a/internal/codexauth/models_version_test.go b/internal/codexauth/models_version_test.go new file mode 100644 index 0000000000..a2b9fd0b56 --- /dev/null +++ b/internal/codexauth/models_version_test.go @@ -0,0 +1,85 @@ +package codexauth + +import ( + "context" + "encoding/json" + "net/http" + "net/http/httptest" + "strings" + "testing" + "time" + + "github.com/Agent-Field/codeaf/internal/provider" +) + +func TestCatalogVersionExposesNewModelsThroughBothDoors(t *testing.T) { + for _, door := range []string{"direct", "translated"} { + t.Run(door, func(t *testing.T) { + now := time.Unix(1_800_000_000, 0) + dir := t.TempDir() + if err := Save(dir, validTokens(now)); err != nil { + t.Fatal(err) + } + backend := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodGet || r.URL.Path != "/models" || + r.Header.Get("Authorization") != "Bearer access-token-secret" || + r.Header.Get("chatgpt-account-id") != "account-one" || + r.Header.Get("originator") != Originator || + r.Header.Get("User-Agent") != provider.DirectUserAgent { + http.Error(w, "unexpected catalog request", http.StatusBadRequest) + return + } + rows := []map[string]any{ + {"slug": "baseline", "visibility": "list"}, + {"slug": "hidden", "visibility": "hide"}, + } + // The backend fixture pins the released version independently of + // clientVersion, so restoring the old advertisement hides the row. + if r.URL.Query().Get("client_version") == "0.162.1" { + rows = append(rows, map[string]any{ + "slug": "new-model", "visibility": "list", "context_window": 272000, + "supported_reasoning_levels": []map[string]string{{"effort": "high"}}, + }) + } + _ = json.NewEncoder(w).Encode(map[string]any{"models": rows}) + })) + defer backend.Close() + options := Options{Backend: backend.URL, HTTPClient: backend.Client(), Now: func() time.Time { return now }} + var ids []string + if door == "direct" { + models, err := List(context.Background(), dir, options) + if err != nil { + t.Fatal(err) + } + for _, model := range models { + ids = append(ids, model.ID) + } + } else { + response, err := ClientWithOptions(dir, options).Get(backend.URL + "/models") + if err != nil { + t.Fatal(err) + } + defer response.Body.Close() + if response.StatusCode != http.StatusOK { + t.Fatalf("catalog status = %d", response.StatusCode) + } + var catalog struct { + Data []struct{ ID string } `json:"data"` + } + if err := json.NewDecoder(response.Body).Decode(&catalog); err != nil { + t.Fatal(err) + } + for _, model := range catalog.Data { + ids = append(ids, model.ID) + } + } + if strings.Join(ids, ",") != "baseline,new-model" { + t.Fatalf("visible models = %v; newer catalog models must be exposed and hidden rows excluded", ids) + } + cached := loadModels(dir) + if len(cached) != 2 || cached[1].ID != "new-model" || cached[1].ContextLength != 272000 || strings.Join(cached[1].ReasoningLevels, ",") != "high" { + t.Fatalf("cached catalog metadata = %+v", cached) + } + }) + } +} diff --git a/internal/codexauth/tokens.go b/internal/codexauth/tokens.go index 0c4ed00c0c..f6b19e30dd 100644 --- a/internal/codexauth/tokens.go +++ b/internal/codexauth/tokens.go @@ -21,7 +21,7 @@ const ( ClientID = "app_EMoamEEZ73f0CkXaXp7hrann" Sentinel = "chatgpt" Originator = "codex_cli_rs" - clientVersion = "0.144.1" + clientVersion = "0.162.1" ) // signInExpiredError is terminal before the provider's ordinary transport