# How Models Are Configured, Tested, and Selected in Pi‑Web: A Deep Dive into the Source Code

> Discover how Pi-Web configures, tests, and selects AI models using JSON, live endpoint validation, and efficient caching. Explore the source code for a deep dive into model management.

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

---

**Pi‑Web manages AI models through a JSON-based provider map stored in `~/.pi/agent/models.json`, validates credentials via a live test endpoint, and binds the chosen provider and model to each chat session through an in-memory cache and RPC manager.**

The `agegr/pi-web` repository implements a complete lifecycle for AI model management inside a local chat application. Every provider credential, endpoint URL, and default preference is tracked through a small set of files and API routes that let users add, verify, and switch models without restarting the server. This guide breaks down exactly how model configuration, testing, and selection in pi-web work, referencing the actual source files and handlers that power the pipeline.

## Persisting Provider Configurations

Pi‑Web keeps the ground truth for all model providers in a JSON file located at **`~/.pi/agent/models.json`** under the user’s Pi data directory. This file is not bundled with the repository; it is created at runtime and holds each provider’s API key, endpoint overrides, and available model list.

The **Next.js app router** exposes this file through **[`app/api/models-config/route.ts`](https://github.com/agegr/pi-web/blob/main/app/api/models-config/route.ts)**. A **GET** request reads **[`models.json`](https://github.com/agegr/pi-web/blob/main/models.json)** and returns the provider-model map to the client. A **PUT** request writes a new JSON object to the same file and immediately calls **`invalidateModelsCache()`** exported by **[`lib/models-cache.ts`](https://github.com/agegr/pi-web/blob/main/lib/models-cache.ts)** so the **in-memory cache** used by the rest of the application is refreshed.

## Editing and Saving Models in the UI

When the settings page mounts, the React component **[`components/ModelsConfig.tsx`](https://github.com/agegr/pi-web/blob/main/components/ModelsConfig.tsx)** sends a **GET** request to `/api/models-config` to load the current map. It renders an editable table where each row represents a provider and columns expose fields such as the API key and endpoint URL.

After the user finishes editing, the component issues a **PUT** request with the updated payload. The server persists the change to disk, invalidates the cache, and returns a success flag, ensuring the UI and backend stay synchronized.

## Testing Model Configurations Before Use

### The Live Test Endpoint

Before a provider is trusted, pi-web validates it through **[`app/api/models-config/test/route.ts`](https://github.com/agegr/pi-web/blob/main/app/api/models-config/test/route.ts)**. This route accepts a **POST** request containing a provider and model identifier. It uses Pi’s internal SDK to make a lightweight call—typically a model list or a brief completion—and returns either `{ ok: true }` or an object with an `error` description.

### Immediate Feedback Loop

This design prevents broken chat sessions caused by stale API keys or unreachable endpoints. Users receive immediate feedback in **[`components/ModelsConfig.tsx`](https://github.com/agegr/pi-web/blob/main/components/ModelsConfig.tsx)** before the configuration is ever saved to **`~/.pi/agent/models.json`**, guaranteeing that only verified providers appear in the selector.

## Selecting a Model for a Chat Session

### Default Model from Global Settings

When a new session is created, pi-web reads the global settings file **`~/.pi/agent/settings.json`** through **`getAgentDir()`** utilities and extracts the **`defaultModel`** field. The client uses this value to pre-fill the Model dropdown so every conversation starts with the user’s preferred AI unless explicitly changed.

### Provider List Rendering

The client fetches the complete provider-model map from `/api/models-config` and builds a grouped selector that lists only providers with a valid configuration. This guards the UI from offering models that lack credentials.

### Runtime Model Switching

If the user chooses a different model inside the **`ChatWindow`** component, the session state updates its **`modelId`** and **`provider`** fields. The **RPC manager** defined in **[`lib/rpc-manager.ts`](https://github.com/agegr/pi-web/blob/main/lib/rpc-manager.ts)** reads these fields to instantiate the correct model for the next completion request, allowing mid-session switches without data loss.

## Client-Side Integration Examples

The following patterns show how the pi-web frontend interacts with the model configuration API.

Fetch the current configuration on mount:

```typescript
// Fetch current model config on the client
async function loadModels() {
  const resp = await fetch('/api/models-config');
  const models = await resp.json();
  // { providers: { openai: { apiKey: "...", models: ["gpt-4", "gpt-3.5"] }, … } }
  return models;
}

```

Persist edits and invalidate the server cache:

```typescript
// Save edited config to disk and invalidate the server cache
async function saveModels(updated: Record<string, unknown>) {
  const resp = await fetch('/api/models-config', {
    method: 'PUT',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(updated),
  });
  const result = await resp.json(); // { success: true }
  return result;
}

```

Verify connectivity before committing changes:

```typescript
// Test a single provider/model configuration
async function testModel(provider: string, modelId: string) {
  const resp = await fetch('/api/models-config/test', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ provider, modelId }),
  });
  const result = await resp.json(); // { ok: true } or { error: "..." }
  return result;
}

```

Start a conversation with a chosen provider and model:

```typescript
// Start a new chat session with a selected provider and model
async function startSession(selectedProvider: string, selectedModel: string) {
  const session = await fetch('/api/agent/new', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      cwd: '/home/user',
      message: 'Hello',
      provider: selectedProvider,
      modelId: selectedModel,
    }),
  });
  const data = await session.json();
  // data contains the new session id — the UI renders chat using that model
  return data;
}

```

## Summary

- **Persistent storage:** Provider settings live in **`~/.pi/agent/models.json`** and are served by **[`app/api/models-config/route.ts`](https://github.com/agegr/pi-web/blob/main/app/api/models-config/route.ts)**.
- **Cache invalidation:** Every **PUT** call triggers **`invalidateModelsCache()`** in **[`lib/models-cache.ts`](https://github.com/agegr/pi-web/blob/main/lib/models-cache.ts)** to refresh the in-memory state.
- **Live validation:** The `/api/models-config/test` endpoint in **[`app/api/models-config/test/route.ts`](https://github.com/agegr/pi-web/blob/main/app/api/models-config/test/route.ts)** performs a real SDK call to confirm credentials before saving.
- **Default selection:** New sessions read **`defaultModel`** from **`~/.pi/agent/settings.json`** via **`getAgentDir()`**.
- **Runtime delegation:** **[`lib/rpc-manager.ts`](https://github.com/agegr/pi-web/blob/main/lib/rpc-manager.ts)** uses the session’s **`provider`** and **`modelId`** fields to route completions to the correct backend.

## Frequently Asked Questions

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

Pi‑Web persists all provider credentials and model lists in **`~/.pi/agent/models.json`** inside the user’s Pi data directory. This file is read and written by the handlers in **[`app/api/models-config/route.ts`](https://github.com/agegr/pi-web/blob/main/app/api/models-config/route.ts)**.

### How does pi-web test a model before it is saved?

The application sends a **POST** request to `/api/models-config/test`, handled by **[`app/api/models-config/test/route.ts`](https://github.com/agegr/pi-web/blob/main/app/api/models-config/test/route.ts)**. The handler uses Pi’s internal SDK to attempt a lightweight call against the provider and returns a success or error payload.

### How is the default model chosen for new chat sessions?

When a session is created, pi-web reads **`~/.pi/agent/settings.json`** using **`getAgentDir()`** utilities and extracts the **`defaultModel`** value. The client pre-fills the model selector with this value.

### Can users switch models after a session has started?

Yes. The **`ChatWindow`** component updates the active session’s **`provider`** and **`modelId`** fields, and **[`lib/rpc-manager.ts`](https://github.com/agegr/pi-web/blob/main/lib/rpc-manager.ts)** instantiates the newly selected model for subsequent completions without restarting the session.