feat(config): add experimental config for gating unstable features (#110)

* feat(anthropic-auto-compact): add aggressive truncation and empty message recovery

Add truncateUntilTargetTokens method, empty content recovery mechanism, and
emptyContentAttemptBySession tracking for robust message handling.

🤖 GENERATED WITH ASSISTANCE OF [OhMyOpenCode](https://github.com/code-yeongyu/oh-my-opencode)

* feat(session-recovery): add auto-resume and recovery callbacks

Implement ResumeConfig, resumeSession() method, and callback support for
enhanced session recovery and resume functionality.

🤖 GENERATED WITH ASSISTANCE OF [OhMyOpenCode](https://github.com/code-yeongyu/oh-my-opencode)

* feat(config): add experimental config schema for gating unstable features

This adds a new 'experimental' config field to the OhMyOpenCode schema that enables fine-grained control over unstable/experimental features:

- aggressive_truncation: Enables aggressive token truncation in anthropic-auto-compact hook for more aggressive token limit handling
- empty_message_recovery: Enables empty message recovery mechanism in anthropic-auto-compact hook for fixing truncation-induced empty message errors
- auto_resume: Enables automatic session resume after recovery in session-recovery hook for seamless recovery experience

The experimental config is optional and all experimental features are disabled by default, ensuring backward compatibility while allowing early adopters to opt-in to cutting-edge features.

🤖 Generated with assistance of [OhMyOpenCode](https://github.com/code-yeongyu/oh-my-opencode)
This commit is contained in:
YeonGyu-Kim
2025-12-19 02:45:59 +09:00
committed by GitHub
parent 7fe6423abf
commit 8cf713e149
11 changed files with 422 additions and 33 deletions

View File

@@ -1,8 +1,19 @@
import { existsSync, readdirSync, readFileSync, writeFileSync } from "node:fs"
import { homedir } from "node:os"
import { join } from "node:path"
import { xdgData } from "xdg-basedir"
const OPENCODE_STORAGE = join(xdgData ?? "", "opencode", "storage")
let OPENCODE_STORAGE = join(xdgData ?? "", "opencode", "storage")
// Fix for macOS where xdg-basedir points to ~/Library/Application Support
// but OpenCode (cli) uses ~/.local/share
if (process.platform === "darwin" && !existsSync(OPENCODE_STORAGE)) {
const localShare = join(homedir(), ".local", "share", "opencode", "storage")
if (existsSync(localShare)) {
OPENCODE_STORAGE = localShare
}
}
const MESSAGE_STORAGE = join(OPENCODE_STORAGE, "message")
const PART_STORAGE = join(OPENCODE_STORAGE, "part")
@@ -171,3 +182,76 @@ export function countTruncatedResults(sessionID: string): number {
return count
}
export interface AggressiveTruncateResult {
success: boolean
sufficient: boolean
truncatedCount: number
totalBytesRemoved: number
targetBytesToRemove: number
truncatedTools: Array<{ toolName: string; originalSize: number }>
}
export function truncateUntilTargetTokens(
sessionID: string,
currentTokens: number,
maxTokens: number,
targetRatio: number = 0.8,
charsPerToken: number = 4
): AggressiveTruncateResult {
const targetTokens = Math.floor(maxTokens * targetRatio)
const tokensToReduce = currentTokens - targetTokens
const charsToReduce = tokensToReduce * charsPerToken
if (tokensToReduce <= 0) {
return {
success: true,
sufficient: true,
truncatedCount: 0,
totalBytesRemoved: 0,
targetBytesToRemove: 0,
truncatedTools: [],
}
}
const results = findToolResultsBySize(sessionID)
if (results.length === 0) {
return {
success: false,
sufficient: false,
truncatedCount: 0,
totalBytesRemoved: 0,
targetBytesToRemove: charsToReduce,
truncatedTools: [],
}
}
let totalRemoved = 0
let truncatedCount = 0
const truncatedTools: Array<{ toolName: string; originalSize: number }> = []
for (const result of results) {
const truncateResult = truncateToolResult(result.partPath)
if (truncateResult.success) {
truncatedCount++
const removedSize = truncateResult.originalSize ?? result.outputSize
totalRemoved += removedSize
truncatedTools.push({
toolName: truncateResult.toolName ?? result.toolName,
originalSize: removedSize,
})
}
}
const sufficient = totalRemoved >= charsToReduce
return {
success: truncatedCount > 0,
sufficient,
truncatedCount,
totalBytesRemoved: totalRemoved,
targetBytesToRemove: charsToReduce,
truncatedTools,
}
}