diff --git a/packages/opencode/src/cli/cmd/auth.ts b/packages/opencode/src/cli/cmd/auth.ts index bbaecfd8c711..64a945781ee6 100644 --- a/packages/opencode/src/cli/cmd/auth.ts +++ b/packages/opencode/src/cli/cmd/auth.ts @@ -3,7 +3,7 @@ import { cmd } from "./cmd" import * as prompts from "@clack/prompts" import { UI } from "../ui" import { ModelsDev } from "../../provider/models" -import { map, pipe, sortBy, values } from "remeda" +import { filter, fromEntries, map, pipe, sortBy, values } from "remeda" import path from "path" import os from "os" import { Config } from "../../config/config" @@ -14,6 +14,17 @@ import type { Hooks } from "@opencode-ai/plugin" type PluginAuth = NonNullable +export function authPlugin(plugins: Hooks[], provider: string | symbol): PluginAuth | undefined { + if (typeof provider !== "string") return + const auths = pipe( + plugins, + filter((x) => x.auth?.provider !== undefined), + map((x) => [x.auth!.provider, x.auth!] as const), + fromEntries(), + ) + return auths[provider] +} + /** * Handle plugin-based authentication flow. * Returns true if auth was handled, false if it should fall through to default handling. @@ -307,9 +318,9 @@ export const AuthLoginCommand = cmd({ if (prompts.isCancel(provider)) throw new UI.CancelledError() - const plugin = await Plugin.list().then((x) => x.find((x) => x.auth?.provider === provider)) - if (plugin && plugin.auth) { - const handled = await handlePluginAuth({ auth: plugin.auth }, provider) + const auth = await Plugin.list().then((x) => authPlugin(x, provider)) + if (auth) { + const handled = await handlePluginAuth({ auth }, provider) if (handled) return } @@ -323,9 +334,9 @@ export const AuthLoginCommand = cmd({ if (prompts.isCancel(provider)) throw new UI.CancelledError() // Check if a plugin provides auth for this custom provider - const customPlugin = await Plugin.list().then((x) => x.find((x) => x.auth?.provider === provider)) - if (customPlugin && customPlugin.auth) { - const handled = await handlePluginAuth({ auth: customPlugin.auth }, provider) + const custom = await Plugin.list().then((x) => authPlugin(x, provider)) + if (custom) { + const handled = await handlePluginAuth({ auth: custom }, provider) if (handled) return } diff --git a/packages/opencode/test/cli/auth-plugin-selection.test.ts b/packages/opencode/test/cli/auth-plugin-selection.test.ts new file mode 100644 index 000000000000..e19f96a6b506 --- /dev/null +++ b/packages/opencode/test/cli/auth-plugin-selection.test.ts @@ -0,0 +1,37 @@ +import { test, expect } from "bun:test" +import type { Hooks } from "@opencode-ai/plugin" + +const plugins = [ + { + auth: { + provider: "openai", + methods: [{ type: "api", label: "First" }], + }, + }, + { + auth: { + provider: "openai", + methods: [{ type: "api", label: "Second" }], + }, + }, +] satisfies Hooks[] + +test("authPlugin picks last auth provider", async () => { + const mod = await import("../../src/cli/cmd/auth") + const pick = (mod as Record).authPlugin as + | ((items: Hooks[], provider: string) => Hooks["auth"] | undefined) + | undefined + + const result = pick?.(plugins, "openai") + expect(result).toBe(plugins[1].auth) +}) + +test("authPlugin returns undefined when missing", async () => { + const mod = await import("../../src/cli/cmd/auth") + const pick = (mod as Record).authPlugin as + | ((items: Hooks[], provider: string) => Hooks["auth"] | undefined) + | undefined + + const result = pick?.(plugins, "anthropic") + expect(result).toBeUndefined() +})