Skip to content

feat(cli-tools): write an OmniRoute model catalog when applying Codex settings - #16910

Merged
diegosouzapw merged 4 commits into
release/v3.8.52from
feat/port-pr-4406-codex-model-catalog-on-apply
Oct 17, 2026
Merged

diegosouzapw merged 4 commits into
release/v3.8.52from
feat/port-pr-4406-codex-model-catalog-on-apply

Conversation

@diegosouzapw

Copy link
Copy Markdown
Collaborator

Summary

  • Applying Codex settings in the dashboard now also writes an OmniRoute model catalog (~/.codex/omniroute-models.json) and sets model_catalog_json, so Codex's /model picker lists routable provider-prefixed ids (cx/gpt-5.5, kr/claude-sonnet-4.5, combos) instead of bare bundled slugs that OmniRoute may not route.
  • Entries are cloned from the installed Codex's own codex debug models --bundled output, not synthesized, because each one carries a version-specific system prompt that Codex validates strictly.
  • Fail-open: if Codex is missing or too old, Apply still succeeds and the response carries catalogWarning. The dashboard shows it as a warning, not an error.

Attribution

Thanks to @kalfian for the original implementation.

Changes

  • src/lib/cliTools/codexModelCatalog.ts (new): pure buildCodexModelCatalog() plus loadBundledCodexCatalog().
    • Each model clones the bundled entry that matches its basename. Anything without a match falls back to gpt-5.5, then the first plain listed entry.
    • Non-Codex ids (anything not resolving to codex through the provider registry, so both codex/ and cx/ count as Codex) have their code-mode flags cleared.
    • Inherited upgrade notices are nulled. Bundled entries are kept as visibility: "hide" with shifted priority.
    • The listed block is capped at 300 entries, since each entry is about 55-60 KB.
    • Codex is spawned with execFile and an argv array (Hard Rule deps: bump qs from 6.14.1 to 6.14.2 #13), with a 15 s timeout, a 16 MB maxBuffer and CODEX_HOME pinned to the directory OmniRoute writes into. The binary is resolved through locateCommandCandidate / getLookupEnv (CLI_CODEX_BIN is honoured).
  • src/lib/cliTools/codexModelCatalogWriter.ts (new): fail-open orchestration.
    • The model list comes from the same unified catalog /v1/models serves: chat models only, hidden models excluded, combos included, the caller's credentials forwarded.
    • The models selected in this Apply (default model and alias targets) are listed first, so the cap never drops them.
    • The API key is never written into the catalog, and catalog content is never logged.
  • src/app/api/cli-tools/codex-settings/route.ts:
    • POST writes the catalog when modelCatalog: true and returns catalogPath/catalogModelCount or catalogWarning.
    • If the flag is omitted, behaviour is unchanged, so other API clients stay byte-compatible.
    • DELETE removes model_catalog_json only when it points to OmniRoute's file, then deletes the file. A user-owned catalog is left alone.
    • The TOML value is stored with forward slashes because this file's TOML writer does not escape backslashes.
  • src/shared/validation/schemas/cli.ts: codexSettingsConfigSchema = cliModelConfigSchema + optional modelCatalog boolean.
  • CodexToolCard.tsx: an opt-out checkbox "List OmniRoute models in Codex's /model picker" (on by default, with a tooltip about the file size and re-applying after a Codex upgrade), plus an amber warning state.
  • i18n: 3 new cliTools.* keys, translated in all 66 locales (no __MISSING__ markers).
  • The catalog file is deliberately not added to createMultiBackup: it is regenerated on every Apply, and backing up about 17 MB per Apply would only add to the backups' disk pressure. config.toml and auth.json are still backed up.

Test plan

  • node --import tsx/esm --test tests/unit/codex-model-catalog.test.ts tests/unit/codex-settings-model-catalog.test.ts. Before the change: builder suite ERR_MODULE_NOT_FOUND, route suite ℹ tests 4 ℹ pass 1 ℹ fail 3. After: ℹ tests 18 ℹ pass 18 ℹ fail 0.
    • The route test uses a temp HOME and a fake codex shell script selected through CLI_CODEX_BIN, so the real ~/.codex is never touched.
    • It also asserts that the child received CODEX_HOME and that the API key is absent from the catalog.
  • npx vitest run --config vitest.config.ts tests/unit/ui/codex-tool-card-model-catalog.test.tsx tests/unit/ui/codex-tool-card-wire-api-default.test.tsx tests/unit/ui/ToolDetailClient.test.tsx: Tests 11 passed (11). The new UI test fails on the base card (2 failed).
  • Existing tests that reference the touched route/schema/card (14 files: codex-settings-, cli-model-config-schema, cli-tools-refuse-to-clobber, codex-config-hooks-migration, route-guard-, cli-catalog-*, hermes keyId, spawn-capable-prefixes): ℹ tests 208 ℹ pass 208 ℹ fail 0. tests/integration/security-hardening.test.ts: ℹ tests 20 ℹ pass 20 ℹ fail 0.
  • Live check against real Codex CLI 0.162.1, in a sandbox HOME/CODEX_HOME: generated the catalog from its real bundle (11 entries), then ran codex debug models with model_catalog_json pointing at it. rc=0, and Codex loaded 16 models: cx/gpt-5.5, kr/claude-sonnet-4.5, glm/glm-5.3, cc/claude-opus-4.7 and my-combo listed with priorities 1-5, and the 11 bundled entries hidden.
  • npm run typecheck:core: EXIT=0. ESLint (with --suppressions-location config/quality/eslint-suppressions.json) on the changed files: clean. npx prettier --check: clean.
  • npm run i18n:check-keys: OK (66 locales). npm run i18n:check-ratio: OK. npm run i18n:check-new-keys: PASS. tests/unit/i18n-vi-completeness.test.ts: ℹ tests 5 ℹ pass 5.
  • node scripts/check/check-pr-test-policy.mjs: PASS. npm run check:test-masking: OK (no weakening).

⚠️ base-red inherited: #15306 — unit/vitest/integration/package-artifact gates on release/v3.8.52

diegosouzapw and others added 4 commits October 16, 2026 20:13
… settings

Codex's /model picker only lists its bundled catalog, so picking a bare slug
sends e.g. gpt-5.5 to OmniRoute, which may not route it. On Apply (opt-out
checkbox, on by default) the Codex settings route now runs
`codex debug models --bundled`, clones each bundled entry (they carry
version-specific system prompts Codex validates strictly) into
~/.codex/omniroute-models.json under OmniRoute's provider-prefixed ids, and
points `model_catalog_json` at it.

- non-Codex upstreams get code-mode flags cleared; inherited upgrade notices
  are dropped; bundled entries stay as hidden so internal slugs resolve
- selected models lead the picker; listed block capped at 300 entries
- fail-open: if Codex is missing/old, Apply still succeeds and returns
  catalogWarning (shown as a warning in the dashboard)
- Reset removes the key only when it points to OmniRoute's file, then
  deletes the file
- spawn via execFile with an argv array (Hard Rule #13) and CODEX_HOME pinned
  to the directory OmniRoute writes into

Co-authored-by: Kalfian <hi@kalfian.com>
Inspired-by: decolua/9router#4406
The catalog file grows ~60 KB per model in ~/.codex, so the Apply checkbox
now starts unchecked. The route already writes only on modelCatalog === true;
an explicit modelCatalog: false test joins the omitted-flag test.
@diegosouzapw
diegosouzapw merged commit 2edaf42 into release/v3.8.52 Oct 17, 2026
7 checks passed
@github-actions

Copy link
Copy Markdown
Contributor

CI Coverage Report

  • Coverage job: skipped
  • PR test policy: success

Coverage artifact was not available for this run.

@diegosouzapw
diegosouzapw deleted the feat/port-pr-4406-codex-model-catalog-on-apply branch October 18, 2026 23:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant