Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 16 additions & 18 deletions packages/opencode/src/tool/hashline_read.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import z from "zod"
import * as fs from "fs"
import * as fs from "fs/promises"
import * as path from "path"
import { Tool } from "./tool"
import { LSP } from "../lsp"
Expand Down Expand Up @@ -48,23 +48,21 @@ export const HashlineReadTool = Tool.define("hashline_read", {
})

if (!stat) {
const dir = path.dirname(filepath)
const base = path.basename(filepath)

const dirEntries = fs.readdirSync(dir)
const suggestions = dirEntries
.filter(
(entry) =>
entry.toLowerCase().includes(base.toLowerCase()) || base.toLowerCase().includes(entry.toLowerCase()),
)
.map((entry) => path.join(dir, entry))
.slice(0, 3)

if (suggestions.length > 0) {
throw new Error(`File not found: ${filepath}\n\nDid you mean one of these?\n${suggestions.join("\n")}`)
await fs.mkdir(path.dirname(filepath), { recursive: true })
await Bun.write(filepath, "")
FileTime.read(ctx.sessionID, filepath)
FileTime.hashlineRead(ctx.sessionID, filepath)

return {
title,
output: [`<path>${filepath}</path>`, `<type>file</type>`, "<content>"].join("\n") + "\n</content>\n\n(File created successfully - empty and ready for editing)",
metadata: {
preview: "",
truncated: false,
loaded: [],
},
}

throw new Error(`File not found: ${filepath}`)
}

if (stat.isDirectory()) {
Expand Down Expand Up @@ -192,4 +190,4 @@ async function isBinaryFile(filepath: string, file: Bun.BunFile): Promise<boolea
}
}
return nonPrintableCount / bytes.length > 0.3
}
}
21 changes: 14 additions & 7 deletions packages/opencode/test/tool/hashline_read.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,17 +109,24 @@ describe("tool.hashline_read binary file detection", () => {
})

describe("tool.hashline_read non-existent file", () => {
test("returns error for non-existent file", async () => {
test("creates non-existent file and returns successfully", async () => {
await using tmp = await tmpdir({})
await Instance.provide({
directory: tmp.path,
fn: async () => {
const hashlineRead = await HashlineReadTool.init()
const error = await hashlineRead
.execute({ filePath: path.join(tmp.path, "nonexistent.txt") }, ctx)
.catch((e) => e)
expect(error).toBeInstanceOf(Error)
expect(error.message).toContain("File not found")
const filePath = path.join(tmp.path, "nonexistent.txt")

expect(await Bun.file(filePath).exists()).toBe(false)

const result = await hashlineRead.execute({ filePath }, ctx)

expect(result).toBeDefined()
expect(await Bun.file(filePath).exists()).toBe(true)
expect(result.output).toContain(`<path>${filePath}</path>`)
expect(result.output).toContain("<type>file</type>")
expect(result.output).toContain("(File created successfully - empty and ready for editing)")
expect(result.metadata.preview).toBe("")
},
})
})
Expand Down Expand Up @@ -265,4 +272,4 @@ describe("tool.hashline_read CJK byte counting", () => {
},
})
})
})
})