From 17707ee8353b3e2ffe34a7a8df82255db476d1a2 Mon Sep 17 00:00:00 2001 From: YeonGyu-Kim Date: Sun, 8 Mar 2026 02:10:40 +0900 Subject: [PATCH] fix(context-window-monitor): use model-specific context limits Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode) Co-authored-by: Sisyphus --- ...indow-monitor.model-context-limits.test.ts | 90 +++++++++++++++++++ src/hooks/context-window-monitor.ts | 16 +++- 2 files changed, 103 insertions(+), 3 deletions(-) create mode 100644 src/hooks/context-window-monitor.model-context-limits.test.ts diff --git a/src/hooks/context-window-monitor.model-context-limits.test.ts b/src/hooks/context-window-monitor.model-context-limits.test.ts new file mode 100644 index 000000000..2b2c9cc61 --- /dev/null +++ b/src/hooks/context-window-monitor.model-context-limits.test.ts @@ -0,0 +1,90 @@ +/// + +import { describe, expect, it } from "bun:test" +import { createContextWindowMonitorHook } from "./context-window-monitor" + +function createOutput() { + return { title: "", output: "original", metadata: null } +} + +describe("context-window-monitor modelContextLimitsCache", () => { + it("does not append reminder below cached non-anthropic threshold", async () => { + // given + const modelContextLimitsCache = new Map() + modelContextLimitsCache.set("opencode/kimi-k2.5-free", 262144) + + const hook = createContextWindowMonitorHook({} as never, { + anthropicContext1MEnabled: false, + modelContextLimitsCache, + }) + const sessionID = "ses_non_anthropic_below_threshold" + + await hook.event({ + event: { + type: "message.updated", + properties: { + info: { + role: "assistant", + sessionID, + providerID: "opencode", + modelID: "kimi-k2.5-free", + finish: true, + tokens: { + input: 150000, + output: 0, + reasoning: 0, + cache: { read: 10000, write: 0 }, + }, + }, + }, + }, + }) + + // when + const output = createOutput() + await hook["tool.execute.after"]({ tool: "bash", sessionID, callID: "call_1" }, output) + + // then + expect(output.output).toBe("original") + }) + + it("appends reminder above cached non-anthropic threshold", async () => { + // given + const modelContextLimitsCache = new Map() + modelContextLimitsCache.set("opencode/kimi-k2.5-free", 262144) + + const hook = createContextWindowMonitorHook({} as never, { + anthropicContext1MEnabled: false, + modelContextLimitsCache, + }) + const sessionID = "ses_non_anthropic_above_threshold" + + await hook.event({ + event: { + type: "message.updated", + properties: { + info: { + role: "assistant", + sessionID, + providerID: "opencode", + modelID: "kimi-k2.5-free", + finish: true, + tokens: { + input: 180000, + output: 0, + reasoning: 0, + cache: { read: 10000, write: 0 }, + }, + }, + }, + }, + }) + + // when + const output = createOutput() + await hook["tool.execute.after"]({ tool: "bash", sessionID, callID: "call_1" }, output) + + // then + expect(output.output).toContain("context remaining") + }) +}) diff --git a/src/hooks/context-window-monitor.ts b/src/hooks/context-window-monitor.ts index 399c0810c..f749bd4e5 100644 --- a/src/hooks/context-window-monitor.ts +++ b/src/hooks/context-window-monitor.ts @@ -7,6 +7,7 @@ const CONTEXT_WARNING_THRESHOLD = 0.70 type ModelCacheStateLike = { anthropicContext1MEnabled: boolean + modelContextLimitsCache?: Map } function getAnthropicActualLimit(modelCacheState?: ModelCacheStateLike): number { @@ -32,6 +33,7 @@ interface TokenInfo { interface CachedTokenState { providerID: string + modelID: string tokens: TokenInfo } @@ -57,13 +59,19 @@ export function createContextWindowMonitorHook( const cached = tokenCache.get(sessionID) if (!cached) return - if (!isAnthropicProvider(cached.providerID)) return + const cachedLimit = modelCacheState?.modelContextLimitsCache?.get( + `${cached.providerID}/${cached.modelID}` + ) + const actualLimit = + cachedLimit ?? + (isAnthropicProvider(cached.providerID) ? getAnthropicActualLimit(modelCacheState) : null) + + if (!actualLimit) return const lastTokens = cached.tokens const totalInputTokens = (lastTokens?.input ?? 0) + (lastTokens?.cache?.read ?? 0) - const actualUsagePercentage = - totalInputTokens / getAnthropicActualLimit(modelCacheState) + const actualUsagePercentage = totalInputTokens / actualLimit if (actualUsagePercentage < CONTEXT_WARNING_THRESHOLD) return @@ -95,6 +103,7 @@ export function createContextWindowMonitorHook( role?: string sessionID?: string providerID?: string + modelID?: string finish?: boolean tokens?: TokenInfo } | undefined @@ -104,6 +113,7 @@ export function createContextWindowMonitorHook( tokenCache.set(info.sessionID, { providerID: info.providerID, + modelID: info.modelID ?? "", tokens: info.tokens, }) }