Files
oh-my-opencode/src/hooks/preemptive-compaction.ts
T

104 lines
3.1 KiB
TypeScript

import type { OhMyOpenCodeConfig } from "../config"
import type { ContextLimitModelCacheState } from "../shared/context-limit-resolver"
import { createPostCompactionDegradationMonitor } from "./preemptive-compaction-degradation-monitor"
import { runPreemptiveCompactionIfNeeded } from "./preemptive-compaction-trigger"
import type {
CachedCompactionState,
PreemptiveCompactionContext,
TokenInfo,
} from "./preemptive-compaction-types"
export function createPreemptiveCompactionHook(
ctx: PreemptiveCompactionContext,
pluginConfig: OhMyOpenCodeConfig,
modelCacheState?: ContextLimitModelCacheState,
) {
const compactionInProgress = new Set<string>()
const compactedSessions = new Set<string>()
const lastCompactionTime = new Map<string, number>()
const tokenCache = new Map<string, CachedCompactionState>()
const postCompactionMonitor = createPostCompactionDegradationMonitor({
client: ctx.client,
directory: ctx.directory,
pluginConfig,
tokenCache,
compactionInProgress,
})
const toolExecuteAfter = async (
input: { tool: string; sessionID: string; callID: string },
_output: { title: string; output: string; metadata: unknown }
) => {
await runPreemptiveCompactionIfNeeded({
ctx,
pluginConfig,
modelCacheState,
sessionID: input.sessionID,
tokenCache,
compactionInProgress,
compactedSessions,
lastCompactionTime,
})
}
const eventHandler = async ({ event }: { event: { type: string; properties?: unknown } }) => {
const props = event.properties as Record<string, unknown> | undefined
if (event.type === "session.deleted") {
const sessionID = (props?.info as { id?: string } | undefined)?.id
if (sessionID) {
compactionInProgress.delete(sessionID)
compactedSessions.delete(sessionID)
lastCompactionTime.delete(sessionID)
tokenCache.delete(sessionID)
postCompactionMonitor.clear(sessionID)
}
return
}
if (event.type === "session.compacted") {
const sessionID = (props?.sessionID as string | undefined)
?? (props?.info as { id?: string } | undefined)?.id
if (sessionID) {
postCompactionMonitor.onSessionCompacted(sessionID)
}
return
}
if (event.type === "message.updated") {
const info = props?.info as {
id?: string
role?: string
sessionID?: string
providerID?: string
modelID?: string
finish?: boolean
tokens?: TokenInfo
} | undefined
if (!info || info.role !== "assistant" || !info.finish || !info.sessionID) return
if (info.providerID && info.tokens) {
tokenCache.set(info.sessionID, {
providerID: info.providerID,
modelID: info.modelID ?? "",
tokens: info.tokens,
})
}
compactedSessions.delete(info.sessionID)
await postCompactionMonitor.onAssistantMessageUpdated({
sessionID: info.sessionID,
id: info.id,
})
}
}
return {
"tool.execute.after": toolExecuteAfter,
event: eventHandler,
}
}