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

View File

@@ -4,6 +4,7 @@ export { createSessionNotification } from "./session-notification";
export { createSessionRecoveryHook, type SessionRecoveryHook } from "./session-recovery"; export { createSessionRecoveryHook, type SessionRecoveryHook } from "./session-recovery";
export { createCommentCheckerHooks } from "./comment-checker"; export { createCommentCheckerHooks } from "./comment-checker";
export { createGrepOutputTruncatorHook } from "./grep-output-truncator"; export { createGrepOutputTruncatorHook } from "./grep-output-truncator";
export { createToolOutputTruncatorHook } from "./tool-output-truncator";
export { createDirectoryAgentsInjectorHook } from "./directory-agents-injector"; export { createDirectoryAgentsInjectorHook } from "./directory-agents-injector";
export { createDirectoryReadmeInjectorHook } from "./directory-readme-injector"; export { createDirectoryReadmeInjectorHook } from "./directory-readme-injector";
export { createEmptyTaskResponseDetectorHook } from "./empty-task-response-detector"; export { createEmptyTaskResponseDetectorHook } from "./empty-task-response-detector";
@@ -13,5 +14,6 @@ export { createClaudeCodeHooksHook } from "./claude-code-hooks";
export { createRulesInjectorHook } from "./rules-injector"; export { createRulesInjectorHook } from "./rules-injector";
export { createBackgroundNotificationHook } from "./background-notification" export { createBackgroundNotificationHook } from "./background-notification"
export { createAutoUpdateCheckerHook } from "./auto-update-checker"; export { createAutoUpdateCheckerHook } from "./auto-update-checker";
export { createUltraworkModeHook } from "./ultrawork-mode";
export { createAgentUsageReminderHook } from "./agent-usage-reminder"; 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, createSessionRecoveryHook,
createSessionNotification, createSessionNotification,
createCommentCheckerHooks, createCommentCheckerHooks,
createGrepOutputTruncatorHook, createToolOutputTruncatorHook,
createDirectoryAgentsInjectorHook, createDirectoryAgentsInjectorHook,
createDirectoryReadmeInjectorHook, createDirectoryReadmeInjectorHook,
createEmptyTaskResponseDetectorHook, createEmptyTaskResponseDetectorHook,
@@ -16,7 +16,7 @@ import {
createRulesInjectorHook, createRulesInjectorHook,
createBackgroundNotificationHook, createBackgroundNotificationHook,
createAutoUpdateCheckerHook, createAutoUpdateCheckerHook,
createUltraworkModeHook, createKeywordDetectorHook,
createAgentUsageReminderHook, createAgentUsageReminderHook,
} from "./hooks"; } from "./hooks";
import { createGoogleAntigravityAuthPlugin } from "./auth/antigravity"; import { createGoogleAntigravityAuthPlugin } from "./auth/antigravity";
@@ -178,8 +178,8 @@ const OhMyOpenCodePlugin: Plugin = async (ctx) => {
const commentChecker = isHookEnabled("comment-checker") const commentChecker = isHookEnabled("comment-checker")
? createCommentCheckerHooks() ? createCommentCheckerHooks()
: null; : null;
const grepOutputTruncator = isHookEnabled("grep-output-truncator") const toolOutputTruncator = isHookEnabled("tool-output-truncator")
? createGrepOutputTruncatorHook(ctx) ? createToolOutputTruncatorHook(ctx)
: null; : null;
const directoryAgentsInjector = isHookEnabled("directory-agents-injector") const directoryAgentsInjector = isHookEnabled("directory-agents-injector")
? createDirectoryAgentsInjectorHook(ctx) ? createDirectoryAgentsInjectorHook(ctx)
@@ -205,8 +205,8 @@ const OhMyOpenCodePlugin: Plugin = async (ctx) => {
const autoUpdateChecker = isHookEnabled("auto-update-checker") const autoUpdateChecker = isHookEnabled("auto-update-checker")
? createAutoUpdateCheckerHook(ctx) ? createAutoUpdateCheckerHook(ctx)
: null; : null;
const ultraworkMode = isHookEnabled("ultrawork-mode") const keywordDetector = isHookEnabled("keyword-detector")
? createUltraworkModeHook() ? createKeywordDetectorHook()
: null; : null;
const agentUsageReminder = isHookEnabled("agent-usage-reminder") const agentUsageReminder = isHookEnabled("agent-usage-reminder")
? createAgentUsageReminderHook(ctx) ? createAgentUsageReminderHook(ctx)
@@ -240,7 +240,7 @@ const OhMyOpenCodePlugin: Plugin = async (ctx) => {
"chat.message": async (input, output) => { "chat.message": async (input, output) => {
await claudeCodeHooks["chat.message"]?.(input, output); await claudeCodeHooks["chat.message"]?.(input, output);
await ultraworkMode?.["chat.message"]?.(input, output); await keywordDetector?.["chat.message"]?.(input, output);
}, },
config: async (config) => { config: async (config) => {
@@ -337,7 +337,7 @@ const OhMyOpenCodePlugin: Plugin = async (ctx) => {
await rulesInjector?.event(input); await rulesInjector?.event(input);
await thinkMode?.event(input); await thinkMode?.event(input);
await anthropicAutoCompact?.event(input); await anthropicAutoCompact?.event(input);
await ultraworkMode?.event(input); await keywordDetector?.event(input);
await agentUsageReminder?.event(input); await agentUsageReminder?.event(input);
const { event } = input; const { event } = input;
@@ -451,7 +451,7 @@ const OhMyOpenCodePlugin: Plugin = async (ctx) => {
"tool.execute.after": async (input, output) => { "tool.execute.after": async (input, output) => {
await claudeCodeHooks["tool.execute.after"](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 contextWindowMonitor?.["tool.execute.after"](input, output);
await commentChecker?.["tool.execute.after"](input, output); await commentChecker?.["tool.execute.after"](input, output);
await directoryAgentsInjector?.["tool.execute.after"](input, output); await directoryAgentsInjector?.["tool.execute.after"](input, output);