All RFCs
RFC 0039
Discussion

Dynamic Model Configuration In Pi

Visibility Public
pi
Authors
Armin Ronacher
Created
May 29, 2026
Updated
May 29, 2026

Pi currently ships a committed TypeScript model catalog generated by generate-models.ts. The generator fetches from models.dev, OpenRouter, and Vercel AI Gateway, then applies many Pi-specific overrides before writing models.generated.ts which is committed to source control and updates regularly.

Currently that file is around 426 KB. It contains more than 900 models across 32 providers and it’s very likely that this file will continue to grow. We already know that the models.dev source data is much larger than what we actually ship. That file was changed almost 400 times over the last 90 days alone. From a user and maintainer perspective, model metadata changes require a new Pi release and whenever someone makes a PR or commit, they often have to reset that file manually to prevent conflicts.

Because of this our desire is to change how we are working with this file, and move it towards an approach where that file can (optionally) be downloaded from pi.dev in pieces, and we stop committing that file.

Goal

We want to move model metadata from a committed generated TypeScript value file to a dynamic, provider-scoped model catalog served by pi.dev, while preserving offline operation and useful SDK type safety.

Pi releases should still bundle a fallback catalog so fresh installs and PI_OFFLINE work without network access. When online, Pi should periodically refresh only the providers the user has configured, so model additions, pricing fixes, compatibility flags, and context-window updates can ship without publishing a new Pi release.

We also want to be able to checkpoint some model updates, so that very old Pi versions do not fetch incompatible meta information.

Proposed Design

Split compile-time types from runtime catalog data.
We want to keep a lightweight generated type definition file for KnownProvider with provider/model ID autocomplete, and API mapping but remove large runtime model values from committed TypeScript. From that point onwards runtime model data comes from JSON catalogs.

Generate one JSON catalog per provider.
We will generate output provider-scoped files such as anthropic.json, openrouter.json, and store that in R2 for models.dev. These files then also get bundled during package/build/release so offline mode continues to work. We however do not commit the generated provider catalog files unless explicitly needed for release artifacts. (TODO: unclear if we want to commit them once per release or just leave out entirely)

During local development npm run build will always produce those files.

Store and Serve Catalog files.
We will make the catalog files available from pi.dev as static files. We will let pi fetch there at a low-frequency schedule (eg: once per day) for all providers that are configured. We also want to send along a parameter for the current model that is selected when a provider refreshes. For motivation see below.

Catalog storage should include schema/version compatibility, e.g. schemaVersion, minPiVersion, and possibly maxPiVersion or channel selection. The details need to be ironed out. It’s just important that if a new model definition requires newer provider code or a dependency update, pi.dev can serve an older compatible catalog to older Pi versions.

Loading provider files in Pi.

  1. Start with bundled fallback catalogs.
  2. Overlay cached remote catalogs when present and compatible.
  3. Overlay user ~/.pi/agent/models.json custom models, provider overrides, and modelOverrides last, preserving current behavior.

Analytics and Model Selection

An added benefit of fetching provider configs dynamically is that it gives us a basic view of what is happening. We are increasingly presented with an avalanche of issues related to model configuration and we are blind with regards to prioritization. Once Pi fetches provider files dynamically we are in a position where we can use the fetch count to deduce provide popularity.

By also sending the current model as a query string parameter for the currently used provider we get a tiny extra signal that can help us to prioritize within it. This signal won’t be very strong, but should be sufficient for us to better understand where our focus should lie…