tweak: make read tool offset 1 indexed instead of 0 to avoid confusion that could be caused by line #s being 1 based (#13198)
This commit is contained in:
@@ -1022,9 +1022,9 @@ export namespace SessionPrompt {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
offset = Math.max(start - 1, 0)
|
offset = Math.max(start, 1)
|
||||||
if (end) {
|
if (end) {
|
||||||
limit = end - offset
|
limit = end - (offset - 1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const args = { filePath: filepath, offset, limit }
|
const args = { filePath: filepath, offset, limit }
|
||||||
|
|||||||
@@ -18,10 +18,13 @@ export const ReadTool = Tool.define("read", {
|
|||||||
description: DESCRIPTION,
|
description: DESCRIPTION,
|
||||||
parameters: z.object({
|
parameters: z.object({
|
||||||
filePath: z.string().describe("The absolute path to the file or directory to read"),
|
filePath: z.string().describe("The absolute path to the file or directory to read"),
|
||||||
offset: z.coerce.number().describe("The 0-based line offset to start reading from").optional(),
|
offset: z.coerce.number().describe("The line number to start reading from (1-indexed)").optional(),
|
||||||
limit: z.coerce.number().describe("The maximum number of lines to read (defaults to 2000)").optional(),
|
limit: z.coerce.number().describe("The maximum number of lines to read (defaults to 2000)").optional(),
|
||||||
}),
|
}),
|
||||||
async execute(params, ctx) {
|
async execute(params, ctx) {
|
||||||
|
if (params.offset !== undefined && params.offset < 1) {
|
||||||
|
throw new Error("offset must be greater than or equal to 1")
|
||||||
|
}
|
||||||
let filepath = params.filePath
|
let filepath = params.filePath
|
||||||
if (!path.isAbsolute(filepath)) {
|
if (!path.isAbsolute(filepath)) {
|
||||||
filepath = path.resolve(Instance.directory, filepath)
|
filepath = path.resolve(Instance.directory, filepath)
|
||||||
@@ -78,9 +81,10 @@ export const ReadTool = Tool.define("read", {
|
|||||||
entries.sort((a, b) => a.localeCompare(b))
|
entries.sort((a, b) => a.localeCompare(b))
|
||||||
|
|
||||||
const limit = params.limit ?? DEFAULT_READ_LIMIT
|
const limit = params.limit ?? DEFAULT_READ_LIMIT
|
||||||
const offset = params.offset || 0
|
const offset = params.offset ?? 1
|
||||||
const sliced = entries.slice(offset, offset + limit)
|
const start = offset - 1
|
||||||
const truncated = offset + sliced.length < entries.length
|
const sliced = entries.slice(start, start + limit)
|
||||||
|
const truncated = start + sliced.length < entries.length
|
||||||
|
|
||||||
const output = [
|
const output = [
|
||||||
`<path>${filepath}</path>`,
|
`<path>${filepath}</path>`,
|
||||||
@@ -138,13 +142,15 @@ export const ReadTool = Tool.define("read", {
|
|||||||
if (isBinary) throw new Error(`Cannot read binary file: ${filepath}`)
|
if (isBinary) throw new Error(`Cannot read binary file: ${filepath}`)
|
||||||
|
|
||||||
const limit = params.limit ?? DEFAULT_READ_LIMIT
|
const limit = params.limit ?? DEFAULT_READ_LIMIT
|
||||||
const offset = params.offset || 0
|
const offset = params.offset ?? 1
|
||||||
|
const start = offset - 1
|
||||||
const lines = await file.text().then((text) => text.split("\n"))
|
const lines = await file.text().then((text) => text.split("\n"))
|
||||||
|
if (start >= lines.length) throw new Error(`Offset ${offset} is out of range for this file (${lines.length} lines)`)
|
||||||
|
|
||||||
const raw: string[] = []
|
const raw: string[] = []
|
||||||
let bytes = 0
|
let bytes = 0
|
||||||
let truncatedByBytes = false
|
let truncatedByBytes = false
|
||||||
for (let i = offset; i < Math.min(lines.length, offset + limit); i++) {
|
for (let i = start; i < Math.min(lines.length, start + limit); i++) {
|
||||||
const line = lines[i].length > MAX_LINE_LENGTH ? lines[i].substring(0, MAX_LINE_LENGTH) + "..." : lines[i]
|
const line = lines[i].length > MAX_LINE_LENGTH ? lines[i].substring(0, MAX_LINE_LENGTH) + "..." : lines[i]
|
||||||
const size = Buffer.byteLength(line, "utf-8") + (raw.length > 0 ? 1 : 0)
|
const size = Buffer.byteLength(line, "utf-8") + (raw.length > 0 ? 1 : 0)
|
||||||
if (bytes + size > MAX_BYTES) {
|
if (bytes + size > MAX_BYTES) {
|
||||||
@@ -156,7 +162,7 @@ export const ReadTool = Tool.define("read", {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const content = raw.map((line, index) => {
|
const content = raw.map((line, index) => {
|
||||||
return `${index + offset + 1}: ${line}`
|
return `${index + offset}: ${line}`
|
||||||
})
|
})
|
||||||
const preview = raw.slice(0, 20).join("\n")
|
const preview = raw.slice(0, 20).join("\n")
|
||||||
|
|
||||||
@@ -164,7 +170,7 @@ export const ReadTool = Tool.define("read", {
|
|||||||
output += content.join("\n")
|
output += content.join("\n")
|
||||||
|
|
||||||
const totalLines = lines.length
|
const totalLines = lines.length
|
||||||
const lastReadLine = offset + raw.length
|
const lastReadLine = offset + raw.length - 1
|
||||||
const hasMoreLines = totalLines > lastReadLine
|
const hasMoreLines = totalLines > lastReadLine
|
||||||
const truncated = hasMoreLines || truncatedByBytes
|
const truncated = hasMoreLines || truncatedByBytes
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ Read a file or directory from the local filesystem. If the path does not exist,
|
|||||||
Usage:
|
Usage:
|
||||||
- The filePath parameter should be an absolute path.
|
- The filePath parameter should be an absolute path.
|
||||||
- By default, this tool returns up to 2000 lines from the start of the file.
|
- By default, this tool returns up to 2000 lines from the start of the file.
|
||||||
|
- The offset parameter is the line number to start from (1-indexed).
|
||||||
- To read later sections, call this tool again with a larger offset.
|
- To read later sections, call this tool again with a larger offset.
|
||||||
- Use the grep tool to find specific content in large files or files with long lines.
|
- 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.
|
- If you are unsure of the correct file path, use the glob tool to look up filenames by glob pattern.
|
||||||
|
|||||||
@@ -258,7 +258,7 @@ describe("tool.read truncation", () => {
|
|||||||
test("respects offset parameter", async () => {
|
test("respects offset parameter", async () => {
|
||||||
await using tmp = await tmpdir({
|
await using tmp = await tmpdir({
|
||||||
init: async (dir) => {
|
init: async (dir) => {
|
||||||
const lines = Array.from({ length: 20 }, (_, i) => `line${i}`).join("\n")
|
const lines = Array.from({ length: 20 }, (_, i) => `line${i + 1}`).join("\n")
|
||||||
await Bun.write(path.join(dir, "offset.txt"), lines)
|
await Bun.write(path.join(dir, "offset.txt"), lines)
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
@@ -275,11 +275,29 @@ describe("tool.read truncation", () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test("throws when offset is beyond end of file", async () => {
|
||||||
|
await using tmp = await tmpdir({
|
||||||
|
init: async (dir) => {
|
||||||
|
const lines = Array.from({ length: 3 }, (_, i) => `line${i + 1}`).join("\n")
|
||||||
|
await Bun.write(path.join(dir, "short.txt"), lines)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
await Instance.provide({
|
||||||
|
directory: tmp.path,
|
||||||
|
fn: async () => {
|
||||||
|
const read = await ReadTool.init()
|
||||||
|
await expect(
|
||||||
|
read.execute({ filePath: path.join(tmp.path, "short.txt"), offset: 4, limit: 5 }, ctx),
|
||||||
|
).rejects.toThrow("Offset 4 is out of range for this file (3 lines)")
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
test("does not mark final directory page as truncated", async () => {
|
test("does not mark final directory page as truncated", async () => {
|
||||||
await using tmp = await tmpdir({
|
await using tmp = await tmpdir({
|
||||||
init: async (dir) => {
|
init: async (dir) => {
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
Array.from({ length: 10 }, (_, i) => Bun.write(path.join(dir, "dir", `file-${i}.txt`), `line${i}`)),
|
Array.from({ length: 10 }, (_, i) => Bun.write(path.join(dir, "dir", `file-${i + 1}.txt`), `line${i}`)),
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
@@ -287,7 +305,7 @@ describe("tool.read truncation", () => {
|
|||||||
directory: tmp.path,
|
directory: tmp.path,
|
||||||
fn: async () => {
|
fn: async () => {
|
||||||
const read = await ReadTool.init()
|
const read = await ReadTool.init()
|
||||||
const result = await read.execute({ filePath: path.join(tmp.path, "dir"), offset: 5, limit: 5 }, ctx)
|
const result = await read.execute({ filePath: path.join(tmp.path, "dir"), offset: 6, limit: 5 }, ctx)
|
||||||
expect(result.metadata.truncated).toBe(false)
|
expect(result.metadata.truncated).toBe(false)
|
||||||
expect(result.output).not.toContain("Showing 5 of 10 entries")
|
expect(result.output).not.toContain("Showing 5 of 10 entries")
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user