2025-09-13 09:46:14 +00:00
import { Session } from "."
import { Identifier } from "../id/id"
import { Instance } from "../project/instance"
import { Provider } from "../provider/provider"
import { MessageV2 } from "./message-v2"
import { SystemPrompt } from "./system"
import { Bus } from "../bus"
2025-10-26 19:50:41 +00:00
import z from "zod"
2025-09-13 09:46:14 +00:00
import { SessionPrompt } from "./prompt"
2025-09-13 09:55:04 +00:00
import { Flag } from "../flag/flag"
2025-09-16 08:52:34 +00:00
import { Token } from "../util/token"
2025-12-05 16:48:22 +00:00
import { Config } from "../config/config"
2025-09-16 08:52:34 +00:00
import { Log } from "../util/log"
2025-11-17 15:57:18 +00:00
import { SessionProcessor } from "./processor"
import { fn } from "@/util/fn"
2025-12-09 01:04:37 +00:00
import { Agent } from "@/agent/agent"
2025-09-13 09:46:14 +00:00
export namespace SessionCompaction {
2025-09-16 08:52:34 +00:00
const log = Log . create ( { service : "session.compaction" } )
2025-09-13 09:46:14 +00:00
export const Event = {
Compacted : Bus.event (
"session.compacted" ,
z . object ( {
sessionID : z.string ( ) ,
} ) ,
) ,
}
2025-12-04 02:09:03 +00:00
export function isOverflow ( input : { tokens : MessageV2.Assistant [ "tokens" ] ; model : Provider.Model } ) {
2025-09-13 09:55:04 +00:00
if ( Flag . OPENCODE_DISABLE_AUTOCOMPACT ) return false
2025-09-13 09:59:18 +00:00
const context = input . model . limit . context
if ( context === 0 ) return false
2025-09-13 09:46:14 +00:00
const count = input . tokens . input + input . tokens . cache . read + input . tokens . output
2025-11-08 01:59:02 +00:00
const output = Math . min ( input . model . limit . output , SessionPrompt . OUTPUT_TOKEN_MAX ) || SessionPrompt . OUTPUT_TOKEN_MAX
2025-09-13 09:59:18 +00:00
const usable = context - output
2025-09-13 09:53:03 +00:00
return count > usable
2025-09-13 09:46:14 +00:00
}
2025-09-17 07:07:24 +00:00
export const PRUNE_MINIMUM = 20 _000
export const PRUNE_PROTECT = 40 _000
2025-09-16 08:52:34 +00:00
// 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" )
2025-11-06 18:20:13 +00:00
const msgs = await Session . messages ( { sessionID : input.sessionID } )
2025-09-16 08:52:34 +00:00
let total = 0
let pruned = 0
const toPrune = [ ]
2025-09-17 07:07:24 +00:00
let turns = 0
2025-09-16 08:52:34 +00:00
2025-09-17 07:07:24 +00:00
loop : for ( let msgIndex = msgs . length - 1 ; msgIndex >= 0 ; msgIndex -- ) {
2025-09-16 08:52:34 +00:00
const msg = msgs [ msgIndex ]
2025-09-17 07:07:24 +00:00
if ( msg . info . role === "user" ) turns ++
if ( turns < 2 ) continue
if ( msg . info . role === "assistant" && msg . info . summary ) break loop
2025-09-16 08:52:34 +00:00
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
2025-09-17 07:07:24 +00:00
if ( total > PRUNE_PROTECT ) {
2025-09-16 08:52:34 +00:00
pruned += estimate
toPrune . push ( part )
}
}
}
}
log . info ( "found" , { pruned , total } )
2025-09-17 07:07:24 +00:00
if ( pruned > PRUNE_MINIMUM ) {
2025-09-16 08:52:34 +00:00
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 } )
}
}
2025-11-17 15:57:18 +00:00
export async function process ( input : {
parentID : string
messages : MessageV2.WithParts [ ]
sessionID : string
model : {
providerID : string
modelID : string
}
2025-11-21 08:13:10 +00:00
agent : string
2025-11-17 15:57:18 +00:00
abort : AbortSignal
2025-11-25 18:10:56 +00:00
auto : boolean
2025-11-17 15:57:18 +00:00
} ) {
const model = await Provider . getModel ( input . model . providerID , input . model . modelID )
2025-11-22 16:32:49 +00:00
const system = [ . . . SystemPrompt . compaction ( model . providerID ) ]
2025-09-13 09:46:14 +00:00
const msg = ( await Session . updateMessage ( {
id : Identifier.ascending ( "message" ) ,
role : "assistant" ,
2025-11-17 15:57:18 +00:00
parentID : input.parentID ,
2025-09-13 09:46:14 +00:00
sessionID : input.sessionID ,
2025-11-21 08:13:10 +00:00
mode : input.agent ,
2025-11-17 15:57:18 +00:00
summary : true ,
2025-09-13 09:46:14 +00:00
path : {
cwd : Instance.directory ,
root : Instance.worktree ,
} ,
cost : 0 ,
tokens : {
output : 0 ,
input : 0 ,
reasoning : 0 ,
cache : { read : 0 , write : 0 } ,
} ,
2025-11-17 15:57:18 +00:00
modelID : input.model.modelID ,
2025-09-13 09:46:14 +00:00
providerID : model.providerID ,
time : {
created : Date.now ( ) ,
} ,
} ) ) as MessageV2 . Assistant
2025-11-17 15:57:18 +00:00
const processor = SessionProcessor . create ( {
assistantMessage : msg ,
2025-10-15 18:44:16 +00:00
sessionID : input.sessionID ,
2025-12-04 02:09:03 +00:00
model : model ,
2025-11-17 15:57:18 +00:00
abort : input.abort ,
} )
2025-12-09 01:04:37 +00:00
const agent = await Agent . get ( input . agent )
2025-12-04 02:09:03 +00:00
const result = await processor . process ( {
2025-12-09 01:04:37 +00:00
requestID : input.parentID ,
agent ,
abort : input.abort ,
sessionID : input.sessionID ,
tools : { } ,
system ,
2025-12-04 02:09:03 +00:00
messages : [
2025-12-09 01:04:37 +00:00
. . . MessageV2 . toModelMessage ( input . messages ) ,
2025-12-04 02:09:03 +00:00
{
role : "user" ,
content : [
2025-11-18 18:09:50 +00:00
{
2025-12-04 02:09:03 +00:00
type : "text" ,
text : "Summarize our conversation above. This summary will be the only context available when the conversation continues, so preserve critical information including: what was accomplished, current work in progress, files involved, next steps, and any key user requests or constraints. Be concise but detailed enough that work can continue seamlessly." ,
2025-11-18 18:09:50 +00:00
} ,
] ,
2025-12-04 02:09:03 +00:00
} ,
] ,
2025-12-09 01:04:37 +00:00
model ,
2025-12-04 02:09:03 +00:00
} )
2025-12-09 01:04:37 +00:00
2025-11-25 18:10:56 +00:00
if ( result === "continue" && input . auto ) {
2025-11-17 15:57:18 +00:00
const continueMsg = await Session . updateMessage ( {
id : Identifier.ascending ( "message" ) ,
role : "user" ,
sessionID : input.sessionID ,
time : {
created : Date.now ( ) ,
} ,
2025-11-21 08:13:10 +00:00
agent : input.agent ,
2025-11-17 15:57:18 +00:00
model : input.model ,
2025-10-22 23:31:36 +00:00
} )
2025-11-17 15:57:18 +00:00
await Session . updatePart ( {
id : Identifier.ascending ( "part" ) ,
messageID : continueMsg.id ,
2025-10-18 16:49:29 +00:00
sessionID : input.sessionID ,
2025-11-17 15:57:18 +00:00
type : "text" ,
synthetic : true ,
text : "Continue if you have next steps" ,
time : {
start : Date.now ( ) ,
end : Date.now ( ) ,
} ,
2025-10-18 16:49:29 +00:00
} )
}
2025-11-18 18:09:50 +00:00
if ( processor . message . error ) return "stop"
2025-11-25 19:47:10 +00:00
Bus . publish ( Event . Compacted , { sessionID : input.sessionID } )
2025-11-17 15:57:18 +00:00
return "continue"
2025-09-13 09:46:14 +00:00
}
2025-11-17 15:57:18 +00:00
export const create = fn (
z . object ( {
sessionID : Identifier.schema ( "session" ) ,
2025-11-21 08:13:10 +00:00
agent : z.string ( ) ,
2025-11-17 15:57:18 +00:00
model : z.object ( {
providerID : z.string ( ) ,
modelID : z.string ( ) ,
} ) ,
2025-11-25 18:10:56 +00:00
auto : z.boolean ( ) ,
2025-11-17 15:57:18 +00:00
} ) ,
async ( input ) = > {
const msg = await Session . updateMessage ( {
id : Identifier.ascending ( "message" ) ,
role : "user" ,
model : input.model ,
sessionID : input.sessionID ,
2025-11-21 08:13:10 +00:00
agent : input.agent ,
2025-11-17 15:57:18 +00:00
time : {
created : Date.now ( ) ,
} ,
} )
await Session . updatePart ( {
id : Identifier.ascending ( "part" ) ,
messageID : msg.id ,
sessionID : msg.sessionID ,
type : "compaction" ,
2025-11-25 18:10:56 +00:00
auto : input.auto ,
2025-11-17 15:57:18 +00:00
} )
} ,
)
2025-09-13 09:46:14 +00:00
}