Archived
Merge pull request #65 from jmfederico/cleanup/auth-provider-hardcodes
refactor: derive auth-provider login options from SDK data
This commit is contained in:
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
"@jmfederico/pi-web": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
Offer API-key and OAuth login options for every provider the agent backend supports each method for, instead of a curated hardcoded list. Providers that support both methods (such as Anthropic and GitHub Copilot) now surface both an API-key and an OAuth login option, driven purely by what the backend reports.
|
||||||
@@ -1,12 +1,14 @@
|
|||||||
import { describe, expect, it } from "vitest";
|
import { describe, expect, it } from "vitest";
|
||||||
import { getLoginProviderOptions, getLogoutProviderOptions, isApiKeyLoginProvider, type AuthProviderRuntime } from "./authProviderOptions";
|
import { getLoginProviderOptions, getLogoutProviderOptions, type AuthProviderRuntime } from "./authProviderOptions";
|
||||||
|
|
||||||
function runtime(): AuthProviderRuntime {
|
function runtime(): AuthProviderRuntime {
|
||||||
const credentials = [{ providerId: "openai", type: "api_key" as const }];
|
const credentials = [{ providerId: "openai", type: "api_key" as const }];
|
||||||
|
// Auth shapes mirror what the Pi SDK actually reports for these providers:
|
||||||
|
// github-copilot supports both methods, openai-codex is oauth-only.
|
||||||
const providers = [
|
const providers = [
|
||||||
{ id: "anthropic", name: "Anthropic", auth: { oauth: {}, apiKey: {} } },
|
{ id: "anthropic", name: "Anthropic", auth: { oauth: {}, apiKey: {} } },
|
||||||
{ id: "github-copilot", name: "GitHub Copilot", auth: { oauth: {} } },
|
{ id: "github-copilot", name: "GitHub Copilot", auth: { oauth: {}, apiKey: {} } },
|
||||||
{ id: "openai-codex", name: "ChatGPT Plus/Pro (Codex Subscription)", auth: { oauth: {}, apiKey: {} } },
|
{ id: "openai-codex", name: "ChatGPT Plus/Pro (Codex Subscription)", auth: { oauth: {} } },
|
||||||
{ id: "openai", name: "OpenAI", auth: { apiKey: {} } },
|
{ id: "openai", name: "OpenAI", auth: { apiKey: {} } },
|
||||||
{ id: "custom", name: "Custom", auth: { apiKey: {} } },
|
{ id: "custom", name: "Custom", auth: { apiKey: {} } },
|
||||||
];
|
];
|
||||||
@@ -18,21 +20,22 @@ function runtime(): AuthProviderRuntime {
|
|||||||
}
|
}
|
||||||
|
|
||||||
describe("auth provider options", () => {
|
describe("auth provider options", () => {
|
||||||
it("keeps OAuth-only providers out of API key login options", () => {
|
it("offers both api-key and oauth login options for every provider the backend supports each method for", () => {
|
||||||
expect(isApiKeyLoginProvider("openai-codex", new Set(["openai-codex"]))).toBe(false);
|
|
||||||
expect(isApiKeyLoginProvider("github-copilot", new Set(["github-copilot"]))).toBe(false);
|
|
||||||
expect(isApiKeyLoginProvider("openai", new Set(["openai-codex"]))).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("builds login options for OAuth-only, dual-auth, and API-key providers", () => {
|
|
||||||
const options = getLoginProviderOptions(runtime());
|
const options = getLoginProviderOptions(runtime());
|
||||||
expect(options).toEqual(expect.arrayContaining([
|
expect(options).toEqual(expect.arrayContaining([
|
||||||
|
// Dual-capable providers surface both login methods, driven purely by SDK data.
|
||||||
expect.objectContaining({ id: "anthropic", authType: "oauth" }),
|
expect.objectContaining({ id: "anthropic", authType: "oauth" }),
|
||||||
expect.objectContaining({ id: "anthropic", authType: "api_key" }),
|
expect.objectContaining({ id: "anthropic", authType: "api_key" }),
|
||||||
expect.objectContaining({ id: "openai", authType: "api_key", status: { configured: true, source: "stored" } }),
|
expect.objectContaining({ id: "github-copilot", authType: "oauth" }),
|
||||||
|
expect.objectContaining({ id: "github-copilot", authType: "api_key" }),
|
||||||
|
// OAuth-only provider surfaces only oauth.
|
||||||
expect.objectContaining({ id: "openai-codex", authType: "oauth" }),
|
expect.objectContaining({ id: "openai-codex", authType: "oauth" }),
|
||||||
|
// API-key-only providers surface only api_key.
|
||||||
|
expect.objectContaining({ id: "openai", authType: "api_key", status: { configured: true, source: "stored" } }),
|
||||||
|
expect.objectContaining({ id: "custom", authType: "api_key" }),
|
||||||
]));
|
]));
|
||||||
expect(options).not.toEqual(expect.arrayContaining([expect.objectContaining({ id: "openai-codex", authType: "api_key" })]));
|
expect(options).not.toEqual(expect.arrayContaining([expect.objectContaining({ id: "openai-codex", authType: "api_key" })]));
|
||||||
|
expect(options).not.toEqual(expect.arrayContaining([expect.objectContaining({ id: "openai", authType: "oauth" })]));
|
||||||
});
|
});
|
||||||
|
|
||||||
it("returns only currently stored credentials for logout", async () => {
|
it("returns only currently stored credentials for logout", async () => {
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
import type { AuthProviderOption, AuthProviderStatus, AuthType } from "../../shared/apiTypes.js";
|
import type { AuthProviderOption, AuthProviderStatus, AuthType } from "../../shared/apiTypes.js";
|
||||||
|
|
||||||
const OAUTH_ONLY_PROVIDERS = new Set(["github-copilot", "openai-codex"]);
|
|
||||||
|
|
||||||
/** Minimal provider shape needed to enumerate login/logout options. */
|
/** Minimal provider shape needed to enumerate login/logout options. */
|
||||||
interface AuthProviderInfo {
|
interface AuthProviderInfo {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -29,7 +27,6 @@ export interface AuthProviderRuntime {
|
|||||||
|
|
||||||
export function getLoginProviderOptions(runtime: AuthProviderRuntime, authType?: AuthType): AuthProviderOption[] {
|
export function getLoginProviderOptions(runtime: AuthProviderRuntime, authType?: AuthType): AuthProviderOption[] {
|
||||||
const providers = runtime.getProviders();
|
const providers = runtime.getProviders();
|
||||||
const oauthProviderIds = new Set(providers.filter((provider) => provider.auth.oauth !== undefined).map((provider) => provider.id));
|
|
||||||
|
|
||||||
const options: AuthProviderOption[] = [];
|
const options: AuthProviderOption[] = [];
|
||||||
for (const provider of providers) {
|
for (const provider of providers) {
|
||||||
@@ -44,7 +41,6 @@ export function getLoginProviderOptions(runtime: AuthProviderRuntime, authType?:
|
|||||||
|
|
||||||
for (const provider of providers) {
|
for (const provider of providers) {
|
||||||
if (provider.auth.apiKey === undefined) continue;
|
if (provider.auth.apiKey === undefined) continue;
|
||||||
if (!isApiKeyLoginProvider(provider.id, oauthProviderIds)) continue;
|
|
||||||
options.push({
|
options.push({
|
||||||
id: provider.id,
|
id: provider.id,
|
||||||
name: provider.name,
|
name: provider.name,
|
||||||
@@ -70,13 +66,6 @@ export async function getLogoutProviderOptions(runtime: AuthProviderRuntime): Pr
|
|||||||
return filterAndSort(options);
|
return filterAndSort(options);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isApiKeyLoginProvider(providerId: string, oauthProviderIds: ReadonlySet<string>): boolean {
|
|
||||||
if (OAUTH_ONLY_PROVIDERS.has(providerId)) return false;
|
|
||||||
if (providerId === "anthropic") return true;
|
|
||||||
if (oauthProviderIds.has(providerId)) return false;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
function filterAndSort(options: AuthProviderOption[], authType?: AuthType): AuthProviderOption[] {
|
function filterAndSort(options: AuthProviderOption[], authType?: AuthType): AuthProviderOption[] {
|
||||||
const filtered = authType === undefined ? options : options.filter((option) => option.authType === authType);
|
const filtered = authType === undefined ? options : options.filter((option) => option.authType === authType);
|
||||||
return filtered.sort((a, b) => a.name.localeCompare(b.name) || a.authType.localeCompare(b.authType) || a.id.localeCompare(b.id));
|
return filtered.sort((a, b) => a.name.localeCompare(b.name) || a.authType.localeCompare(b.authType) || a.id.localeCompare(b.id));
|
||||||
|
|||||||
Reference in New Issue
Block a user