fix: propagate abort signal to inline read tool (#21584)

This commit is contained in:
Kit Langton
2026-04-08 21:07:55 -04:00
committed by GitHub
parent 51535d8ef3
commit 2bdd279467
4 changed files with 185 additions and 79 deletions

View File

@@ -42,24 +42,25 @@ import { Agent } from "../agent/agent"
export namespace ToolRegistry {
const log = Log.create({ service: "tool.registry" })
type TaskDef = Tool.InferDef<typeof TaskTool>
type ReadDef = Tool.InferDef<typeof ReadTool>
type State = {
custom: Tool.Def[]
builtin: Tool.Def[]
task: TaskDef
read: ReadDef
}
export interface Interface {
readonly ids: () => Effect.Effect<string[]>
readonly all: () => Effect.Effect<Tool.Def[]>
readonly named: {
task: Tool.Info
read: Tool.Info
}
readonly named: () => Effect.Effect<{ task: TaskDef; read: ReadDef }>
readonly tools: (model: {
providerID: ProviderID
modelID: ModelID
agent: Agent.Info
}) => Effect.Effect<Tool.Def[]>
readonly fromID: (id: string) => Effect.Effect<Tool.Def>
}
export class Service extends ServiceMap.Service<Service, Interface>()("@opencode/ToolRegistry") {}
@@ -183,6 +184,8 @@ export namespace ToolRegistry {
...(Flag.OPENCODE_EXPERIMENTAL_LSP_TOOL ? [tool.lsp] : []),
...(Flag.OPENCODE_EXPERIMENTAL_PLAN_MODE && Flag.OPENCODE_CLIENT === "cli" ? [tool.plan] : []),
],
task: tool.task,
read: tool.read,
}
}),
)
@@ -192,13 +195,6 @@ export namespace ToolRegistry {
return [...s.builtin, ...s.custom] as Tool.Def[]
})
const fromID: Interface["fromID"] = Effect.fn("ToolRegistry.fromID")(function* (id: string) {
const tools = yield* all()
const match = tools.find((tool) => tool.id === id)
if (!match) return yield* Effect.die(`Tool not found: ${id}`)
return match
})
const ids: Interface["ids"] = Effect.fn("ToolRegistry.ids")(function* () {
return (yield* all()).map((tool) => tool.id)
})
@@ -245,7 +241,12 @@ export namespace ToolRegistry {
)
})
return Service.of({ ids, all, named: { task, read }, tools, fromID })
const named: Interface["named"] = Effect.fn("ToolRegistry.named")(function* () {
const s = yield* InstanceState.get(state)
return { task: s.task, read: s.read }
})
return Service.of({ ids, all, named, tools })
}),
)

View File

@@ -60,6 +60,13 @@ export namespace Tool {
export type InferMetadata<T> =
T extends Info<any, infer M> ? M : T extends Effect.Effect<Info<any, infer M>, any, any> ? M : never
export type InferDef<T> =
T extends Info<infer P, infer M>
? Def<P, M>
: T extends Effect.Effect<Info<infer P, infer M>, any, any>
? Def<P, M>
: never
function wrap<Parameters extends z.ZodType, Result extends Metadata>(
id: string,
init: (() => Promise<DefWithoutID<Parameters, Result>>) | DefWithoutID<Parameters, Result>,
@@ -118,7 +125,7 @@ export namespace Tool {
)
}
export function init(info: Info): Effect.Effect<Def> {
export function init<P extends z.ZodType, M extends Metadata>(info: Info<P, M>): Effect.Effect<Def<P, M>> {
return Effect.gen(function* () {
const init = yield* Effect.promise(() => info.init())
return {