Skip to content

core: embed models.dev snapshot instead of compile-time define - #41838

Open
kitlangton wants to merge 2 commits into
v2from
models-snapshot-core
Open

core: embed models.dev snapshot instead of compile-time define#41838
kitlangton wants to merge 2 commits into
v2from
models-snapshot-core

Conversation

@kitlangton

@kitlangton kitlangton commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

What

Moves the models.dev catalog snapshot into core as a real static import. The snapshot lives at packages/core/src/models-dev/snapshot.txt (raw api.json), is loaded in ModelsDev.layer via import snapshotText from "./models-dev/snapshot.txt" with { type: "text" }, and is refreshed with bun run script/update-models-snapshot.ts in packages/core. The OPENCODE_MODELS_DEV compile-time define and its build-time fetch plumbing are deleted.

Why

The catalog snapshot reached core only through OPENCODE_MODELS_DEV, a bare identifier injected as a define exclusively by the CLI builds. Any embedded/SDK-built server got no snapshot: with a cold cache and fetch disabled or failed, the catalog was empty, ModelsDevPlugin registered zero env methods, and the provider env-key credential chain in integration.ts resolveConnections silently never engaged even when e.g. ANTHROPIC_API_KEY was set. CLI users were masked by the define plus saved credentials, so the dead chain only showed in embedded profiles.

How

  • packages/core/script/update-models-snapshot.ts fetches https://models.opencode.ai/api.json, validates it parses non-empty, and writes src/models-dev/snapshot.txt; wired as the update-models-snapshot package script.
  • loadSnapshot decodes the imported text through the same CatalogJson schema used by fetchAndWrite (shared decodeCatalog helper), so a corrupt snapshot fails loudly instead of silently.
  • Import mechanism: a .txt text import rather than snapshot.json with { type: "text" } — Vite's built-in JSON plugin parses .json regardless of import attributes, which would hand the CLI build an object where the code expects a string. .txt is text natively under bun (test runner and compiled builds), typechecks via a src/text.d.ts wildcard declaration mirroring the existing markdown.d.ts, and the CLI's rawTextPlugin was extended from .md to .md/.txt for the vite-node build.
  • Deleted: the declare const OPENCODE_MODELS_DEV plumbing in models-dev.ts, the define entries in packages/cli/script/build.ts and packages/cli/vite.node.config.ts (including NodeBuildInput.models), and packages/cli/script/generate.ts (the build-time fetch, now unused).
  • ModelsDev.Options gains an optional snapshot: boolean (default true) so tests exercising cold-cache fetch paths can disable the bundled floor; production callers are unaffected.
  • Snapshot marked linguist-generated in .gitattributes.

Scope

The snapshot is the boot-time floor, not a replacement for live fetch: population order (file → KV cache → bundled snapshot → fetch) and the periodic refresh loop are unchanged. Trade-off: the bundled snapshot ages with the repo where CLI releases previously fetched a fresh copy at build time — but the 5-minute background refresh still updates the catalog on top at runtime, so there is no behavior regression, and hermetic builds (e.g. nix) no longer need a network fetch or MODELS_DEV_API_JSON injection. Regenerate with bun run update-models-snapshot in packages/core whenever freshness matters.

Testing

  • New regression test: with an empty KV cache and fetch disabled, get() returns the bundled snapshot with a non-empty provider list and environment names populated (anthropicANTHROPIC_API_KEY), confirming the env-key chain has data in embedded profiles.
  • Existing cold-cache fetch-path tests updated to pass snapshot: false where the bundled floor would mask the behavior under test.
  • packages/core: bun run typecheck clean, bun test 1665 pass / 0 fail.
  • packages/cli: bun run typecheck clean; script/build-node.ts --bundle-only --skip-install builds and the bundle runs --version/--help (exercises the .txt import through the vite build).
  • Repo root bun run typecheck clean (33/33 tasks).
  • bunx oxlint on touched files: 0 errors, no new warnings.

Review round (adversarial, pre-handoff)

A full adversarial review verified: population order byte-identical to v2 (file → KV → snapshot → fetch), refresh loop and cache writes ungated by snapshot presence (no bundle-time freeze), no dangling OPENCODE_MODELS_DEV/generate.ts references, snapshot artifact valid (183 providers), parse lazy (cold-cache only). It also found one Critical, fixed in the follow-up commit:

  • Vite shipped the snapshot as a dead asset URL. .txt is a Vite built-in asset type, so the raw-text plugin's load never ran — the node bundle contained snapshotText = "/assets/snapshot-*.txt" and cold-cache catalog access died decoding it. Fixed with enforce: "pre", plus a build assertion that fails if any text import surfaces as an asset URL (the bundle builds and runs --help convincingly without its content, so only content assertions catch this class).
  • Pre-existing bug, same class, repaired by the same fix: current published node CLIs ship "/assets/anthropic-*.txt" as prompt/tool-description strings (base.txt, anthropic prompt, patch tool). The build assertion now guards these too.
  • Nits addressed: redundant text.d.ts deleted (bun-types already declares *.txt — the earlier claim about needing it was wrong), refresh script gained a 100-provider floor and Windows-safe path printing.

The models.dev catalog snapshot previously reached core only through the
OPENCODE_MODELS_DEV compile-time define, injected solely by the CLI
builds. Any embedded/SDK-built server got no snapshot: with a cold cache
and fetch disabled or failed, the catalog was empty, ModelsDevPlugin
registered zero env methods, and the provider env-key credential chain
never engaged even with e.g. ANTHROPIC_API_KEY set.

Commit the snapshot at packages/core/src/models-dev/snapshot.txt (raw
api.json, refreshed via script/update-models-snapshot.ts) and load it in
loadSnapshot via a static text import, so every build profile gets the
same boot-time floor. Runtime fetch/refresh behavior is unchanged: file
and KV cache still take precedence, and the periodic refresh still
fetches on top. Delete the define plumbing from the CLI build scripts
and vite config, and the build-time fetch in script/generate.ts.
@kitlangton
kitlangton marked this pull request as ready for review August 11, 2026 20:55
Vite's built-in asset plugin claims known asset types (.txt) before
normal-priority plugins load, rewriting the import to an asset URL
string — the models.dev snapshot shipped as "/assets/snapshot-*.txt"
and cold-cache catalog access died decoding it. Pre-existing instances
of the same class: the node bundle's prompt and tool-description .txt
imports. enforce: "pre" intercepts first; a build assertion now fails
the build if text imports surface as asset URLs, since the bundle still
builds and runs --help convincingly without their content.

Also: drop the redundant text.d.ts (bun-types already declares *.txt),
give the snapshot refresh script a provider-count floor and
Windows-safe path printing.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant