# How Model Runtime Capabilities Determine Provider Listing in Pi Web

> Discover how Pi Web lists providers by examining model runtime capabilities. Learn about the three-layer pipeline querying authentication methods, credential status, and model counts.

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

---

**Pi Web builds its provider lists from the real-time capabilities reported by the `ModelRuntime` supplied by the `@earendil-works/pi-coding-agent` SDK, using a three-layer pipeline that queries authentication methods, credential status, and model counts.**

Pi Web's *Models* panel doesn't rely on hard-coded provider definitions. Instead, it dynamically generates API-key and OAuth provider listings based on what the underlying runtime actually supports. This capability-driven architecture automatically adapts to SDK updates and user-specific configuration stored in [`auth.json`](https://github.com/agegr/pi-web/blob/main/auth.json) or [`models.json`](https://github.com/agegr/pi-web/blob/main/models.json).

## The Three-Layer Provider Listing Pipeline

### Layer 1: Collect Raw Provider Data from ModelRuntime

The function `collectProviderListingInputs` in [`lib/provider-listing-runtime.ts`](https://github.com/agegr/pi-web/blob/main/lib/provider-listing-runtime.ts) queries the runtime for complete provider metadata:

- All available providers via `modelRuntime.getProviders()`
- Each provider's authentication configuration (`auth.apiKey?.login`, `auth.oauth`)
- Current authentication status via `modelRuntime.getProviderAuthStatus(id)`
- Stored credentials via `modelRuntime.listCredentials()`
- Model counts via `modelRuntime.getModels()`

```ts
// lib/provider-listing-runtime.ts
const models = modelRuntime.getModels();
const credentialTypes = new Map<string, ProviderCredentialType>();
// …populate credentialTypes from modelRuntime.listCredentials()
return modelRuntime.getProviders().map(provider => ({
  id: provider.id,
  name: provider.name,
  hasApiKeyLogin: Boolean(provider.auth.apiKey?.login),
  hasOAuth: Boolean(provider.auth.oauth),
  oauthName: provider.auth.oauth?.name,
  status: modelRuntime.getProviderAuthStatus(provider.id),
  credentialType: credentialTypes.get(provider.id),
  modelCount: models.filter(m => m.provider === provider.id).length,
}));

```

### Layer 2: Deduplicate Provider Entries

The helper `dedupeById` in [`lib/provider-listing.ts`](https://github.com/agegr/pi-web/blob/main/lib/provider-listing.ts) removes duplicate entries that appear when a provider supports multiple authentication methods.

### Layer 3: Build Distinct API-Key and OAuth Listings

**`buildApiKeyProviderList`** filters for API-key providers in [`lib/provider-listing.ts`](https://github.com/agegr/pi-web/blob/main/lib/provider-listing.ts):

- Excludes providers without `hasApiKeyLogin`
- Skips custom [`models.json`](https://github.com/agegr/pi-web/blob/main/models.json) entries (identified via `CUSTOM_PROVIDER_SOURCES`)
- Marks a provider as `configured` only when credentials exist **and** the credential type is not OAuth

```ts
// lib/provider-listing.ts
export function buildApiKeyProviderList(providers) {
  const result = [];
  for (const p of dedupeById(providers)) {
    if (!p.hasApiKeyLogin) continue;
    if (p.status.source && CUSTOM_PROVIDER_SOURCES.has(p.status.source)) continue;
    const configured = p.status.configured && p.credentialType !== "oauth";
    result.push({
      id: p.id,
      displayName: p.name,
      configured,
      ...(configured && p.status.source ? { source: p.status.source } : {}),
      modelCount: p.modelCount,
      supportsOAuth: p.hasOAuth,
    });
  }
  return result;
}

```

**`buildOAuthProviderList`** handles OAuth providers:

- Includes only providers with `hasOAuth` enabled
- Records login status via `credentialType === "oauth"`
- Uses display name fallbacks: `OAUTH_DISPLAY_NAMES[p.id]`, `p.oauthName`, or `p.name`

```ts
// lib/provider-listing.ts
export function buildOAuthProviderList(providers) {
  const result = [];
  for (const p of dedupeById(providers)) {
    if (!p.hasOAuth) continue;
    result.push({
      id: p.id,
      name: OAUTH_DISPLAY_NAMES[p.id] ?? p.oauthName ?? p.name,
      usesCallbackServer: false,
      loggedIn: p.credentialType === "oauth",
      supportsApiKey: p.hasApiKeyLogin,
    });
  }
  return result;
}

```

## Practical Implementation Example

```ts
import { collectProviderListingInputs } from "@/lib/provider-listing-runtime";
import {
  buildApiKeyProviderList,
  buildOAuthProviderList,
} from "@/lib/provider-listing";

/** Example: retrieve both provider lists for the Models panel */
async function getProviderLists(modelRuntime) {
  const inputs = await collectProviderListingInputs(modelRuntime);
  const apiKeyList = buildApiKeyProviderList(inputs);
  const oauthList = buildOAuthProviderList(inputs);
  return { apiKeyList, oauthList };
}

/* Usage inside an API route (app/api/models-config/catalog/route.ts) */
export async function GET() {
  const runtime = await getModelRuntime(); // provided by pi-coding-agent
  const { apiKeyList, oauthList } = await getProviderLists(runtime);
  return Response.json({ apiKeyList, oauthList });
}

```

## Key Source Files in agegr/pi-web

| File | Purpose |
|------|---------|
| [`lib/provider-listing.ts`](https://github.com/agegr/pi-web/blob/main/lib/provider-listing.ts) | Pure helpers that dedupe providers and build API-key & OAuth listings. |
| [`lib/provider-listing-runtime.ts`](https://github.com/agegr/pi-web/blob/main/lib/provider-listing-runtime.ts) | Adapter that transforms `ModelRuntime` into helper-compatible input shapes. |
| [`app/api/models-config/catalog/route.ts`](https://github.com/agegr/pi-web/blob/main/app/api/models-config/catalog/route.ts) | API entry point serving provider data to the UI. |

## Summary

- **Runtime-driven discovery**: Pi Web queries `ModelRuntime` methods (`getProviders()`, `getProviderAuthStatus()`, `listCredentials()`, `getModels()`) rather than using static provider definitions.
- **Dual listing generation**: Separate functions produce API-key and OAuth provider lists with distinct filtering and metadata logic.
- **Automatic adaptation**: The system responds to SDK changes (new providers, auth method changes) and user configuration without code modifications.
- **Credential-aware status**: A provider's "configured" state depends on actual stored credentials and their type, preventing false positives for OAuth-authenticated providers.

## Frequently Asked Questions

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

Pi Web treats dual-auth providers as separate entries in each list. The `dedupeById` helper prevents duplicates within a single list, but `buildApiKeyProviderList` includes `supportsOAuth: true` to indicate hybrid capability, while `buildOAuthProviderList` includes `supportsApiKey: true`. This allows the UI to show appropriate configuration options for each authentication method.

### Why does the configured status require `credentialType !== "oauth"` for API-key providers?

This check prevents confusion when a user authenticates via OAuth. Without it, the API-key provider would appear configured despite having no API key stored. The `credentialType` check ensures the "configured" badge accurately reflects API-key readiness, not general authentication status.

### What happens when a provider switches from OAuth-only to dual-auth support?

The Pi Web UI automatically adapts. The `modelRuntime.getProviders()` call returns updated `auth` configuration, `collectProviderListingInputs` sets both `hasApiKeyLogin` and `hasOAuth` flags, and both listing functions include the provider in their respective outputs. No manual provider list updates are required.

### Where does Pi Web store provider credentials and how are they accessed?

Credentials live in [`auth.json`](https://github.com/agegr/pi-web/blob/main/auth.json) (API keys) and are retrieved via `modelRuntime.listCredentials()`. OAuth tokens are similarly stored and tracked. The `modelRuntime.getProviderAuthStatus(id)` method abstracts the storage details, returning a standardized status object that the listing pipeline consumes.