Skip to content

Cache provider model catalogs on disk - #522

Open
mjlbach wants to merge 2 commits into
vercel-labs:mainfrom
mjlbach:perf/codex-model-catalog-cache
Open

Cache provider model catalogs on disk#522
mjlbach wants to merge 2 commits into
vercel-labs:mainfrom
mjlbach:perf/codex-model-catalog-cache

Conversation

@mjlbach

@mjlbach mjlbach commented Aug 29, 2026

Copy link
Copy Markdown

Summary

  • cache provider model catalogs on disk under ~/.fx/cache/<provider>-models-<partition-hash>.json with a six-hour TTL, so a cold CLI process stops paying an authenticated catalog fetch before its first request
  • shared mechanics in core/gateway/catalog_disk_cache.zig: a versioned envelope gated on a cache schema version and a provider format version, bounded file and body sizes, temp-file-and-rename writes, and every cache read/write failure treated as a miss rather than an error
  • Codex: partitioned by hashed account id, gated on the Codex protocol client version, and a cached body must pass the same validation as an endpoint response (including the reviewer-model check)
  • Grok: both catalog endpoints (subscription models and modalities) cached as one unit so a hit reproduces the same join the network path performs — a cold Grok process previously paid two sequential fetches
  • Gateway: cached at fetchModelCatalogResponse, the choke point shared by the runtime provider and the model picker; partitioned by credential source, team, account (or the credential itself when no stable account id exists), and resolved URL; bodies are stored only after they parse with the full-view rules, so a cached body can never be weaker than a fetched one
  • Grok and the gateway gate entries on the fx build version since their parse rules live in this binary; each provider disables its cache under its loopback e2e endpoint overrides, so e2e runs observe every catalog request and perform no durable writes

Measured live against chatgpt.com: cold Codex catalog fetch 1,221 ms, warm cache hit ~8 ms, on an otherwise identical request path. Note the model picker and fx models also serve from the cache within the TTL.

Prior art at pinned revisions: Codex uses a client-version-gated disk cache with a five-minute TTL (models-manager/src/cache.rs, manager.rs); OpenCode serves disk or embedded metadata immediately and refreshes in the background under a cross-process lock (models-dev.ts); pi compiles the Codex catalog into the release (openai-codex.models.ts).

Testing

  • zig fmt --check on touched files
  • zig build test — full suite passes; new tests cover the shared envelope (fresh round trip, stale/future/version-mismatch/schema-mismatch/malformed/missing entries, empty and oversized store rejection), Codex reviewer-model validation of cached bodies, the Grok combined-body round trip, and gateway partition separation by credential, team, URL, and public access
  • bun test tui-auth-source-selection.test.ts (54) and gateway-stream-lifecycle.test.ts pass; cli.test.ts is 117/118, with the one failure (fx status --json expecting update_channel: "stable" against a local dev build) reproducing identically on origin/main
  • live: first Codex request logs catalog cache outcome=miss then stored; the next logs outcome=hit

🤖 Generated with Claude Code

mjlbach and others added 2 commits August 29, 2026 09:42
Store the authenticated Codex model catalog under
~/.fx/cache/codex-models-<account-hash>.json and serve it for six hours
before refetching. Entries are gated on a cache schema version and the
Codex protocol client version, partitioned per account, written
atomically, and validated with the same rules as endpoint responses.
Every cache failure is a miss, never an error. The cache is disabled
under the FX_E2E_OPENAI_CODEX_MODELS_URL override so e2e runs observe
every request and perform no durable writes.

Measured live: cold catalog fetch 1,221 ms; warm cache hit ~8 ms.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Extract the envelope, load, and store mechanics into
core/gateway/catalog_disk_cache.zig and key every entry on a provider
config: file prefix, format version, TTL, and body bound. Codex keeps
its protocol client version as the format gate; Grok and the gateway
gate on the fx build version because their parse rules live in this
binary.

Grok caches both catalog endpoints as one unit so a hit reproduces the
same subscription/modalities join the network path performs. The
gateway caches the raw catalog body at fetchModelCatalogResponse, the
choke point shared by the runtime provider and the picker, partitioned
by credential source, team, account (or credential when no stable
account id exists), and resolved URL; bodies are stored only after they
parse with the full-view rules. Each provider disables its cache under
its loopback e2e endpoint overrides.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@mjlbach mjlbach changed the title Cache the Codex model catalog on disk Cache provider model catalogs on disk Aug 29, 2026
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