YeonGyu-Kim 3bbe0cbb1d feat: implement SDK/HTTP pruning for deduplication and tool-output truncation on SQLite
- executeDeduplication: now async, reads messages from SDK on SQLite via
  client.session.messages() instead of JSON file reads
- truncateToolOutputsByCallId: now async, uses truncateToolResultAsync()
  HTTP PATCH on SQLite instead of file-based truncateToolResult()
- deduplication-recovery: passes client through to both functions
- recovery-hook: passes ctx.client to attemptDeduplicationRecovery

Removes the last intentional feature gap on SQLite backend — dynamic
context pruning (dedup + tool-output truncation) now works on both
JSON and SQLite storage backends.
2026-02-16 16:13:40 +09:00

78 lines
2.2 KiB
TypeScript

import type { PluginInput } from "@opencode-ai/plugin"
import type { ParsedTokenLimitError } from "./types"
import type { ExperimentalConfig } from "../../config"
import type { DeduplicationConfig } from "./pruning-deduplication"
import type { PruningState } from "./pruning-types"
import { executeDeduplication } from "./pruning-deduplication"
import { truncateToolOutputsByCallId } from "./pruning-tool-output-truncation"
import { log } from "../../shared/logger"
type OpencodeClient = PluginInput["client"]
function createPruningState(): PruningState {
return {
toolIdsToPrune: new Set<string>(),
currentTurn: 0,
fileOperations: new Map(),
toolSignatures: new Map(),
erroredTools: new Map(),
}
}
function isPromptTooLongError(parsed: ParsedTokenLimitError): boolean {
return !parsed.errorType.toLowerCase().includes("non-empty content")
}
function getDeduplicationPlan(
experimental?: ExperimentalConfig,
): { config: DeduplicationConfig; protectedTools: Set<string> } | null {
const pruningConfig = experimental?.dynamic_context_pruning
if (!pruningConfig?.enabled) return null
const deduplicationEnabled = pruningConfig.strategies?.deduplication?.enabled
if (deduplicationEnabled === false) return null
const protectedTools = new Set(pruningConfig.protected_tools ?? [])
return {
config: {
enabled: true,
protectedTools: pruningConfig.protected_tools ?? [],
},
protectedTools,
}
}
export async function attemptDeduplicationRecovery(
sessionID: string,
parsed: ParsedTokenLimitError,
experimental: ExperimentalConfig | undefined,
client?: OpencodeClient,
): Promise<void> {
if (!isPromptTooLongError(parsed)) return
const plan = getDeduplicationPlan(experimental)
if (!plan) return
const pruningState = createPruningState()
const prunedCount = await executeDeduplication(
sessionID,
pruningState,
plan.config,
plan.protectedTools,
client,
)
const { truncatedCount } = await truncateToolOutputsByCallId(
sessionID,
pruningState.toolIdsToPrune,
client,
)
if (prunedCount > 0 || truncatedCount > 0) {
log("[auto-compact] deduplication recovery applied", {
sessionID,
prunedCount,
truncatedCount,
})
}
}