How pi-web Integrates with Pi's Auth System for OAuth and API Keys
pi-web acts as a thin HTTP wrapper around Pi's ModelRuntime class, delegating all OAuth and API-key operations to the @earendil-works/pi-coding-agent SDK while managing interactive prompts via Server-Sent Events.
The agegr/pi-web repository implements a complete authentication layer that bridges the Pi coding agent's runtime with a web-based UI. By leveraging the ModelRuntime class provided by the Pi SDK, pi-web exposes REST endpoints that handle both interactive OAuth flows and static API-key storage without persisting sensitive credentials in the application layer.
Architecture Overview
The ModelRuntime Wrapper
At the core of pi-web's auth integration is the ModelRuntime class from @earendil-works/pi-coding-agent. Each authentication endpoint creates a fresh runtime instance via ModelRuntime.create() to access configured providers and stored credentials. This design keeps the web layer stateless while delegating credential management to the underlying Pi runtime.
All auth-related API routes live under app/api/auth/ and follow a consistent pattern: initialize the runtime, execute a login or logout operation with custom prompt handlers, and call invalidateModelsCache() to refresh the available model list.
OAuth Authentication Flow
Listing Available Providers
The entry point for OAuth discovery is GET /api/auth/providers, implemented in app/api/auth/providers/route.ts. This endpoint instantiates a ModelRuntime and calls modelRuntime.getProviders() to retrieve the full provider catalog.
The route filters the results to exclude non-OAuth providers (such as Anthropic) and enriches each entry with a loggedIn boolean indicating whether valid credentials already exist. The response includes the provider's ID, display name, and authentication status, enabling the UI to show appropriate connection buttons.
SSE-Based OAuth Login
The OAuth handshake operates through app/api/auth/login/[provider]/route.ts, which implements a Server-Sent Events (SSE) stream for bidirectional communication without WebSockets.
When a client opens GET /api/auth/login/[provider], the server:
- Creates a
ModelRuntimeinstance - Invokes
modelRuntime.login(provider, "oauth", { … })with a custom prompt callback - Registers a one-time token in
globalThis.__piLoginCallbacksthat maps to a Promise awaiting user input
As the OAuth flow progresses, ModelRuntime emits prompt requests (select, prompt, manual_code) that pi-web forwards to the client via SSE messages (select_request, prompt_request, device_code). The client renders the appropriate UI and submits responses via POST /api/auth/login/[provider], which resolves the stored Promise in the callback registry.
Upon successful authentication, the endpoint calls invalidateModelsCache() to ensure the newly available models appear in the UI immediately.
API Key Management
Storing API Keys
For providers that require static credentials, pi-web exposes app/api/auth/api-key/[provider]/route.ts supporting GET, POST, and DELETE methods.
The GET handler returns the provider's authentication status and configuration source without exposing the secret. The POST handler receives the raw API key from the client and executes modelRuntime.login(provider, "api_key", { … }). During this flow, the runtime prompts the user to select the appropriate secret field (api-key or bearer-token) via the callback mechanism, then accepts the submitted key through a secret prompt type.
After successful storage, the endpoint triggers invalidateModelsCache() to refresh the model catalog.
Removing Credentials
The DELETE method on the same route invokes modelRuntime.logout(provider), which erases the stored API key or OAuth tokens from the Pi runtime's credential store. This operation also invalidates the models cache to remove provider-specific models from the UI.
Cache Synchronization Pattern
Every authentication state change—whether OAuth completion, API-key storage, or logout—concludes with a call to invalidateModelsCache(). This function ensures the UI receives an updated list of available models based on the current credential set. By centralizing cache invalidation in the auth layer, pi-web maintains consistency between the Pi runtime's credential state and the web interface's provider selection.
Implementation Examples
The following examples demonstrate how to interact with pi-web's authentication endpoints from a client application.
Fetch the list of OAuth-enabled providers:
// GET /api/auth/providers
fetch('/api/auth/providers')
.then(r => r.json())
.then(data => console.log('Providers:', data.providers));
Initiate an OAuth login using Server-Sent Events:
// GET /api/auth/login/[provider] (SSE stream)
const provider = 'openai';
const es = new EventSource(`/api/auth/login/${provider}`);
es.onmessage = ev => {
const msg = JSON.parse(ev.data);
if (msg.type === 'select_request') {
// Show UI for user selection, then POST response
fetch(`/api/auth/login/${provider}`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ token: msg.token, code: selectedOption })
});
}
// Handle prompt_request, auth, device_code, success, error...
};
Submit an API key for storage:
// POST /api/auth/api-key/[provider]
fetch('/api/auth/api-key/openai', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ apiKey: 'sk-...' })
})
.then(r => r.json())
.then(res => console.log('API key stored:', res.success));
Remove stored credentials:
// DELETE /api/auth/api-key/[provider]
fetch('/api/auth/api-key/openai', { method: 'DELETE' })
.then(r => r.json())
.then(res => console.log('Key removed:', res.success));
Summary
- pi-web delegates authentication to Pi's
ModelRuntimefrom@earendil-works/pi-coding-agent, creating a fresh runtime instance for each request inapp/api/auth/. - OAuth flows use SSE streams managed in
app/api/auth/login/[provider]/route.ts, with interactive prompts handled throughglobalThis.__piLoginCallbacks. - API keys are stored via
modelRuntime.login()with type"api_key"inapp/api/auth/api-key/[provider]/route.ts, supporting GET, POST, and DELETE operations. - Cache invalidation via
invalidateModelsCache()ensures the UI syncs with credential changes immediately after login or logout.
Frequently Asked Questions
What is ModelRuntime in pi-web?
ModelRuntime is the core class provided by the @earendil-works/pi-coding-agent SDK that pi-web wraps to handle authentication. It manages provider configurations, credential storage, and OAuth negotiations. In pi-web, each auth endpoint creates a runtime via ModelRuntime.create() to access these capabilities without implementing auth logic directly.
How does pi-web handle OAuth callbacks without blocking the server?
pi-web implements OAuth as an SSE (Server-Sent Events) stream in app/api/auth/login/[provider]/route.ts. Instead of blocking during user interaction, the server registers a Promise in globalThis.__piLoginCallbacks and sends prompt requests to the client. The client POSTs responses back to the same endpoint, which resolves the Promise and continues the OAuth flow asynchronously.
Can pi-web store both OAuth and API key credentials for the same provider?
Yes. The ModelRuntime class supports multiple authentication methods per provider. pi-web exposes separate endpoints for OAuth (/api/auth/login/[provider]) and API keys (/api/auth/api-key/[provider]), allowing users to choose their preferred authentication method. The runtime manages these credentials independently.
Where are authentication credentials stored in pi-web?
pi-web does not store credentials in its own application layer. Instead, it delegates storage to the Pi runtime via ModelRuntime.login() and ModelRuntime.logout(). The web layer only transiently handles secrets during the API key submission POST request, while the underlying Pi SDK manages secure persistence and retrieval.
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 →