feat: configure auto compaction threshold

This commit is contained in:
marius-kilocode
2026-05-13 18:09:54 +02:00
parent cdbae6c24c
commit 4860e654ca
30 changed files with 263 additions and 28 deletions
+7
View File
@@ -115,6 +115,7 @@ const LogLevelRef = Schema.Literals(["DEBUG", "INFO", "WARN", "ERROR"]).annotate
identifier: "LogLevel",
description: "Log level",
})
const Percent = Schema.Number.check(Schema.isGreaterThan(0), Schema.isLessThanOrEqualTo(100)) // kilocode_change
// kilocode_change - KiloIndexingConfig is still a Zod schema; bridge via ZodOverride
const IndexingRef = Schema.Any.annotate({ [ZodOverride]: KiloIndexingConfig })
@@ -277,6 +278,12 @@ export const Info = Schema.Struct({
auto: Schema.optional(Schema.Boolean).annotate({
description: "Enable automatic compaction when context is full (default: true)",
}),
// kilocode_change start
threshold_percent: Schema.optional(Schema.NullOr(Percent)).annotate({
description:
"Percentage of the model input/context window that triggers automatic compaction. The reserved safety buffer still applies if it would compact sooner.",
}),
// kilocode_change end
prune: Schema.optional(Schema.Boolean).annotate({
description: "Enable pruning of old tool outputs (default: true)",
}),
@@ -0,0 +1,15 @@
import type { Config } from "@/config/config"
import type { Provider } from "@/provider/provider"
export namespace KiloSessionOverflow {
export function limit(input: { cfg: Config.Info; model: Provider.Model; usable: number }) {
const percent = input.cfg.compaction?.threshold_percent
if (typeof percent !== "number") return input.usable
const context = input.model.limit.input || input.model.limit.context
if (context === 0) return input.usable
const cap = Math.floor(context * (percent / 100))
return Math.min(input.usable, cap)
}
}
+5 -1
View File
@@ -2,6 +2,7 @@ import type { Config } from "@/config/config"
import type { Provider } from "@/provider/provider"
import { ProviderTransform } from "@/provider/transform"
import type { MessageV2 } from "./message-v2"
import { KiloSessionOverflow } from "@/kilocode/session/overflow" // kilocode_change
const COMPACTION_BUFFER = 20_000
@@ -22,5 +23,8 @@ export function isOverflow(input: { cfg: Config.Info; tokens: MessageV2.Assistan
const count =
input.tokens.total || input.tokens.input + input.tokens.output + input.tokens.cache.read + input.tokens.cache.write
return count >= usable(input)
// kilocode_change start
const cap = KiloSessionOverflow.limit({ cfg: input.cfg, model: input.model, usable: usable(input) })
return count >= cap
// kilocode_change end
}
@@ -0,0 +1,78 @@
import { describe, expect, test } from "bun:test"
import { Config } from "@/config/config"
import type { Provider } from "@/provider/provider"
import type { MessageV2 } from "@/session/message-v2"
import { isOverflow } from "@/session/overflow"
function cfg(compaction?: Config.Info["compaction"]) {
return Config.Info.zod.parse({ compaction })
}
function model(opts: { context: number; output: number; input?: number }): Provider.Model {
return {
id: "test-model",
providerID: "test",
name: "Test",
limit: {
context: opts.context,
input: opts.input,
output: opts.output,
},
cost: { input: 0, output: 0, cache: { read: 0, write: 0 } },
capabilities: {
toolcall: true,
attachment: false,
reasoning: false,
temperature: true,
input: { text: true, image: false, audio: false, video: false },
output: { text: true, image: false, audio: false, video: false },
},
api: { npm: "@ai-sdk/anthropic" },
options: {},
} as Provider.Model
}
function tokens(count: number): MessageV2.Assistant["tokens"] {
return { input: count, output: 0, reasoning: 0, cache: { read: 0, write: 0 } }
}
describe("Kilo auto-compaction threshold", () => {
test("triggers at the configured context percentage", () => {
const conf = cfg({ threshold_percent: 75 })
const mdl = model({ context: 200_000, output: 32_000 })
expect(isOverflow({ cfg: conf, model: mdl, tokens: tokens(149_999) })).toBe(false)
expect(isOverflow({ cfg: conf, model: mdl, tokens: tokens(150_000) })).toBe(true)
})
test("keeps the reserved safety trigger when it is lower", () => {
const conf = cfg({ threshold_percent: 95 })
const mdl = model({ context: 200_000, output: 32_000 })
expect(isOverflow({ cfg: conf, model: mdl, tokens: tokens(167_999) })).toBe(false)
expect(isOverflow({ cfg: conf, model: mdl, tokens: tokens(168_000) })).toBe(true)
})
test("uses a model input limit when present", () => {
const conf = cfg({ threshold_percent: 75 })
const mdl = model({ context: 400_000, input: 200_000, output: 32_000 })
expect(isOverflow({ cfg: conf, model: mdl, tokens: tokens(149_999) })).toBe(false)
expect(isOverflow({ cfg: conf, model: mdl, tokens: tokens(150_000) })).toBe(true)
})
test("ignores a cleared threshold", () => {
const conf = cfg({ threshold_percent: null })
const mdl = model({ context: 200_000, output: 32_000 })
expect(isOverflow({ cfg: conf, model: mdl, tokens: tokens(150_000) })).toBe(false)
expect(isOverflow({ cfg: conf, model: mdl, tokens: tokens(168_000) })).toBe(true)
})
test("still respects disabled auto-compaction", () => {
const conf = cfg({ auto: false, threshold_percent: 75 })
const mdl = model({ context: 200_000, output: 32_000 })
expect(isOverflow({ cfg: conf, model: mdl, tokens: tokens(150_000) })).toBe(false)
})
})