返回 CodeWhale
CATALOG_REFRESH.md
根目录 / docs / CATALOG_REFRESH.md
1 # Catalog refresh
2
3 > 阅读简体中文版:[zh_hans/CATALOG_REFRESH.md](zh_hans/CATALOG_REFRESH.md)。
4
5 How Codewhale keeps model metadata current — what already auto-updates, what
6 is hand-maintained, and what a scheduled catalog job should (and should not) do.
7
8 Related docs: [`PROVIDERS.md`](./PROVIDERS.md), RFC
9 [`rfcs/UNIFIED_PROVIDER_LOGIN.md`](./rfcs/UNIFIED_PROVIDER_LOGIN.md).
10
11 ---
12
13 ## Short answer
14
15 | Question | Answer |
16 |---|---|
17 | Do users need a special model just to refresh models? | **No.** |
18 | Does Codewhale auto-update the public model catalog? | **Yes, at runtime**, from [Models.dev](https://models.dev/catalog.json), ~24 h TTL. |
19 | Is the offline bundled seed auto-committed in CI? | **No, but it is generated.** A maintainer runs `seed lock` and `seed render` and opens a PR; CI fails a hand edit (`seed render --check`). |
20 | Should an LLM rewrite catalog JSON? | **No.** Ingest is deterministic public JSON. An LLM can *review* a PR, not own the source of truth. |
21
22 ---
23
24 ## Layers (lowest → highest priority)
25
26 The shared catalog compiler applies these layers from lowest to highest:
27
28 ```
29 0 bundled Models.dev
30 10 live Models.dev
31 12 Codewhale corrections (applied to layers 0 and 10 as they load)
32 15 verified cloud facts (optional, off by default)
33 20 exact provider-owned live roster
34 25 Codewhale account roster
35 30 config.toml
36 40 user overrides
37 policy DENY (final)
38 ```
39
40 Codewhale corrections live in `crates/config/assets/catalog_corrections.json`.
41 They are field patches in the cloud-facts `ModelFact` shape, applied by the same
42 patch code to every Models.dev row, offline seed and live refresh alike, so a
43 correction holds on every install. Use one when an upstream fact is true but
44 misleading for a Codewhale route: `pricing_withheld` (a reason) clears the price
45 so the route reports it as unknown, for tiered rates, plan quota and billing
46 surfaces the catalog cannot tell apart; `max_output` and the other fields patch
47 limits. Every entry carries its reason. Corrections only fix rows that exist,
48 never add or hide one, and signed cloud facts can still override them. A
49 corrected row keeps its own source; a price a correction owns reports
50 `CatalogSource::CodewhaleBundled` as its price source. Do not hand-edit the offline seed
51 to hold a value back: a live refresh replaces the seed row, so the hold would
52 work only offline.
53
54 Cloud facts use the existing compiler and provider lake, as described in
55 [`CLOUD_FACTS.md`](./CLOUD_FACTS.md). Capability provenance and price provenance
56 are separate: a capability patch cannot relabel inherited prices. Cloud price
57 patches replace the entire price block; unspecified token classes stay unknown.
58
59 Route resolution also binds provider kind, configured identity and endpoint.
60 A fresh provider-owned roster is authoritative for its exact scope. Explicit
61 model selections remain explicit. Codex account observations/native cache and
62 Ollama endpoint tags keep their dedicated availability rules; a public catalog
63 row does not prove that an account can call that model. The installed Codex
64 `account/read` and `model/list` path is documented in
65 [`PROVIDERS.md`](./PROVIDERS.md).
66
67 Legacy completion lists remain a last fallback where no applicable catalog
68 exists. Bundled seeds and static transport/billing rules remain release-owned;
69 refreshing catalog metadata does not introduce a new wire dialect or change
70 credential/billing ownership.
71
72 Key code:
73
74 | Piece | Path | Role |
75 |---|---|---|
76 | Live fetch + cache | `crates/tui/src/models_dev_live.rs` | Background refresh, TTL, atomic write, freshness status |
77 | Schema / parse | `crates/config/src/models_dev.rs` | Network-free Models.dev JSON shape |
78 | Compile + provenance | `crates/config/src/catalog.rs` | Ordered sources, independent price provenance, policy deny, id normalization |
79 | Provider lake merge | `crates/tui/src/provider_lake.rs` | Shared catalog projection with exact route-scoped provider authority |
80 | Offline seed asset | `crates/config/assets/models_dev.bundled.json` | Compact offline fallback only (`_meta.role` says so) |
81 | Codewhale corrections | `crates/config/assets/catalog_corrections.json` | Field patches applied to every Models.dev row (`crates/config/src/catalog/corrections.rs`) |
82 | Validation script | `scripts/catalog_models_dev.py` | Secret-free fetch/validate dry-run (#4117) |
83 | Script tests | `scripts/catalog_models_dev_test.py` | Offline shape/scrub checks |
84
85 ---
86
87 ## What already auto-updates (runtime)
88
89 When the TUI/runtime starts (and is not disabled):
90
91 1. Seed pickers from the **on-disk cache** if present (even if stale).
92 2. If the cache is missing or older than **24 hours**, **background-fetch**
93 Models.dev (15 s timeout, explicit Codewhale user-agent, **no credentials**).
94 3. On success: atomic write to
95 `~/.codewhale/catalog/models-dev-catalog.json` and publish rows into
96 ProviderLake as `CatalogSource::ModelsDevLive` — layer 10, carrying no
97 endpoint fingerprint. Models.dev is a public catalog describing a model, so
98 a refreshed row is treated exactly like the layer-0 seed it supersedes and
99 stays correctable by layer 15. `CatalogSource::Live` is reserved for a
100 provider's own credential-scoped `/models` answer at layer 20.
101 4. On failure: keep prior cache or fall back to the **bundled** seed. Model
102 selection never hard-fails because Models.dev is down.
103
104 ### Manual force refresh
105
106 In the TUI:
107
108 ```text
109 /model refresh
110 ```
111
112 That dispatches `AppAction::RefreshModelsDevCatalog` (async; does not block
113 the composer). When admitted cloud-facts settings are enabled, it also requests
114 a cloud refresh; hard-disable and trust-key checks still apply. Implementation lives under
115 `crates/tui/src/commands/groups/core/core.rs` and
116 `crates/tui/src/models_dev_live.rs`.
117
118 ### Env knobs (tests / dogfood / offline)
119
120 | Variable | Effect |
121 |---|---|
122 | `CODEWHALE_MODELS_DEV_URL` | Override base URL or full `*.json` catalog URL |
123 | `CODEWHALE_MODELS_DEV_PATH` | Load catalog from a local file; skip network |
124 | `CODEWHALE_DISABLE_MODELS_DEV_FETCH` | Truthy → never hit the network (`1` / `true` / `yes` / `on`) |
125
126 Defaults:
127
128 - Catalog URL: `https://models.dev/catalog.json`
129 - TTL: `24 * 60 * 60` seconds (`DEFAULT_MODELS_DEV_TTL_SECS`)
130 - Cache file name: `models-dev-catalog.json` under the Codewhale `catalog`
131 state dir
132
133 Freshness values exposed for UI / status chips: `bundled` | `live` | `stale` |
134 `failed`.
135
136 ---
137
138 ## What does **not** auto-update (repo / release)
139
140 These stay hand-maintained or release-lane work until a scheduled PR lands:
141
142 | Surface | Why it drifts |
143 |---|---|
144 | `models_dev.bundled.json` | Offline seed, generated from a reviewed spec and a pinned lock (see below); refreshed by PR, not at runtime |
145 | `provider_descriptors.json` default model IDs | Product choice, not pure catalog dump; constant projections are generated |
146 | `catalog_corrections.json` `reviewed` | Intrinsic/selector/transport compatibility facts with exact source receipts; generated into the same seed |
147 | Rust pricing policy | Vendor billing windows, CNY conversion, withheld/tiered behavior; pure reference observations live in the reviewed supplement |
148 | New `ProviderKind` / wire dialect | Needs code, not only JSON |
149
150 Runtime live refresh **does not** rewrite those files. Users on a recent
151 install with network still see new Models.dev rows; fresh clones offline, CI
152 hermetic runs, and first-boot without cache still depend on the seed.
153
154 ---
155
156 ## Maintainer tooling (no LLM)
157
158 ### Validate / dry-run fetch
159
160 ```bash
161 # Fetch Models.dev + print counts (never writes disk)
162 python3 scripts/catalog_models_dev.py refresh
163
164 # Validate the committed offline seed still parses as Models.dev-shaped JSON
165 python3 scripts/catalog_models_dev.py snapshot --check \
166 crates/config/assets/models_dev.bundled.json
167
168 # OpenRouter public /models listing (no API key), dry-run only
169 python3 scripts/catalog_models_dev.py refresh --provider openrouter \
170 --sort newest --limit 100
171 ```
172
173 Design constraints of the script (intentional):
174
175 - Public endpoints only — no `Authorization` headers, no API keys.
176 - Credential-shaped keys are scrubbed if present in remote JSON.
177 - `refresh` and `snapshot` never write (`--write` / `--write-cache` fail
178 closed). The one write path is `seed lock`, which pins only the rows the
179 spec references, projected onto allowlisted fields.
180
181 ### Regenerating the offline seed (#6396)
182
183 `crates/config/assets/models_dev.bundled.json` is generated. Never edit it by
184 hand: CI runs `seed render --check` and fails on any difference.
185
186 | File | Holds | Edited by |
187 |---|---|---|
188 | `scripts/catalog/models_dev_seed.toml` | Which upstream rows to carry, their Codewhale provider id, wire id, default, canonical join, and the few curated rows upstream does not list | Hand, reviewed |
189 | `scripts/catalog/models_dev_seed.lock.json` | The referenced upstream rows, allowlisted, plus the source URL, fetch time and sha256 | `seed lock` only |
190 | `crates/config/assets/catalog_corrections.json` | Deliberate holds: withheld prices, clamped limits, reasoning controls | Hand, reviewed; applies online too |
191 | `crates/config/assets/catalog_corrections.json` `reviewed` | Source-preserved intrinsic facts, scoped aliases, completion references, public labels/source-support dates, pure route facts and reference prices | Hand, reviewed; source receipts retained |
192 | `crates/config/assets/models_dev.bundled.json` | The rendered seed including the reviewed supplement | `seed render` only |
193
194 The spec selects and maps; it cannot state a value that disagrees with
195 upstream (unknown keys are refused). If an upstream value is wrong for a
196 Codewhale route, add a correction instead. Corrections apply to both the seed
197 and live rows; a hold made only by hand-editing the seed would vanish on the
198 first live refresh.
199
200 1. `python3 scripts/catalog_models_dev.py seed lock --dry-run` prints the
201 review report: field changes per row, corrections that upstream now
202 agrees with (delete them), and upstream models not carried. It fails when
203 a referenced row disappeared upstream, or a curated row now exists
204 upstream (switch it to a derived row).
205 2. Edit the spec or the corrections as the report requires.
206 3. `python3 scripts/catalog_models_dev.py seed lock` writes the lock.
207 4. `python3 scripts/catalog_models_dev.py seed render` writes the seed.
208 5. Check that default wire IDs still match `DEFAULT_*_MODEL`, run the
209 catalog tests, and open a PR with the report in its body.
210
211 Optional: use a cheap model to summarize “new / removed / default-risk” in
212 the PR body — never as the author of the JSON.
213
214 ---
215
216 ## Recommended scheduled job (not shipped yet)
217
218 Goal: keep the **in-repo offline seed** from rotting, without giving CI write
219 power over secrets or unsupervised LLM rewrites.
220
221 ```text
222 cron (daily or weekly)
223 → fetch Models.dev (public, no keys)
224 → validate shape + scrub
225 → compare against crates/config/assets/models_dev.bundled.json
226 (and optionally report new ids vs provider defaults)
227 → if material change: open PR
228 title: chore(catalog): refresh Models.dev offline seed
229 → optional: include an agent-written, human-readable diff summary in the PR body
230 ```
231
232 Such a job would run `seed lock` and `seed render` and open the PR. A PR
233 opened with the default `GITHUB_TOKEN` does not trigger CI, so it needs a bot
234 token or GitHub App, which a maintainer has to provision.
235
236 ### In scope for automation
237
238 - Deterministic catalog ingest from Models.dev
239 - Secret-free PR diffs
240 - Drift reports (new model ids, missing defaults, pricing presence)
241
242 ### Out of scope for automation
243
244 - Claude Pro/Max / subscription OAuth “model discovery” (not a supported
245 third-party path; Anthropic expects API keys for third-party tools)
246 - LLM-authored edits to `models.rs` / `provider.rs` without review
247 - Force-pushing `main` or silent asset rewrites on the default branch
248 - Treating Models.dev as the only truth for OAuth-scoped routes (Codex
249 roster remains special-cased)
250
251 ### Suggested workflow home
252
253 `CodeWhale/.github/workflows/catalog-refresh.yml` (or similar), reusing
254 `scripts/catalog_models_dev.py` after a deliberate **write-safe** extension
255 that only runs in CI with a bot token for PR creation — still not on
256 `workflow_dispatch` without review if writes land in-repo.
257
258 Nightly today (`/.github/workflows/nightly.yml`) builds release artifacts
259 only; it does **not** refresh catalogs.
260
261 ---
262
263 ## Do we need a “model dedicated to updating models”?
264
265 **No for the core loop.**
266
267 | Job | Right tool |
268 |---|---|
269 | Keep known models/windows/prices from Models.dev fresh for users | Runtime live fetch (already shipped) |
270 | Keep offline seed + release assets current in git | Scheduled CI → PR (to build) |
271 | Decide whether to bump a product default model | Human (or agent *review* on the PR) |
272 | Wire a brand-new provider kind / dialect | Human PR + tests |
273
274 An LLM is optional **review** of a catalog PR. It is a poor **source of
275 truth** for catalog JSON.
276
277 ---
278
279 ## Auth note (Claude / Anthropic)
280
281 Anthropic model **catalog** refresh does not require Claude Pro/Max OAuth.
282 Models.dev is public. Codewhale’s Anthropic route remains **API-key-based**
283 for inference (`ANTHROPIC_API_KEY`). Do not couple catalog automation to
284 subscription OAuth or Claude Code identity headers.
285
286 ---
287
288 ## Quick operator checklist
289
290 - [ ] Running install: confirm network not blocked; optional
291 `/model refresh` after a big vendor launch.
292 - [ ] Offline / CI hermetic: set `CODEWHALE_DISABLE_MODELS_DEV_FETCH=1` or
293 point `CODEWHALE_MODELS_DEV_PATH` at a fixture.
294 - [ ] Before release: `seed lock --dry-run` to see how far the offline seed
295 has drifted from Models.dev; re-lock by PR if it matters. Skim
296 `PROVIDERS.md` for known drift.
297 - [ ] After Models.dev adds a major family you ship by default: consider
298 seed PR + default-model decision separately.
299 - [ ] Never paste API keys into catalog assets or the automation script env
300 for Models.dev refresh.
301
302 ---
303
304 ## Issue / design anchors
305
306 - Live Models.dev layer: #4187
307 - Bundled seed demoted (not competing truth): #4188
308 - Catalog automation script (validate / dry-run): #4117
309 - Generated offline seed and runtime corrections: #6396
310 - Deeper metadata inventory and drift list: the `codewhale-ops` repo
311
312 ### Retired unscoped metadata reader
313
314 The former models crate cache reader and its separate bundled asset, and the
315 TUI-only model registry, are retired. Existing installed legacy cache files are
316 preserved and are never imported as provider or public-label authority.
317 `config::catalog` owns the immutable compiled intrinsic projection; Engine's
318 existing provider lake and scoped catalog cache still own live/account/config
319 facts. Identical wire names at different endpoints do not create a canonical
320 join, a public label, or an unscoped price. The bundled freshness clock uses the
321 actual seed lock fetch timestamp and rechecks the current clock on each query.
322
323 Compatibility completion lists reference the provider descriptor defaults and
324 catalog groups rather than repeating their values. Kimi's generation default
325 remains distinct from direct/membership route limits. Unknown capabilities and
326 name-suffix budgets remain explicitly unverified. Website model dates describe
327 source support, with the prior proven dates retained in the same reviewed owner;
328 they do not assert a provider release date or current API availability.
329
329 lines MARKDOWN