summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorBaptiste Cavallo <[email protected]>2025-11-16 23:31:41 +0100
committerGitHub <[email protected]>2025-11-16 16:31:41 -0600
commit72e604744d8e650b1999f91b6cbefdcfaf91fedf (patch)
treea4ce143da41582d796b484018ad3455c138ec123
parent832be6e7eb2bb054ae2d71bbf03abeff17e8da6c (diff)
downloadopencode-72e604744d8e650b1999f91b6cbefdcfaf91fedf.tar.gz
opencode-72e604744d8e650b1999f91b6cbefdcfaf91fedf.zip
fix(batch): restore per-tool UI feedback + UX improvements (#4387)
-rw-r--r--.opencode/opencode.json5
-rw-r--r--packages/opencode/src/tool/batch.ts81
2 files changed, 70 insertions, 16 deletions
diff --git a/.opencode/opencode.json b/.opencode/opencode.json
index 7da874d36..ca396aa13 100644
--- a/.opencode/opencode.json
+++ b/.opencode/opencode.json
@@ -1,4 +1,7 @@
{
"$schema": "https://opencode.ai/config.json",
- "plugin": ["opencode-openai-codex-auth"]
+ "plugin": ["opencode-openai-codex-auth"],
+ "experimental": {
+ "batch_tool": true
+ }
}
diff --git a/packages/opencode/src/tool/batch.ts b/packages/opencode/src/tool/batch.ts
index 4cb37132d..45c62eb29 100644
--- a/packages/opencode/src/tool/batch.ts
+++ b/packages/opencode/src/tool/batch.ts
@@ -31,6 +31,7 @@ export const BatchTool = Tool.define("batch", async () => {
return `Invalid parameters for tool 'batch':\n${formattedErrors}\n\nExpected payload format:\n [{"tool": "tool_name", "parameters": {...}}, {...}]`
},
async execute(params, ctx) {
+ const { Session } = await import("../session")
const { Identifier } = await import("../id/id")
const toolCalls = params.tool_calls
@@ -39,26 +40,36 @@ export const BatchTool = Tool.define("batch", async () => {
const availableTools = await ToolRegistry.tools("", "")
const toolMap = new Map(availableTools.map((t) => [t.id, t]))
+ const partIDs = new Map<(typeof toolCalls)[0], string>()
for (const call of toolCalls) {
- if (DISALLOWED.has(call.tool)) {
- throw new Error(
- `tool '${call.tool}' is not allowed in batch. Disallowed tools: ${Array.from(DISALLOWED).join(", ")}`,
- )
- }
- if (!toolMap.has(call.tool)) {
- const allowed = Array.from(toolMap.keys()).filter((name) => !FILTERED_FROM_SUGGESTIONS.has(name))
- throw new Error(`tool '${call.tool}' is not available. Available tools: ${allowed.join(", ")}`)
- }
+ const partID = Identifier.ascending("part")
+ partIDs.set(call, partID)
+ Session.updatePart({
+ id: partID,
+ messageID: ctx.messageID,
+ sessionID: ctx.sessionID,
+ type: "tool",
+ tool: call.tool,
+ callID: partID,
+ state: {
+ status: "pending",
+ input: call.parameters,
+ raw: JSON.stringify(call),
+ },
+ })
}
const executeCall = async (call: (typeof toolCalls)[0]) => {
- if (ctx.abort.aborted) {
- return { success: false as const, tool: call.tool, error: new Error("Aborted") }
- }
-
- const partID = Identifier.ascending("part")
+ const callStartTime = Date.now()
+ const partID = partIDs.get(call)!
try {
+ if (DISALLOWED.has(call.tool)) {
+ throw new Error(
+ `Tool '${call.tool}' is not allowed in batch. Disallowed tools: ${Array.from(DISALLOWED).join(", ")}`,
+ )
+ }
+
const tool = toolMap.get(call.tool)
if (!tool) {
const availableToolsList = Array.from(toolMap.keys()).filter((name) => !FILTERED_FROM_SUGGESTIONS.has(name))
@@ -68,13 +79,53 @@ export const BatchTool = Tool.define("batch", async () => {
const result = await tool.execute(validatedParams, { ...ctx, callID: partID })
+ await Session.updatePart({
+ id: partID,
+ messageID: ctx.messageID,
+ sessionID: ctx.sessionID,
+ type: "tool",
+ tool: call.tool,
+ callID: partID,
+ state: {
+ status: "completed",
+ input: call.parameters,
+ output: result.output,
+ title: result.title,
+ metadata: result.metadata,
+ attachments: result.attachments,
+ time: {
+ start: callStartTime,
+ end: Date.now(),
+ },
+ },
+ })
+
return { success: true as const, tool: call.tool, result }
} catch (error) {
+ await Session.updatePart({
+ id: partID,
+ messageID: ctx.messageID,
+ sessionID: ctx.sessionID,
+ type: "tool",
+ tool: call.tool,
+ callID: partID,
+ state: {
+ status: "error",
+ input: call.parameters,
+ error: error instanceof Error ? error.message : String(error),
+ time: {
+ start: callStartTime,
+ end: Date.now(),
+ },
+ },
+ })
+
return { success: false as const, tool: call.tool, error }
}
}
- const results = await Promise.all(toolCalls.flatMap((call) => executeCall(call)))
+ const results = await Promise.all(toolCalls.map((call) => executeCall(call)))
+
const successfulCalls = results.filter((r) => r.success).length
const failedCalls = toolCalls.length - successfulCalls