feat(truncate): allow configuring tool output truncation limits (#23770)

Co-authored-by: rgs_ramp <rgs@ramp.com>
Co-authored-by: Aiden Cline <63023139+rekram1-node@users.noreply.github.com>
This commit is contained in:
rahul
2026-04-23 17:43:33 -04:00
committed by GitHub
parent e50a688ca3
commit f8c6ddd4cb
4 changed files with 106 additions and 12 deletions

View File

@@ -2,6 +2,7 @@ import { describe, test, expect } from "bun:test"
import { NodeFileSystem } from "@effect/platform-node"
import { Effect, FileSystem, Layer } from "effect"
import { Truncate } from "../../src/tool"
import { Config } from "../../src/config"
import { Identifier } from "../../src/id/id"
import { Process } from "../../src/util"
import { Filesystem } from "../../src/util"
@@ -14,6 +15,14 @@ const ROOT = path.resolve(import.meta.dir, "..", "..")
const it = testEffect(Layer.mergeAll(Truncate.defaultLayer, NodeFileSystem.layer))
const configuredLayer = (cfg: Config.Info) =>
Layer.mergeAll(
Truncate.defaultLayer,
NodeFileSystem.layer,
Layer.mock(Config.Service)({ get: () => Effect.succeed(cfg) }),
)
const configuredIt = (cfg: Config.Info) => testEffect(configuredLayer(cfg))
describe("Truncate", () => {
describe("output", () => {
it.live("truncates large json file by bytes", () =>
@@ -94,6 +103,61 @@ describe("Truncate", () => {
expect(Truncate.MAX_BYTES).toBe(50 * 1024)
})
it.live("limits() falls back to MAX_LINES/MAX_BYTES when Config is not provided", () =>
Effect.gen(function* () {
const svc = yield* Truncate.Service
const resolved = yield* svc.limits()
expect(resolved.maxLines).toBe(Truncate.MAX_LINES)
expect(resolved.maxBytes).toBe(Truncate.MAX_BYTES)
}),
)
describe("with tool_output config", () => {
const limitsIt = configuredIt({ tool_output: { max_lines: 123, max_bytes: 456 } })
limitsIt.live("limits() reflects config overrides", () =>
Effect.gen(function* () {
const resolved = yield* (yield* Truncate.Service).limits()
expect(resolved.maxLines).toBe(123)
expect(resolved.maxBytes).toBe(456)
}),
)
// Huge byte budget isolates line truncation. 100 lines against max_lines: 10
// proves the configured line limit is what `output()` enforces.
const lineIt = configuredIt({ tool_output: { max_lines: 10, max_bytes: 1024 * 1024 } })
lineIt.live("output() truncates to configured max_lines", () =>
Effect.gen(function* () {
const content = Array.from({ length: 100 }, (_, i) => `line${i}`).join("\n")
const result = yield* (yield* Truncate.Service).output(content)
expect(result.truncated).toBe(true)
expect(result.content).toContain("...90 lines truncated...")
}),
)
// Huge line budget isolates byte truncation.
const byteIt = configuredIt({ tool_output: { max_lines: 1_000_000, max_bytes: 100 } })
byteIt.live("output() truncates to configured max_bytes", () =>
Effect.gen(function* () {
const content = "a".repeat(1000)
const result = yield* (yield* Truncate.Service).output(content)
expect(result.truncated).toBe(true)
expect(result.content).toContain("bytes truncated...")
}),
)
const overrideIt = configuredIt({ tool_output: { max_lines: 10, max_bytes: 100 } })
overrideIt.live("per-call options still override config", () =>
Effect.gen(function* () {
const content = Array.from({ length: 50 }, (_, i) => `line${i}`).join("\n")
const result = yield* (yield* Truncate.Service).output(content, {
maxLines: 1000,
maxBytes: 1024 * 1024,
})
expect(result.truncated).toBe(false)
}),
)
})
it.live("large single-line file truncates with byte message", () =>
Effect.gen(function* () {
const svc = yield* Truncate.Service