-
Notifications
You must be signed in to change notification settings - Fork 5.2k
Expand file tree
/
Copy pathpreemptive-compaction-trigger.ts
More file actions
141 lines (122 loc) · 4.21 KB
/
Copy pathpreemptive-compaction-trigger.ts
File metadata and controls
141 lines (122 loc) · 4.21 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
import { CompactionConfigSchema } from "../config/schema/compaction"
import type { OhMyOpenCodeConfig } from "../config"
import {
resolveActualContextLimit,
type ContextLimitModelCacheState,
} from "../shared/context-limit-resolver"
import { log } from "../shared/logger"
import { resolveCompactionModel } from "./shared/compaction-model-resolver"
import type {
CachedCompactionState,
PreemptiveCompactionContext,
} from "./preemptive-compaction-types"
const PREEMPTIVE_COMPACTION_TIMEOUT_MS = 60_000
declare function setTimeout(handler: () => void, timeout?: number): unknown
declare function clearTimeout(timeoutID: unknown): void
async function withTimeout<TValue>(
promise: Promise<TValue>,
timeoutMs: number,
errorMessage: string,
): Promise<TValue> {
let timeoutID: unknown
const timeoutPromise = new Promise<never>((_, reject) => {
timeoutID = setTimeout(() => {
reject(new Error(errorMessage))
}, timeoutMs)
})
return await Promise.race([promise, timeoutPromise]).finally(() => {
clearTimeout(timeoutID)
})
}
export async function runPreemptiveCompactionIfNeeded(args: {
ctx: PreemptiveCompactionContext
pluginConfig: OhMyOpenCodeConfig
modelCacheState?: ContextLimitModelCacheState
sessionID: string
tokenCache: Map<string, CachedCompactionState>
compactionInProgress: Set<string>
compactedSessions: Set<string>
lastCompactionTime: Map<string, number>
toolName?: string
}): Promise<void> {
const {
ctx,
pluginConfig,
modelCacheState,
sessionID,
tokenCache,
compactionInProgress,
compactedSessions,
lastCompactionTime,
toolName,
} = args
const compactionConfig = CompactionConfigSchema.parse(pluginConfig.compaction ?? {})
if (!compactionConfig.enabled) return
if (compactedSessions.has(sessionID) || compactionInProgress.has(sessionID)) return
const lastTime = lastCompactionTime.get(sessionID)
if (lastTime && Date.now() - lastTime < compactionConfig.cooldown_ms) return
if (toolName === "background_output") return
const cached = tokenCache.get(sessionID)
if (!cached) return
const actualLimit = resolveActualContextLimit(
cached.providerID,
cached.modelID,
modelCacheState,
)
if (actualLimit === null) {
log("[preemptive-compaction] Skipping preemptive compaction: unknown context limit for model", {
providerID: cached.providerID,
modelID: cached.modelID,
})
return
}
const totalInputTokens = (cached.tokens.input ?? 0) + (cached.tokens.cache?.read ?? 0)
const usageRatio = totalInputTokens / actualLimit
if (usageRatio < compactionConfig.preemptive_threshold || !cached.modelID) return
compactionInProgress.add(sessionID)
lastCompactionTime.set(sessionID, Date.now())
try {
const { providerID: targetProviderID, modelID: targetModelID } = resolveCompactionModel(
pluginConfig,
sessionID,
cached.providerID,
cached.modelID,
)
await withTimeout(
ctx.client.session.summarize({
path: { id: sessionID },
body: { providerID: targetProviderID, modelID: targetModelID, auto: true },
query: { directory: ctx.directory },
}),
PREEMPTIVE_COMPACTION_TIMEOUT_MS,
`Compaction summarize timed out after ${PREEMPTIVE_COMPACTION_TIMEOUT_MS}ms`,
)
compactedSessions.add(sessionID)
} catch (error) {
const errorMessage = String(error)
log("[preemptive-compaction] Compaction failed", {
sessionID,
providerID: cached.providerID,
modelID: cached.modelID,
error: errorMessage,
})
ctx.client.tui.showToast({
body: {
title: "Preemptive compaction failed",
message: `Context window is above ${Math.round(compactionConfig.preemptive_threshold * 100)}% and auto-compaction could not run. The session may grow large. Error: ${errorMessage}`,
variant: "warning",
duration: 10000,
},
}).catch((toastError: unknown) => {
const toastErrorMessage = String(toastError)
log("[preemptive-compaction] Failed to show toast", {
sessionID,
toastError: toastErrorMessage,
})
if (toastError instanceof Error) return
})
if (error instanceof Error) return
} finally {
compactionInProgress.delete(sessionID)
}
}