How Pi‑Web Model Discovery Fetches Upstream Provider Lists: A Deep Dive
Pi‑Web's model discovery system fetches upstream provider lists through a server-side API route (/api/models-config/discover) that constructs provider-specific URLs, authenticates via query parameters, and parses the response into standardized DiscoveredModel objects.
The agegr/pi-web repository implements a clean separation between client-side model discovery initiation and server-side upstream fetching. This architecture keeps provider credentials secure while enabling dynamic model catalog population from external AI providers like Anthropic and OpenAI.
How Model Discovery Works in Pi‑Web
The discovery flow spans three core files: lib/model-discovery.ts, lib/model-discovery-auth.ts, and app/api/models-config/discover/route.ts. Each handles a distinct phase of the pipeline.
Authentication Resolution
Before any upstream request occurs, the system extracts provider credentials from the incoming request. The resolveProviderAuth helper in lib/model-discovery.ts (and its wrapper resolveModelDiscoveryAuth in the auth module) parses the query string for authentication data:
// lib/model-discovery.ts
export function resolveProviderAuth(request: Request): ProviderAuth {
const url = new URL(request.url);
const provider = url.searchParams.get("provider") ?? "";
const apiKey = url.searchParams.get("apiKey");
const token = url.searchParams.get("token");
if (apiKey) return { type: "apiKey", key: apiKey, provider };
if (token) return { type: "oauth", token, provider };
return { type: "none", provider };
}
The ProviderAuth union type supports three authentication modes: API key, OAuth token, and no authentication.
URL Construction for Server-Side Discovery
The buildModelsListUrl function constructs a local endpoint URL that the Pi SDK intercepts and translates into provider-specific upstream requests:
// lib/model-discovery.ts
export function buildModelsListUrl(provider: string, auth: ProviderAuth) {
const base = `http://localhost:${process.env.PORT ?? 3000}/api/models-config/discover`;
const params = new URLSearchParams();
params.append("provider", provider);
if (auth.type === "apiKey") {
params.append("apiKey", auth.key);
} else if (auth.type === "oauth") {
params.append("token", auth.token);
}
return `${base}?${params}`;
}
Note the deliberate use of localhost—this URL is not meant for direct client consumption. The Pi SDK server intercepts this call and handles the actual HTTP request to the upstream provider, abstracting provider-specific APIs and keeping credentials server-bound.
API Route Orchestration
The Next.js App Router handler in app/api/models-config/discover/route.ts ties the flow together:
// app/api/models-config/discover/route.ts
export async function GET(request: Request) {
const auth = resolveModelDiscoveryAuth(request);
const url = buildModelsListUrl(auth.provider, auth);
const response = await fetch(url, { method: "GET" });
if (!response.ok) {
return NextResponse.json({ error: "Failed to fetch models" }, { status: 502 });
}
const payload = await response.json();
const models = parseDiscoveredModels(payload);
return NextResponse.json(models);
}
The route returns 502 Bad Gateway if the upstream fetch fails, clearly distinguishing provider unavailability from application errors.
Response Parsing and Normalization
Raw provider responses vary in structure. The parseDiscoveredModels function normalizes these into a consistent DiscoveredModel[] array:
// lib/model-discovery.ts
export function parseDiscoveredModels(value: unknown): DiscoveredModel[] {
if (!Array.isArray(value)) {
console.warn("Unexpected discovery payload", value);
return [];
}
return value.flatMap((entry) => {
if (typeof entry !== "object" || entry == null) return [];
const { provider, models } = entry as { provider: string; models: ModelInfo[] };
if (!Array.isArray(models)) return [];
return models.map((m) => ({ ...m, provider } as DiscoveredModel));
});
}
The parser is defensive: it silently drops malformed entries rather than failing the entire request, ensuring partial availability does not break discovery.
Complete Model Discovery Flow
Here is the end-to-end flow a client application uses:
// Client initiates discovery
const params = new URLSearchParams({
provider: "anthropic",
apiKey: process.env.ANTHROPIC_API_KEY!
});
const response = await fetch(`/api/models-config/discover?${params}`);
const models: DiscoveredModel[] = await response.json();
// models[] now contains: { id, name, description, provider: "anthropic", ... }
On the server, this triggers:
- Auth extraction via
resolveModelDiscoveryAuth - URL construction via
buildModelsListUrl - Server-side fetch to the Pi SDK discovery endpoint
- Upstream provider request (handled by Pi SDK internals)
- Response parsing via
parseDiscoveredModels - JSON serialization back to the client
Key Design Decisions
| Decision | Rationale |
|---|---|
| Server-side credential handling | Prevents API keys and OAuth tokens from exposing to browser environments |
| Local URL interception | Allows Pi SDK to inject provider-specific logic without client code changes |
| Defensive parsing | Graceful degradation when providers return unexpected payload shapes |
| Flat model structure | Simplifies UI consumption by attaching provider to every model object |
File Reference Map
| File Path | Primary Responsibility |
|---|---|
lib/model-discovery.ts |
URL building, response parsing, auth resolution utilities |
lib/model-discovery-auth.ts |
Typed auth extraction from request (wraps resolveProviderAuth) |
app/api/models-config/discover/route.ts |
HTTP handler orchestrating the discovery pipeline |
@/lib/pi-types |
ModelInfo base type definition |
@/lib/model-catalog |
Model type integration |
Summary
- Model discovery in
agegr/pi-webuses a server-side API route (/api/models-config/discover) to proxy upstream provider requests securely. - Authentication flows through query parameters parsed by
resolveProviderAuthand normalized to theProviderAuthtype. - URL construction via
buildModelsListUrlcreates local endpoints that the Pi SDK intercepts for actual upstream communication. - Response parsing via
parseDiscoveredModelsflattens and validates provider-specific payloads into uniformDiscoveredModelobjects. - The architecture keeps credentials server-bound while exposing a simple JSON API for client consumption.
Frequently Asked Questions
What authentication methods does Pi‑Web model discovery support?
Pi‑Web supports three authentication modes as defined in the ProviderAuth type: API key (type: "apiKey"), OAuth token (type: "oauth"), and no authentication (type: "none"). These are extracted from query parameters in resolveProviderAuth and passed through to the Pi SDK for upstream requests.
Why does buildModelsListUrl use localhost instead of the actual provider URL?
The localhost URL serves as a server-side interception point for the Pi SDK. The Pi SDK captures this request internally and translates it into the appropriate provider-specific HTTP call. This design abstracts provider API differences and ensures credentials never travel to the client.
How does parseDiscoveredModels handle malformed upstream responses?
The parser implements defensive validation: it checks that the payload is an array, filters out non-object entries, verifies the models property exists as an array, and uses flatMap to silently drop invalid entries. This prevents partial provider failures from breaking the entire discovery flow.
Can model discovery work without authentication?
Yes. When neither apiKey nor token query parameters are present, resolveProviderAuth returns { type: "none", provider }. Whether this succeeds depends on the upstream provider's public API availability—the Pi SDK will attempt the request regardless.
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 →