export * as ConfigExternalPlugin from "./external" import type { Plugin as EffectPlugin } from "@opencode-ai/plugin/v2/effect" import type { Plugin as PromisePlugin } from "@opencode-ai/plugin/v2/promise" import { Effect, Schema } from "effect" import path from "path" import { fileURLToPath, pathToFileURL } from "url" import { Config } from "../../config" import { FSUtil } from "../../fs-util" import { Location } from "../../location" import { Npm } from "../../npm" import { define } from "../../plugin/internal" import { PluginPromise } from "../../plugin/promise" const PluginModule = Schema.Struct({ default: Schema.Union([ Schema.Struct({ id: Schema.String, effect: Schema.declare( (input): input is EffectPlugin["effect"] => typeof input === "function", ), }), Schema.Struct({ id: Schema.String, setup: Schema.declare( (input): input is PromisePlugin["setup"] => typeof input === "function", ), }), ]), }) export const Plugin = define({ id: "config-plugin", effect: Effect.fn(function* (ctx) { const config = yield* Config.Service const fs = yield* FSUtil.Service const location = yield* Location.Service const npm = yield* Npm.Service yield* Effect.gen(function* () { const configured: { package: string; options?: Record }[] = [] for (const entry of yield* config.entries()) { if (entry.type === "document") { const directory = entry.path ? path.dirname(entry.path) : location.directory for (const item of entry.info.plugins ?? []) { const ref = typeof item === "string" ? { package: item } : item const packageName = (() => { if (ref.package.startsWith("file://")) return fileURLToPath(ref.package) if (ref.package.startsWith("./") || ref.package.startsWith("../")) { return path.resolve(directory, ref.package) } return ref.package })() configured.push({ package: packageName, options: ref.options }) } } if (entry.type === "directory") { const files = yield* fs .glob("{plugin,plugins}/*.{ts,js}", { cwd: entry.path, absolute: true, include: "file", dot: true, symlink: true, }) .pipe(Effect.orElseSucceed(() => [])) files.sort() for (const file of files) configured.push({ package: file }) } } for (const ref of configured) { yield* Effect.gen(function* () { const entrypoint = path.isAbsolute(ref.package) ? pathToFileURL(ref.package).href : (yield* npm.add(ref.package)).entrypoint if (!entrypoint) return const mod = yield* Effect.promise(() => import(entrypoint)) const value = (yield* Schema.decodeUnknownEffect(PluginModule)(mod)).default const plugin = "effect" in value ? value : PluginPromise.fromPromise(value) yield* ctx.plugin.add({ id: plugin.id, effect: (host) => plugin.effect({ ...host, options: ref.options ?? {} }), }) }).pipe(Effect.ignoreCause) } }).pipe(Effect.forkScoped({ startImmediately: true })) }), })