Skip to content

Commit 52c75e5

Browse files
rekram1-nodeRalf Waldukat
authored andcommitted
feat: adjust read tool so that it can handle dirs too (anomalyco#13090)
1 parent 8315018 commit 52c75e5

3 files changed

Lines changed: 11 additions & 36 deletions

File tree

packages/opencode/src/tool/read.ts

Lines changed: 8 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,10 @@ export const ReadTool = Tool.define("read", {
1818
description: DESCRIPTION,
1919
parameters: z.object({
2020
filePath: z.string().describe("The absolute path to the file or directory to read"),
21-
offset: z.coerce.number().describe("The line number to start reading from (1-indexed)").optional(),
21+
offset: z.coerce.number().describe("The 0-based line offset to start reading from").optional(),
2222
limit: z.coerce.number().describe("The maximum number of lines to read (defaults to 2000)").optional(),
2323
}),
2424
async execute(params, ctx) {
25-
if (params.offset !== undefined && params.offset < 1) {
26-
throw new Error("offset must be greater than or equal to 1")
27-
}
2825
let filepath = params.filePath
2926
if (!path.isAbsolute(filepath)) {
3027
filepath = path.resolve(Instance.directory, filepath)
@@ -81,10 +78,9 @@ export const ReadTool = Tool.define("read", {
8178
entries.sort((a, b) => a.localeCompare(b))
8279

8380
const limit = params.limit ?? DEFAULT_READ_LIMIT
84-
const offset = params.offset ?? 1
85-
const start = offset - 1
86-
const sliced = entries.slice(start, start + limit)
87-
const truncated = start + sliced.length < entries.length
81+
const offset = params.offset || 0
82+
const sliced = entries.slice(offset, offset + limit)
83+
const truncated = offset + sliced.length < entries.length
8884

8985
const output = [
9086
`<path>${filepath}</path>`,
@@ -142,15 +138,13 @@ export const ReadTool = Tool.define("read", {
142138
if (isBinary) throw new Error(`Cannot read binary file: ${filepath}`)
143139

144140
const limit = params.limit ?? DEFAULT_READ_LIMIT
145-
const offset = params.offset ?? 1
146-
const start = offset - 1
141+
const offset = params.offset || 0
147142
const lines = await file.text().then((text) => text.split("\n"))
148-
if (start >= lines.length) throw new Error(`Offset ${offset} is out of range for this file (${lines.length} lines)`)
149143

150144
const raw: string[] = []
151145
let bytes = 0
152146
let truncatedByBytes = false
153-
for (let i = start; i < Math.min(lines.length, start + limit); i++) {
147+
for (let i = offset; i < Math.min(lines.length, offset + limit); i++) {
154148
const line = lines[i].length > MAX_LINE_LENGTH ? lines[i].substring(0, MAX_LINE_LENGTH) + "..." : lines[i]
155149
const size = Buffer.byteLength(line, "utf-8") + (raw.length > 0 ? 1 : 0)
156150
if (bytes + size > MAX_BYTES) {
@@ -162,15 +156,15 @@ export const ReadTool = Tool.define("read", {
162156
}
163157

164158
const content = raw.map((line, index) => {
165-
return `${index + offset}: ${line}`
159+
return `${index + offset + 1}: ${line}`
166160
})
167161
const preview = raw.slice(0, 20).join("\n")
168162

169163
let output = [`<path>${filepath}</path>`, `<type>file</type>`, "<content>"].join("\n")
170164
output += content.join("\n")
171165

172166
const totalLines = lines.length
173-
const lastReadLine = offset + raw.length - 1
167+
const lastReadLine = offset + raw.length
174168
const hasMoreLines = totalLines > lastReadLine
175169
const truncated = hasMoreLines || truncatedByBytes
176170

packages/opencode/src/tool/read.txt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ Read a file or directory from the local filesystem. If the path does not exist,
33
Usage:
44
- The filePath parameter should be an absolute path.
55
- By default, this tool returns up to 2000 lines from the start of the file.
6-
- The offset parameter is the line number to start from (1-indexed).
76
- To read later sections, call this tool again with a larger offset.
87
- Use the grep tool to find specific content in large files or files with long lines.
98
- If you are unsure of the correct file path, use the glob tool to look up filenames by glob pattern.

packages/opencode/test/tool/read.test.ts

Lines changed: 3 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -258,7 +258,7 @@ describe("tool.read truncation", () => {
258258
test("respects offset parameter", async () => {
259259
await using tmp = await tmpdir({
260260
init: async (dir) => {
261-
const lines = Array.from({ length: 20 }, (_, i) => `line${i + 1}`).join("\n")
261+
const lines = Array.from({ length: 20 }, (_, i) => `line${i}`).join("\n")
262262
await Bun.write(path.join(dir, "offset.txt"), lines)
263263
},
264264
})
@@ -275,37 +275,19 @@ describe("tool.read truncation", () => {
275275
})
276276
})
277277

278-
test("throws when offset is beyond end of file", async () => {
279-
await using tmp = await tmpdir({
280-
init: async (dir) => {
281-
const lines = Array.from({ length: 3 }, (_, i) => `line${i + 1}`).join("\n")
282-
await Bun.write(path.join(dir, "short.txt"), lines)
283-
},
284-
})
285-
await Instance.provide({
286-
directory: tmp.path,
287-
fn: async () => {
288-
const read = await ReadTool.init()
289-
await expect(
290-
read.execute({ filePath: path.join(tmp.path, "short.txt"), offset: 4, limit: 5 }, ctx),
291-
).rejects.toThrow("Offset 4 is out of range for this file (3 lines)")
292-
},
293-
})
294-
})
295-
296278
test("does not mark final directory page as truncated", async () => {
297279
await using tmp = await tmpdir({
298280
init: async (dir) => {
299281
await Promise.all(
300-
Array.from({ length: 10 }, (_, i) => Bun.write(path.join(dir, "dir", `file-${i + 1}.txt`), `line${i}`)),
282+
Array.from({ length: 10 }, (_, i) => Bun.write(path.join(dir, "dir", `file-${i}.txt`), `line${i}`)),
301283
)
302284
},
303285
})
304286
await Instance.provide({
305287
directory: tmp.path,
306288
fn: async () => {
307289
const read = await ReadTool.init()
308-
const result = await read.execute({ filePath: path.join(tmp.path, "dir"), offset: 6, limit: 5 }, ctx)
290+
const result = await read.execute({ filePath: path.join(tmp.path, "dir"), offset: 5, limit: 5 }, ctx)
309291
expect(result.metadata.truncated).toBe(false)
310292
expect(result.output).not.toContain("Showing 5 of 10 entries")
311293
},

0 commit comments

Comments
 (0)