enable session pruning and allow disabling with OPENCODE_DISABLE_PRUNE

This commit is contained in:
Dax Raad
2025-09-16 04:52:34 -04:00
parent 52fcdcc37b
commit 3aeac02bf1
4 changed files with 46 additions and 30 deletions

View File

@@ -3,6 +3,7 @@ export namespace Flag {
export const OPENCODE_CONFIG = process.env["OPENCODE_CONFIG"] export const OPENCODE_CONFIG = process.env["OPENCODE_CONFIG"]
export const OPENCODE_CONFIG_CONTENT = process.env["OPENCODE_CONFIG_CONTENT"] export const OPENCODE_CONFIG_CONTENT = process.env["OPENCODE_CONFIG_CONTENT"]
export const OPENCODE_DISABLE_AUTOUPDATE = truthy("OPENCODE_DISABLE_AUTOUPDATE") export const OPENCODE_DISABLE_AUTOUPDATE = truthy("OPENCODE_DISABLE_AUTOUPDATE")
export const OPENCODE_DISABLE_PRUNE = truthy("OPENCODE_DISABLE_PRUNE")
export const OPENCODE_PERMISSION = process.env["OPENCODE_PERMISSION"] export const OPENCODE_PERMISSION = process.env["OPENCODE_PERMISSION"]
export const OPENCODE_DISABLE_DEFAULT_PLUGINS = truthy("OPENCODE_DISABLE_DEFAULT_PLUGINS") export const OPENCODE_DISABLE_DEFAULT_PLUGINS = truthy("OPENCODE_DISABLE_DEFAULT_PLUGINS")
export const OPENCODE_DISABLE_LSP_DOWNLOAD = truthy("OPENCODE_DISABLE_LSP_DOWNLOAD") export const OPENCODE_DISABLE_LSP_DOWNLOAD = truthy("OPENCODE_DISABLE_LSP_DOWNLOAD")

View File

@@ -11,8 +11,12 @@ import z from "zod/v4"
import type { ModelsDev } from "../provider/models" import type { ModelsDev } from "../provider/models"
import { SessionPrompt } from "./prompt" import { SessionPrompt } from "./prompt"
import { Flag } from "../flag/flag" import { Flag } from "../flag/flag"
import { Token } from "../util/token"
import { Log } from "../util/log"
export namespace SessionCompaction { export namespace SessionCompaction {
const log = Log.create({ service: "session.compaction" })
export const Event = { export const Event = {
Compacted: Bus.event( Compacted: Bus.event(
"session.compacted", "session.compacted",
@@ -32,6 +36,46 @@ export namespace SessionCompaction {
return count > usable return count > usable
} }
// goes backwards through parts until there are 40_000 tokens worth of tool
// calls. then erases output of previous tool calls. idea is to throw away old
// tool calls that are no longer relevant.
export async function prune(input: { sessionID: string }) {
if (Flag.OPENCODE_DISABLE_PRUNE) return
log.info("pruning")
const msgs = await Session.messages(input.sessionID)
let total = 0
let pruned = 0
const toPrune = []
loop: for (let msgIndex = msgs.length - 2; msgIndex >= 0; msgIndex--) {
const msg = msgs[msgIndex]
if (msg.info.role === "assistant" && msg.info.summary) return
for (let partIndex = msg.parts.length - 1; partIndex >= 0; partIndex--) {
const part = msg.parts[partIndex]
if (part.type === "tool")
if (part.state.status === "completed") {
if (part.state.time.compacted) break loop
const estimate = Token.estimate(part.state.output)
total += estimate
if (total > 40_000) {
pruned += estimate
toPrune.push(part)
}
}
}
}
log.info("found", { pruned, total })
if (pruned > 20_000) {
for (const part of toPrune) {
if (part.state.status === "completed") {
part.state.time.compacted = Date.now()
await Session.updatePart(part)
}
}
log.info("pruned", { count: toPrune.length })
}
}
export async function run(input: { sessionID: string; providerID: string; modelID: string }) { export async function run(input: { sessionID: string; providerID: string; modelID: string }) {
await Session.update(input.sessionID, (draft) => { await Session.update(input.sessionID, (draft) => {
draft.time.compacting = Date.now() draft.time.compacting = Date.now()

View File

@@ -16,7 +16,6 @@ import { Log } from "../util/log"
import { MessageV2 } from "./message-v2" import { MessageV2 } from "./message-v2"
import { Project } from "../project/project" import { Project } from "../project/project"
import { Instance } from "../project/instance" import { Instance } from "../project/instance"
import { Token } from "../util/token"
import { SessionPrompt } from "./prompt" import { SessionPrompt } from "./prompt"
export namespace Session { export namespace Session {
@@ -293,34 +292,6 @@ export namespace Session {
return part return part
} }
// goes backwards through parts until there are 40_000 tokens worth of tool
// calls. then erases output of previous tool calls. idea is to throw away old
// tool calls that are no longer relevant.
export async function prune(input: { sessionID: string }) {
const msgs = await messages(input.sessionID)
let sum = 0
for (let msgIndex = msgs.length - 2; msgIndex >= 0; msgIndex--) {
const msg = msgs[msgIndex]
if (msg.info.role === "assistant" && msg.info.summary) return
for (let partIndex = msg.parts.length - 1; partIndex >= 0; partIndex--) {
const part = msg.parts[partIndex]
if (part.type === "tool")
if (part.state.status === "completed") {
if (part.state.time.compacted) return
sum += Token.estimate(part.state.output)
if (sum > 40_000) {
log.info("pruning", {
sum,
id: part.id,
})
part.state.time.compacted = Date.now()
await updatePart(part)
}
}
}
}
}
export function getUsage(model: ModelsDev.Model, usage: LanguageModelUsage, metadata?: ProviderMetadata) { export function getUsage(model: ModelsDev.Model, usage: LanguageModelUsage, metadata?: ProviderMetadata) {
const tokens = { const tokens = {
input: usage.inputTokens ?? 0, input: usage.inputTokens ?? 0,

View File

@@ -321,7 +321,7 @@ export namespace SessionPrompt {
item.callback(result) item.callback(result)
} }
state().queued.delete(input.sessionID) state().queued.delete(input.sessionID)
// Session.prune(input) SessionCompaction.prune(input)
return result return result
} }
} }