Parcourir la source

ignore: add truncation funcs (#7178)

Aiden Cline il y a 1 mois
Parent
commit
429240f439

+ 60 - 0
packages/opencode/src/session/truncation.ts

@@ -0,0 +1,60 @@
+export namespace Truncate {
+  export const MAX_LINES = 2000
+  export const MAX_BYTES = 50 * 1024
+
+  export interface Result {
+    content: string
+    truncated: boolean
+  }
+
+  export interface Options {
+    maxLines?: number
+    maxBytes?: number
+    direction?: "head" | "tail"
+  }
+
+  export function output(text: string, options: Options = {}): Result {
+    const maxLines = options.maxLines ?? MAX_LINES
+    const maxBytes = options.maxBytes ?? MAX_BYTES
+    const direction = options.direction ?? "head"
+    const lines = text.split("\n")
+    const totalBytes = Buffer.byteLength(text, "utf-8")
+
+    if (lines.length <= maxLines && totalBytes <= maxBytes) {
+      return { content: text, truncated: false }
+    }
+
+    const out: string[] = []
+    var i = 0
+    var bytes = 0
+    var hitBytes = false
+
+    if (direction === "head") {
+      for (i = 0; i < lines.length && i < maxLines; i++) {
+        const size = Buffer.byteLength(lines[i], "utf-8") + (i > 0 ? 1 : 0)
+        if (bytes + size > maxBytes) {
+          hitBytes = true
+          break
+        }
+        out.push(lines[i])
+        bytes += size
+      }
+      const removed = hitBytes ? totalBytes - bytes : lines.length - out.length
+      const unit = hitBytes ? "chars" : "lines"
+      return { content: `${out.join("\n")}\n\n...${removed} ${unit} truncated...`, truncated: true }
+    }
+
+    for (i = lines.length - 1; i >= 0 && out.length < maxLines; i--) {
+      const size = Buffer.byteLength(lines[i], "utf-8") + (out.length > 0 ? 1 : 0)
+      if (bytes + size > maxBytes) {
+        hitBytes = true
+        break
+      }
+      out.unshift(lines[i])
+      bytes += size
+    }
+    const removed = hitBytes ? totalBytes - bytes : lines.length - out.length
+    const unit = hitBytes ? "chars" : "lines"
+    return { content: `...${removed} ${unit} truncated...\n\n${out.join("\n")}`, truncated: true }
+  }
+}

Fichier diff supprimé car celui-ci est trop grand
+ 0 - 0
packages/opencode/test/session/fixtures/models-api.json


+ 79 - 0
packages/opencode/test/session/truncation.test.ts

@@ -0,0 +1,79 @@
+import { describe, test, expect } from "bun:test"
+import { Truncate } from "../../src/session/truncation"
+import path from "path"
+
+const FIXTURES_DIR = path.join(import.meta.dir, "fixtures")
+
+describe("Truncate", () => {
+  describe("output", () => {
+    test("truncates large json file by bytes", async () => {
+      const content = await Bun.file(path.join(FIXTURES_DIR, "models-api.json")).text()
+      const result = Truncate.output(content)
+
+      expect(result.truncated).toBe(true)
+      expect(Buffer.byteLength(result.content, "utf-8")).toBeLessThanOrEqual(Truncate.MAX_BYTES + 100)
+      expect(result.content).toContain("truncated...")
+    })
+
+    test("returns content unchanged when under limits", () => {
+      const content = "line1\nline2\nline3"
+      const result = Truncate.output(content)
+
+      expect(result.truncated).toBe(false)
+      expect(result.content).toBe(content)
+    })
+
+    test("truncates by line count", () => {
+      const lines = Array.from({ length: 100 }, (_, i) => `line${i}`).join("\n")
+      const result = Truncate.output(lines, { maxLines: 10 })
+
+      expect(result.truncated).toBe(true)
+      expect(result.content.split("\n").length).toBeLessThanOrEqual(12)
+      expect(result.content).toContain("...90 lines truncated...")
+    })
+
+    test("truncates by byte count", () => {
+      const content = "a".repeat(1000)
+      const result = Truncate.output(content, { maxBytes: 100 })
+
+      expect(result.truncated).toBe(true)
+      expect(result.content).toContain("truncated...")
+    })
+
+    test("truncates from head by default", () => {
+      const lines = Array.from({ length: 10 }, (_, i) => `line${i}`).join("\n")
+      const result = Truncate.output(lines, { maxLines: 3 })
+
+      expect(result.truncated).toBe(true)
+      expect(result.content).toContain("line0")
+      expect(result.content).toContain("line1")
+      expect(result.content).toContain("line2")
+      expect(result.content).not.toContain("line9")
+    })
+
+    test("truncates from tail when direction is tail", () => {
+      const lines = Array.from({ length: 10 }, (_, i) => `line${i}`).join("\n")
+      const result = Truncate.output(lines, { maxLines: 3, direction: "tail" })
+
+      expect(result.truncated).toBe(true)
+      expect(result.content).toContain("line7")
+      expect(result.content).toContain("line8")
+      expect(result.content).toContain("line9")
+      expect(result.content).not.toContain("line0")
+    })
+
+    test("uses default MAX_LINES and MAX_BYTES", () => {
+      expect(Truncate.MAX_LINES).toBe(2000)
+      expect(Truncate.MAX_BYTES).toBe(50 * 1024)
+    })
+
+    test("large single-line file truncates with byte message", async () => {
+      const content = await Bun.file(path.join(FIXTURES_DIR, "models-api.json")).text()
+      const result = Truncate.output(content)
+
+      expect(result.truncated).toBe(true)
+      expect(result.content).toContain("chars truncated...")
+      expect(Buffer.byteLength(content, "utf-8")).toBeGreaterThan(Truncate.MAX_BYTES)
+    })
+  })
+})

Certains fichiers n'ont pas été affichés car il y a eu trop de fichiers modifiés dans ce diff