diff --git a/src/hooks/grep-output-truncator.ts b/src/hooks/grep-output-truncator.ts deleted file mode 100644 index 8ec0d63..0000000 --- a/src/hooks/grep-output-truncator.ts +++ /dev/null @@ -1,131 +0,0 @@ -import type { PluginInput } from "@opencode-ai/plugin" - -const ANTHROPIC_ACTUAL_LIMIT = 200_000 -const CHARS_PER_TOKEN_ESTIMATE = 4 -const TARGET_MAX_TOKENS = 50_000 - -interface AssistantMessageInfo { - role: "assistant" - tokens: { - input: number - output: number - reasoning: number - cache: { read: number; write: number } - } -} - -interface MessageWrapper { - info: { role: string } & Partial -} - -function estimateTokens(text: string): number { - return Math.ceil(text.length / CHARS_PER_TOKEN_ESTIMATE) -} - -function truncateToTokenLimit(output: string, maxTokens: number): { result: string; truncated: boolean } { - const currentTokens = estimateTokens(output) - - if (currentTokens <= maxTokens) { - return { result: output, truncated: false } - } - - const lines = output.split("\n") - - if (lines.length <= 3) { - const maxChars = maxTokens * CHARS_PER_TOKEN_ESTIMATE - return { - result: output.slice(0, maxChars) + "\n\n[Output truncated due to context window limit]", - truncated: true, - } - } - - const headerLines = lines.slice(0, 3) - const contentLines = lines.slice(3) - - const headerText = headerLines.join("\n") - const headerTokens = estimateTokens(headerText) - const availableTokens = maxTokens - headerTokens - 50 - - if (availableTokens <= 0) { - return { - result: headerText + "\n\n[Content truncated due to context window limit]", - truncated: true, - } - } - - let resultLines: string[] = [] - let currentTokenCount = 0 - - for (const line of contentLines) { - const lineTokens = estimateTokens(line + "\n") - if (currentTokenCount + lineTokens > availableTokens) { - break - } - resultLines.push(line) - currentTokenCount += lineTokens - } - - const truncatedContent = [...headerLines, ...resultLines].join("\n") - const removedCount = contentLines.length - resultLines.length - - return { - result: truncatedContent + `\n\n[${removedCount} more lines truncated due to context window limit]`, - truncated: true, - } -} - -export function createGrepOutputTruncatorHook(ctx: PluginInput) { - const GREP_TOOLS = ["grep", "Grep", "safe_grep"] - - const toolExecuteAfter = async ( - input: { tool: string; sessionID: string; callID: string }, - output: { title: string; output: string; metadata: unknown } - ) => { - if (!GREP_TOOLS.includes(input.tool)) return - - const { sessionID } = input - - try { - const response = await ctx.client.session.messages({ - path: { id: sessionID }, - }) - - const messages = (response.data ?? response) as MessageWrapper[] - - const assistantMessages = messages - .filter((m) => m.info.role === "assistant") - .map((m) => m.info as AssistantMessageInfo) - - if (assistantMessages.length === 0) return - - // Use only the last assistant message's input tokens - // This reflects the ACTUAL current context window usage (post-compaction) - const lastAssistant = assistantMessages[assistantMessages.length - 1] - const lastTokens = lastAssistant.tokens - const totalInputTokens = (lastTokens?.input ?? 0) + (lastTokens?.cache?.read ?? 0) - - const remainingTokens = ANTHROPIC_ACTUAL_LIMIT - totalInputTokens - - const maxOutputTokens = Math.min( - remainingTokens * 0.5, - TARGET_MAX_TOKENS - ) - - if (maxOutputTokens <= 0) { - output.output = "[Output suppressed - context window exhausted]" - return - } - - const { result, truncated } = truncateToTokenLimit(output.output, maxOutputTokens) - if (truncated) { - output.output = result - } - } catch { - // Graceful degradation - } - } - - return { - "tool.execute.after": toolExecuteAfter, - } -} diff --git a/src/hooks/index.ts b/src/hooks/index.ts index d882e20..cd7bb49 100644 --- a/src/hooks/index.ts +++ b/src/hooks/index.ts @@ -3,7 +3,6 @@ export { createContextWindowMonitorHook } from "./context-window-monitor"; export { createSessionNotification } from "./session-notification"; export { createSessionRecoveryHook, type SessionRecoveryHook, type SessionRecoveryOptions } from "./session-recovery"; export { createCommentCheckerHooks } from "./comment-checker"; -export { createGrepOutputTruncatorHook } from "./grep-output-truncator"; export { createToolOutputTruncatorHook } from "./tool-output-truncator"; export { createDirectoryAgentsInjectorHook } from "./directory-agents-injector"; export { createDirectoryReadmeInjectorHook } from "./directory-readme-injector"; diff --git a/src/hooks/tool-output-truncator.ts b/src/hooks/tool-output-truncator.ts index 28326c2..c3a8f08 100644 --- a/src/hooks/tool-output-truncator.ts +++ b/src/hooks/tool-output-truncator.ts @@ -1,8 +1,9 @@ import type { PluginInput } from "@opencode-ai/plugin" import { createDynamicTruncator } from "../shared/dynamic-truncator" -// Note: "grep" and "Grep" are handled by dedicated grep-output-truncator.ts const TRUNCATABLE_TOOLS = [ + "grep", + "Grep", "safe_grep", "glob", "Glob",