From 5b1085d2294b6b529145376997b72452b2464191 Mon Sep 17 00:00:00 2001 From: Dax Raad Date: Sun, 17 May 2026 16:37:21 -0400 Subject: [PATCH] fix: store well-known logins in new auth service --- packages/core/src/auth-well-known.ts | 5 +++ packages/opencode/src/cli/cmd/providers.ts | 48 +++++++++++++++------ packages/opencode/src/effect/app-runtime.ts | 2 + 3 files changed, 41 insertions(+), 14 deletions(-) diff --git a/packages/core/src/auth-well-known.ts b/packages/core/src/auth-well-known.ts index e7c0526c31..9f7990301f 100644 --- a/packages/core/src/auth-well-known.ts +++ b/packages/core/src/auth-well-known.ts @@ -51,6 +51,7 @@ export interface Interface { readonly get: (url: string) => Effect.Effect readonly set: (url: string, entry: Entry) => Effect.Effect readonly remove: (url: string) => Effect.Effect + readonly metadata: (url: string) => Effect.Effect readonly configs: () => Effect.Effect } @@ -177,6 +178,10 @@ export const layer = Layer.effect( ) }), + metadata: Effect.fn("AuthWellKnown.metadata.public")(function* (url) { + return (yield* metadata(url)).metadata + }), + configs: Effect.fn("AuthWellKnown.configs")(function* () { const documents = yield* Effect.all( Object.entries(yield* SynchronizedRef.get(state)).map(([url, entry]) => diff --git a/packages/opencode/src/cli/cmd/providers.ts b/packages/opencode/src/cli/cmd/providers.ts index 25f1bf968c..a8863139fa 100644 --- a/packages/opencode/src/cli/cmd/providers.ts +++ b/packages/opencode/src/cli/cmd/providers.ts @@ -1,4 +1,5 @@ import { Auth } from "../../auth" +import { AuthWellKnown } from "@opencode-ai/core/auth-well-known" import { cmd } from "./cmd" import { CliError, effectCmd, fail } from "../effect-cmd" import { UI } from "../ui" @@ -252,6 +253,7 @@ export const ProvidersListCommand = effectCmd({ instance: false, handler: Effect.fn("Cli.providers.list")(function* (_args) { const authSvc = yield* Auth.Service + const authWellKnown = yield* AuthWellKnown.Service const modelsDev = yield* ModelsDev.Service UI.empty() @@ -259,7 +261,8 @@ export const ProvidersListCommand = effectCmd({ const homedir = os.homedir() const displayPath = authPath.startsWith(homedir) ? authPath.replace(homedir, "~") : authPath yield* Prompt.intro(`Credentials ${UI.Style.TEXT_DIM}${displayPath}`) - const results = Object.entries(yield* Effect.orDie(authSvc.all())) + const results = Object.entries(yield* Effect.orDie(authSvc.all())).filter(([, result]) => result.type !== "wellknown") + const wellKnownResults = Object.entries(yield* Effect.orDie(authWellKnown.all())) const database = yield* modelsDev.get() for (const [providerID, result] of results) { @@ -267,7 +270,11 @@ export const ProvidersListCommand = effectCmd({ yield* Prompt.log.info(`${name} ${UI.Style.TEXT_DIM}${result.type}`) } - yield* Prompt.outro(`${results.length} credentials`) + for (const [url] of wellKnownResults) { + yield* Prompt.log.info(`${url} ${UI.Style.TEXT_DIM}wellknown`) + } + + yield* Prompt.outro(`${results.length + wellKnownResults.length} credentials`) const activeEnvVars: Array<{ provider: string; envVar: string }> = [] @@ -316,19 +323,19 @@ export const ProvidersLoginCommand = effectCmd({ }), handler: Effect.fn("Cli.providers.login")(function* (args) { const authSvc = yield* Auth.Service + const authWellKnown = yield* AuthWellKnown.Service UI.empty() yield* Prompt.intro("Add credential") if (args.url) { const url = args.url.replace(/\/+$/, "") - const wellknown = (yield* cliTry(`Failed to load auth provider metadata from ${url}: `, () => - fetch(`${url}/.well-known/opencode`).then((x) => x.json()), - )) as { - auth: { command: string[]; env: string } - } + const wellknown = yield* authWellKnown.metadata(url).pipe( + Effect.mapError((error) => new CliError({ message: `Failed to load auth provider metadata from ${url}: ${errorMessage(error)}` })), + ) + if (!wellknown.auth) return yield* fail(`Auth provider metadata from ${url} is missing auth configuration`) yield* Prompt.log.info(`Running \`${wellknown.auth.command.join(" ")}\``) const abort = new AbortController() - const proc = Process.spawn(wellknown.auth.command, { stdout: "pipe", stderr: "inherit", abort: abort.signal }) + const proc = Process.spawn([...wellknown.auth.command], { stdout: "pipe", stderr: "inherit", abort: abort.signal }) if (!proc.stdout) { yield* Prompt.log.error("Failed") yield* Prompt.outro("Done") @@ -342,7 +349,7 @@ export const ProvidersLoginCommand = effectCmd({ yield* Prompt.outro("Done") return } - yield* Effect.orDie(authSvc.set(url, { type: "wellknown", key: wellknown.auth.env, token: token.trim() })) + yield* Effect.orDie(authWellKnown.set(url, new AuthWellKnown.Entry({ key: wellknown.auth.env, token: token.trim() }))) yield* Prompt.log.success("Logged into " + url) yield* Prompt.outro("Done") return @@ -492,10 +499,20 @@ export const ProvidersLogoutCommand = effectCmd({ instance: false, handler: Effect.fn("Cli.providers.logout")(function* (_args) { const authSvc = yield* Auth.Service + const authWellKnown = yield* AuthWellKnown.Service const modelsDev = yield* ModelsDev.Service UI.empty() - const credentials: Array<[string, Auth.Info]> = Object.entries(yield* Effect.orDie(authSvc.all())) + const credentials = [ + ...Object.entries(yield* Effect.orDie(authSvc.all())) + .filter(([, value]) => value.type !== "wellknown") + .map(([key, value]) => ({ key, type: value.type, auth: "provider" as const })), + ...Object.keys(yield* Effect.orDie(authWellKnown.all())).map((key) => ({ + key, + type: "wellknown" as const, + auth: "wellknown" as const, + })), + ] yield* Prompt.intro("Remove credential") if (credentials.length === 0) { yield* Prompt.log.error("No credentials found") @@ -504,12 +521,15 @@ export const ProvidersLogoutCommand = effectCmd({ const database = yield* modelsDev.get() const selected = yield* Prompt.select({ message: "Select provider", - options: credentials.map(([key, value]) => ({ - label: (database[key]?.name || key) + UI.Style.TEXT_DIM + " (" + value.type + ")", - value: key, + options: credentials.map((item, index) => ({ + label: (database[item.key]?.name || item.key) + UI.Style.TEXT_DIM + " (" + item.type + ")", + value: index, })), }) - yield* Effect.orDie(authSvc.remove(yield* promptValue(selected))) + const credential = credentials[yield* promptValue(selected)] + if (!credential) return + if (credential.auth === "wellknown") yield* Effect.orDie(authWellKnown.remove(credential.key)) + else yield* Effect.orDie(authSvc.remove(credential.key)) yield* Prompt.outro("Logout successful") }), }) diff --git a/packages/opencode/src/effect/app-runtime.ts b/packages/opencode/src/effect/app-runtime.ts index 0ce876ddc6..a94b4d19bc 100644 --- a/packages/opencode/src/effect/app-runtime.ts +++ b/packages/opencode/src/effect/app-runtime.ts @@ -3,6 +3,7 @@ import { attach } from "./run-service" import * as Observability from "@opencode-ai/core/effect/observability" import { AppFileSystem } from "@opencode-ai/core/filesystem" +import { AuthWellKnown } from "@opencode-ai/core/auth-well-known" import { Bus } from "@/bus" import { Auth } from "@/auth" import { Account } from "@/account/account" @@ -62,6 +63,7 @@ import { RuntimeFlags } from "@/effect/runtime-flags" export const AppLayer = Layer.mergeAll( Npm.defaultLayer, AppFileSystem.defaultLayer, + AuthWellKnown.defaultLayer, Bus.defaultLayer, Auth.defaultLayer, Account.defaultLayer,