Skip to content

Commit f9fcf1f

Browse files
committed
feat(provider): list the models a ChatGPT login serves
The ChatGPT picker offered a built-in list that went stale as OpenAI added models and retired others for ChatGPT sign-in, so a fresh login could default to a model its account no longer serves. The backend returns no models to client_version 0.50.0, so requests now send the current Codex CLI version, including the sign-in scope check.
1 parent bed4037 commit f9fcf1f

7 files changed

Lines changed: 608 additions & 23 deletions

File tree

‎docs/IMPLEMENTATION.md‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -420,6 +420,8 @@ This release changes no settings, OAuth-store, or session persistence format and
420420

421421
**OpenCode Go multi-protocol.** Selectable ids come from live `GET /zen/go/v1/models` (process-cached; packaged seed when cold or the fetch fails). Byte and model-count caps fail closed as malformed — never a truncated prefix. Protocol routing stays on the local map; unknown live ids use Chat Completions. `buildGoSource` / `resolveGoEndpoint` pick the adapter and base URL per model (not a single provider-wide OpenAI route). When Go is the active provider, subscription usage is fetched for the status bar and omitted on auth/network failure.
422422

423+
**ChatGPT model catalog.** Each ChatGPT (Codex OAuth) profile lists the models its account serves: after startup the TUI fetches `GET /codex/models` with the profile's refreshed token and account id, keeps entries whose `visibility` is `list`, orders them by `priority`, and the first becomes the profile's default (`src/provider/codex-model-catalog.ts`). The list is cached per profile and account for the process, and fleet workers spawned from the TUI read it through the live catalog. Until a fetch succeeds the profile uses `CODEX_DEFAULT_MODELS`; a later failed fetch keeps the last live list. Headless runs never fetch, so they and their workers always use `CODEX_DEFAULT_MODELS`. The backend filters the catalog by the request's `client_version`: an old `CODEX_CLIENT_VERSION` returns fewer models or none. Only an empty list logs a warning ("listed no models"); a shortened list is silent, so when a model the Codex CLI offers is missing here, bump `CODEX_CLIENT_VERSION` to a current Codex CLI release.
424+
423425
### CLI Verbs and Flags
424426

425427
Printed by `corbits --help` / `-h` from `CLI_HELP_TEXT` in `src/config/index.ts`

‎src/auth/codex/constants.ts‎

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { callbackTargetFor } from "@corbits/oauth-core";
2-
import { codexOAuthConfig } from "@corbits/codex-provider";
2+
import { CODEX_BASE_URL, codexOAuthConfig } from "@corbits/codex-provider";
33

44
export {
55
CODEX_BASE_URL,
@@ -12,19 +12,24 @@ export const CODEX_CALLBACK_HOST = callbackTarget.host;
1212
export const CODEX_CALLBACK_PORT = callbackTarget.port;
1313
export const CODEX_CALLBACK_PATH = callbackTarget.path;
1414

15-
// The account's available model catalog. The models endpoint requires a
16-
// client_version query param.
17-
export const CODEX_MODELS_PATH = "/codex/models";
18-
export const CODEX_CLIENT_VERSION = "0.50.0";
15+
const CODEX_MODELS_PATH = "/codex/models";
16+
// The backend filters /codex/models by client_version: an older version gets
17+
// fewer models or none, and the per-model minimal_client_version does not
18+
// predict which. Track a released Codex CLI version.
19+
export const CODEX_CLIENT_VERSION = "0.165.0";
20+
// The account's available model catalog, read by both the sign-in scope
21+
// check and the live model catalog so the two cannot drift.
22+
export const CODEX_MODELS_URL = `${CODEX_BASE_URL}${CODEX_MODELS_PATH}?client_version=${encodeURIComponent(CODEX_CLIENT_VERSION)}`;
1923

2024
// Client identity the Codex backend expects on usage/model requests, matching
2125
// the public Codex CLI originator.
2226
export const CODEX_ORIGINATOR = "codex_cli_rs";
2327

24-
// The models a ChatGPT sign-in can select, in the Codex backend's priority
25-
// order (GET /codex/models); the first entry is the ChatGPT default. OpenAI
26-
// retires models for ChatGPT sign-in on its own schedule, independently of the
27-
// OpenAI API, so this list and its default do not follow the API-key path.
28+
// The models a ChatGPT sign-in can select when its live catalog (GET
29+
// /codex/models) is unavailable, in the backend's priority order; the first
30+
// entry is the ChatGPT default. OpenAI retires models for ChatGPT sign-in on
31+
// its own schedule, independently of the OpenAI API, so this list and its
32+
// default do not follow the API-key path.
2833
export const CODEX_DEFAULT_MODELS = [
2934
"gpt-6.1-sol",
3035
"gpt-6-astra",

‎src/auth/oauth-scope-check.ts‎

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,7 @@
1111

1212
import { isOAuthTokenEndpointError } from "./token-session-boundary.js";
1313

14-
import {
15-
CODEX_BASE_URL,
16-
CODEX_MODELS_PATH,
17-
CODEX_CLIENT_VERSION,
18-
} from "./codex/constants.js";
14+
import { CODEX_MODELS_URL } from "./codex/constants.js";
1915
import { refreshStagedCodexTokens } from "./codex/session.js";
2016
import type { CodexTokens } from "./codex/store.js";
2117
import { codexAuthHeadersForToken } from "./codex/auth-headers.js";
@@ -151,10 +147,10 @@ async function checkCodexScope(
151147
commandName,
152148
),
153149
(headers) =>
154-
fetch(
155-
`${CODEX_BASE_URL}${CODEX_MODELS_PATH}?client_version=${encodeURIComponent(CODEX_CLIENT_VERSION)}`,
156-
{ headers, signal: AbortSignal.timeout(SCOPE_CHECK_TIMEOUT_MS) },
157-
),
150+
fetch(CODEX_MODELS_URL, {
151+
headers,
152+
signal: AbortSignal.timeout(SCOPE_CHECK_TIMEOUT_MS),
153+
}),
158154
);
159155
}
160156

‎src/config/codex-providers.ts‎

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
1-
import {
2-
CODEX_BASE_URL,
3-
CODEX_DEFAULT_MODELS,
4-
} from "../auth/codex/constants.js";
1+
import { CODEX_BASE_URL } from "../auth/codex/constants.js";
52
import type { CodexProfile } from "../auth/codex/store.js";
3+
import { selectableCodexModelIds } from "../provider/codex-model-catalog.js";
64
import { createOAuthProviderProjection } from "./oauth-providers.js";
75

86
export const CODEX_PROVIDER_PREFIX = "codex/";
@@ -13,7 +11,7 @@ export const CODEX_PROVIDER_PREFIX = "codex/";
1311
const projection = createOAuthProviderProjection<CodexProfile>({
1412
prefix: CODEX_PROVIDER_PREFIX,
1513
baseURL: CODEX_BASE_URL,
16-
models: () => CODEX_DEFAULT_MODELS,
14+
models: selectableCodexModelIds,
1715
catalogExtras: (profile) => ({
1816
codexProfile: profile.name,
1917
...(profile.tokens.accountId !== undefined

0 commit comments

Comments
 (0)