Archived
Migrate authProviderOptions to ModelRuntime API
Rederive login/logout provider options from runtime.getProviders() + listCredentials() + getProviderAuthStatus() instead of the removed authStorage.getOAuthProviders()/list()/get() + getAll()/ getProviderDisplayName() surface (Pi 0.80.8+). - Replace the AuthProviderModelRegistry structural interface with a runtime-shaped AuthProviderRuntime (getProviders/listCredentials/ getProviderAuthStatus); a real ModelRuntime satisfies it. - Make getLoginProviderOptions/getLogoutProviderOptions async to match the await call sites already in authService.ts. - OAuth-capable providers = auth.oauth present; api-key providers = auth.apiKey present, preserving OAUTH_ONLY_PROVIDERS / isApiKeyLoginProvider logic. Display names from Provider.name. - Update the test double to the new runtime shape. Slice 2 of the authStorage migration relay. tsc: 31 -> 28 errors (remaining are cross-slice: slices 3/4/5).
This commit is contained in:
@@ -1,27 +1,18 @@
|
|||||||
import { describe, expect, it } from "vitest";
|
import { describe, expect, it } from "vitest";
|
||||||
import { getLoginProviderOptions, getLogoutProviderOptions, isApiKeyLoginProvider, type AuthProviderModelRegistry } from "./authProviderOptions";
|
import { getLoginProviderOptions, getLogoutProviderOptions, isApiKeyLoginProvider, type AuthProviderRuntime } from "./authProviderOptions";
|
||||||
|
|
||||||
function registry(): AuthProviderModelRegistry {
|
function runtime(): AuthProviderRuntime {
|
||||||
const credentials = new Map<string, { type: "oauth" | "api_key" }>();
|
const credentials = [{ providerId: "openai", type: "api_key" as const }];
|
||||||
credentials.set("openai", { type: "api_key" });
|
const providers = [
|
||||||
|
{ id: "anthropic", name: "Anthropic", auth: { oauth: {}, apiKey: {} } },
|
||||||
|
{ id: "github-copilot", name: "GitHub Copilot", auth: { oauth: {} } },
|
||||||
|
{ id: "openai-codex", name: "ChatGPT Plus/Pro (Codex Subscription)", auth: { oauth: {}, apiKey: {} } },
|
||||||
|
{ id: "openai", name: "OpenAI", auth: { apiKey: {} } },
|
||||||
|
{ id: "custom", name: "Custom", auth: { apiKey: {} } },
|
||||||
|
];
|
||||||
return {
|
return {
|
||||||
authStorage: {
|
getProviders: () => providers,
|
||||||
getOAuthProviders: () => [
|
listCredentials: () => Promise.resolve(credentials),
|
||||||
{ id: "anthropic", name: "Anthropic (Claude Pro/Max)" },
|
|
||||||
{ id: "github-copilot", name: "GitHub Copilot" },
|
|
||||||
{ id: "openai-codex", name: "ChatGPT Plus/Pro (Codex Subscription)" },
|
|
||||||
],
|
|
||||||
list: () => Array.from(credentials.keys()),
|
|
||||||
get: (provider: string) => credentials.get(provider),
|
|
||||||
},
|
|
||||||
getAll: () => [
|
|
||||||
{ provider: "anthropic" },
|
|
||||||
{ provider: "openai" },
|
|
||||||
{ provider: "openai-codex" },
|
|
||||||
{ provider: "github-copilot" },
|
|
||||||
{ provider: "custom" },
|
|
||||||
],
|
|
||||||
getProviderDisplayName: (provider: string) => ({ anthropic: "Anthropic", openai: "OpenAI", custom: "Custom" }[provider] ?? provider),
|
|
||||||
getProviderAuthStatus: (provider: string) => (provider === "openai" ? { configured: true, source: "stored" } : { configured: false }),
|
getProviderAuthStatus: (provider: string) => (provider === "openai" ? { configured: true, source: "stored" } : { configured: false }),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -33,8 +24,8 @@ describe("auth provider options", () => {
|
|||||||
expect(isApiKeyLoginProvider("openai", new Set(["openai-codex"]))).toBe(true);
|
expect(isApiKeyLoginProvider("openai", new Set(["openai-codex"]))).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("builds login options for OAuth-only, dual-auth, and API-key providers", () => {
|
it("builds login options for OAuth-only, dual-auth, and API-key providers", async () => {
|
||||||
const options = getLoginProviderOptions(registry());
|
const options = await getLoginProviderOptions(runtime());
|
||||||
expect(options).toEqual(expect.arrayContaining([
|
expect(options).toEqual(expect.arrayContaining([
|
||||||
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" }),
|
||||||
@@ -44,8 +35,8 @@ describe("auth provider options", () => {
|
|||||||
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" })]));
|
||||||
});
|
});
|
||||||
|
|
||||||
it("returns only currently stored credentials for logout", () => {
|
it("returns only currently stored credentials for logout", async () => {
|
||||||
expect(getLogoutProviderOptions(registry())).toEqual([
|
expect(await getLogoutProviderOptions(runtime())).toEqual([
|
||||||
expect.objectContaining({ id: "openai", authType: "api_key" }),
|
expect.objectContaining({ id: "openai", authType: "api_key" }),
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -2,51 +2,69 @@ import type { AuthProviderOption, AuthProviderStatus, AuthType } from "../../sha
|
|||||||
|
|
||||||
const OAUTH_ONLY_PROVIDERS = new Set(["github-copilot", "openai-codex"]);
|
const OAUTH_ONLY_PROVIDERS = new Set(["github-copilot", "openai-codex"]);
|
||||||
|
|
||||||
export interface AuthProviderModelRegistry {
|
/** Minimal provider shape needed to enumerate login/logout options. */
|
||||||
authStorage: {
|
interface AuthProviderInfo {
|
||||||
getOAuthProviders(): { id: string; name: string }[];
|
id: string;
|
||||||
list(): string[];
|
name: string;
|
||||||
get(provider: string): { type: AuthType } | undefined;
|
auth: { apiKey?: unknown; oauth?: unknown };
|
||||||
};
|
|
||||||
getAll(): { provider: string }[];
|
|
||||||
getProviderDisplayName(provider: string): string;
|
|
||||||
getProviderAuthStatus(provider: string): AuthProviderStatus;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getLoginProviderOptions(modelRegistry: AuthProviderModelRegistry, authType?: AuthType): AuthProviderOption[] {
|
/** Non-secret stored-credential metadata, keyed by provider id. */
|
||||||
const oauthProviders = modelRegistry.authStorage.getOAuthProviders();
|
interface AuthProviderCredentialInfo {
|
||||||
const oauthProviderIds = new Set(oauthProviders.map((provider) => provider.id));
|
providerId: string;
|
||||||
const options: AuthProviderOption[] = oauthProviders.map((provider) => ({
|
type: AuthType;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Structural slice of the SDK `ModelRuntime` used to derive auth provider
|
||||||
|
* options. Kept structural (rather than `Pick<ModelRuntime, ...>`) so tests can
|
||||||
|
* supply a lightweight double without constructing a full runtime; a real
|
||||||
|
* `ModelRuntime` satisfies it.
|
||||||
|
*/
|
||||||
|
export interface AuthProviderRuntime {
|
||||||
|
getProviders(): readonly AuthProviderInfo[];
|
||||||
|
listCredentials(): Promise<readonly AuthProviderCredentialInfo[]>;
|
||||||
|
getProviderAuthStatus(providerId: string): AuthProviderStatus;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getLoginProviderOptions(runtime: AuthProviderRuntime, authType?: AuthType): Promise<AuthProviderOption[]> {
|
||||||
|
const providers = runtime.getProviders();
|
||||||
|
const oauthProviderIds = new Set(providers.filter((provider) => provider.auth.oauth !== undefined).map((provider) => provider.id));
|
||||||
|
|
||||||
|
const options: AuthProviderOption[] = [];
|
||||||
|
for (const provider of providers) {
|
||||||
|
if (provider.auth.oauth === undefined) continue;
|
||||||
|
options.push({
|
||||||
id: provider.id,
|
id: provider.id,
|
||||||
name: provider.name,
|
name: provider.name,
|
||||||
authType: "oauth",
|
authType: "oauth",
|
||||||
status: modelRegistry.getProviderAuthStatus(provider.id),
|
status: runtime.getProviderAuthStatus(provider.id),
|
||||||
}));
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const modelProviders = new Set(modelRegistry.getAll().map((model) => model.provider));
|
for (const provider of providers) {
|
||||||
for (const providerId of modelProviders) {
|
if (provider.auth.apiKey === undefined) continue;
|
||||||
if (!isApiKeyLoginProvider(providerId, oauthProviderIds)) continue;
|
if (!isApiKeyLoginProvider(provider.id, oauthProviderIds)) continue;
|
||||||
options.push({
|
options.push({
|
||||||
id: providerId,
|
id: provider.id,
|
||||||
name: modelRegistry.getProviderDisplayName(providerId),
|
name: provider.name,
|
||||||
authType: "api_key",
|
authType: "api_key",
|
||||||
status: modelRegistry.getProviderAuthStatus(providerId),
|
status: runtime.getProviderAuthStatus(provider.id),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return filterAndSort(options, authType);
|
return filterAndSort(options, authType);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getLogoutProviderOptions(modelRegistry: AuthProviderModelRegistry): AuthProviderOption[] {
|
export async function getLogoutProviderOptions(runtime: AuthProviderRuntime): Promise<AuthProviderOption[]> {
|
||||||
|
const providerNames = new Map(runtime.getProviders().map((provider) => [provider.id, provider.name]));
|
||||||
const options: AuthProviderOption[] = [];
|
const options: AuthProviderOption[] = [];
|
||||||
for (const providerId of modelRegistry.authStorage.list()) {
|
for (const credential of await runtime.listCredentials()) {
|
||||||
const credential = modelRegistry.authStorage.get(providerId);
|
|
||||||
if (credential === undefined) continue;
|
|
||||||
options.push({
|
options.push({
|
||||||
id: providerId,
|
id: credential.providerId,
|
||||||
name: modelRegistry.getProviderDisplayName(providerId),
|
name: providerNames.get(credential.providerId) ?? credential.providerId,
|
||||||
authType: credential.type,
|
authType: credential.type,
|
||||||
status: modelRegistry.getProviderAuthStatus(providerId),
|
status: runtime.getProviderAuthStatus(credential.providerId),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return filterAndSort(options);
|
return filterAndSort(options);
|
||||||
|
|||||||
Reference in New Issue
Block a user