From d09d7cc1ff4d3b4bc23902f8a80d8787741fb77c Mon Sep 17 00:00:00 2001 From: Federico Jaramillo Martinez Date: Fri, 17 Jul 2026 20:58:46 +0200 Subject: [PATCH] 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). --- .../sessions/authProviderOptions.test.ts | 41 ++++------ src/server/sessions/authProviderOptions.ts | 80 ++++++++++++------- 2 files changed, 65 insertions(+), 56 deletions(-) diff --git a/src/server/sessions/authProviderOptions.test.ts b/src/server/sessions/authProviderOptions.test.ts index e6fa31e..f3e3707 100644 --- a/src/server/sessions/authProviderOptions.test.ts +++ b/src/server/sessions/authProviderOptions.test.ts @@ -1,27 +1,18 @@ 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 { - const credentials = new Map(); - credentials.set("openai", { type: "api_key" }); +function runtime(): AuthProviderRuntime { + const credentials = [{ providerId: "openai", type: "api_key" as const }]; + 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 { - authStorage: { - getOAuthProviders: () => [ - { 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), + getProviders: () => providers, + listCredentials: () => Promise.resolve(credentials), 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); }); - it("builds login options for OAuth-only, dual-auth, and API-key providers", () => { - const options = getLoginProviderOptions(registry()); + it("builds login options for OAuth-only, dual-auth, and API-key providers", async () => { + const options = await getLoginProviderOptions(runtime()); expect(options).toEqual(expect.arrayContaining([ expect.objectContaining({ id: "anthropic", authType: "oauth" }), 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" })])); }); - it("returns only currently stored credentials for logout", () => { - expect(getLogoutProviderOptions(registry())).toEqual([ + it("returns only currently stored credentials for logout", async () => { + expect(await getLogoutProviderOptions(runtime())).toEqual([ expect.objectContaining({ id: "openai", authType: "api_key" }), ]); }); diff --git a/src/server/sessions/authProviderOptions.ts b/src/server/sessions/authProviderOptions.ts index 58211d8..34b7cdb 100644 --- a/src/server/sessions/authProviderOptions.ts +++ b/src/server/sessions/authProviderOptions.ts @@ -2,51 +2,69 @@ import type { AuthProviderOption, AuthProviderStatus, AuthType } from "../../sha const OAUTH_ONLY_PROVIDERS = new Set(["github-copilot", "openai-codex"]); -export interface AuthProviderModelRegistry { - authStorage: { - getOAuthProviders(): { id: string; name: string }[]; - list(): string[]; - get(provider: string): { type: AuthType } | undefined; - }; - getAll(): { provider: string }[]; - getProviderDisplayName(provider: string): string; - getProviderAuthStatus(provider: string): AuthProviderStatus; +/** Minimal provider shape needed to enumerate login/logout options. */ +interface AuthProviderInfo { + id: string; + name: string; + auth: { apiKey?: unknown; oauth?: unknown }; } -export function getLoginProviderOptions(modelRegistry: AuthProviderModelRegistry, authType?: AuthType): AuthProviderOption[] { - const oauthProviders = modelRegistry.authStorage.getOAuthProviders(); - const oauthProviderIds = new Set(oauthProviders.map((provider) => provider.id)); - const options: AuthProviderOption[] = oauthProviders.map((provider) => ({ - id: provider.id, - name: provider.name, - authType: "oauth", - status: modelRegistry.getProviderAuthStatus(provider.id), - })); +/** Non-secret stored-credential metadata, keyed by provider id. */ +interface AuthProviderCredentialInfo { + providerId: string; + type: AuthType; +} - const modelProviders = new Set(modelRegistry.getAll().map((model) => model.provider)); - for (const providerId of modelProviders) { - if (!isApiKeyLoginProvider(providerId, oauthProviderIds)) continue; +/** + * Structural slice of the SDK `ModelRuntime` used to derive auth provider + * options. Kept structural (rather than `Pick`) 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; + getProviderAuthStatus(providerId: string): AuthProviderStatus; +} + +export async function getLoginProviderOptions(runtime: AuthProviderRuntime, authType?: AuthType): Promise { + 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: providerId, - name: modelRegistry.getProviderDisplayName(providerId), + id: provider.id, + name: provider.name, + authType: "oauth", + status: runtime.getProviderAuthStatus(provider.id), + }); + } + + for (const provider of providers) { + if (provider.auth.apiKey === undefined) continue; + if (!isApiKeyLoginProvider(provider.id, oauthProviderIds)) continue; + options.push({ + id: provider.id, + name: provider.name, authType: "api_key", - status: modelRegistry.getProviderAuthStatus(providerId), + status: runtime.getProviderAuthStatus(provider.id), }); } return filterAndSort(options, authType); } -export function getLogoutProviderOptions(modelRegistry: AuthProviderModelRegistry): AuthProviderOption[] { +export async function getLogoutProviderOptions(runtime: AuthProviderRuntime): Promise { + const providerNames = new Map(runtime.getProviders().map((provider) => [provider.id, provider.name])); const options: AuthProviderOption[] = []; - for (const providerId of modelRegistry.authStorage.list()) { - const credential = modelRegistry.authStorage.get(providerId); - if (credential === undefined) continue; + for (const credential of await runtime.listCredentials()) { options.push({ - id: providerId, - name: modelRegistry.getProviderDisplayName(providerId), + id: credential.providerId, + name: providerNames.get(credential.providerId) ?? credential.providerId, authType: credential.type, - status: modelRegistry.getProviderAuthStatus(providerId), + status: runtime.getProviderAuthStatus(credential.providerId), }); } return filterAndSort(options);