How Model Configuration and Provider Authentication Work in Pi Web

TL;DR: Pi Web stores model configurations in ~/.pi/agent/models.json (lib/models-config-store.ts) and provider credentials in ~/.pi/agent/auth.json (lib/provider-credential-store.ts), with file locking, cache invalidation, and automatic UI synchronization through dedicated API routes.

Pi Web separates model configuration from provider authentication into two distinct persistence layers. This design lets you customize which AI models are available while securely managing API keys and OAuth tokens. All configuration flows through the Pi agent directory at ~/.pi/agent/, with TypeScript utilities handling normalization, caching, and concurrent access safety.

Model Configuration Storage

Pi Web persists model settings in a JSON file that survives restarts and syncs with the UI in real time.

The models.json File Structure

The configuration lives at ~/.pi/agent/models.json. It maps providers to their available models and optional pricing data:

{
  "providers": {
    "anthropic": {
      "models": [{
        "id": "claude-3-5-sonnet",
        "name": "Claude 3.5 Sonnet",
        "cost": { "input": 0.003, "output": 0.015, "cacheRead": 0.0003, "cacheWrite": 0.00375 }
      }]
    }
  }
}

Reading and Writing Configuration

The lib/models-config-store.ts module provides three core functions:

  • readModelsConfig() — Loads and parses models.json, returning an empty object if the file is missing or malformed.
  • writeModelsConfig(config) — Normalizes data (fills missing cost keys, strips empty providers) and writes atomically.
  • invalidateModelsCache() — Clears the in-memory cache so subsequent reads reflect the new data.

Example: Adding a new model programmatically

import { readModelsConfig, writeModelsConfig } from "@/lib/models-config-store";

const cfg = readModelsConfig();
cfg.providers ??= {};
cfg.providers.openai = {
  models: [{
    id: "gpt-4o",
    name: "GPT-4o",
    cost: { input: 0.005, output: 0.015, cacheRead: 0, cacheWrite: 0 }
  }]
};

writeModelsConfig(cfg); // Normalizes, writes, and invalidates cache

The normalization step enforces that every model's cost object contains exactly four keys: input, output, cacheRead, and cacheWrite. Missing values default to 0.

Provider Credential Storage

API keys and OAuth tokens live in a separate file with stricter access controls.

The auth.json File and Security Model

Credentials are stored in ~/.pi/agent/auth.json with 0600 permissions (owner read/write only). The lib/provider-credential-store.ts module implements:

  • storeProviderCredential(providerId, credential) — Writes or updates a credential object.
  • removeStoredCredentialIfType(providerId, expectedType) — Conditionally removes credentials to prevent accidental OAuth deletion during API-key operations.

Both functions wrap their read-modify-write cycles in proper-lockfile locks to eliminate race conditions when multiple processes access the file.

Storing and Removing Credentials

Store an API key:

import { storeProviderCredential } from "@/lib/provider-credential-store";

await storeProviderCredential("openai", {
  type: "api_key",
  key: "sk-xxxxxxxxxxxxxxxxxxxxxxxx"
});

Safely remove only OAuth credentials (leaving API keys intact):

import { removeStoredCredentialIfType } from "@/lib/provider-credential-store";

const result = await removeStoredCredentialIfType("anthropic", "oauth");
// result.status: "removed" | "not_found" | "type_mismatch" | "error"

The type_mismatch return value protects against concurrent auth method changes — the credential stays in place if it doesn't match the expected type.

Building Provider Lists for the UI

The frontend displays available providers through lib/provider-listing.ts, which dynamically discovers capabilities from the Pi SDK without hardcoding provider names.

Provider Discovery and Deduplication

The module exports two key functions:

  • buildApiKeyProviderList(inputs) — Filters providers supporting API-key authentication.
  • buildOAuthProviderList(inputs) — Filters providers supporting OAuth flows.

Both accept ProviderListingInput objects describing each provider's status, capabilities, and current authentication state. The functions automatically deduplicate providers that appear with multiple authentication methods (e.g., Anthropic supporting both API key and OAuth).

Example: Generating provider lists:

import { buildApiKeyProviderList, buildOAuthProviderList } from "@/lib/provider-listing";

const inputs: ProviderListingInput[] = /* obtained from ModelRuntime */;
const apiKeyProviders = buildApiKeyProviderList(inputs);
const oauthProviders = buildOAuthProviderList(inputs);

This approach means new providers automatically appear in the UI as soon as the Pi SDK defines them — no code changes required.

Model Catalog and Metadata Resolution

Raw provider catalogs need normalization before display. The lib/model-catalog.ts module handles this transformation.

Catalog Processing Functions

  • flattenModelsDevCatalog(raw) — Converts nested provider responses into flat ModelCatalogEntry objects.
  • recommendModelCatalogPreset(entries, modelId, providerId) — Uses consensus algorithms to infer missing metadata and pricing.
  • searchModelCatalog(entries, query) — Filters and ranks models by relevance.

Example: Resolving model metadata:

import { flattenModelsDevCatalog, recommendModelCatalogPreset } from "@/lib/model-catalog";

const rawCatalog = /* fetched from provider's /models endpoint */;
const entries = flattenModelsDevCatalog(rawCatalog);
const recommendation = recommendModelCatalogPreset(entries, "claude-3-5-sonnet", "anthropic");

console.log(recommendation.preset); // Normalized name, context window, pricing

When provider data is incomplete, the consensus algorithm falls back to built-in knowledge bases while flagging confidence levels.

API Routes and Data Flow

The frontend synchronizes with these stores through Next.js API routes:

Endpoint Handler Purpose
GET /api/models-config readModelsConfig() Fetch current model configuration
POST /api/models-config writeModelsConfig() Save updated configuration
GET /api/auth/all-providers buildApiKeyProviderList() + buildOAuthProviderList() List providers and auth status
POST /api/auth/api-key/[provider] storeProviderCredential() Add or update API key

The complete flow when a user adds a model:

  1. UI calls GET /api/models-config to populate the editor.
  2. User saves changes → UI posts to POST /api/models-config.
  3. writeModelsConfig() normalizes data, writes to disk, and calls invalidateModelsCache().
  4. Subsequent reads see fresh data immediately.

Caching and Performance

Pi Web implements a two-tier caching strategy:

  • In-memory cache (lib/models-cache.ts) — Avoids repeated disk reads during the same session.
  • Cache invalidation — Triggered automatically on every write to models.json.

The credential store bypasses caching entirely for security: auth.json is read fresh on every credential operation, with file locks ensuring consistency.

Summary

  • Model configuration persists to ~/.pi/agent/models.json via lib/models-config-store.ts, with automatic normalization and cache invalidation.
  • Provider credentials store in ~/.pi/agent/auth.json via lib/provider-credential-store.ts, protected by 0600 permissions and file-level locking.
  • Provider discovery in lib/provider-listing.ts dynamically builds UI lists without hardcoding provider names.
  • Model metadata resolves through lib/model-catalog.ts using consensus algorithms for missing data.
  • API routes bridge frontend and backend, ensuring real-time synchronization with proper error handling.

Frequently Asked Questions

What happens if models.json is corrupted or missing?

readModelsConfig() returns an empty object {}, allowing the UI to start fresh. The file is recreated on the first save operation via writeModelsConfig().

Can multiple Pi Web instances run concurrently without corrupting configuration?

Yes. The credential store uses proper-lockfile for exclusive file access. Model configuration writes are atomic (file replacement), and cache invalidation ensures readers see consistent state.

How does Pi Web handle providers that support both API key and OAuth?

lib/provider-listing.ts deduplicates these automatically. The UI shows a single provider entry with both authentication options available. removeStoredCredentialIfType() prevents one method from accidentally deleting the other's credentials.

Where does pricing information come from if not specified in models.json?

recommendModelCatalogPreset() in lib/model-catalog.ts applies consensus algorithms across multiple data sources: provider APIs, embedded knowledge bases, and community-contributed metadata. Explicit values in models.json always take precedence.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →