feat: enable hashline by default
parent
ce5c827a6e
commit
9ef803be82
|
|
@ -1186,11 +1186,16 @@ export namespace Config {
|
|||
.object({
|
||||
disable_paste_summary: z.boolean().optional(),
|
||||
batch_tool: z.boolean().optional().describe("Enable the batch tool"),
|
||||
hashline_edit: z.boolean().optional().describe("Enable hashline-backed edit/read tool behavior"),
|
||||
hashline_edit: z
|
||||
.boolean()
|
||||
.optional()
|
||||
.describe("Enable hashline-backed edit/read tool behavior (default true, set false to disable)"),
|
||||
hashline_autocorrect: z
|
||||
.boolean()
|
||||
.optional()
|
||||
.describe("Enable hashline autocorrect cleanup for copied prefixes and formatting artifacts"),
|
||||
.describe(
|
||||
"Enable hashline autocorrect cleanup for copied prefixes and formatting artifacts (default true)",
|
||||
),
|
||||
openTelemetry: z
|
||||
.boolean()
|
||||
.optional()
|
||||
|
|
|
|||
|
|
@ -467,9 +467,9 @@ export const EditTool = Tool.define("edit", {
|
|||
}
|
||||
|
||||
const config = await Config.get()
|
||||
if (config.experimental?.hashline_edit !== true) {
|
||||
if (config.experimental?.hashline_edit === false) {
|
||||
throw new Error(
|
||||
"Hashline edit payload is disabled. Enable experimental.hashline_edit to use hashline operations.",
|
||||
"Hashline edit payload is disabled. Set experimental.hashline_edit to true to use hashline operations.",
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -483,7 +483,7 @@ export const EditTool = Tool.define("edit", {
|
|||
return executeHashline(
|
||||
hashlineParams,
|
||||
ctx,
|
||||
config.experimental?.hashline_autocorrect === true || Bun.env.OPENCODE_HL_AUTOCORRECT === "1",
|
||||
config.experimental?.hashline_autocorrect !== false || Bun.env.OPENCODE_HL_AUTOCORRECT === "1",
|
||||
)
|
||||
},
|
||||
})
|
||||
|
|
|
|||
|
|
@ -12,11 +12,12 @@ Legacy schema (always supported):
|
|||
- The edit fails if `oldString` matches multiple locations and `replaceAll` is not true.
|
||||
- Use `replaceAll: true` for global replacements.
|
||||
|
||||
Hashline schema (requires `experimental.hashline_edit: true`):
|
||||
Hashline schema (default behavior):
|
||||
- `{ filePath, edits, delete?, rename? }`
|
||||
- Do not mix legacy fields (`oldString/newString/replaceAll`) with hashline fields (`edits/delete/rename`) in one call.
|
||||
- Use strict anchor references from `Read` output: `LINE#ID`.
|
||||
- Optional cleanup behavior can be enabled with `experimental.hashline_autocorrect: true`.
|
||||
- Hashline mode can be turned off with `experimental.hashline_edit: false`.
|
||||
- Autocorrect cleanup is on by default and can be turned off with `experimental.hashline_autocorrect: false`.
|
||||
- When `Read` returns `LINE#ID:<content>`, prefer hashline operations.
|
||||
- Operations:
|
||||
- `set_line { line, text }`
|
||||
|
|
|
|||
|
|
@ -194,7 +194,7 @@ export const ReadTool = Tool.define("read", {
|
|||
throw new Error(`Offset ${offset} is out of range for this file (${lines} lines)`)
|
||||
}
|
||||
|
||||
const useHashline = (await Config.get()).experimental?.hashline_edit === true
|
||||
const useHashline = (await Config.get()).experimental?.hashline_edit !== false
|
||||
const content = raw.map((line, index) => {
|
||||
const lineNumber = index + offset
|
||||
if (useHashline) return `${hashlineRef(lineNumber, full[index])}:${line}`
|
||||
|
|
|
|||
|
|
@ -8,8 +8,8 @@ Usage:
|
|||
- Use the grep tool to find specific content in large files or files with long lines.
|
||||
- If you are unsure of the correct file path, use the glob tool to look up filenames by glob pattern.
|
||||
- Contents are returned with a line prefix.
|
||||
- Default format: `<line>: <content>` (example: `1: foo`).
|
||||
- When `experimental.hashline_edit` is enabled: `LINE#ID:<content>` (example: `1#AB:foo`). Use these anchors for hashline edits.
|
||||
- Default format: `LINE#ID:<content>` (example: `1#AB:foo`). Use these anchors for hashline edits.
|
||||
- Legacy format can be restored with `experimental.hashline_edit: false`: `<line>: <content>` (example: `1: foo`).
|
||||
- For directories, entries are returned one per line (without line numbers) with a trailing `/` for subdirectories.
|
||||
- Any line longer than 2000 characters is truncated.
|
||||
- Call this tool in parallel when you know there are multiple files you want to read.
|
||||
|
|
|
|||
|
|
@ -143,7 +143,7 @@ export namespace ToolRegistry {
|
|||
return model.providerID === "opencode" || Flag.OPENCODE_ENABLE_EXA
|
||||
}
|
||||
|
||||
if (config.experimental?.hashline_edit === true) {
|
||||
if (config.experimental?.hashline_edit !== false) {
|
||||
if (t.id === "apply_patch") return false
|
||||
return true
|
||||
}
|
||||
|
|
|
|||
|
|
@ -744,6 +744,11 @@ describe("tool.edit", () => {
|
|||
|
||||
test("rejects hashline payload when experimental mode is disabled", async () => {
|
||||
await using tmp = await tmpdir({
|
||||
config: {
|
||||
experimental: {
|
||||
hashline_edit: false,
|
||||
},
|
||||
},
|
||||
init: async (dir) => {
|
||||
await fs.writeFile(path.join(dir, "file.txt"), "a", "utf-8")
|
||||
},
|
||||
|
|
|
|||
|
|
@ -270,10 +270,10 @@ describe("tool.read truncation", () => {
|
|||
fn: async () => {
|
||||
const read = await ReadTool.init()
|
||||
const result = await read.execute({ filePath: path.join(tmp.path, "offset.txt"), offset: 10, limit: 5 }, ctx)
|
||||
expect(result.output).toContain("10: line10")
|
||||
expect(result.output).toContain("14: line14")
|
||||
expect(result.output).not.toContain("9: line10")
|
||||
expect(result.output).not.toContain("15: line15")
|
||||
expect(result.output).toContain(hashlineLine(10, "line10"))
|
||||
expect(result.output).toContain(hashlineLine(14, "line14"))
|
||||
expect(result.output).not.toContain(hashlineLine(9, "line9"))
|
||||
expect(result.output).not.toContain(hashlineLine(15, "line15"))
|
||||
expect(result.output).toContain("line10")
|
||||
expect(result.output).toContain("line14")
|
||||
expect(result.output).not.toContain("line0")
|
||||
|
|
@ -445,13 +445,8 @@ root_type Monster;`
|
|||
})
|
||||
|
||||
describe("tool.read hashline output", () => {
|
||||
test("returns LINE#ID prefixes when hashline mode is enabled", async () => {
|
||||
test("returns LINE#ID prefixes by default", async () => {
|
||||
await using tmp = await tmpdir({
|
||||
config: {
|
||||
experimental: {
|
||||
hashline_edit: true,
|
||||
},
|
||||
},
|
||||
init: async (dir) => {
|
||||
await Bun.write(path.join(dir, "hashline.txt"), "foo\nbar")
|
||||
},
|
||||
|
|
@ -471,6 +466,11 @@ describe("tool.read hashline output", () => {
|
|||
|
||||
test("keeps legacy line prefixes when hashline mode is disabled", async () => {
|
||||
await using tmp = await tmpdir({
|
||||
config: {
|
||||
experimental: {
|
||||
hashline_edit: false,
|
||||
},
|
||||
},
|
||||
init: async (dir) => {
|
||||
await Bun.write(path.join(dir, "legacy.txt"), "foo\nbar")
|
||||
},
|
||||
|
|
|
|||
|
|
@ -7,14 +7,8 @@ describe("tool.registry hashline routing", () => {
|
|||
test.each([
|
||||
{ providerID: "openai", modelID: "gpt-5" },
|
||||
{ providerID: "anthropic", modelID: "claude-3-7-sonnet" },
|
||||
])("disables apply_patch and enables edit when experimental hashline is on (%o)", async (model) => {
|
||||
await using tmp = await tmpdir({
|
||||
config: {
|
||||
experimental: {
|
||||
hashline_edit: true,
|
||||
},
|
||||
},
|
||||
})
|
||||
])("disables apply_patch and enables edit by default (%o)", async (model) => {
|
||||
await using tmp = await tmpdir()
|
||||
|
||||
await Instance.provide({
|
||||
directory: tmp.path,
|
||||
|
|
@ -28,8 +22,14 @@ describe("tool.registry hashline routing", () => {
|
|||
})
|
||||
})
|
||||
|
||||
test("keeps existing GPT apply_patch routing when experimental hashline is off", async () => {
|
||||
await using tmp = await tmpdir()
|
||||
test("keeps existing GPT apply_patch routing when hashline is explicitly disabled", async () => {
|
||||
await using tmp = await tmpdir({
|
||||
config: {
|
||||
experimental: {
|
||||
hashline_edit: false,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
await Instance.provide({
|
||||
directory: tmp.path,
|
||||
|
|
@ -45,8 +45,14 @@ describe("tool.registry hashline routing", () => {
|
|||
})
|
||||
})
|
||||
|
||||
test("keeps existing non-GPT routing when experimental hashline is off", async () => {
|
||||
await using tmp = await tmpdir()
|
||||
test("keeps existing non-GPT routing when hashline is explicitly disabled", async () => {
|
||||
await using tmp = await tmpdir({
|
||||
config: {
|
||||
experimental: {
|
||||
hashline_edit: false,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
await Instance.provide({
|
||||
directory: tmp.path,
|
||||
|
|
|
|||
Loading…
Reference in New Issue