diff --git a/README.md b/README.md index a2a9ace..dbdfaac 100644 --- a/README.md +++ b/README.md @@ -231,6 +231,14 @@ As long as you have an openai-like endpoint, it should work. - `/config` - Open configuration panel - `/cost` - Show token usage and costs - `/clear` - Clear conversation history +- `/compact-threshold` - View or set auto-compact threshold ratio (e.g. `/compact-threshold 0.85` or `/compact-threshold 85%`) + + | Value | Effect | Use Case | + |-------|--------|----------| + | 0.80 | Compress earlier, more conservative | Small context models (e.g. deepseek 131k) | + | 0.85 | Balanced | Medium context models | + | 0.90 | Default | Large context models (e.g. Claude 200k) | + - `/init` - Initialize project context ## Multi-Model Intelligent Collaboration diff --git a/README.zh-CN.md b/README.zh-CN.md index 45a7a27..33111d4 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -141,6 +141,14 @@ Kode 同时使用 `~/.kode` 目录(存放额外数据,如内存文件)和 - `/config` - 打开配置面板 - `/cost` - 显示 token 使用量和成本 - `/clear` - 清除对话历史 +- `/compact-threshold` - 查看或设置自动压缩阈值比例(如 `/compact-threshold 0.85` 或 `/compact-threshold 85%`) + + | 值 | 效果 | 适用场景 | + |----|------|----------| + | 0.80 | 更早压缩,更保守 | 小上下文模型(如 deepseek 131k) | + | 0.85 | 平衡 | 中等上下文模型 | + | 0.90 | 默认 | 大上下文模型(如 Claude 200k) | + - `/init` - 初始化项目上下文 ## 多模型智能协同 diff --git a/src/commands/compact-threshold.ts b/src/commands/compact-threshold.ts new file mode 100644 index 0000000..8904ec3 --- /dev/null +++ b/src/commands/compact-threshold.ts @@ -0,0 +1,91 @@ +import chalk from 'chalk' +import type { Command } from '@commands' +import { getGlobalConfig, saveGlobalConfig } from '@utils/config' +import { + AUTO_COMPACT_THRESHOLD_RATIO, + getAutoCompactThresholdRatio, + isValidAutoCompactThresholdRatio, +} from '@utils/session/autoCompactThreshold' + +const HELP_ARGS = new Set(['help', '-h', '--help', '?']) +const RESET_ARGS = new Set(['reset', 'default']) + +function parseThresholdInput(raw: string): number | null { + const trimmed = raw.trim() + if (!trimmed) return null + + let valueText = trimmed + let isPercent = false + + if (valueText.endsWith('%')) { + isPercent = true + valueText = valueText.slice(0, -1).trim() + } + + if (!valueText) return null + + const value = Number(valueText) + if (!Number.isFinite(value)) return null + + let ratio = value + // Treat bare values >1 as percentages (85 => 0.85) while still allowing ratios like 0.85. + if (isPercent || (value > 1 && value <= 100)) { + ratio = value / 100 + } + + return isValidAutoCompactThresholdRatio(ratio) ? ratio : null +} + +function formatRatio(ratio: number): string { + const percent = Math.round(ratio * 100) + return `${ratio} (${percent}%)` +} + +const compactThreshold = { + type: 'local', + name: 'compact-threshold', + description: 'View or set the auto-compact threshold ratio', + isEnabled: true, + isHidden: false, + argumentHint: '[ratio]', + userFacingName() { + return 'compact-threshold' + }, + async call(args) { + const raw = args.trim() + + if (!raw || HELP_ARGS.has(raw)) { + const configured = getGlobalConfig().autoCompactThreshold + const isCustom = isValidAutoCompactThresholdRatio(configured) + const ratio = getAutoCompactThresholdRatio() + const defaultNote = isCustom ? '' : ' (default)' + + return [ + `Auto-compact threshold: ${formatRatio(ratio)}${defaultNote}`, + 'Usage: /compact-threshold 0.85', + 'Tip: You can also use percentages, e.g. /compact-threshold 85%', + ].join('\n') + } + + if (RESET_ARGS.has(raw)) { + const nextConfig = { ...getGlobalConfig() } + delete nextConfig.autoCompactThreshold + saveGlobalConfig(nextConfig) + return `Auto-compact threshold reset to default (${AUTO_COMPACT_THRESHOLD_RATIO}).` + } + + const parsed = parseThresholdInput(raw) + if (!parsed) { + return [ + `Invalid threshold: ${chalk.bold(raw)}`, + 'Provide a ratio greater than 0 and less than 1 (e.g. 0.85 or 85%).', + ].join('\n') + } + + const config = getGlobalConfig() + saveGlobalConfig({ ...config, autoCompactThreshold: parsed }) + return `Auto-compact threshold set to ${formatRatio(parsed)}.` + }, +} satisfies Command + +export default compactThreshold diff --git a/src/commands/index.ts b/src/commands/index.ts index d9f9c94..4b33995 100644 --- a/src/commands/index.ts +++ b/src/commands/index.ts @@ -2,6 +2,7 @@ import React from 'react' import bug from './bug' import clear from './clear' import compact from './compact' +import compactThreshold from './compact-threshold' import config from './config' import cost from './cost' import ctxViz from './ctx-viz' @@ -92,6 +93,7 @@ const COMMANDS = memoize((): Command[] => [ agents, clear, compact, + compactThreshold, config, cost, doctor, diff --git a/src/core/config/schema.ts b/src/core/config/schema.ts index ba0b28b..c593ec4 100644 --- a/src/core/config/schema.ts +++ b/src/core/config/schema.ts @@ -165,6 +165,7 @@ export type GlobalConfig = { } primaryProvider?: ProviderType maxTokens?: number + autoCompactThreshold?: number hasAcknowledgedCostThreshold?: boolean oauthAccount?: AccountInfo proxy?: string @@ -187,6 +188,7 @@ export const GLOBAL_CONFIG_KEYS = [ 'primaryProvider', 'preferredNotifChannel', 'maxTokens', + 'autoCompactThreshold', ] as const export type GlobalConfigKey = (typeof GLOBAL_CONFIG_KEYS)[number] @@ -214,4 +216,3 @@ export type ProjectMcpServerDefinitions = { mcpJsonPath: string mcprcPath: string } - diff --git a/src/utils/session/autoCompactCore.ts b/src/utils/session/autoCompactCore.ts index 8cfb77f..bc6c311 100644 --- a/src/utils/session/autoCompactCore.ts +++ b/src/utils/session/autoCompactCore.ts @@ -13,7 +13,6 @@ import { getModelManager } from '@utils/model' import { debug as debugLogger } from '@utils/log/debugLogger' import { logError } from '@utils/log' import { - AUTO_COMPACT_THRESHOLD_RATIO, calculateAutoCompactThresholds, } from './autoCompactThreshold' @@ -63,11 +62,7 @@ Focus on information essential for continuing the conversation effectively, incl async function calculateThresholds(tokenCount: number) { const contextLimit = await getMainConversationContextLimit() - return calculateAutoCompactThresholds( - tokenCount, - contextLimit, - AUTO_COMPACT_THRESHOLD_RATIO, - ) + return calculateAutoCompactThresholds(tokenCount, contextLimit) } async function shouldAutoCompact(messages: Message[]): Promise { diff --git a/src/utils/session/autoCompactThreshold.ts b/src/utils/session/autoCompactThreshold.ts index 1107fda..4271f1a 100644 --- a/src/utils/session/autoCompactThreshold.ts +++ b/src/utils/session/autoCompactThreshold.ts @@ -1,9 +1,30 @@ +import { getGlobalConfig } from '@utils/config' + export const AUTO_COMPACT_THRESHOLD_RATIO = 0.9 +export function isValidAutoCompactThresholdRatio( + value: unknown, +): value is number { + return ( + typeof value === 'number' && + Number.isFinite(value) && + value > 0 && + value < 1 + ) +} + +export function getAutoCompactThresholdRatio(): number { + const config = getGlobalConfig() + if (isValidAutoCompactThresholdRatio(config.autoCompactThreshold)) { + return config.autoCompactThreshold + } + return AUTO_COMPACT_THRESHOLD_RATIO +} + export function calculateAutoCompactThresholds( tokenCount: number, contextLimit: number, - ratio: number = AUTO_COMPACT_THRESHOLD_RATIO, + ratio: number = getAutoCompactThresholdRatio(), ): { isAboveAutoCompactThreshold: boolean percentUsed: number