Files
pi_mcps/zoo_backup/home/tools/context_budget.ts
T
2026-06-24 19:27:14 +02:00

92 lines
3.2 KiB
TypeScript

import { parametersSchema as z, defineCustomTool } from "@roo-code/types"
import { statSync, readdirSync } from "fs"
import { join, relative } from "path"
export default defineCustomTool({
name: "context_budget",
description: "Estimate token cost of reading files or directories. Returns file count, total lines, chars, and estimated tokens. Helps decide whether to load full files or use targeted reads.",
parameters: z.object({
paths: z.array(z.string()).describe("File or directory paths to analyze"),
recursive: z.boolean().optional().describe("Recurse into directories. Default: true"),
extensions: z.array(z.string()).optional().describe("Filter by file extensions, e.g. ['.java', '.ts']. Default: all files"),
}),
async execute({ paths, recursive = true, extensions }) {
let totalFiles = 0
let totalLines = 0
let totalChars = 0
const breakdown: Array<{ path: string; lines: number; chars: number; tokens: number }> = []
function processFile(filePath: string) {
try {
const stat = statSync(filePath)
if (!stat.isFile()) return
if (extensions && !extensions.some(ext => filePath.endsWith(ext))) return
const { readFileSync } = require("fs")
const content = readFileSync(filePath, "utf-8")
const lines = content.split("\n").length
const chars = content.length
const tokens = Math.ceil(chars / 4)
totalFiles++
totalLines += lines
totalChars += chars
breakdown.push({ path: filePath, lines, chars, tokens })
} catch (e) {
// skip unreadable files
}
}
function processDir(dirPath: string, recurse: boolean) {
try {
const entries = readdirSync(dirPath, { withFileTypes: true })
for (const entry of entries) {
if (entry.name.startsWith(".") || entry.name === "node_modules" || entry.name === "target" || entry.name === ".git") continue
const fullPath = join(dirPath, entry.name)
if (entry.isFile()) {
processFile(fullPath)
} else if (entry.isDirectory() && recurse) {
processDir(fullPath, recurse)
}
}
} catch (e) {
// skip unreadable dirs
}
}
for (const p of paths) {
try {
const stat = statSync(p)
if (stat.isFile()) {
processFile(p)
} else if (stat.isDirectory()) {
processDir(p, recursive !== false)
}
} catch (e) {
breakdown.push({ path: p, lines: 0, chars: 0, tokens: 0 })
}
}
const totalTokens = Math.ceil(totalChars / 4)
// Sort by tokens descending, take top 15
breakdown.sort((a, b) => b.tokens - a.tokens)
const topFiles = breakdown.slice(0, 15)
const result = {
summary: {
files: totalFiles,
totalLines,
totalChars,
estimatedTokens: totalTokens,
warning: totalTokens > 50000 ? "⚠️ LARGE — will consume significant context budget" :
totalTokens > 20000 ? "⚠️ MEDIUM — consider targeted reads" :
"✅ Fits comfortably in context",
},
largestFiles: topFiles,
}
return JSON.stringify(result, null, 2)
},
})