# How Model Configuration and Provider Authentication Work in Pi Web

> Learn how Pi Web manages model configurations and provider authentication. Discover where your settings are stored and how the system synchronizes them automatically.

- Repository: [Alex Yang/pi-web](https://github.com/agegr/pi-web)
- Tags: deep-dive
- Published: 2026-08-14

---

**TL;DR:** Pi Web stores model configurations in `~/.pi/agent/models.json` ([`lib/models-config-store.ts`](https://github.com/agegr/pi-web/blob/main/lib/models-config-store.ts)) and provider credentials in `~/.pi/agent/auth.json` ([`lib/provider-credential-store.ts`](https://github.com/agegr/pi-web/blob/main/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:

```json
{
  "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`](https://github.com/agegr/pi-web/blob/main/lib/models-config-store.ts)** module provides three core functions:

- **`readModelsConfig()`** — Loads and parses [`models.json`](https://github.com/agegr/pi-web/blob/main/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

```typescript
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`](https://github.com/agegr/pi-web/blob/main/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:

```typescript
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):

```typescript
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`](https://github.com/agegr/pi-web/blob/main/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:

```typescript
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`](https://github.com/agegr/pi-web/blob/main/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:

```typescript
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`](https://github.com/agegr/pi-web/blob/main/lib/models-cache.ts)) — Avoids repeated disk reads during the same session.
- **Cache invalidation** — Triggered automatically on every write to [`models.json`](https://github.com/agegr/pi-web/blob/main/models.json).

The credential store bypasses caching entirely for security: **[`auth.json`](https://github.com/agegr/pi-web/blob/main/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`](https://github.com/agegr/pi-web/blob/main/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`](https://github.com/agegr/pi-web/blob/main/lib/provider-credential-store.ts), protected by 0600 permissions and file-level locking.
- **Provider discovery** in [`lib/provider-listing.ts`](https://github.com/agegr/pi-web/blob/main/lib/provider-listing.ts) dynamically builds UI lists without hardcoding provider names.
- **Model metadata** resolves through [`lib/model-catalog.ts`](https://github.com/agegr/pi-web/blob/main/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`](https://github.com/agegr/pi-web/blob/main/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`](https://github.com/agegr/pi-web/blob/main/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`](https://github.com/agegr/pi-web/blob/main/models.json) always take precedence.