Skip to content

Commit a9459c0

Browse files
committed
Improve preemptive compaction with Claude model filtering and configurable context limits
- Limit preemptive compaction to Claude models only (opus, sonnet, haiku pattern) - Add support for detecting `anthropic-beta: context-1m-*` header to use 1M context limit for Sonnet models - Add `getModelLimit` callback to read model limits from OpenCode config (`provider.*.models.*.limit.context`) - Remove hardcoded MODEL_CONTEXT_LIMITS and replace with pattern-based model detection - Cache model context limits from config at startup for performance This enables flexible per-model context limit configuration without hardcoding limits in the plugin. Generated with assistance of OhMyOpenCode
1 parent 12ccb7f commit a9459c0

File tree

2 files changed

+57
-28
lines changed

2 files changed

+57
-28
lines changed

src/hooks/preemptive-compaction/index.ts

Lines changed: 17 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,12 @@ export interface SummarizeContext {
1818

1919
export type BeforeSummarizeCallback = (ctx: SummarizeContext) => Promise<void> | void
2020

21+
export type GetModelLimitCallback = (providerID: string, modelID: string) => number | undefined
22+
2123
export interface PreemptiveCompactionOptions {
2224
experimental?: ExperimentalConfig
2325
onBeforeSummarize?: BeforeSummarizeCallback
26+
getModelLimit?: GetModelLimitCallback
2427
}
2528

2629
interface MessageInfo {
@@ -38,33 +41,11 @@ interface MessageWrapper {
3841
info: MessageInfo
3942
}
4043

41-
const MODEL_CONTEXT_LIMITS: Record<string, number> = {
42-
"claude-opus-4": 200_000,
43-
"claude-sonnet-4": 200_000,
44-
"claude-haiku-4": 200_000,
45-
"gpt-4o": 128_000,
46-
"gpt-4o-mini": 128_000,
47-
"gpt-4-turbo": 128_000,
48-
"gpt-4": 8_192,
49-
"gpt-5": 1_000_000,
50-
"o1": 200_000,
51-
"o1-mini": 128_000,
52-
"o1-preview": 128_000,
53-
"o3": 200_000,
54-
"o3-mini": 200_000,
55-
"gemini-2.0-flash": 1_000_000,
56-
"gemini-2.5-flash": 1_000_000,
57-
"gemini-2.5-pro": 2_000_000,
58-
"gemini-3-pro": 2_000_000,
59-
}
44+
const CLAUDE_MODEL_PATTERN = /claude-(opus|sonnet|haiku)/i
45+
const CLAUDE_DEFAULT_CONTEXT_LIMIT = 200_000
6046

61-
function getContextLimit(modelID: string): number {
62-
for (const [key, limit] of Object.entries(MODEL_CONTEXT_LIMITS)) {
63-
if (modelID.includes(key)) {
64-
return limit
65-
}
66-
}
67-
return 200_000
47+
function isSupportedModel(modelID: string): boolean {
48+
return CLAUDE_MODEL_PATTERN.test(modelID)
6849
}
6950

7051
function createState(): PreemptiveCompactionState {
@@ -80,6 +61,7 @@ export function createPreemptiveCompactionHook(
8061
) {
8162
const experimental = options?.experimental
8263
const onBeforeSummarize = options?.onBeforeSummarize
64+
const getModelLimit = options?.getModelLimit
8365
const enabled = experimental?.preemptive_compaction !== false
8466
const threshold = experimental?.preemptive_compaction_threshold ?? DEFAULT_THRESHOLD
8567

@@ -104,7 +86,15 @@ export function createPreemptiveCompactionHook(
10486
if (!tokens) return
10587

10688
const modelID = lastAssistant.modelID ?? ""
107-
const contextLimit = getContextLimit(modelID)
89+
const providerID = lastAssistant.providerID ?? ""
90+
91+
if (!isSupportedModel(modelID)) {
92+
log("[preemptive-compaction] skipping unsupported model", { modelID })
93+
return
94+
}
95+
96+
const configLimit = getModelLimit?.(providerID, modelID)
97+
const contextLimit = configLimit ?? CLAUDE_DEFAULT_CONTEXT_LIMIT
10898
const totalUsed = tokens.input + tokens.cache.read + tokens.output
10999

110100
if (totalUsed < MIN_TOKENS_FOR_COMPACTION) return
@@ -124,7 +114,6 @@ export function createPreemptiveCompactionHook(
124114
state.compactionInProgress.add(sessionID)
125115
state.lastCompactionTime.set(sessionID, Date.now())
126116

127-
const providerID = lastAssistant.providerID
128117
if (!providerID || !modelID) {
129118
state.compactionInProgress.delete(sessionID)
130119
return

src/index.ts

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,20 @@ const OhMyOpenCodePlugin: Plugin = async (ctx) => {
213213
const disabledHooks = new Set(pluginConfig.disabled_hooks ?? []);
214214
const isHookEnabled = (hookName: HookName) => !disabledHooks.has(hookName);
215215

216+
const modelContextLimitsCache = new Map<string, number>();
217+
let anthropicContext1MEnabled = false;
218+
219+
const getModelLimit = (providerID: string, modelID: string): number | undefined => {
220+
const key = `${providerID}/${modelID}`;
221+
const cached = modelContextLimitsCache.get(key);
222+
if (cached) return cached;
223+
224+
if (providerID === "anthropic" && anthropicContext1MEnabled && modelID.includes("sonnet")) {
225+
return 1_000_000;
226+
}
227+
return undefined;
228+
};
229+
216230
const todoContinuationEnforcer = isHookEnabled("todo-continuation-enforcer")
217231
? createTodoContinuationEnforcer(ctx)
218232
: null;
@@ -261,6 +275,7 @@ const OhMyOpenCodePlugin: Plugin = async (ctx) => {
261275
const preemptiveCompaction = createPreemptiveCompactionHook(ctx, {
262276
experimental: pluginConfig.experimental,
263277
onBeforeSummarize: compactionContextInjector,
278+
getModelLimit,
264279
});
265280
const rulesInjector = isHookEnabled("rules-injector")
266281
? createRulesInjectorHook(ctx)
@@ -329,6 +344,31 @@ const OhMyOpenCodePlugin: Plugin = async (ctx) => {
329344
},
330345

331346
config: async (config) => {
347+
type ProviderConfig = {
348+
options?: { headers?: Record<string, string> }
349+
models?: Record<string, { limit?: { context?: number } }>
350+
}
351+
const providers = config.provider as Record<string, ProviderConfig> | undefined;
352+
353+
const anthropicBeta = providers?.anthropic?.options?.headers?.["anthropic-beta"];
354+
anthropicContext1MEnabled = anthropicBeta?.includes("context-1m") ?? false;
355+
356+
if (providers) {
357+
for (const [providerID, providerConfig] of Object.entries(providers)) {
358+
const models = providerConfig?.models;
359+
if (models) {
360+
for (const [modelID, modelConfig] of Object.entries(models)) {
361+
const contextLimit = modelConfig?.limit?.context;
362+
if (contextLimit) {
363+
modelContextLimitsCache.set(`${providerID}/${modelID}`, contextLimit);
364+
}
365+
}
366+
}
367+
368+
369+
}
370+
}
371+
332372
const builtinAgents = createBuiltinAgents(
333373
pluginConfig.disabled_agents,
334374
pluginConfig.agents,

0 commit comments

Comments
 (0)