How Pi Web Lists and Handles Capability-Driven Providers
Pi Web discovers and categorizes providers for its Models and Auth panels by inspecting each provider's declared capabilities—specifically whether it supports API-key login, OAuth login, or both—rather than relying on hard-coded provider lists.
The agegr/pi-web repository implements a two-stage pipeline that transforms raw provider metadata from the Pi SDK into UI-ready lists. This design automatically adapts when providers add or remove authentication methods, eliminating manual synchronization between the SDK and the web interface.
Understanding the Provider Listing Pipeline
Pi Web's provider discovery splits cleanly into data collection and list derivation. This separation keeps the core logic testable and the API routes thin.
Stage 1: Collect Raw Provider Data
The collectProviderListingInputs function in lib/provider-listing-runtime.ts interrogates the Pi SDK to build a uniform ProviderListingInput for every known provider.
It performs three key operations:
- Pulls the complete model list via
modelRuntime.getModels() - Gathers credential types (
api_keyoroauth) throughmodelRuntime.listCredentials() - Records per-provider metadata including auth status and model count
// lib/provider-listing-runtime.ts
export async function collectProviderListingInputs(
modelRuntime: ModelRuntime,
): Promise<ProviderListingInput[]> {
const models = modelRuntime.getModels();
const credentialTypes = new Map<string, ProviderCredentialType>();
for (const credential of await modelRuntime.listCredentials()) {
if (credential.type === "api_key" || credential.type === "oauth") {
credentialTypes.set(credential.providerId, credential.type);
}
}
return modelRuntime.getProviders().map(provider => ({
id: provider.id,
name: provider.name,
hasApiKeyLogin: Boolean(provider.auth.apiKey?.login),
hasOAuth: Boolean(provider.auth.oauth),
...(provider.auth.oauth?.name ? { oauthName: provider.auth.oauth.name } : {}),
status: modelRuntime.getProviderAuthStatus(provider.id),
...(credentialTypes.has(provider.id)
? { credentialType: credentialTypes.get(provider.id) }
: {}),
modelCount: models.filter(m => m.provider === provider.id).length,
}));
}
Each provider object captures boolean capability flags (hasApiKeyLogin, hasOAuth) that drive all downstream filtering decisions.
Stage 2: Derive Concrete Lists
The lib/provider-listing.ts module contains two pure functions that transform ProviderListingInput arrays into panel-specific formats: buildApiKeyProviderList and buildOAuthProviderList.
Building the API-Key Provider List
The buildApiKeyProviderList function applies three filters:
- Capability gate: Excludes providers without
hasApiKeyLogin - Source exclusion: Skips custom providers from
models.json(rendered separately) - Credential conflict prevention: Marks a provider as configured only when the stored credential is not an OAuth token
This last check prevents dual-auth providers like Anthropic from appearing in both panels simultaneously when the user has authenticated via OAuth.
// lib/provider-listing.ts – API-key list
export function buildApiKeyProviderList(
providers: readonly ProviderListingInput[],
): ApiKeyProviderListing[] {
const result: ApiKeyProviderListing[] = [];
for (const provider of dedupeById(providers)) {
if (!provider.hasApiKeyLogin) continue;
if (provider.status.source && CUSTOM_PROVIDER_SOURCES.has(provider.status.source)) continue;
const configured = provider.status.configured && provider.credentialType !== "oauth";
result.push({
id: provider.id,
displayName: provider.name,
configured,
...(configured && provider.status.source ? { source: provider.status.source } : {}),
modelCount: provider.modelCount,
supportsOAuth: provider.hasOAuth,
});
}
return result;
}
Building the OAuth Provider List
The buildOAuthProviderList function follows a simpler pattern:
- Includes only providers with
hasOAuthset to true - Resolves display names through
OAUTH_DISPLAY_NAMESmapping or the provider'soauthName - Sets
loggedInbased on whether the storedcredentialTypeequals"oauth"
// lib/provider-listing.ts – OAuth list
export function buildOAuthProviderList(
providers: readonly ProviderListingInput[],
): OAuthProviderListing[] {
const result: OAuthProviderListing[] = [];
for (const provider of dedupeById(providers)) {
if (!provider.hasOAuth) continue;
result.push({
id: provider.id,
name: OAUTH_DISPLAY_NAMES[provider.id] ?? provider.oauthName ?? provider.name,
usesCallbackServer: false,
loggedIn: provider.credentialType === "oauth",
supportsApiKey: provider.hasApiKeyLogin,
});
}
return result;
}
Both functions use dedupeById to handle any duplicate provider entries gracefully.
How API Routes Expose Provider Lists
The capability-driven lists surface through two distinct API endpoints, each delegating to the pipeline described above.
API-Key Providers Endpoint
The GET /api/auth/all-providers route returns providers capable of API-key authentication:
// app/api/auth/all-providers/route.ts
const modelRuntime = await ModelRuntime.create();
const providers = buildApiKeyProviderList(
await collectProviderListingInputs(modelRuntime),
);
return Response.json({ providers });
OAuth Providers Endpoint
The GET /api/auth/providers route returns OAuth-capable providers:
// app/api/auth/providers/route.ts
const modelRuntime = await ModelRuntime.create();
const providers = buildOAuthProviderList(
await collectProviderListingInputs(modelRuntime),
);
return Response.json({ providers });
Consuming Provider Lists from the Client
Frontend code fetches these lists through standard fetch calls against the API routes.
Fetch API-key capable providers:
import { agentClient } from "@/lib/agent-client";
async function fetchApiKeyProviders() {
const resp = await fetch("/api/auth/all-providers");
const { providers } = await resp.json();
return providers; // [{ id, displayName, configured, supportsOAuth, … }]
}
Fetch OAuth capable providers:
async function fetchOAuthProviders() {
const resp = await fetch("/api/auth/providers");
const { providers } = await resp.json();
return providers; // [{ id, name, loggedIn, supportsApiKey, … }]
}
Key Files in the Provider Listing System
| File | Purpose |
|---|---|
lib/provider-listing.ts |
Pure logic for transforming raw inputs into UI-ready lists; defines buildApiKeyProviderList and buildOAuthProviderList |
lib/provider-listing-runtime.ts |
Runtime data collection from Pi SDK; defines collectProviderListingInputs |
app/api/auth/all-providers/route.ts |
API endpoint for API-key provider discovery |
app/api/auth/providers/route.ts |
API endpoint for OAuth provider discovery |
lib/provider-credential-store.ts |
Persistence layer for provider credentials |
Summary
- Pi Web uses capability flags (
hasApiKeyLogin,hasOAuth) to dynamically categorize providers instead of hard-coded IDs - Two-stage pipeline:
collectProviderListingInputsgathers raw data from the Pi SDK, thenbuildApiKeyProviderListandbuildOAuthProviderListderive filtered, formatted lists - Dual-auth providers (those supporting both API-key and OAuth) are handled carefully: OAuth credentials prevent API-key panel membership to avoid duplicate entries
- Automatic synchronization: Provider lists stay current with SDK updates because they're rebuilt from capability declarations on every request
- Clean separation of concerns between runtime data collection, pure transformation logic, and thin API route handlers
Frequently Asked Questions
What makes Pi Web's provider listing "capability-driven"?
The system inspects each provider's declared authentication capabilities—specifically whether provider.auth.apiKey?.login or provider.auth.oauth exists—rather than maintaining a static registry. When the Pi SDK updates a provider to add OAuth support, Pi Web automatically includes it in the OAuth panel without code changes.
How does Pi Web prevent a provider from appearing in both panels?
The buildApiKeyProviderList function checks provider.credentialType !== "oauth" when determining the configured flag. If a user has stored an OAuth credential for a dual-auth provider like Anthropic, that provider's configured status becomes false in the API-key list, keeping it exclusive to the OAuth panel.
Where does the provider metadata originate?
All metadata comes from the Pi SDK's ModelRuntime class. The collectProviderListingInputs function calls modelRuntime.getProviders(), modelRuntime.getModels(), modelRuntime.listCredentials(), and modelRuntime.getProviderAuthStatus() to assemble a complete picture of available providers and their authentication state.
Can custom providers appear in these lists?
Custom providers defined in models.json are explicitly excluded from the API-key list via the CUSTOM_PROVIDER_SOURCES check. These providers render through a separate UI path, keeping the standard provider lists clean and predictable.
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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →