fix(tool): align multiedit schema with single-file edits
This commit is contained in:
parent
a49b5adfbd
commit
f2cbcacf00
4 changed files with 83 additions and 19 deletions
|
|
@ -10,7 +10,6 @@ export const Parameters = Schema.Struct({
|
||||||
edits: Schema.mutable(
|
edits: Schema.mutable(
|
||||||
Schema.Array(
|
Schema.Array(
|
||||||
Schema.Struct({
|
Schema.Struct({
|
||||||
filePath: Schema.String.annotate({ description: "The absolute path to the file to modify" }),
|
|
||||||
oldString: Schema.String.annotate({ description: "The text to replace" }),
|
oldString: Schema.String.annotate({ description: "The text to replace" }),
|
||||||
newString: Schema.String.annotate({
|
newString: Schema.String.annotate({
|
||||||
description: "The text to replace it with (must be different from oldString)",
|
description: "The text to replace it with (must be different from oldString)",
|
||||||
|
|
@ -32,17 +31,10 @@ export const MultiEditTool = Tool.define(
|
||||||
return {
|
return {
|
||||||
description: DESCRIPTION,
|
description: DESCRIPTION,
|
||||||
parameters: Parameters,
|
parameters: Parameters,
|
||||||
execute: (
|
execute: (params: Schema.Schema.Type<typeof Parameters>, ctx: Tool.Context) =>
|
||||||
params: {
|
|
||||||
filePath: string
|
|
||||||
edits: Array<{ filePath: string; oldString: string; newString: string; replaceAll?: boolean }>
|
|
||||||
},
|
|
||||||
ctx: Tool.Context,
|
|
||||||
) =>
|
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
const results = []
|
const results = yield* Effect.forEach(params.edits, (entry) =>
|
||||||
for (const [, entry] of params.edits.entries()) {
|
edit.execute(
|
||||||
const result = yield* edit.execute(
|
|
||||||
{
|
{
|
||||||
filePath: params.filePath,
|
filePath: params.filePath,
|
||||||
oldString: entry.oldString,
|
oldString: entry.oldString,
|
||||||
|
|
@ -51,8 +43,7 @@ export const MultiEditTool = Tool.define(
|
||||||
},
|
},
|
||||||
ctx,
|
ctx,
|
||||||
)
|
)
|
||||||
results.push(result)
|
)
|
||||||
}
|
|
||||||
return {
|
return {
|
||||||
title: path.relative(Instance.worktree, params.filePath),
|
title: path.relative(Instance.worktree, params.filePath),
|
||||||
metadata: {
|
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",
|
"description": "Array of edit operations to perform sequentially on the file",
|
||||||
"items": {
|
"items": {
|
||||||
"properties": {
|
"properties": {
|
||||||
"filePath": {
|
|
||||||
"description": "The absolute path to the file to modify",
|
|
||||||
"type": "string",
|
|
||||||
},
|
|
||||||
"newString": {
|
"newString": {
|
||||||
"description": "The text to replace it with (must be different from oldString)",
|
"description": "The text to replace it with (must be different from oldString)",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
|
|
@ -246,7 +242,6 @@ exports[`tool parameters JSON Schema (wire shape) multiedit 1`] = `
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"required": [
|
"required": [
|
||||||
"filePath",
|
|
||||||
"oldString",
|
"oldString",
|
||||||
"newString",
|
"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", () => {
|
test("accepts an edit entry", () => {
|
||||||
const parsed = parse(MultiEdit, {
|
const parsed = parse(MultiEdit, {
|
||||||
filePath: "/a",
|
filePath: "/a",
|
||||||
edits: [{ filePath: "/a", oldString: "x", newString: "y" }],
|
edits: [{ oldString: "x", newString: "y" }],
|
||||||
})
|
})
|
||||||
expect(parsed.edits.length).toBe(1)
|
expect(parsed.edits.length).toBe(1)
|
||||||
})
|
})
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue