From 11df83713e5c37a6d5ff0193efadc0bb7e007521 Mon Sep 17 00:00:00 2001 From: YeonGyu-Kim Date: Fri, 13 Mar 2026 12:36:07 +0900 Subject: [PATCH] refactor(preemptive-compaction): use shared context-limit resolver to eliminate duplicated logic --- .../preemptive-compaction.aws-bedrock.test.ts | 64 +++++++++++++++++++ src/hooks/preemptive-compaction.ts | 56 ++++++---------- 2 files changed, 82 insertions(+), 38 deletions(-) create mode 100644 src/hooks/preemptive-compaction.aws-bedrock.test.ts diff --git a/src/hooks/preemptive-compaction.aws-bedrock.test.ts b/src/hooks/preemptive-compaction.aws-bedrock.test.ts new file mode 100644 index 000000000..9ce47ac8c --- /dev/null +++ b/src/hooks/preemptive-compaction.aws-bedrock.test.ts @@ -0,0 +1,64 @@ +/// + +import { describe, expect, it, mock } from "bun:test" + +import { OhMyOpenCodeConfigSchema } from "../config" + +const { createPreemptiveCompactionHook } = await import("./preemptive-compaction") + +type HookContext = Parameters[0] + +function createMockContext(): HookContext { + return { + client: { + session: { + messages: mock(() => Promise.resolve({ data: [] })), + summarize: mock(() => Promise.resolve({})), + }, + tui: { + showToast: mock(() => Promise.resolve()), + }, + }, + directory: "/tmp/test", + } +} + +describe("preemptive-compaction aws-bedrock-anthropic", () => { + it("triggers compaction for aws-bedrock-anthropic provider when usage exceeds threshold", async () => { + // given + const ctx = createMockContext() + const pluginConfig = OhMyOpenCodeConfigSchema.parse({}) + const hook = createPreemptiveCompactionHook(ctx, pluginConfig) + const sessionID = "ses_aws_bedrock_anthropic_high" + + await hook.event({ + event: { + type: "message.updated", + properties: { + info: { + role: "assistant", + sessionID, + providerID: "aws-bedrock-anthropic", + modelID: "claude-sonnet-4-6", + finish: true, + tokens: { + input: 170000, + output: 1000, + reasoning: 0, + cache: { read: 10000, write: 0 }, + }, + }, + }, + }, + }) + + // when + await hook["tool.execute.after"]( + { tool: "bash", sessionID, callID: "call_aws_bedrock_1" }, + { title: "", output: "test", metadata: null }, + ) + + // then + expect(ctx.client.session.summarize).toHaveBeenCalledTimes(1) + }) +}) diff --git a/src/hooks/preemptive-compaction.ts b/src/hooks/preemptive-compaction.ts index b1b6c3bbf..b75679e40 100644 --- a/src/hooks/preemptive-compaction.ts +++ b/src/hooks/preemptive-compaction.ts @@ -1,23 +1,13 @@ import { log } from "../shared/logger" import type { OhMyOpenCodeConfig } from "../config" +import { + resolveActualContextLimit, + type ContextLimitModelCacheState, +} from "../shared/context-limit-resolver" import { resolveCompactionModel } from "./shared/compaction-model-resolver" -const DEFAULT_ACTUAL_LIMIT = 200_000 const PREEMPTIVE_COMPACTION_TIMEOUT_MS = 120_000 -type ModelCacheStateLike = { - anthropicContext1MEnabled: boolean - modelContextLimitsCache?: Map -} - -function getAnthropicActualLimit(modelCacheState?: ModelCacheStateLike): number { - return (modelCacheState?.anthropicContext1MEnabled ?? false) || - process.env.ANTHROPIC_1M_CONTEXT === "true" || - process.env.VERTEX_ANTHROPIC_1M_CONTEXT === "true" - ? 1_000_000 - : DEFAULT_ACTUAL_LIMIT -} - const PREEMPTIVE_COMPACTION_THRESHOLD = 0.78 interface TokenInfo { @@ -33,7 +23,7 @@ interface CachedCompactionState { tokens: TokenInfo } -function withTimeout( +async function withTimeout( promise: Promise, timeoutMs: number, errorMessage: string, @@ -46,17 +36,13 @@ function withTimeout( }, timeoutMs) }) - return Promise.race([promise, timeoutPromise]).finally(() => { + return await Promise.race([promise, timeoutPromise]).finally(() => { if (timeoutID !== undefined) { clearTimeout(timeoutID) } }) } -function isAnthropicProvider(providerID: string): boolean { - return providerID === "anthropic" || providerID === "google-vertex-anthropic" -} - type PluginInput = { client: { session: { @@ -76,7 +62,7 @@ type PluginInput = { export function createPreemptiveCompactionHook( ctx: PluginInput, pluginConfig: OhMyOpenCodeConfig, - modelCacheState?: ModelCacheStateLike, + modelCacheState?: ContextLimitModelCacheState, ) { const compactionInProgress = new Set() const compactedSessions = new Set() @@ -92,24 +78,18 @@ export function createPreemptiveCompactionHook( const cached = tokenCache.get(sessionID) if (!cached) return - const isAnthropic = isAnthropicProvider(cached.providerID) - const modelSpecificLimit = !isAnthropic - ? modelCacheState?.modelContextLimitsCache?.get(`${cached.providerID}/${cached.modelID}`) - : undefined + const actualLimit = resolveActualContextLimit( + cached.providerID, + cached.modelID, + modelCacheState, + ) - let actualLimit: number - if (isAnthropic) { - actualLimit = getAnthropicActualLimit(modelCacheState) - } else { - if (modelSpecificLimit === undefined) { - log("[preemptive-compaction] Skipping preemptive compaction: unknown context limit for model", { - providerID: cached.providerID, - modelID: cached.modelID, - }) - return - } - - actualLimit = modelSpecificLimit + if (actualLimit === null) { + log("[preemptive-compaction] Skipping preemptive compaction: unknown context limit for model", { + providerID: cached.providerID, + modelID: cached.modelID, + }) + return } const lastTokens = cached.tokens