wip
parent
f939060cd1
commit
ece719dc77
|
|
@ -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 }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
Loading…
Reference in New Issue