|
|
@@ -6,6 +6,8 @@ import { tmpdir } from "../fixture/fixture"
|
|
|
import { PermissionNext } from "../../src/permission/next"
|
|
|
import { Agent } from "../../src/agent/agent"
|
|
|
|
|
|
+const FIXTURES_DIR = path.join(import.meta.dir, "fixtures")
|
|
|
+
|
|
|
const ctx = {
|
|
|
sessionID: "test",
|
|
|
messageID: "",
|
|
|
@@ -165,3 +167,123 @@ describe("tool.read env file blocking", () => {
|
|
|
})
|
|
|
})
|
|
|
})
|
|
|
+
|
|
|
+describe("tool.read truncation", () => {
|
|
|
+ test("truncates large file by bytes and sets truncated metadata", async () => {
|
|
|
+ await using tmp = await tmpdir({
|
|
|
+ init: async (dir) => {
|
|
|
+ const content = await Bun.file(path.join(FIXTURES_DIR, "models-api.json")).text()
|
|
|
+ await Bun.write(path.join(dir, "large.json"), content)
|
|
|
+ },
|
|
|
+ })
|
|
|
+ await Instance.provide({
|
|
|
+ directory: tmp.path,
|
|
|
+ fn: async () => {
|
|
|
+ const read = await ReadTool.init()
|
|
|
+ const result = await read.execute({ filePath: path.join(tmp.path, "large.json") }, ctx)
|
|
|
+ expect(result.metadata.truncated).toBe(true)
|
|
|
+ expect(result.output).toContain("Output truncated at")
|
|
|
+ expect(result.output).toContain("bytes")
|
|
|
+ },
|
|
|
+ })
|
|
|
+ })
|
|
|
+
|
|
|
+ test("truncates by line count when limit is specified", async () => {
|
|
|
+ await using tmp = await tmpdir({
|
|
|
+ init: async (dir) => {
|
|
|
+ const lines = Array.from({ length: 100 }, (_, i) => `line${i}`).join("\n")
|
|
|
+ await Bun.write(path.join(dir, "many-lines.txt"), lines)
|
|
|
+ },
|
|
|
+ })
|
|
|
+ await Instance.provide({
|
|
|
+ directory: tmp.path,
|
|
|
+ fn: async () => {
|
|
|
+ const read = await ReadTool.init()
|
|
|
+ const result = await read.execute({ filePath: path.join(tmp.path, "many-lines.txt"), limit: 10 }, ctx)
|
|
|
+ expect(result.metadata.truncated).toBe(true)
|
|
|
+ expect(result.output).toContain("File has more lines")
|
|
|
+ expect(result.output).toContain("line0")
|
|
|
+ expect(result.output).toContain("line9")
|
|
|
+ expect(result.output).not.toContain("line10")
|
|
|
+ },
|
|
|
+ })
|
|
|
+ })
|
|
|
+
|
|
|
+ test("does not truncate small file", async () => {
|
|
|
+ await using tmp = await tmpdir({
|
|
|
+ init: async (dir) => {
|
|
|
+ await Bun.write(path.join(dir, "small.txt"), "hello world")
|
|
|
+ },
|
|
|
+ })
|
|
|
+ await Instance.provide({
|
|
|
+ directory: tmp.path,
|
|
|
+ fn: async () => {
|
|
|
+ const read = await ReadTool.init()
|
|
|
+ const result = await read.execute({ filePath: path.join(tmp.path, "small.txt") }, ctx)
|
|
|
+ expect(result.metadata.truncated).toBe(false)
|
|
|
+ expect(result.output).toContain("End of file")
|
|
|
+ },
|
|
|
+ })
|
|
|
+ })
|
|
|
+
|
|
|
+ test("respects offset parameter", async () => {
|
|
|
+ await using tmp = await tmpdir({
|
|
|
+ init: async (dir) => {
|
|
|
+ const lines = Array.from({ length: 20 }, (_, i) => `line${i}`).join("\n")
|
|
|
+ await Bun.write(path.join(dir, "offset.txt"), lines)
|
|
|
+ },
|
|
|
+ })
|
|
|
+ await Instance.provide({
|
|
|
+ directory: tmp.path,
|
|
|
+ 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("line10")
|
|
|
+ expect(result.output).toContain("line14")
|
|
|
+ expect(result.output).not.toContain("line0")
|
|
|
+ expect(result.output).not.toContain("line15")
|
|
|
+ },
|
|
|
+ })
|
|
|
+ })
|
|
|
+
|
|
|
+ test("truncates long lines", async () => {
|
|
|
+ await using tmp = await tmpdir({
|
|
|
+ init: async (dir) => {
|
|
|
+ const longLine = "x".repeat(3000)
|
|
|
+ await Bun.write(path.join(dir, "long-line.txt"), longLine)
|
|
|
+ },
|
|
|
+ })
|
|
|
+ await Instance.provide({
|
|
|
+ directory: tmp.path,
|
|
|
+ fn: async () => {
|
|
|
+ const read = await ReadTool.init()
|
|
|
+ const result = await read.execute({ filePath: path.join(tmp.path, "long-line.txt") }, ctx)
|
|
|
+ expect(result.output).toContain("...")
|
|
|
+ expect(result.output.length).toBeLessThan(3000)
|
|
|
+ },
|
|
|
+ })
|
|
|
+ })
|
|
|
+
|
|
|
+ test("image files set truncated to false", async () => {
|
|
|
+ await using tmp = await tmpdir({
|
|
|
+ init: async (dir) => {
|
|
|
+ // 1x1 red PNG
|
|
|
+ const png = Buffer.from(
|
|
|
+ "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8DwHwAFBQIAX8jx0gAAAABJRU5ErkJggg==",
|
|
|
+ "base64",
|
|
|
+ )
|
|
|
+ await Bun.write(path.join(dir, "image.png"), png)
|
|
|
+ },
|
|
|
+ })
|
|
|
+ await Instance.provide({
|
|
|
+ directory: tmp.path,
|
|
|
+ fn: async () => {
|
|
|
+ const read = await ReadTool.init()
|
|
|
+ const result = await read.execute({ filePath: path.join(tmp.path, "image.png") }, ctx)
|
|
|
+ expect(result.metadata.truncated).toBe(false)
|
|
|
+ expect(result.attachments).toBeDefined()
|
|
|
+ expect(result.attachments?.length).toBe(1)
|
|
|
+ },
|
|
|
+ })
|
|
|
+ })
|
|
|
+})
|