Skip to content
Open
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
218 changes: 148 additions & 70 deletions packages/opencode/src/session/prompt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -406,20 +406,46 @@ 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 })
const result = await taskTool.execute(taskArgs, taskCtx).catch(async (error) => {
const errorOutput: {
error: Error
result?: { title: string; output: string; metadata: any }
} = {
error: error as Error,
result: undefined,
}
await Plugin.trigger(
"tool.execute.error",
{
tool: "task",
sessionID,
callID: part.id,
args: taskArgs,
},
errorOutput,
)
if (errorOutput.result) {
return errorOutput.result
}
executionError = errorOutput.error
log.error("subtask execution failed", {
error: errorOutput.error,
agent: task.agent,
description: task.description,
})
return undefined
})
await Plugin.trigger(
"tool.execute.after",
{
tool: "task",
sessionID,
callID: part.id,
},
result,
)
if (result) {
await Plugin.trigger(
"tool.execute.after",
{
tool: "task",
sessionID,
callID: part.id,
},
result,
)
}
assistantMessage.finish = "tool-calls"
assistantMessage.time.completed = Date.now()
await Session.updateMessage(assistantMessage)
Expand All @@ -432,7 +458,7 @@ export namespace SessionPrompt {
title: result.title,
metadata: result.metadata,
output: result.output,
attachments: result.attachments,
attachments: "attachments" in result ? result.attachments : undefined,
time: {
...part.state.time,
end: Date.now(),
Expand Down Expand Up @@ -716,17 +742,41 @@ export namespace SessionPrompt {
args,
},
)
const result = await item.execute(args, ctx)
await Plugin.trigger(
"tool.execute.after",
{
tool: item.id,
sessionID: ctx.sessionID,
callID: ctx.callID,
},
result,
)
return result
try {
const result = await item.execute(args, ctx)
await Plugin.trigger(
"tool.execute.after",
{
tool: item.id,
sessionID: ctx.sessionID,
callID: ctx.callID,
},
result,
)
return result
} catch (error) {
const errorOutput: {
error: Error
result?: { title: string; output: string; metadata: any }
} = {
error: error as Error,
result: undefined,
}
await Plugin.trigger(
"tool.execute.error",
{
tool: item.id,
sessionID: ctx.sessionID,
callID: ctx.callID,
args,
},
errorOutput,
)
if (errorOutput.result) {
return errorOutput.result
}
throw errorOutput.error
}
},
})
}
Expand Down Expand Up @@ -758,65 +808,93 @@ export namespace SessionPrompt {
always: ["*"],
})

const result = await execute(args, opts)
try {
const result = await execute(args, opts)

await Plugin.trigger(
"tool.execute.after",
{
tool: key,
sessionID: ctx.sessionID,
callID: opts.toolCallId,
},
result,
)
await Plugin.trigger(
"tool.execute.after",
{
tool: key,
sessionID: ctx.sessionID,
callID: opts.toolCallId,
},
result,
)

const textParts: string[] = []
const attachments: MessageV2.FilePart[] = []
const textParts: string[] = []
const attachments: MessageV2.FilePart[] = []

for (const contentItem of result.content) {
if (contentItem.type === "text") {
textParts.push(contentItem.text)
} else if (contentItem.type === "image") {
attachments.push({
id: Identifier.ascending("part"),
sessionID: input.session.id,
messageID: input.processor.message.id,
type: "file",
mime: contentItem.mimeType,
url: `data:${contentItem.mimeType};base64,${contentItem.data}`,
})
} else if (contentItem.type === "resource") {
const { resource } = contentItem
if (resource.text) {
textParts.push(resource.text)
}
if (resource.blob) {
for (const contentItem of result.content) {
if (contentItem.type === "text") {
textParts.push(contentItem.text)
} else if (contentItem.type === "image") {
attachments.push({
id: Identifier.ascending("part"),
sessionID: input.session.id,
messageID: input.processor.message.id,
type: "file",
mime: resource.mimeType ?? "application/octet-stream",
url: `data:${resource.mimeType ?? "application/octet-stream"};base64,${resource.blob}`,
filename: resource.uri,
mime: contentItem.mimeType,
url: `data:${contentItem.mimeType};base64,${contentItem.data}`,
})
} else if (contentItem.type === "resource") {
const { resource } = contentItem
if (resource.text) {
textParts.push(resource.text)
}
if (resource.blob) {
attachments.push({
id: Identifier.ascending("part"),
sessionID: input.session.id,
messageID: input.processor.message.id,
type: "file",
mime: resource.mimeType ?? "application/octet-stream",
url: `data:${resource.mimeType ?? "application/octet-stream"};base64,${resource.blob}`,
filename: resource.uri,
})
}
}
}
}

const truncated = await Truncate.output(textParts.join("\n\n"), {}, input.agent)
const metadata = {
...(result.metadata ?? {}),
truncated: truncated.truncated,
...(truncated.truncated && { outputPath: truncated.outputPath }),
}
const truncated = await Truncate.output(textParts.join("\n\n"), {}, input.agent)
const metadata = {
...(result.metadata ?? {}),
truncated: truncated.truncated,
...(truncated.truncated && { outputPath: truncated.outputPath }),
}

return {
title: "",
metadata,
output: truncated.content,
attachments,
content: result.content, // directly return content to preserve ordering when outputting to model
return {
title: "",
metadata,
output: truncated.content,
attachments,
content: result.content, // directly return content to preserve ordering when outputting to model
}
} catch (error) {
const errorOutput: {
error: Error
result?: { title: string; output: string; metadata: any }
} = {
error: error as Error,
result: undefined,
}
await Plugin.trigger(
"tool.execute.error",
{
tool: key,
sessionID: ctx.sessionID,
callID: opts.toolCallId,
args,
},
errorOutput,
)
if (errorOutput.result) {
// Return with content array for MCP tool consistency
return {
...errorOutput.result,
content: [{ type: "text" as const, text: errorOutput.result.output }],
}
}
throw errorOutput.error
}
}
tools[key] = item
Expand Down
11 changes: 11 additions & 0 deletions packages/plugin/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,17 @@ export interface Hooks {
metadata: any
},
) => Promise<void>
"tool.execute.error"?: (
input: { tool: string; sessionID: string; callID: string; args: any },
output: {
error: Error
result?: {
title: string
output: string
metadata: any
}
},
) => Promise<void>
"experimental.chat.messages.transform"?: (
input: {},
output: {
Expand Down
Loading