diff --git a/packages/opencode/src/session/overflow.ts b/packages/opencode/src/session/overflow.ts index 3374d1c9f8d4..3e3d25012654 100644 --- a/packages/opencode/src/session/overflow.ts +++ b/packages/opencode/src/session/overflow.ts @@ -5,15 +5,12 @@ import type { Provider } from "@/provider/provider" import { ProviderTransform } from "@/provider/transform" import type { MessageV2 } from "./message-v2" -const COMPACTION_BUFFER = 20_000 - export function usable(input: { cfg: ConfigV1.Info; model: Provider.Model; outputTokenMax?: number }) { const context = input.model.limit.context if (context === 0) return 0 const reserved = - input.cfg.compaction?.reserved ?? - Math.min(COMPACTION_BUFFER, ProviderTransform.maxOutputTokens(input.model, input.outputTokenMax)) + input.cfg.compaction?.reserved ?? ProviderTransform.maxOutputTokens(input.model, input.outputTokenMax) return input.model.limit.input ? Math.max(0, input.model.limit.input - reserved) : Math.max(0, context - ProviderTransform.maxOutputTokens(input.model, input.outputTokenMax)) diff --git a/packages/opencode/test/session/compaction.test.ts b/packages/opencode/test/session/compaction.test.ts index 63276bfe197d..dfa7906a0a9d 100644 --- a/packages/opencode/test/session/compaction.test.ts +++ b/packages/opencode/test/session/compaction.test.ts @@ -454,11 +454,10 @@ describe("session.compaction.isOverflow", () => { ), ) - // ─── Bug reproduction tests ─────────────────────────────────────────── - // These tests demonstrate that when limit.input is set, isOverflow() - // does not subtract any headroom for the next model response. This means - // compaction only triggers AFTER we've already consumed the full input - // budget, leaving zero room for the next API call's output tokens. + // ─── Output headroom regression tests ───────────────────────────────── + // When limit.input is set, isOverflow() still reserves headroom for the next + // model response. This keeps compaction from waiting until the full input + // budget is consumed, which would leave no room for the next API call's output. // // Compare: without limit.input, usable = context - output (reserves space). // With limit.input, usable = limit.input (reserves nothing). @@ -467,7 +466,7 @@ describe("session.compaction.isOverflow", () => { // Open PRs: #6875, #12924 it.live( - "BUG: no headroom when limit.input is set — compaction should trigger near boundary but does not", + "reserves output headroom when limit.input is set", provideTmpdirInstance(() => Effect.gen(function* () { const compact = yield* SessionCompaction.Service @@ -493,7 +492,7 @@ describe("session.compaction.isOverflow", () => { ) it.live( - "BUG: without limit.input, same token count correctly triggers compaction", + "without limit.input, same token count correctly triggers compaction", provideTmpdirInstance(() => Effect.gen(function* () { const compact = yield* SessionCompaction.Service @@ -513,7 +512,7 @@ describe("session.compaction.isOverflow", () => { ) it.live( - "BUG: asymmetry — limit.input model allows 30K more usage before compaction than equivalent model without it", + "limit.input model agrees with equivalent context-limited model", provideTmpdirInstance(() => Effect.gen(function* () { const compact = yield* SessionCompaction.Service