Ver Fonte

fix(tool): align multiedit schema with single-file edits

Kit Langton há 1 dia atrás
pai
commit
f2cbcacf00

+ 4 - 13
packages/opencode/src/tool/multiedit.ts

@@ -10,7 +10,6 @@ export const Parameters = Schema.Struct({
   edits: Schema.mutable(
     Schema.Array(
       Schema.Struct({
-        filePath: Schema.String.annotate({ description: "The absolute path to the file to modify" }),
         oldString: Schema.String.annotate({ description: "The text to replace" }),
         newString: Schema.String.annotate({
           description: "The text to replace it with (must be different from oldString)",
@@ -32,17 +31,10 @@ export const MultiEditTool = Tool.define(
     return {
       description: DESCRIPTION,
       parameters: Parameters,
-      execute: (
-        params: {
-          filePath: string
-          edits: Array<{ filePath: string; oldString: string; newString: string; replaceAll?: boolean }>
-        },
-        ctx: Tool.Context,
-      ) =>
+      execute: (params: Schema.Schema.Type<typeof Parameters>, ctx: Tool.Context) =>
         Effect.gen(function* () {
-          const results = []
-          for (const [, entry] of params.edits.entries()) {
-            const result = yield* edit.execute(
+          const results = yield* Effect.forEach(params.edits, (entry) =>
+            edit.execute(
               {
                 filePath: params.filePath,
                 oldString: entry.oldString,
@@ -51,8 +43,7 @@ export const MultiEditTool = Tool.define(
               },
               ctx,
             )
-            results.push(result)
-          }
+          )
           return {
             title: path.relative(Instance.worktree, params.filePath),
             metadata: {

+ 0 - 5
packages/opencode/test/tool/__snapshots__/parameters.test.ts.snap

@@ -228,10 +228,6 @@ exports[`tool parameters JSON Schema (wire shape) multiedit 1`] = `
       "description": "Array of edit operations to perform sequentially on the file",
       "items": {
         "properties": {
-          "filePath": {
-            "description": "The absolute path to the file to modify",
-            "type": "string",
-          },
           "newString": {
             "description": "The text to replace it with (must be different from oldString)",
             "type": "string",
@@ -246,7 +242,6 @@ exports[`tool parameters JSON Schema (wire shape) multiedit 1`] = `
           },
         },
         "required": [
-          "filePath",
           "oldString",
           "newString",
         ],

+ 78 - 0
packages/opencode/test/tool/multiedit.test.ts

@@ -0,0 +1,78 @@
+import { afterEach, describe, expect } from "bun:test"
+import fs from "fs/promises"
+import path from "path"
+import { Effect, Layer } from "effect"
+import { Agent } from "../../src/agent/agent"
+import { Bus } from "../../src/bus"
+import { AppFileSystem } from "@opencode-ai/shared/filesystem"
+import { Format } from "../../src/format"
+import { LSP } from "../../src/lsp"
+import { Instance } from "../../src/project/instance"
+import { MessageID, SessionID } from "../../src/session/schema"
+import { MultiEditTool } from "../../src/tool/multiedit"
+import { Truncate, Tool } from "../../src/tool"
+import * as CrossSpawnSpawner from "../../src/effect/cross-spawn-spawner"
+import { provideTmpdirInstance } from "../fixture/fixture"
+import { testEffect } from "../lib/effect"
+
+const ctx = {
+  sessionID: SessionID.make("ses_test-multiedit-session"),
+  messageID: MessageID.make(""),
+  callID: "",
+  agent: "build",
+  abort: AbortSignal.any([]),
+  messages: [],
+  metadata: () => Effect.void,
+  ask: () => Effect.void,
+}
+
+afterEach(async () => {
+  await Instance.disposeAll()
+})
+
+const it = testEffect(
+  Layer.mergeAll(
+    LSP.defaultLayer,
+    AppFileSystem.defaultLayer,
+    Format.defaultLayer,
+    Bus.layer,
+    CrossSpawnSpawner.defaultLayer,
+    Truncate.defaultLayer,
+    Agent.defaultLayer,
+  ),
+)
+
+const init = Effect.fn("MultiEditToolTest.init")(function* () {
+  const info = yield* MultiEditTool
+  return yield* info.init()
+})
+
+const run = Effect.fn("MultiEditToolTest.run")(function* (
+  args: Tool.InferParameters<typeof MultiEditTool>,
+  next: Tool.Context = ctx,
+) {
+  const tool = yield* init()
+  return yield* tool.execute(args, next)
+})
+
+describe("tool.multiedit", () => {
+  it.live("applies multiple edits to the same file in sequence", () =>
+    provideTmpdirInstance((dir) =>
+      Effect.gen(function* () {
+        const filePath = path.join(dir, "file.txt")
+        yield* Effect.promise(() => fs.writeFile(filePath, "alpha\nbeta\ngamma\n", "utf-8"))
+
+        yield* run({
+          filePath,
+          edits: [
+            { oldString: "alpha", newString: "delta" },
+            { oldString: "gamma", newString: "omega" },
+          ],
+        })
+
+        const content = yield* Effect.promise(() => fs.readFile(filePath, "utf-8"))
+        expect(content).toBe("delta\nbeta\nomega\n")
+      }),
+    ),
+  )
+})

+ 1 - 1
packages/opencode/test/tool/parameters.test.ts

@@ -179,7 +179,7 @@ describe("tool parameters", () => {
     test("accepts an edit entry", () => {
       const parsed = parse(MultiEdit, {
         filePath: "/a",
-        edits: [{ filePath: "/a", oldString: "x", newString: "y" }],
+        edits: [{ oldString: "x", newString: "y" }],
       })
       expect(parsed.edits.length).toBe(1)
     })