feat(hooks): add tool-output-truncator for dynamic context-aware truncation

Refactor grep-output-truncator into a general-purpose tool-output-truncator
that applies dynamic truncation to multiple tools based on context window usage.

Truncated tools:
- Grep, safe_grep (existing)
- Glob, safe_glob (new)
- lsp_find_references (new)
- lsp_document_symbols (new)
- lsp_workspace_symbols (new)
- lsp_diagnostics (new)
- ast_grep_search (new)

Uses the new dynamic-truncator utility from shared/ for context-aware
output size limits based on remaining context window tokens.

🤖 GENERATED WITH ASSISTANCE OF [OhMyOpenCode](https://github.com/code-yeongyu/oh-my-opencode)
This commit is contained in:
YeonGyu-Kim
2025-12-14 10:56:50 +09:00
parent 521bcd5667
commit 25a5c2eeb4
4 changed files with 52 additions and 11 deletions

View File

@@ -44,6 +44,7 @@ export const HookNameSchema = z.enum([
"session-notification",
"comment-checker",
"grep-output-truncator",
"tool-output-truncator",
"directory-agents-injector",
"directory-readme-injector",
"empty-task-response-detector",
@@ -52,7 +53,7 @@ export const HookNameSchema = z.enum([
"rules-injector",
"background-notification",
"auto-update-checker",
"ultrawork-mode",
"keyword-detector",
"agent-usage-reminder",
])

View File

@@ -4,6 +4,7 @@ export { createSessionNotification } from "./session-notification";
export { createSessionRecoveryHook, type SessionRecoveryHook } 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";
export { createEmptyTaskResponseDetectorHook } from "./empty-task-response-detector";
@@ -13,5 +14,6 @@ export { createClaudeCodeHooksHook } from "./claude-code-hooks";
export { createRulesInjectorHook } from "./rules-injector";
export { createBackgroundNotificationHook } from "./background-notification"
export { createAutoUpdateCheckerHook } from "./auto-update-checker";
export { createUltraworkModeHook } from "./ultrawork-mode";
export { createAgentUsageReminderHook } from "./agent-usage-reminder";
export { createKeywordDetectorHook } from "./keyword-detector";

View File

@@ -0,0 +1,38 @@
import type { PluginInput } from "@opencode-ai/plugin"
import { createDynamicTruncator } from "../shared/dynamic-truncator"
const TRUNCATABLE_TOOLS = [
"Grep",
"safe_grep",
"Glob",
"safe_glob",
"lsp_find_references",
"lsp_document_symbols",
"lsp_workspace_symbols",
"lsp_diagnostics",
"ast_grep_search",
]
export function createToolOutputTruncatorHook(ctx: PluginInput) {
const truncator = createDynamicTruncator(ctx)
const toolExecuteAfter = async (
input: { tool: string; sessionID: string; callID: string },
output: { title: string; output: string; metadata: unknown }
) => {
if (!TRUNCATABLE_TOOLS.includes(input.tool)) return
try {
const { result, truncated } = await truncator.truncate(input.sessionID, output.output)
if (truncated) {
output.output = result
}
} catch {
// Graceful degradation - don't break tool execution
}
}
return {
"tool.execute.after": toolExecuteAfter,
}
}

View File

@@ -6,7 +6,7 @@ import {
createSessionRecoveryHook,
createSessionNotification,
createCommentCheckerHooks,
createGrepOutputTruncatorHook,
createToolOutputTruncatorHook,
createDirectoryAgentsInjectorHook,
createDirectoryReadmeInjectorHook,
createEmptyTaskResponseDetectorHook,
@@ -16,7 +16,7 @@ import {
createRulesInjectorHook,
createBackgroundNotificationHook,
createAutoUpdateCheckerHook,
createUltraworkModeHook,
createKeywordDetectorHook,
createAgentUsageReminderHook,
} from "./hooks";
import { createGoogleAntigravityAuthPlugin } from "./auth/antigravity";
@@ -178,8 +178,8 @@ const OhMyOpenCodePlugin: Plugin = async (ctx) => {
const commentChecker = isHookEnabled("comment-checker")
? createCommentCheckerHooks()
: null;
const grepOutputTruncator = isHookEnabled("grep-output-truncator")
? createGrepOutputTruncatorHook(ctx)
const toolOutputTruncator = isHookEnabled("tool-output-truncator")
? createToolOutputTruncatorHook(ctx)
: null;
const directoryAgentsInjector = isHookEnabled("directory-agents-injector")
? createDirectoryAgentsInjectorHook(ctx)
@@ -205,8 +205,8 @@ const OhMyOpenCodePlugin: Plugin = async (ctx) => {
const autoUpdateChecker = isHookEnabled("auto-update-checker")
? createAutoUpdateCheckerHook(ctx)
: null;
const ultraworkMode = isHookEnabled("ultrawork-mode")
? createUltraworkModeHook()
const keywordDetector = isHookEnabled("keyword-detector")
? createKeywordDetectorHook()
: null;
const agentUsageReminder = isHookEnabled("agent-usage-reminder")
? createAgentUsageReminderHook(ctx)
@@ -240,7 +240,7 @@ const OhMyOpenCodePlugin: Plugin = async (ctx) => {
"chat.message": async (input, output) => {
await claudeCodeHooks["chat.message"]?.(input, output);
await ultraworkMode?.["chat.message"]?.(input, output);
await keywordDetector?.["chat.message"]?.(input, output);
},
config: async (config) => {
@@ -337,7 +337,7 @@ const OhMyOpenCodePlugin: Plugin = async (ctx) => {
await rulesInjector?.event(input);
await thinkMode?.event(input);
await anthropicAutoCompact?.event(input);
await ultraworkMode?.event(input);
await keywordDetector?.event(input);
await agentUsageReminder?.event(input);
const { event } = input;
@@ -451,7 +451,7 @@ const OhMyOpenCodePlugin: Plugin = async (ctx) => {
"tool.execute.after": async (input, output) => {
await claudeCodeHooks["tool.execute.after"](input, output);
await grepOutputTruncator?.["tool.execute.after"](input, output);
await toolOutputTruncator?.["tool.execute.after"](input, output);
await contextWindowMonitor?.["tool.execute.after"](input, output);
await commentChecker?.["tool.execute.after"](input, output);
await directoryAgentsInjector?.["tool.execute.after"](input, output);