2
0
Эх сурвалжийг харах

fix(batch): restore per-tool UI feedback + UX improvements (#4387)

Baptiste Cavallo 3 сар өмнө
parent
commit
72e604744d

+ 4 - 1
.opencode/opencode.json

@@ -1,4 +1,7 @@
 {
 {
   "$schema": "https://opencode.ai/config.json",
   "$schema": "https://opencode.ai/config.json",
-  "plugin": ["opencode-openai-codex-auth"]
+  "plugin": ["opencode-openai-codex-auth"],
+  "experimental": {
+    "batch_tool": true
+  }
 }
 }

+ 66 - 15
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": {...}}, {...}]`
       return `Invalid parameters for tool 'batch':\n${formattedErrors}\n\nExpected payload format:\n  [{"tool": "tool_name", "parameters": {...}}, {...}]`
     },
     },
     async execute(params, ctx) {
     async execute(params, ctx) {
+      const { Session } = await import("../session")
       const { Identifier } = await import("../id/id")
       const { Identifier } = await import("../id/id")
 
 
       const toolCalls = params.tool_calls
       const toolCalls = params.tool_calls
@@ -39,26 +40,36 @@ export const BatchTool = Tool.define("batch", async () => {
       const availableTools = await ToolRegistry.tools("", "")
       const availableTools = await ToolRegistry.tools("", "")
       const toolMap = new Map(availableTools.map((t) => [t.id, t]))
       const toolMap = new Map(availableTools.map((t) => [t.id, t]))
 
 
+      const partIDs = new Map<(typeof toolCalls)[0], string>()
       for (const call of toolCalls) {
       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]) => {
       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 {
         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)
           const tool = toolMap.get(call.tool)
           if (!tool) {
           if (!tool) {
             const availableToolsList = Array.from(toolMap.keys()).filter((name) => !FILTERED_FROM_SUGGESTIONS.has(name))
             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 })
           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 }
           return { success: true as const, tool: call.tool, result }
         } catch (error) {
         } 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 }
           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 successfulCalls = results.filter((r) => r.success).length
       const failedCalls = toolCalls.length - successfulCalls
       const failedCalls = toolCalls.length - successfulCalls