pull/7239/head
Aiden Cline 2026-01-07 12:54:13 -06:00
parent f939060cd1
commit ece719dc77
4 changed files with 40 additions and 9 deletions

View File

@ -4,7 +4,10 @@ import { Global } from "../global"
import { Identifier } from "../id/id"
import { iife } from "../util/iife"
import { lazy } from "../util/lazy"
import { PermissionNext } from "../permission/next"
import type { Agent } from "../agent/agent"
// what models does opencode provider support? Read: https://models.dev/api.json
export namespace Truncate {
export const MAX_LINES = 2000
export const MAX_BYTES = 50 * 1024
@ -38,7 +41,13 @@ export namespace Truncate {
}
})
export async function output(text: string, options: Options = {}): Promise<Result> {
function hasTaskTool(agent?: Agent.Info): boolean {
if (!agent?.permission) return false
const rule = PermissionNext.evaluate("task", "*", agent.permission)
return rule.action !== "deny"
}
export async function output(text: string, options: Options = {}, agent?: Agent.Info): Promise<Result> {
const maxLines = options.maxLines ?? MAX_LINES
const maxBytes = options.maxBytes ?? MAX_BYTES
const direction = options.direction ?? "head"
@ -85,10 +94,12 @@ export namespace Truncate {
const filepath = path.join(DIR, id)
await Bun.write(Bun.file(filepath), text)
const base = `Full output written to: ${filepath}\nUse Grep to search the full content and Read with offset/limit to read specific sections`
const hint = hasTaskTool(agent) ? `${base} (or use Task tool to delegate and save context).` : `${base}.`
const message =
direction === "head"
? `${preview}\n\n...${removed} ${unit} truncated...\n\nFull output written to: ${filepath}\nUse Read or Grep to view the full content.`
: `...${removed} ${unit} truncated...\n\nFull output written to: ${filepath}\nUse Read or Grep to view the full content.\n\n${preview}`
? `${preview}\n\n...${removed} ${unit} truncated...\n\n${hint}`
: `...${removed} ${unit} truncated...\n\n${hint}\n\n${preview}`
return { content: message, truncated: true, outputPath: filepath }
}

View File

@ -60,12 +60,12 @@ export namespace ToolRegistry {
function fromPlugin(id: string, def: ToolDefinition): Tool.Info {
return {
id,
init: async () => ({
init: async (initCtx) => ({
parameters: z.object(def.args),
description: def.description,
execute: async (args, ctx) => {
const result = await def.execute(args as any, ctx)
const out = await Truncate.output(result)
const out = await Truncate.output(result, {}, initCtx?.agent)
return {
title: "",
output: out.truncated ? out.content : result,

View File

@ -50,8 +50,8 @@ export namespace Tool {
): Info<Parameters, Result> {
return {
id,
init: async (ctx) => {
const toolInfo = init instanceof Function ? await init(ctx) : init
init: async (initCtx) => {
const toolInfo = init instanceof Function ? await init(initCtx) : init
const execute = toolInfo.execute
toolInfo.execute = async (args, ctx) => {
try {
@ -66,7 +66,7 @@ export namespace Tool {
)
}
const result = await execute(args, ctx)
const truncated = await Truncate.output(result.output)
const truncated = await Truncate.output(result.output, {}, initCtx?.agent)
return {
...result,
output: truncated.content,

View File

@ -83,12 +83,32 @@ describe("Truncate", () => {
expect(result.outputPath).toBeDefined()
expect(result.outputPath).toContain("tool_")
expect(result.content).toContain("Full output written to:")
expect(result.content).toContain("Use Read or Grep to view the full content")
expect(result.content).toContain("Grep")
const written = await Bun.file(result.outputPath!).text()
expect(written).toBe(lines)
})
test("suggests Task tool when agent has task permission", async () => {
const lines = Array.from({ length: 100 }, (_, i) => `line${i}`).join("\n")
const agent = { permission: [{ permission: "task", pattern: "*", action: "allow" as const }] }
const result = await Truncate.output(lines, { maxLines: 10 }, agent as any)
expect(result.truncated).toBe(true)
expect(result.content).toContain("Grep")
expect(result.content).toContain("Task tool")
})
test("omits Task tool hint when agent lacks task permission", async () => {
const lines = Array.from({ length: 100 }, (_, i) => `line${i}`).join("\n")
const agent = { permission: [{ permission: "task", pattern: "*", action: "deny" as const }] }
const result = await Truncate.output(lines, { maxLines: 10 }, agent as any)
expect(result.truncated).toBe(true)
expect(result.content).toContain("Grep")
expect(result.content).not.toContain("Task tool")
})
test("does not write file when not truncated", async () => {
const content = "short content"
const result = await Truncate.output(content)