# How ModelsConfig in pi-web Reads and Writes models.json and Manages Auth Providers

> Learn how ModelsConfig in pi-web reads and writes models.json, managing auth providers and persisting user model configurations locally. Understand its atomic file operations and ProviderCredentialStore API usage.

- Repository: [Alex Yang/pi-web](https://github.com/agegr/pi-web)
- Tags: internals
- Published: 2026-07-31

---

**ModelsConfig in pi-web** persists user model configurations to `~/.pi/agent/models.json` using atomic file operations and synchronizes authentication state via [`auth.json`](https://github.com/agegr/pi-web/blob/main/auth.json) through the ProviderCredentialStore API.

The pi-web repository implements a robust configuration management system that separates model definitions from authentication credentials. By leveraging atomic file operations in [`lib/atomic-file.ts`](https://github.com/agegr/pi-web/blob/main/lib/atomic-file.ts), the application prevents data corruption during concurrent read/write cycles. This article examines the exact mechanisms used to persist model configurations and manage OAuth and API-key providers across [`models.json`](https://github.com/agegr/pi-web/blob/main/models.json) and [`auth.json`](https://github.com/agegr/pi-web/blob/main/auth.json).

## Reading Model Configuration from models.json

### Atomic File Access in the GET Handler

The read operation originates in [`app/api/models-config/route.ts`](https://github.com/agegr/pi-web/blob/main/app/api/models-config/route.ts), where the `GET` handler opens `~/.pi/agent/models.json` using **`AtomicFile.read()`**. This method guarantees a consistent snapshot by preventing reads during active write operations.

```typescript
// app/api/models-config/route.ts
import { AtomicFile } from '@/lib/atomic-file';
import { NextResponse } from 'next/server';
import { join } from 'path';
import { homedir } from 'os';

const MODELS_PATH = join(homedir(), '.pi', 'agent', 'models.json');

export async function GET() {
  try {
    const data = await AtomicFile.read(MODELS_PATH);
    const modelsConfig = JSON.parse(data);
    return NextResponse.json(modelsConfig);
  } catch (error) {
    return NextResponse.json(
      { error: 'Failed to read configuration' }, 
      { status: 500 }
    );
  }
}

```

The handler returns a `ModelsConfig` object containing an array of model entries and a map of provider credentials, which the frontend consumes to render the current state.

## Writing Configuration with Atomic Updates

### Schema Validation and Safe Persistence

When updating configurations via `PUT /api/models-config`, the system validates the incoming payload against the **ModelsConfig** schema before calling **`AtomicFile.write()`**. This utility writes to a temporary file and renames it atomically, eliminating the risk of partial writes or corruption during the operation.

```typescript
// app/api/models-config/route.ts
import { AtomicFile } from '@/lib/atomic-file';
import { validateModelsConfig } from '@/lib/validation';

export async function PUT(request: Request) {
  try {
    const payload = await request.json();
    const validatedConfig = validateModelsConfig(payload);
    
    const serialized = JSON.stringify(validatedConfig, null, 2);
    await AtomicFile.write(MODELS_PATH, serialized);
    
    return NextResponse.json({ success: true });
  } catch (error) {
    return NextResponse.json(
      { error: 'Invalid configuration format' }, 
      { status: 400 }
    );
  }
}

```

## Managing Authentication Providers

### Loading Credentials from auth.json

Authentication data resides in **`~/.pi/agent/auth.json`** and is managed by **[`lib/provider-credential-store.ts`](https://github.com/agegr/pi-web/blob/main/lib/provider-credential-store.ts)**. The `ProviderCredentialStore.load()` method reads this JSON file and builds a memory map keyed by provider ID, supporting both OAuth tokens and API keys.

```typescript
// lib/provider-credential-store.ts
import { AtomicFile } from './atomic-file';
import { join } from 'path';
import { homedir } from 'os';

const AUTH_PATH = join(homedir(), '.pi', 'agent', 'auth.json');

export interface ProviderCredential {
  type: 'oauth' | 'api-key';
  value: string;
  expiresAt?: number;
}

export class ProviderCredentialStore {
  private credentials: Map<string, ProviderCredential> = new Map();
  
  async load(): Promise<void> {
    const data = await AtomicFile.read(AUTH_PATH);
    const authData = JSON.parse(data);
    
    for (const [providerId, credential] of Object.entries(authData)) {
      this.credentials.set(providerId, credential as ProviderCredential);
    }
  }
  
  get(providerId: string): ProviderCredential | undefined {
    return this.credentials.get(providerId);
  }
}

```

### Deduplicating Providers in provider-listing.ts

The **[`lib/provider-listing.ts`](https://github.com/agegr/pi-web/blob/main/lib/provider-listing.ts)** module generates unified provider lists while avoiding duplicate entries. When a provider supports both OAuth and API-key authentication, **`AuthStorage.listProviders()`** returns the provider only once, preventing UI clutter while preserving information about available authentication methods.

```typescript
// lib/provider-listing.ts
import { ProviderCredentialStore } from './provider-credential-store';

interface Provider {
  id: string;
  name: string;
  authenticated: boolean;
  supportsOAuth: boolean;
  supportsApiKey: boolean;
}

export async function listProviders(): Promise<Provider[]> {
  const store = new ProviderCredentialStore();
  await store.load();
  
  const providers = await AuthStorage.listProviders();
  
  return providers.map(provider => ({
    id: provider.id,
    name: provider.name,
    authenticated: store.get(provider.id) !== undefined,
    supportsOAuth: provider.oauthSupported,
    supportsApiKey: provider.apiKeySupported
  }));
}

```

### Updating Credentials and State Synchronization

When users modify credentials via `/api/auth/login/*` or `/api/auth/api-key/*` routes, the system invokes **`ProviderCredentialStore.save()`** or **`remove()`**. These methods rewrite [`auth.json`](https://github.com/agegr/pi-web/blob/main/auth.json) using atomic operations. After any change, the ModelsConfig route re-reads the credentials so the UI reflects the current authentication state.

```typescript
// lib/provider-credential-store.ts
export class ProviderCredentialStore {
  async save(providerId: string, credential: ProviderCredential): Promise<void> {
    await this.load();
    this.credentials.set(providerId, credential);
    await this.persist();
  }
  
  async remove(providerId: string): Promise<void> {
    this.credentials.delete(providerId);
    await this.persist();
  }
  
  private async persist(): Promise<void> {
    const data = Object.fromEntries(this.credentials);
    await AtomicFile.write(AUTH_PATH, JSON.stringify(data, null, 2));
  }
}

```

## Frontend Integration

The **[`components/ModelsConfig.tsx`](https://github.com/agegr/pi-web/blob/main/components/ModelsConfig.tsx)** React component orchestrates the user interface, calling the API routes to fetch configurations, test model connections, and persist changes. It maintains local state while synchronizing with the atomic file operations occurring server-side in [`app/api/models-config/route.ts`](https://github.com/agegr/pi-web/blob/main/app/api/models-config/route.ts).

## Summary

- **ModelsConfig** stores user configurations in `~/.pi/agent/models.json` and manages them through [`app/api/models-config/route.ts`](https://github.com/agegr/pi-web/blob/main/app/api/models-config/route.ts) with GET and PUT handlers.
- **AtomicFile** operations in [`lib/atomic-file.ts`](https://github.com/agegr/pi-web/blob/main/lib/atomic-file.ts) ensure safe concurrent access via `AtomicFile.read()` and `AtomicFile.write()`, which use temporary files and atomic renames.
- **ProviderCredentialStore** in [`lib/provider-credential-store.ts`](https://github.com/agegr/pi-web/blob/main/lib/provider-credential-store.ts) handles authentication data in `~/.pi/agent/auth.json`, supporting both OAuth and API-key methods through a unified interface.
- **Provider deduplication** occurs in [`lib/provider-listing.ts`](https://github.com/agegr/pi-web/blob/main/lib/provider-listing.ts) through `AuthStorage.listProviders()`, preventing duplicate entries when providers support multiple authentication mechanisms.
- All credential modifications route through `/api/auth/login/*` or `/api/auth/api-key/*` endpoints, which trigger atomic writes followed by state synchronization in the ModelsConfig layer.

## Frequently Asked Questions

### Where does pi-web store model configurations?

Pi-web stores model configurations in **`~/.pi/agent/models.json`** within the user's home directory. This JSON file contains an array of model entries and provider credential references that the ModelsConfig API reads and writes via atomic file operations.

### How does ModelsConfig prevent file corruption during concurrent writes?

The system prevents corruption by using **`AtomicFile.write()`** from [`lib/atomic-file.ts`](https://github.com/agegr/pi-web/blob/main/lib/atomic-file.ts), which writes content to a temporary file and then renames it atomically to the target path. This ensures that concurrent readers always see complete, valid data and never encounter partial writes or truncated files.

### Can a provider support both OAuth and API key authentication simultaneously?

Yes, providers can implement both methods, but **[`lib/provider-listing.ts`](https://github.com/agegr/pi-web/blob/main/lib/provider-listing.ts)** deduplicates these entries through `AuthStorage.listProviders()`. The UI displays each provider once regardless of supported authentication mechanisms, while `ProviderCredentialStore` tracks the specific credential type and value used for that provider.

### How does the UI synchronize when authentication credentials change?

After credential updates via `/api/auth/login/*` or `/api/auth/api-key/*` routes, the ModelsConfig endpoint re-reads the authentication state from [`auth.json`](https://github.com/agegr/pi-web/blob/main/auth.json). This ensures that when [`components/ModelsConfig.tsx`](https://github.com/agegr/pi-web/blob/main/components/ModelsConfig.tsx) fetches configuration data, it receives the current authentication status without requiring a page reload or manual refresh.