diff --git a/packages/opencode/src/session/prompt.ts b/packages/opencode/src/session/prompt.ts index d1f4072586e4..819bcf8a30b1 100644 --- a/packages/opencode/src/session/prompt.ts +++ b/packages/opencode/src/session/prompt.ts @@ -403,15 +403,6 @@ export namespace SessionPrompt { subagent_type: task.agent, command: task.command, } - await Plugin.trigger( - "tool.execute.before", - { - tool: "task", - sessionID, - callID: part.id, - }, - { args: taskArgs }, - ) let executionError: Error | undefined const taskAgent = await Agent.get(task.agent) const taskCtx: Tool.Context = { @@ -440,10 +431,18 @@ export namespace SessionPrompt { }) }, } - const result = await taskTool.execute(taskArgs, taskCtx).catch((error) => { - executionError = error - log.error("subtask execution failed", { error, agent: task.agent, description: task.description }) - return undefined + const result = await Tool.invoke({ + tool: "task", + sessionID, + callID: part.id, + agent: task.agent, + args: taskArgs, + fn: () => + taskTool.execute(taskArgs, taskCtx).catch((error) => { + executionError = error + log.error("subtask execution failed", { error, agent: task.agent, description: task.description }) + return undefined + }), }) const attachments = result?.attachments?.map((attachment) => ({ ...attachment, @@ -451,16 +450,6 @@ export namespace SessionPrompt { sessionID, messageID: assistantMessage.id, })) - await Plugin.trigger( - "tool.execute.after", - { - tool: "task", - sessionID, - callID: part.id, - args: taskArgs, - }, - result, - ) assistantMessage.finish = "tool-calls" assistantMessage.time.completed = Date.now() await Session.updateMessage(assistantMessage) @@ -791,38 +780,23 @@ export namespace SessionPrompt { inputSchema: jsonSchema(schema as any), async execute(args, options) { const ctx = context(args, options) - await Plugin.trigger( - "tool.execute.before", - { - tool: item.id, - sessionID: ctx.sessionID, - callID: ctx.callID, - }, - { - args, - }, - ) - const result = await item.execute(args, ctx) - const output = { - ...result, - attachments: result.attachments?.map((attachment) => ({ + const result = await Tool.invoke({ + tool: item.id, + sessionID: ctx.sessionID, + callID: ctx.callID, + agent: ctx.agent, + args, + fn: () => item.execute(args, ctx), + }) + return { + ...result!, + attachments: result?.attachments?.map((attachment) => ({ ...attachment, id: Identifier.ascending("part"), sessionID: ctx.sessionID, messageID: input.processor.message.id, })), } - await Plugin.trigger( - "tool.execute.after", - { - tool: item.id, - sessionID: ctx.sessionID, - callID: ctx.callID, - args, - }, - output, - ) - return output }, }) } diff --git a/packages/opencode/src/tool/tool.ts b/packages/opencode/src/tool/tool.ts index 0e78ba665cfc..793dae831a18 100644 --- a/packages/opencode/src/tool/tool.ts +++ b/packages/opencode/src/tool/tool.ts @@ -45,6 +45,62 @@ export namespace Tool { export type InferParameters = T extends Info ? z.infer

: never export type InferMetadata = T extends Info ? M : never + export type Result = Awaited>["execute"]>> + + let pending: Promise | undefined + export async function invoke(input: { + tool: string + sessionID: string + callID?: string + agent?: string + args: any + fn: () => Promise + }): Promise { + pending ??= import("../plugin") + const { Plugin } = await pending + await Plugin.trigger( + "tool.execute.before", + { + tool: input.tool, + sessionID: input.sessionID, + callID: input.callID ?? "", + agent: input.agent, + }, + { args: input.args }, + ) + let result: Result | undefined + try { + result = await input.fn() + } catch (e) { + await Plugin.trigger( + "tool.execute.after", + { + tool: input.tool, + sessionID: input.sessionID, + callID: input.callID ?? "", + args: input.args, + agent: input.agent, + }, + { title: "", output: "", metadata: { error: e }, status: "error" as const }, + ) + throw e + } + await Plugin.trigger( + "tool.execute.after", + { + tool: input.tool, + sessionID: input.sessionID, + callID: input.callID ?? "", + args: input.args, + agent: input.agent, + }, + result + ? { ...result, status: "success" as const } + : { title: "", output: "", metadata: {}, status: "error" as const }, + ) + return result + } + export function define( id: string, init: Info["init"] | Awaited["init"]>>, diff --git a/packages/plugin/src/index.ts b/packages/plugin/src/index.ts index 76370d1d5a7f..126692e20063 100644 --- a/packages/plugin/src/index.ts +++ b/packages/plugin/src/index.ts @@ -182,7 +182,7 @@ export interface Hooks { output: { parts: Part[] }, ) => Promise "tool.execute.before"?: ( - input: { tool: string; sessionID: string; callID: string }, + input: { tool: string; sessionID: string; callID: string; agent?: string }, output: { args: any }, ) => Promise "shell.env"?: ( @@ -190,11 +190,12 @@ export interface Hooks { output: { env: Record }, ) => Promise "tool.execute.after"?: ( - input: { tool: string; sessionID: string; callID: string; args: any }, + input: { tool: string; sessionID: string; callID: string; args: any; agent?: string }, output: { title: string output: string metadata: any + status?: "success" | "error" }, ) => Promise "experimental.chat.messages.transform"?: (