refactor(tool-registry): yield Config/Plugin services, use Effect.forEach (#19363)

This commit is contained in:
Kit Langton
2026-03-27 09:53:00 -04:00
committed by GitHub
parent d2bfa92e74
commit 7b44918149
2 changed files with 60 additions and 57 deletions
+1 -1
View File
@@ -194,7 +194,7 @@ export namespace Plugin {
}), }),
) )
const defaultLayer = layer.pipe(Layer.provide(Bus.layer)) export const defaultLayer = layer.pipe(Layer.provide(Bus.layer))
const { runPromise } = makeRuntime(Service, defaultLayer) const { runPromise } = makeRuntime(Service, defaultLayer)
export async function trigger< export async function trigger<
+59 -56
View File
@@ -54,6 +54,9 @@ export namespace ToolRegistry {
export const layer = Layer.effect( export const layer = Layer.effect(
Service, Service,
Effect.gen(function* () { Effect.gen(function* () {
const config = yield* Config.Service
const plugin = yield* Plugin.Service
const cache = yield* InstanceState.make<State>( const cache = yield* InstanceState.make<State>(
Effect.fn("ToolRegistry.state")(function* (ctx) { Effect.fn("ToolRegistry.state")(function* (ctx) {
const custom: Tool.Info[] = [] const custom: Tool.Info[] = []
@@ -82,35 +85,34 @@ export namespace ToolRegistry {
} }
} }
yield* Effect.promise(async () => { const dirs = yield* config.directories()
const matches = await Config.directories().then((dirs) => const matches = dirs.flatMap((dir) =>
dirs.flatMap((dir) => Glob.scanSync("{tool,tools}/*.{js,ts}", { cwd: dir, absolute: true, dot: true, symlink: true }),
Glob.scanSync("{tool,tools}/*.{js,ts}", { cwd: dir, absolute: true, dot: true, symlink: true }), )
), if (matches.length) yield* config.waitForDependencies()
for (const match of matches) {
const namespace = path.basename(match, path.extname(match))
const mod = yield* Effect.promise(() =>
import(process.platform === "win32" ? match : pathToFileURL(match).href),
) )
if (matches.length) await Config.waitForDependencies() for (const [id, def] of Object.entries<ToolDefinition>(mod)) {
for (const match of matches) { custom.push(fromPlugin(id === "default" ? namespace : `${namespace}_${id}`, def))
const namespace = path.basename(match, path.extname(match))
const mod = await import(process.platform === "win32" ? match : pathToFileURL(match).href)
for (const [id, def] of Object.entries<ToolDefinition>(mod)) {
custom.push(fromPlugin(id === "default" ? namespace : `${namespace}_${id}`, def))
}
} }
}
const plugins = await Plugin.list() const plugins = yield* plugin.list()
for (const plugin of plugins) { for (const p of plugins) {
for (const [id, def] of Object.entries(plugin.tool ?? {})) { for (const [id, def] of Object.entries(p.tool ?? {})) {
custom.push(fromPlugin(id, def)) custom.push(fromPlugin(id, def))
}
} }
}) }
return { custom } return { custom }
}), }),
) )
async function all(custom: Tool.Info[]): Promise<Tool.Info[]> { const all = Effect.fn("ToolRegistry.all")(function* (custom: Tool.Info[]) {
const cfg = await Config.get() const cfg = yield* config.get()
const question = ["app", "cli", "desktop"].includes(Flag.OPENCODE_CLIENT) || Flag.OPENCODE_ENABLE_QUESTION_TOOL const question = ["app", "cli", "desktop"].includes(Flag.OPENCODE_CLIENT) || Flag.OPENCODE_ENABLE_QUESTION_TOOL
return [ return [
@@ -134,7 +136,7 @@ export namespace ToolRegistry {
...(Flag.OPENCODE_EXPERIMENTAL_PLAN_MODE && Flag.OPENCODE_CLIENT === "cli" ? [PlanExitTool] : []), ...(Flag.OPENCODE_EXPERIMENTAL_PLAN_MODE && Flag.OPENCODE_CLIENT === "cli" ? [PlanExitTool] : []),
...custom, ...custom,
] ]
} })
const register = Effect.fn("ToolRegistry.register")(function* (tool: Tool.Info) { const register = Effect.fn("ToolRegistry.register")(function* (tool: Tool.Info) {
const state = yield* InstanceState.get(cache) const state = yield* InstanceState.get(cache)
@@ -148,7 +150,7 @@ export namespace ToolRegistry {
const ids = Effect.fn("ToolRegistry.ids")(function* () { const ids = Effect.fn("ToolRegistry.ids")(function* () {
const state = yield* InstanceState.get(cache) const state = yield* InstanceState.get(cache)
const tools = yield* Effect.promise(() => all(state.custom)) const tools = yield* all(state.custom)
return tools.map((t) => t.id) return tools.map((t) => t.id)
}) })
@@ -157,40 +159,37 @@ export namespace ToolRegistry {
agent?: Agent.Info, agent?: Agent.Info,
) { ) {
const state = yield* InstanceState.get(cache) const state = yield* InstanceState.get(cache)
const allTools = yield* Effect.promise(() => all(state.custom)) const allTools = yield* all(state.custom)
return yield* Effect.promise(() => const filtered = allTools.filter((tool) => {
Promise.all( if (tool.id === "codesearch" || tool.id === "websearch") {
allTools return model.providerID === ProviderID.opencode || Flag.OPENCODE_ENABLE_EXA
.filter((tool) => { }
// Enable websearch/codesearch for zen users OR via enable flag
if (tool.id === "codesearch" || tool.id === "websearch") {
return model.providerID === ProviderID.opencode || Flag.OPENCODE_ENABLE_EXA
}
// use apply tool in same format as codex const usePatch =
const usePatch = model.modelID.includes("gpt-") && !model.modelID.includes("oss") && !model.modelID.includes("gpt-4")
model.modelID.includes("gpt-") && !model.modelID.includes("oss") && !model.modelID.includes("gpt-4") if (tool.id === "apply_patch") return usePatch
if (tool.id === "apply_patch") return usePatch if (tool.id === "edit" || tool.id === "write") return !usePatch
if (tool.id === "edit" || tool.id === "write") return !usePatch
return true return true
}) })
.map(async (tool) => { return yield* Effect.forEach(
using _ = log.time(tool.id) filtered,
const next = await tool.init({ agent }) Effect.fnUntraced(function* (tool) {
const output = { using _ = log.time(tool.id)
description: next.description, const next = yield* Effect.promise(() => tool.init({ agent }))
parameters: next.parameters, const output = {
} description: next.description,
await Plugin.trigger("tool.definition", { toolID: tool.id }, output) parameters: next.parameters,
return { }
id: tool.id, yield* plugin.trigger("tool.definition", { toolID: tool.id }, output)
...next, return {
description: output.description, id: tool.id,
parameters: output.parameters, ...next,
} description: output.description,
}), parameters: output.parameters,
), } as Awaited<ReturnType<Tool.Info["init"]>> & { id: string }
}),
{ concurrency: "unbounded" },
) )
}) })
@@ -198,7 +197,11 @@ export namespace ToolRegistry {
}), }),
) )
const { runPromise } = makeRuntime(Service, layer) export const defaultLayer = Layer.unwrap(
Effect.sync(() => layer.pipe(Layer.provide(Config.defaultLayer), Layer.provide(Plugin.defaultLayer))),
)
const { runPromise } = makeRuntime(Service, defaultLayer)
export async function register(tool: Tool.Info) { export async function register(tool: Tool.Info) {
return runPromise((svc) => svc.register(tool)) return runPromise((svc) => svc.register(tool))
@@ -214,7 +217,7 @@ export namespace ToolRegistry {
modelID: ModelID modelID: ModelID
}, },
agent?: Agent.Info, agent?: Agent.Info,
) { ): Promise<(Awaited<ReturnType<Tool.Info["init"]>> & { id: string })[]> {
return runPromise((svc) => svc.tools(model, agent)) return runPromise((svc) => svc.tools(model, agent))
} }
} }