Skip to content

Commit ee55662

Browse files
author
李冠辰
committed
fix(opencode): normalize edit tool argument aliases
1 parent ca354f8 commit ee55662

5 files changed

Lines changed: 65 additions & 1 deletion

File tree

packages/opencode/src/tool/edit.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,13 @@ export const EditTool = Tool.define(
6666
return {
6767
description: DESCRIPTION,
6868
parameters: Parameters,
69+
normalizeArguments: (args: unknown) =>
70+
Tool.normalizeAliases(args, {
71+
filePath: ["file_path", "path"],
72+
oldString: ["old_string", "oldText"],
73+
newString: ["new_string", "newText"],
74+
replaceAll: ["replace_all"],
75+
}),
6976
execute: (params: Schema.Schema.Type<typeof Parameters>, ctx: Tool.Context) =>
7077
Effect.gen(function* () {
7178
if (!params.filePath) {

packages/opencode/src/tool/tool.ts

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ export interface Def<
5858
description: string
5959
parameters: Parameters
6060
jsonSchema?: JSONSchema7
61+
normalizeArguments?(args: unknown): unknown
6162
execute(args: Schema.Schema.Type<Parameters>, ctx: Context): Effect.Effect<ExecuteResult<M>>
6263
formatValidationError?(error: unknown): string
6364
}
@@ -94,6 +95,21 @@ export type InferDef<T> =
9495
? Def<P, M>
9596
: never
9697

98+
export function normalizeAliases(input: unknown, aliases: Record<string, string[]>): unknown {
99+
if (!isRecord(input)) return input
100+
const output = { ...input }
101+
for (const [key, names] of Object.entries(aliases)) {
102+
if (output[key] !== undefined) continue
103+
const name = names.find((alias) => input[alias] !== undefined)
104+
if (name) output[key] = input[name]
105+
}
106+
return output
107+
}
108+
109+
function isRecord(input: unknown): input is Record<string, unknown> {
110+
return typeof input === "object" && input !== null && !Array.isArray(input)
111+
}
112+
97113
function wrap<Parameters extends Schema.Decoder<unknown>, Result extends Metadata>(
98114
id: string,
99115
init: Init<Parameters, Result>,
@@ -116,7 +132,7 @@ function wrap<Parameters extends Schema.Decoder<unknown>, Result extends Metadat
116132
...(ctx.callID ? { "tool.call_id": ctx.callID } : {}),
117133
}
118134
return Effect.gen(function* () {
119-
const decoded = yield* decode(args).pipe(
135+
const decoded = yield* decode(toolInfo.normalizeArguments ? toolInfo.normalizeArguments(args) : args).pipe(
120136
Effect.mapError(
121137
(error) =>
122138
new InvalidArgumentsError({

packages/opencode/src/tool/write.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,11 @@ export const WriteTool = Tool.define(
3535
return {
3636
description: DESCRIPTION,
3737
parameters: Parameters,
38+
normalizeArguments: (args: unknown) =>
39+
Tool.normalizeAliases(args, {
40+
filePath: ["file_path", "path"],
41+
content: ["fileContent", "contents"],
42+
}),
3843
execute: (params: { content: string; filePath: string }, ctx: Tool.Context) =>
3944
Effect.gen(function* () {
4045
const instance = yield* InstanceState.context

packages/opencode/test/tool/edit.test.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,12 @@ const run = Effect.fn("EditToolTest.run")(function* (
5454
return yield* tool.execute(args, next)
5555
})
5656

57+
const runUnknown = Effect.fn("EditToolTest.runUnknown")(function* (args: unknown, next: Tool.Context = ctx) {
58+
const tool = yield* init()
59+
const execute = tool.execute as unknown as (args: unknown, ctx: Tool.Context) => ReturnType<typeof tool.execute>
60+
return yield* execute(args, next)
61+
})
62+
5763
const fail = Effect.fn("EditToolTest.fail")(function* (args: Tool.InferParameters<typeof EditTool>) {
5864
const exit = yield* run(args).pipe(Effect.exit)
5965
if (Exit.isFailure(exit)) {
@@ -157,6 +163,18 @@ describe("tool.edit", () => {
157163
}),
158164
)
159165

166+
it.instance("normalizes common model argument aliases", () =>
167+
Effect.gen(function* () {
168+
const test = yield* TestInstance
169+
const filepath = path.join(test.directory, "alias.txt")
170+
yield* put(filepath, "old content")
171+
172+
yield* runUnknown({ file_path: filepath, old_string: "old", new_string: "new" })
173+
174+
expect(yield* load(filepath)).toBe("new content")
175+
}),
176+
)
177+
160178
it.instance("replaces the first visible line in BOM files", () =>
161179
Effect.gen(function* () {
162180
const test = yield* TestInstance

packages/opencode/test/tool/write.test.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,12 @@ const run = Effect.fn("WriteToolTest.run")(function* (
5555
return yield* tool.execute(args, next)
5656
})
5757

58+
const runUnknown = Effect.fn("WriteToolTest.runUnknown")(function* (args: unknown, next: Tool.Context = ctx) {
59+
const tool = yield* init()
60+
const execute = tool.execute as unknown as (args: unknown, ctx: Tool.Context) => ReturnType<typeof tool.execute>
61+
return yield* execute(args, next)
62+
})
63+
5864
describe("tool.write", () => {
5965
describe("new file creation", () => {
6066
it.instance("writes content to new file", () =>
@@ -71,6 +77,18 @@ describe("tool.write", () => {
7177
}),
7278
)
7379

80+
it.instance("normalizes common model argument aliases", () =>
81+
Effect.gen(function* () {
82+
const test = yield* TestInstance
83+
const filepath = path.join(test.directory, "alias.txt")
84+
85+
yield* runUnknown({ path: filepath, fileContent: "alias content" })
86+
87+
const content = yield* Effect.promise(() => fs.readFile(filepath, "utf-8"))
88+
expect(content).toBe("alias content")
89+
}),
90+
)
91+
7492
it.instance("creates parent directories if needed", () =>
7593
Effect.gen(function* () {
7694
const test = yield* TestInstance

0 commit comments

Comments
 (0)