refactor(effect): simplify task tool registry bridge

This commit is contained in:
Kit Langton
2026-04-08 13:59:58 -04:00
parent 186063fbed
commit 0f3a2a7b67
4 changed files with 62 additions and 62 deletions

View File

@@ -47,6 +47,7 @@ import { Process } from "@/util/process"
import { Cause, Effect, Exit, Layer, Option, Scope, ServiceMap } from "effect"
import { InstanceState } from "@/effect/instance-state"
import { makeRuntime } from "@/effect/run-service"
import { TaskTool } from "@/tool/task"
// @ts-ignore
globalThis.AI_SDK_LOG_WARNINGS = false
@@ -558,7 +559,7 @@ NOTE: At any point in time through this workflow you should feel free to ask the
}) {
const { task, model, lastUser, sessionID, session, msgs } = input
const ctx = yield* InstanceState.context
const taskTool = yield* registry.fromID("task")
const taskTool = yield* registry.fromID(TaskTool.id)
const taskModel = task.model ? yield* getModel(task.model.providerID, task.model.modelID, sessionID) : model
const assistantMessage: MessageV2.Assistant = yield* sessions.updateMessage({
id: MessageID.ascending(),
@@ -581,7 +582,7 @@ NOTE: At any point in time through this workflow you should feel free to ask the
sessionID: assistantMessage.sessionID,
type: "tool",
callID: ulid(),
tool: "task",
tool: TaskTool.id,
state: {
status: "running",
input: {

View File

@@ -82,18 +82,10 @@ export namespace ToolRegistry {
const config = yield* Config.Service
const plugin = yield* Plugin.Service
const info = <T extends Tool.Info, R = never>(
tool: T | Effect.Effect<T, never, R>,
): Effect.Effect<T, never, R> => (Effect.isEffect(tool) ? tool : Effect.succeed(tool))
const build = <T extends Tool.Info, R = never>(
tool: T | Effect.Effect<T, never, R>,
): Effect.Effect<Tool.Def, never, R> => info(tool).pipe(Effect.flatMap(Tool.init))
const task = yield* info(TaskTool)
const read = yield* info(ReadTool)
const askInfo = yield* info(QuestionTool)
const todoInfo = yield* info(TodoWriteTool)
const task = yield* TaskTool
const read = yield* ReadTool
const question = yield* QuestionTool
const todo = yield* TodoWriteTool
const state = yield* InstanceState.make<State>(
Effect.fn("ToolRegistry.state")(function* (ctx) {
@@ -147,47 +139,49 @@ export namespace ToolRegistry {
}
const cfg = yield* config.get()
const question =
const questionEnabled =
["app", "cli", "desktop"].includes(Flag.OPENCODE_CLIENT) || Flag.OPENCODE_ENABLE_QUESTION_TOOL
const invalid = yield* build(InvalidTool)
const bash = yield* build(BashTool)
const readDef = yield* build(read)
const glob = yield* build(GlobTool)
const grep = yield* build(GrepTool)
const edit = yield* build(EditTool)
const write = yield* build(WriteTool)
const taskDef = yield* build(task)
const fetch = yield* build(WebFetchTool)
const todo = yield* build(todoInfo)
const search = yield* build(WebSearchTool)
const code = yield* build(CodeSearchTool)
const skill = yield* build(SkillTool)
const patch = yield* build(ApplyPatchTool)
const ask = yield* build(askInfo)
const lsp = yield* build(LspTool)
const plan = yield* build(PlanExitTool)
const tool = yield* Effect.all({
invalid: Tool.init(InvalidTool),
bash: Tool.init(BashTool),
read: Tool.init(read),
glob: Tool.init(GlobTool),
grep: Tool.init(GrepTool),
edit: Tool.init(EditTool),
write: Tool.init(WriteTool),
task: Tool.init(task),
fetch: Tool.init(WebFetchTool),
todo: Tool.init(todo),
search: Tool.init(WebSearchTool),
code: Tool.init(CodeSearchTool),
skill: Tool.init(SkillTool),
patch: Tool.init(ApplyPatchTool),
question: Tool.init(question),
lsp: Tool.init(LspTool),
plan: Tool.init(PlanExitTool),
})
return {
custom,
builtin: [
invalid,
...(question ? [ask] : []),
bash,
readDef,
glob,
grep,
edit,
write,
taskDef,
fetch,
todo,
search,
code,
skill,
patch,
...(Flag.OPENCODE_EXPERIMENTAL_LSP_TOOL ? [lsp] : []),
...(Flag.OPENCODE_EXPERIMENTAL_PLAN_MODE && Flag.OPENCODE_CLIENT === "cli" ? [plan] : []),
tool.invalid,
...(questionEnabled ? [tool.question] : []),
tool.bash,
tool.read,
tool.glob,
tool.grep,
tool.edit,
tool.write,
tool.task,
tool.fetch,
tool.todo,
tool.search,
tool.code,
tool.skill,
tool.patch,
...(Flag.OPENCODE_EXPERIMENTAL_LSP_TOOL ? [tool.lsp] : []),
...(Flag.OPENCODE_EXPERIMENTAL_PLAN_MODE && Flag.OPENCODE_CLIENT === "cli" ? [tool.plan] : []),
],
}
}),
@@ -237,7 +231,7 @@ export namespace ToolRegistry {
id: tool.id,
description: [
output.description,
tool.id === "task" ? yield* TaskDescription(input.agent) : undefined,
tool.id === TaskTool.id ? yield* TaskDescription(input.agent) : undefined,
tool.id === SkillTool.id ? yield* SkillDescription(input.agent) : undefined,
]
.filter(Boolean)

View File

@@ -10,6 +10,8 @@ import { Config } from "../config/config"
import { Permission } from "@/permission"
import { Effect } from "effect"
const id = "task"
const parameters = z.object({
description: z.string().describe("A short (3-5 words) description of the task"),
prompt: z.string().describe("The task for the agent to perform"),
@@ -24,7 +26,7 @@ const parameters = z.object({
})
export const TaskTool = Tool.defineEffect(
"task",
id,
Effect.gen(function* () {
const agent = yield* Agent.Service
const config = yield* Config.Service
@@ -35,7 +37,7 @@ export const TaskTool = Tool.defineEffect(
if (!ctx.extra?.bypassAgentCheck) {
yield* Effect.promise(() =>
ctx.ask({
permission: "task",
permission: id,
patterns: [params.subagent_type],
always: ["*"],
metadata: {
@@ -51,7 +53,7 @@ export const TaskTool = Tool.defineEffect(
return yield* Effect.fail(new Error(`Unknown agent type: ${params.subagent_type} is not a valid agent type`))
}
const canTask = next.permission.some((rule) => rule.permission === "task")
const canTask = next.permission.some((rule) => rule.permission === id)
const canTodo = next.permission.some((rule) => rule.permission === "todowrite")
const taskID = params.task_id
@@ -81,7 +83,7 @@ export const TaskTool = Tool.defineEffect(
? []
: [
{
permission: "task" as const,
permission: id,
pattern: "*" as const,
action: "deny" as const,
},

View File

@@ -98,24 +98,27 @@ export namespace Tool {
}
}
export function define<Parameters extends z.ZodType, Result extends Metadata>(
id: string,
export function define<Parameters extends z.ZodType, Result extends Metadata, ID extends string = string>(
id: ID,
init: (() => Promise<DefWithoutID<Parameters, Result>>) | DefWithoutID<Parameters, Result>,
): Info<Parameters, Result> {
): Info<Parameters, Result> & { id: ID } {
return {
id,
init: wrap(id, init),
}
}
export function defineEffect<Parameters extends z.ZodType, Result extends Metadata, R>(
id: string,
export function defineEffect<Parameters extends z.ZodType, Result extends Metadata, R, ID extends string = string>(
id: ID,
init: Effect.Effect<(() => Promise<DefWithoutID<Parameters, Result>>) | DefWithoutID<Parameters, Result>, never, R>,
): Effect.Effect<Info<Parameters, Result>, never, R> {
return Effect.map(init, (next) => ({ id, init: wrap(id, next) }))
): Effect.Effect<Info<Parameters, Result>, never, R> & { id: ID } {
return Object.assign(
Effect.map(init, (next) => ({ id, init: wrap(id, next) })),
{ id },
)
}
export function init(info: Info): Effect.Effect<Def, never, any> {
export function init(info: Info): Effect.Effect<Def> {
return Effect.gen(function* () {
const init = yield* Effect.promise(() => info.init())
return {