Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 18 additions & 7 deletions packages/opencode/src/cli/cmd/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -14,6 +14,17 @@ import type { Hooks } from "@opencode-ai/plugin"

type PluginAuth = NonNullable<Hooks["auth"]>

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.
Expand Down Expand Up @@ -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
}

Expand All @@ -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
}

Expand Down
37 changes: 37 additions & 0 deletions packages/opencode/test/cli/auth-plugin-selection.test.ts
Original file line number Diff line number Diff line change
@@ -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<string, unknown>).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<string, unknown>).authPlugin as
| ((items: Hooks[], provider: string) => Hooks["auth"] | undefined)
| undefined

const result = pick?.(plugins, "anthropic")
expect(result).toBeUndefined()
})
Loading