705f792e87
Move the Global module from packages/opencode/src/global to packages/core/src/global to provide a unified location for managing XDG directories and application paths. This eliminates duplicate path definitions across packages and ensures consistent access to data, config, cache, state, log, and bin directories throughout the codebase.
98 lines
3.4 KiB
TypeScript
98 lines
3.4 KiB
TypeScript
import path from "path"
|
|
import { Effect, Layer, Record, Result, Schema, Context } from "effect"
|
|
import { zod } from "@/util/effect-zod"
|
|
import { Global } from "@opencode-ai/core/global"
|
|
import { AppFileSystem } from "@opencode-ai/core/filesystem"
|
|
|
|
export const OAUTH_DUMMY_KEY = "opencode-oauth-dummy-key"
|
|
|
|
const file = path.join(Global.Path.data, "auth.json")
|
|
|
|
const fail = (message: string) => (cause: unknown) => new AuthError({ message, cause })
|
|
|
|
export class Oauth extends Schema.Class<Oauth>("OAuth")({
|
|
type: Schema.Literal("oauth"),
|
|
refresh: Schema.String,
|
|
access: Schema.String,
|
|
expires: Schema.Number,
|
|
accountId: Schema.optional(Schema.String),
|
|
enterpriseUrl: Schema.optional(Schema.String),
|
|
}) {}
|
|
|
|
export class Api extends Schema.Class<Api>("ApiAuth")({
|
|
type: Schema.Literal("api"),
|
|
key: Schema.String,
|
|
metadata: Schema.optional(Schema.Record(Schema.String, Schema.String)),
|
|
}) {}
|
|
|
|
export class WellKnown extends Schema.Class<WellKnown>("WellKnownAuth")({
|
|
type: Schema.Literal("wellknown"),
|
|
key: Schema.String,
|
|
token: Schema.String,
|
|
}) {}
|
|
|
|
const _Info = Schema.Union([Oauth, Api, WellKnown]).annotate({ discriminator: "type", identifier: "Auth" })
|
|
export const Info = Object.assign(_Info, { zod: zod(_Info) })
|
|
export type Info = Schema.Schema.Type<typeof _Info>
|
|
|
|
export class AuthError extends Schema.TaggedErrorClass<AuthError>()("AuthError", {
|
|
message: Schema.String,
|
|
cause: Schema.optional(Schema.Defect),
|
|
}) {}
|
|
|
|
export interface Interface {
|
|
readonly get: (providerID: string) => Effect.Effect<Info | undefined, AuthError>
|
|
readonly all: () => Effect.Effect<Record<string, Info>, AuthError>
|
|
readonly set: (key: string, info: Info) => Effect.Effect<void, AuthError>
|
|
readonly remove: (key: string) => Effect.Effect<void, AuthError>
|
|
}
|
|
|
|
export class Service extends Context.Service<Service, Interface>()("@opencode/Auth") {}
|
|
|
|
export const layer = Layer.effect(
|
|
Service,
|
|
Effect.gen(function* () {
|
|
const fsys = yield* AppFileSystem.Service
|
|
const decode = Schema.decodeUnknownOption(Info)
|
|
|
|
const all = Effect.fn("Auth.all")(function* () {
|
|
if (process.env.OPENCODE_AUTH_CONTENT) {
|
|
try {
|
|
return JSON.parse(process.env.OPENCODE_AUTH_CONTENT)
|
|
} catch (err) {}
|
|
}
|
|
|
|
const data = (yield* fsys.readJson(file).pipe(Effect.orElseSucceed(() => ({})))) as Record<string, unknown>
|
|
return Record.filterMap(data, (value) => Result.fromOption(decode(value), () => undefined))
|
|
})
|
|
|
|
const get = Effect.fn("Auth.get")(function* (providerID: string) {
|
|
return (yield* all())[providerID]
|
|
})
|
|
|
|
const set = Effect.fn("Auth.set")(function* (key: string, info: Info) {
|
|
const norm = key.replace(/\/+$/, "")
|
|
const data = yield* all()
|
|
if (norm !== key) delete data[key]
|
|
delete data[norm + "/"]
|
|
yield* fsys
|
|
.writeJson(file, { ...data, [norm]: info }, 0o600)
|
|
.pipe(Effect.mapError(fail("Failed to write auth data")))
|
|
})
|
|
|
|
const remove = Effect.fn("Auth.remove")(function* (key: string) {
|
|
const norm = key.replace(/\/+$/, "")
|
|
const data = yield* all()
|
|
delete data[key]
|
|
delete data[norm]
|
|
yield* fsys.writeJson(file, data, 0o600).pipe(Effect.mapError(fail("Failed to write auth data")))
|
|
})
|
|
|
|
return Service.of({ get, all, set, remove })
|
|
}),
|
|
)
|
|
|
|
export const defaultLayer = layer.pipe(Layer.provide(AppFileSystem.defaultLayer))
|
|
|
|
export * as Auth from "."
|