mirror of
https://github.com/anomalyco/opencode.git
synced 2026-05-18 01:58:43 +00:00
fix(tool): align multiedit schema with single-file edits
This commit is contained in:
@@ -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: {
|
||||
|
||||
@@ -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
packages/opencode/test/tool/multiedit.test.ts
Normal file
78
packages/opencode/test/tool/multiedit.test.ts
Normal file
@@ -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")
|
||||
}),
|
||||
),
|
||||
)
|
||||
})
|
||||
@@ -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)
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user