# How Dual-Auth Provider Handling Works in `provider-listing.ts`

> Learn how dual-auth provider handling works in provider-listing.ts. Discover dynamic SDK definitions, API-key and OAuth lists, and UI toggles for authentication methods.

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

---

**Dual-auth providers in [`provider-listing.ts`](https://github.com/agegr/pi-web/blob/main/provider-listing.ts) are discovered dynamically from SDK definitions and split into two separate lists—API-key and OAuth—with cross-flags (`supportsOAuth`/`supportsApiKey`) enabling UI toggles between authentication methods.**

The [`provider-listing.ts`](https://github.com/agegr/pi-web/blob/main/provider-listing.ts) module in the [agegr/pi-web](https://github.com/agegr/pi-web) repository powers the **Models** panel's provider selection interface. It processes providers that support multiple authentication methods—such as Anthropic, GitHub Copilot, Kimi-coding, OpenRouter, Radius, and XAI—and ensures each appears correctly without duplicate entries.

## How Dual-Auth Capability Is Discovered

A provider qualifies as **dual-auth** when it declares both `hasApiKeyLogin` and `hasOAuth` in its SDK definition. The module reads this directly from `ProviderListingInput` objects rather than inferring from hard-coded IDs.

> "Which providers are dual-auth is a property of the SDK's provider definitions and changes between releases — read it from `auth`, never assume it from an id."
> — Source comment in [[`/lib/provider-listing.ts`](https://github.com/agegr/pi-web/blob/main//lib/provider-listing.ts)](https://github.com/agegr/pi-web/blob/main/lib/provider-listing.ts#L4-L11)

Each input contains:
- `hasApiKeyLogin` – Boolean flag for API-key support
- `hasOAuth` – Boolean flag for OAuth support
- `credentialType` – Currently stored credential type (`"api_key"` | `"oauth"`)
- `status.configured` – Whether authentication is active
- `modelCount` and display metadata

## Deduplication of Provider Entries

The same provider may appear multiple times in raw SDK output. The `dedupeById` helper preserves first occurrence while removing duplicates:

```typescript
function dedupeById(providers: readonly ProviderListingInput[]): ProviderListingInput[] {
  const seen = new Set<string>();
  const result: ProviderListingInput[] = [];
  for (const provider of providers) {
    if (seen.has(provider.id)) continue;
    seen.add(provider.id);
    result.push(provider);
  }
  return result;
}

```

Source: [`/lib/provider-listing.ts#L62-L70`](https://github.com/agegr/pi-web/blob/main/lib/provider-listing.ts#L62-L70)

## Building the API-Key Provider List

The `buildApiKeyProviderList` function constructs entries for the **API-key** tab with specific dual-auth logic:

- **Skips** providers without `hasApiKeyLogin`
- **Excludes** custom [`models.json`](https://github.com/agegr/pi-web/blob/main/models.json) providers (handled separately)
- **Marks as unconfigured** when `credentialType === "oauth"`—preventing OAuth-authenticated dual-auth providers from appearing active in both lists
- **Adds `supportsOAuth`** flag when `hasOAuth` is true

```typescript
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,
});

```

Source: [`/lib/provider-listing.ts#L81-L99`](https://github.com/agegr/pi-web/blob/main/lib/provider-listing.ts#L81-L99)

## Building the OAuth Provider List

The `buildOAuthProviderList` function handles the **OAuth** tab:

- **Includes only** providers with `hasOAuth`
- **Uses friendly display names** from `OAUTH_DISPLAY_NAMES` mapping or falls back to `oauthName`/`name`
- **Sets `loggedIn`** when `credentialType === "oauth"`
- **Adds `supportsApiKey`** flag for dual-auth detection

```typescript
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,
});

```

Source: [`/lib/provider-listing.ts#L102-L117`](https://github.com/agegr/pi-web/blob/main/lib/provider-listing.ts#L102-L117)

## Complete Implementation Example

```typescript
import {
  buildApiKeyProviderList,
  buildOAuthProviderList,
  ProviderListingInput,
} from "./provider-listing";

const rawProviders: ProviderListingInput[] = [
  {
    id: "anthropic",
    name: "Anthropic",
    hasApiKeyLogin: true,
    hasOAuth: true,
    oauthName: "Anthropic (Claude Pro/Max)",
    status: { configured: true },
    credentialType: "oauth",
    modelCount: 5,
  },
  // additional providers...
];

const apiKeyList = buildApiKeyProviderList(rawProviders);
const oauthList = buildOAuthProviderList(rawProviders);

console.log(apiKeyList);
// Anthropic appears with configured: false, supportsOAuth: true

console.log(oauthList);
// Anthropic appears with loggedIn: true, supportsApiKey: true

```

## Related Files in the Authentication Pipeline

| File | Purpose |
|------|---------|
| [`lib/provider-listing.ts`](https://github.com/agegr/pi-web/blob/main/lib/provider-listing.ts) | Core dual-auth detection and list building |
| [`lib/provider-listing-runtime.ts`](https://github.com/agegr/pi-web/blob/main/lib/provider-listing-runtime.ts) | SDK data fetching wrapper |
| [`lib/provider-credential-store.ts`](https://github.com/agegr/pi-web/blob/main/lib/provider-credential-store.ts) | Credential storage and `credentialType` resolution |
| [`components/models-config-helpers.ts`](https://github.com/agegr/pi-web/blob/main/components/models-config-helpers.ts) | UI rendering with authentication toggles |

## Summary

- **Dual-auth providers** are identified by SDK flags (`hasApiKeyLogin` + `hasOAuth`), never hard-coded IDs
- **`dedupeById`** eliminates duplicate provider entries before list construction
- **`supportsOAuth`** and **`supportsApiKey`** flags enable UI toggles between authentication methods
- **OAuth-active providers** appear unconfigured in the API-key list to prevent duplication
- The architecture supports future SDK providers automatically without code changes

## Frequently Asked Questions

### How does [`provider-listing.ts`](https://github.com/agegr/pi-web/blob/main/provider-listing.ts) know if a provider supports both authentication methods?

The module reads `hasApiKeyLogin` and `hasOAuth` boolean flags directly from each `ProviderListingInput` object supplied by the SDK. These flags are defined in the provider's authentication schema, making detection dynamic rather than based on maintained ID lists.

### What prevents a dual-auth provider from appearing twice in the UI?

The `credentialType` field determines active authentication. When `credentialType === "oauth"`, the `buildApiKeyProviderList` function sets `configured: false` for that provider, so it only appears active in the OAuth list. The UI renders one row per provider with toggle capabilities instead of duplicate entries.

### Can custom providers defined in [`models.json`](https://github.com/agegr/pi-web/blob/main/models.json) appear in these lists?

No. The **`CUSTOM_PROVIDER_SOURCES`** Set filters out providers with `status.source` matching custom entries. These are rendered separately in the UI through different components, keeping the built-in provider lists clean.

### What happens when the SDK adds a new dual-auth provider?

No code changes are required. Since [`provider-listing.ts`](https://github.com/agegr/pi-web/blob/main/provider-listing.ts) reads authentication capabilities from SDK-provided flags rather than hard-coded logic, new dual-auth providers automatically receive correct `supportsOAuth`/`supportsApiKey` flags and proper list placement based on their `credentialType`.