feat(models): fetch the openai-codex catalog live

The ChatGPT Codex catalog was compiled in: four models read once from the
Codex CLI cache. The backend moved on — `gpt-6-astra` shipped, and
`gpt-5.3-codex-spark` left — so the picker showed a table that no longer
matched the account.

`GET https://chatgpt.com/backend-api/codex/models?client_version=<v>`
serves that table. Verified against a live ChatGPT account: the OAuth
Bearer alone returns 200, the query is required (400 without it), and the
backend gates the catalog on it — `0.104.0` answers with
`codex-auto-review` alone, `0.150.0` omits `gpt-6-astra`, `0.155.1`
serves the full set. `CODEX_CLIENT_VERSION` pins the current upstream
release (openai/codex rust-v0.155.1) and the codex `User-Agent` now
derives from it, so the two can never drift.

`ListingDialect::OpenaiCodex` + `parse_openai_codex_listing` keep the
`visibility == "list" && supported_in_api` models and carry the wire
context window and per-model reasoning levels; `hardcoded_catalog` and
its short-circuit in the fetch path are gone. The catalog advertises an
`ultra` tier kigi has no level for — `think_efforts_to_options` drops it
with a warning, as it does any unknown token.
This commit is contained in:
ZacharyZhang-NY committed 2026-09-21 15:12:54 -04:00
1 parent 781fb44659
commit c61977cb70
4 files changed
+213 -240

No files matched your search

@@ -1198,11 +1198,19 @@ pub const COPILOT_INITIATOR: &str = "user";
pub const CODEX_ORIGINATOR: &str = "codex_cli_rs";
/// `OpenAI-Beta` opt-in the Codex Responses endpoint requires.
pub const CODEX_OPENAI_BETA: &str = "responses=experimental";
/// Codex CLI version kigi presents to the Codex backend. The models catalog
/// (`/codex/models?client_version=`) is GATED on it: an old version answers
/// with a truncated model list (0.104.0 serves no user-facing model at all),
/// so this tracks a real upstream release (openai/codex rust-v0.155.1).
macro_rules! codex_client_version {
() => {
"0.155.1"
};
}
pub const CODEX_CLIENT_VERSION: &str = codex_client_version!();
/// `User-Agent` presented on the Codex path (overrides the default kigi UA,
/// openai-codex-gated). The Codex backend does not strictly validate the UA
/// string (Pi ships its own and it works), so this is a stable best-effort
/// identity, not a pinned build.
pub const CODEX_USER_AGENT: &str = "codex_cli_rs/0.104.0";
/// openai-codex-gated), carrying the same pinned version.
pub const CODEX_USER_AGENT: &str = concat!("codex_cli_rs/", codex_client_version!());
/// JWT payload claim namespace carrying the ChatGPT account id.
const CODEX_JWT_AUTH_CLAIM: &str = "https://api.openai.com/auth";
@@ -1635,6 +1643,16 @@ mod tests {
use super::*;
use serde_json::json;
/// The catalog gate and the UA must claim the SAME codex version: the
/// backend serves a truncated model list to an old client_version.
#[test]
fn codex_user_agent_carries_the_pinned_client_version() {
assert_eq!(
CODEX_USER_AGENT,
format!("codex_cli_rs/{CODEX_CLIENT_VERSION}")
);
}
/// The Codex backend rejects `role: system` input outright
/// (400 `{"detail":"System messages are not allowed"}`) — its system
/// channel is the top-level `instructions` field, and stateless