experimental well-known auth support

This commit is contained in:
Dax Raad
2025-07-29 19:30:24 -04:00
parent 4c34b69ae6
commit 9bedd62da4
3 changed files with 53 additions and 9 deletions

View File

@@ -16,7 +16,13 @@ export namespace Auth {
key: z.string(), key: z.string(),
}) })
export const Info = z.discriminatedUnion("type", [Oauth, Api]) export const WellKnown = z.object({
type: z.literal("wellknown"),
key: z.string(),
token: z.string(),
})
export const Info = z.discriminatedUnion("type", [Oauth, Api, WellKnown])
export type Info = z.infer<typeof Info> export type Info = z.infer<typeof Info>
const filepath = path.join(Global.Path.data, "auth.json") const filepath = path.join(Global.Path.data, "auth.json")

View File

@@ -61,20 +61,45 @@ export const AuthListCommand = cmd({
prompts.log.info(`${provider} ${UI.Style.TEXT_DIM}${envVar}`) prompts.log.info(`${provider} ${UI.Style.TEXT_DIM}${envVar}`)
} }
prompts.outro( prompts.outro(`${activeEnvVars.length} environment variable` + (activeEnvVars.length === 1 ? "" : "s"))
`${activeEnvVars.length} environment variable`
+ (activeEnvVars.length === 1 ? "" : "s")
)
} }
}, },
}) })
export const AuthLoginCommand = cmd({ export const AuthLoginCommand = cmd({
command: "login", command: "login [url]",
describe: "log in to a provider", describe: "log in to a provider",
async handler() { builder: (yargs) =>
UI.empty() yargs.positional("url", {
describe: "opencode auth provider",
type: "string",
}),
async handler(args) {
prompts.intro("Add credential") prompts.intro("Add credential")
if (args.url) {
const wellknown = await fetch(`${args.url}/.well-known/opencode`).then((x) => x.json())
prompts.log.info(`Running \`${wellknown.auth.command.join(" ")}\``)
const proc = Bun.spawn({
cmd: wellknown.auth.command,
stdout: "pipe",
})
const exit = await proc.exited
if (exit !== 0) {
prompts.log.error("Failed")
prompts.outro("Done")
return
}
const token = await new Response(proc.stdout).text()
await Auth.set(args.url, {
type: "wellknown",
key: wellknown.auth.env,
token: token.trim(),
})
prompts.log.success("Logged into " + args.url)
prompts.outro("Done")
return
}
UI.empty()
const providers = await ModelsDev.get() const providers = await ModelsDev.get()
const priority: Record<string, number> = { const priority: Record<string, number> = {
anthropic: 0, anthropic: 0,

View File

@@ -11,11 +11,13 @@ import { lazy } from "../util/lazy"
import { NamedError } from "../util/error" import { NamedError } from "../util/error"
import matter from "gray-matter" import matter from "gray-matter"
import { Flag } from "../flag/flag" import { Flag } from "../flag/flag"
import { Auth } from "../auth"
export namespace Config { export namespace Config {
const log = Log.create({ service: "config" }) const log = Log.create({ service: "config" })
export const state = App.state("config", async (app) => { export const state = App.state("config", async (app) => {
const auth = await Auth.all()
let result = await global() let result = await global()
for (const file of ["opencode.jsonc", "opencode.json"]) { for (const file of ["opencode.jsonc", "opencode.json"]) {
const found = await Filesystem.findUp(file, app.path.cwd, app.path.root) const found = await Filesystem.findUp(file, app.path.cwd, app.path.root)
@@ -30,6 +32,14 @@ export namespace Config {
log.debug("loaded custom config", { path: Flag.OPENCODE_CONFIG }) log.debug("loaded custom config", { path: Flag.OPENCODE_CONFIG })
} }
for (const [key, value] of Object.entries(auth)) {
if (value.type === "wellknown") {
process.env[value.key] = value.token
const wellknown = await fetch(`${key}/.well-known/opencode`).then((x) => x.json())
result = mergeDeep(result, await loadRaw(JSON.stringify(wellknown.config ?? {}), process.cwd()))
}
}
result.agent = result.agent || {} result.agent = result.agent || {}
const markdownAgents = [ const markdownAgents = [
...(await Filesystem.globUp("agent/*.md", Global.Path.config, Global.Path.config)), ...(await Filesystem.globUp("agent/*.md", Global.Path.config, Global.Path.config)),
@@ -307,7 +317,10 @@ export namespace Config {
throw new JsonError({ path: configPath }, { cause: err }) throw new JsonError({ path: configPath }, { cause: err })
}) })
if (!text) return {} if (!text) return {}
return loadRaw(text, configPath)
}
async function loadRaw(text: string, configPath: string) {
text = text.replace(/\{env:([^}]+)\}/g, (_, varName) => { text = text.replace(/\{env:([^}]+)\}/g, (_, varName) => {
return process.env[varName] || "" return process.env[varName] || ""
}) })