fix: wait for dependencies before loading custom tools and plugins (#12227)
This commit is contained in:
@@ -30,6 +30,7 @@ import { GlobalBus } from "@/bus/global"
|
|||||||
import { Event } from "../server/event"
|
import { Event } from "../server/event"
|
||||||
import { PackageRegistry } from "@/bun/registry"
|
import { PackageRegistry } from "@/bun/registry"
|
||||||
import { proxied } from "@/util/proxied"
|
import { proxied } from "@/util/proxied"
|
||||||
|
import { iife } from "@/util/iife"
|
||||||
|
|
||||||
export namespace Config {
|
export namespace Config {
|
||||||
const log = Log.create({ service: "config" })
|
const log = Log.create({ service: "config" })
|
||||||
@@ -144,6 +145,8 @@ export namespace Config {
|
|||||||
log.debug("loading config from OPENCODE_CONFIG_DIR", { path: Flag.OPENCODE_CONFIG_DIR })
|
log.debug("loading config from OPENCODE_CONFIG_DIR", { path: Flag.OPENCODE_CONFIG_DIR })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const deps = []
|
||||||
|
|
||||||
for (const dir of unique(directories)) {
|
for (const dir of unique(directories)) {
|
||||||
if (dir.endsWith(".opencode") || dir === Flag.OPENCODE_CONFIG_DIR) {
|
if (dir.endsWith(".opencode") || dir === Flag.OPENCODE_CONFIG_DIR) {
|
||||||
for (const file of ["opencode.jsonc", "opencode.json"]) {
|
for (const file of ["opencode.jsonc", "opencode.json"]) {
|
||||||
@@ -156,10 +159,12 @@ export namespace Config {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const shouldInstall = await needsInstall(dir)
|
deps.push(
|
||||||
if (shouldInstall) {
|
iife(async () => {
|
||||||
await installDependencies(dir)
|
const shouldInstall = await needsInstall(dir)
|
||||||
}
|
if (shouldInstall) await installDependencies(dir)
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
result.command = mergeDeep(result.command ?? {}, await loadCommand(dir))
|
result.command = mergeDeep(result.command ?? {}, await loadCommand(dir))
|
||||||
result.agent = mergeDeep(result.agent, await loadAgent(dir))
|
result.agent = mergeDeep(result.agent, await loadAgent(dir))
|
||||||
@@ -233,9 +238,15 @@ export namespace Config {
|
|||||||
return {
|
return {
|
||||||
config: result,
|
config: result,
|
||||||
directories,
|
directories,
|
||||||
|
deps,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
export async function waitForDependencies() {
|
||||||
|
const deps = await state().then((x) => x.deps)
|
||||||
|
await Promise.all(deps)
|
||||||
|
}
|
||||||
|
|
||||||
export async function installDependencies(dir: string) {
|
export async function installDependencies(dir: string) {
|
||||||
const pkg = path.join(dir, "package.json")
|
const pkg = path.join(dir, "package.json")
|
||||||
const targetVersion = Installation.isLocal() ? "latest" : Installation.VERSION
|
const targetVersion = Installation.isLocal() ? "latest" : Installation.VERSION
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ export namespace Plugin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const plugins = [...(config.plugin ?? [])]
|
const plugins = [...(config.plugin ?? [])]
|
||||||
|
if (plugins.length) await Config.waitForDependencies()
|
||||||
if (!Flag.OPENCODE_DISABLE_DEFAULT_PLUGINS) {
|
if (!Flag.OPENCODE_DISABLE_DEFAULT_PLUGINS) {
|
||||||
plugins.push(...BUILTIN)
|
plugins.push(...BUILTIN)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,18 +35,15 @@ export namespace ToolRegistry {
|
|||||||
const custom = [] as Tool.Info[]
|
const custom = [] as Tool.Info[]
|
||||||
const glob = new Bun.Glob("{tool,tools}/*.{js,ts}")
|
const glob = new Bun.Glob("{tool,tools}/*.{js,ts}")
|
||||||
|
|
||||||
for (const dir of await Config.directories()) {
|
const matches = await Config.directories().then((dirs) =>
|
||||||
for await (const match of glob.scan({
|
dirs.flatMap((dir) => [...glob.scanSync({ cwd: dir, absolute: true, followSymlinks: true, dot: true })]),
|
||||||
cwd: dir,
|
)
|
||||||
absolute: true,
|
if (matches.length) await Config.waitForDependencies()
|
||||||
followSymlinks: true,
|
for (const match of matches) {
|
||||||
dot: true,
|
const namespace = path.basename(match, path.extname(match))
|
||||||
})) {
|
const mod = await import(match)
|
||||||
const namespace = path.basename(match, path.extname(match))
|
for (const [id, def] of Object.entries<ToolDefinition>(mod)) {
|
||||||
const mod = await import(match)
|
custom.push(fromPlugin(id === "default" ? namespace : `${namespace}_${id}`, def))
|
||||||
for (const [id, def] of Object.entries<ToolDefinition>(mod)) {
|
|
||||||
custom.push(fromPlugin(id === "default" ? namespace : `${namespace}_${id}`, def))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -73,4 +73,50 @@ describe("tool.registry", () => {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test("loads tools with external dependencies without crashing", async () => {
|
||||||
|
await using tmp = await tmpdir({
|
||||||
|
init: async (dir) => {
|
||||||
|
const opencodeDir = path.join(dir, ".opencode")
|
||||||
|
await fs.mkdir(opencodeDir, { recursive: true })
|
||||||
|
|
||||||
|
const toolsDir = path.join(opencodeDir, "tools")
|
||||||
|
await fs.mkdir(toolsDir, { recursive: true })
|
||||||
|
|
||||||
|
await Bun.write(
|
||||||
|
path.join(opencodeDir, "package.json"),
|
||||||
|
JSON.stringify({
|
||||||
|
name: "custom-tools",
|
||||||
|
dependencies: {
|
||||||
|
"@opencode-ai/plugin": "^0.0.0",
|
||||||
|
cowsay: "^1.6.0",
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
await Bun.write(
|
||||||
|
path.join(toolsDir, "cowsay.ts"),
|
||||||
|
[
|
||||||
|
"import { say } from 'cowsay'",
|
||||||
|
"export default {",
|
||||||
|
" description: 'tool that imports cowsay at top level',",
|
||||||
|
" args: { text: { type: 'string' } },",
|
||||||
|
" execute: async ({ text }: { text: string }) => {",
|
||||||
|
" return say({ text })",
|
||||||
|
" },",
|
||||||
|
"}",
|
||||||
|
"",
|
||||||
|
].join("\n"),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
await Instance.provide({
|
||||||
|
directory: tmp.path,
|
||||||
|
fn: async () => {
|
||||||
|
const ids = await ToolRegistry.ids()
|
||||||
|
expect(ids).toContain("cowsay")
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user