wip
This commit is contained in:
parent
f939060cd1
commit
ece719dc77
4 changed files with 40 additions and 9 deletions
|
|
@ -4,7 +4,10 @@ import { Global } from "../global"
|
||||||
import { Identifier } from "../id/id"
|
import { Identifier } from "../id/id"
|
||||||
import { iife } from "../util/iife"
|
import { iife } from "../util/iife"
|
||||||
import { lazy } from "../util/lazy"
|
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 namespace Truncate {
|
||||||
export const MAX_LINES = 2000
|
export const MAX_LINES = 2000
|
||||||
export const MAX_BYTES = 50 * 1024
|
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 maxLines = options.maxLines ?? MAX_LINES
|
||||||
const maxBytes = options.maxBytes ?? MAX_BYTES
|
const maxBytes = options.maxBytes ?? MAX_BYTES
|
||||||
const direction = options.direction ?? "head"
|
const direction = options.direction ?? "head"
|
||||||
|
|
@ -85,10 +94,12 @@ export namespace Truncate {
|
||||||
const filepath = path.join(DIR, id)
|
const filepath = path.join(DIR, id)
|
||||||
await Bun.write(Bun.file(filepath), text)
|
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 =
|
const message =
|
||||||
direction === "head"
|
direction === "head"
|
||||||
? `${preview}\n\n...${removed} ${unit} truncated...\n\nFull output written to: ${filepath}\nUse Read or Grep to view the full content.`
|
? `${preview}\n\n...${removed} ${unit} truncated...\n\n${hint}`
|
||||||
: `...${removed} ${unit} truncated...\n\nFull output written to: ${filepath}\nUse Read or Grep to view the full content.\n\n${preview}`
|
: `...${removed} ${unit} truncated...\n\n${hint}\n\n${preview}`
|
||||||
|
|
||||||
return { content: message, truncated: true, outputPath: filepath }
|
return { content: message, truncated: true, outputPath: filepath }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -60,12 +60,12 @@ export namespace ToolRegistry {
|
||||||
function fromPlugin(id: string, def: ToolDefinition): Tool.Info {
|
function fromPlugin(id: string, def: ToolDefinition): Tool.Info {
|
||||||
return {
|
return {
|
||||||
id,
|
id,
|
||||||
init: async () => ({
|
init: async (initCtx) => ({
|
||||||
parameters: z.object(def.args),
|
parameters: z.object(def.args),
|
||||||
description: def.description,
|
description: def.description,
|
||||||
execute: async (args, ctx) => {
|
execute: async (args, ctx) => {
|
||||||
const result = await def.execute(args as any, 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 {
|
return {
|
||||||
title: "",
|
title: "",
|
||||||
output: out.truncated ? out.content : result,
|
output: out.truncated ? out.content : result,
|
||||||
|
|
|
||||||
|
|
@ -50,8 +50,8 @@ export namespace Tool {
|
||||||
): Info<Parameters, Result> {
|
): Info<Parameters, Result> {
|
||||||
return {
|
return {
|
||||||
id,
|
id,
|
||||||
init: async (ctx) => {
|
init: async (initCtx) => {
|
||||||
const toolInfo = init instanceof Function ? await init(ctx) : init
|
const toolInfo = init instanceof Function ? await init(initCtx) : init
|
||||||
const execute = toolInfo.execute
|
const execute = toolInfo.execute
|
||||||
toolInfo.execute = async (args, ctx) => {
|
toolInfo.execute = async (args, ctx) => {
|
||||||
try {
|
try {
|
||||||
|
|
@ -66,7 +66,7 @@ export namespace Tool {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
const result = await execute(args, ctx)
|
const result = await execute(args, ctx)
|
||||||
const truncated = await Truncate.output(result.output)
|
const truncated = await Truncate.output(result.output, {}, initCtx?.agent)
|
||||||
return {
|
return {
|
||||||
...result,
|
...result,
|
||||||
output: truncated.content,
|
output: truncated.content,
|
||||||
|
|
|
||||||
|
|
@ -83,12 +83,32 @@ describe("Truncate", () => {
|
||||||
expect(result.outputPath).toBeDefined()
|
expect(result.outputPath).toBeDefined()
|
||||||
expect(result.outputPath).toContain("tool_")
|
expect(result.outputPath).toContain("tool_")
|
||||||
expect(result.content).toContain("Full output written to:")
|
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()
|
const written = await Bun.file(result.outputPath!).text()
|
||||||
expect(written).toBe(lines)
|
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 () => {
|
test("does not write file when not truncated", async () => {
|
||||||
const content = "short content"
|
const content = "short content"
|
||||||
const result = await Truncate.output(content)
|
const result = await Truncate.output(content)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue