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

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. A GET request reads 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 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 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. 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 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 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:

// 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:

// 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:

// 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:

// 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.
  • Cache invalidation: Every PUT call triggers invalidateModelsCache() in 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 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 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.

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. 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 instantiates the newly selected model for subsequent completions without restarting the session.

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 →