Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 23 additions & 49 deletions packages/opencode/src/session/prompt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
Expand Down Expand Up @@ -440,27 +431,25 @@ 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,
id: Identifier.ascending("part"),
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)
Expand Down Expand Up @@ -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
},
})
}
Expand Down
56 changes: 56 additions & 0 deletions packages/opencode/src/tool/tool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,62 @@ export namespace Tool {
export type InferParameters<T extends Info> = T extends Info<infer P> ? z.infer<P> : never
export type InferMetadata<T extends Info> = T extends Info<any, infer M> ? M : never

export type Result = Awaited<ReturnType<Awaited<ReturnType<Info["init"]>>["execute"]>>

let pending: Promise<typeof import("../plugin")> | undefined
export async function invoke(input: {
tool: string
sessionID: string
callID?: string
agent?: string
args: any
fn: () => Promise<Result | undefined>
}): Promise<Result | undefined> {
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<Parameters extends z.ZodType, Result extends Metadata>(
id: string,
init: Info<Parameters, Result>["init"] | Awaited<ReturnType<Info<Parameters, Result>["init"]>>,
Expand Down
5 changes: 3 additions & 2 deletions packages/plugin/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -182,19 +182,20 @@ export interface Hooks {
output: { parts: Part[] },
) => Promise<void>
"tool.execute.before"?: (
input: { tool: string; sessionID: string; callID: string },
input: { tool: string; sessionID: string; callID: string; agent?: string },
output: { args: any },
) => Promise<void>
"shell.env"?: (
input: { cwd: string; sessionID?: string; callID?: string },
output: { env: Record<string, string> },
) => Promise<void>
"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<void>
"experimental.chat.messages.transform"?: (
Expand Down
Loading