fix(shared): extract shared context limit resolver to eliminate monitor/truncator drift

- New context-limit-resolver.ts with resolveActualContextLimit() shared helper
- Anthropic provider detection now uses .includes('anthropic') instead of hard-coded IDs
- Both context-window-monitor and dynamic-truncator use the shared resolver
- Added missing test cases: Anthropic+1M disabled+cached limit, non-Anthropic without cache
This commit is contained in:
YeonGyu-Kim
2026-03-11 21:45:44 +09:00
parent d4232c9eac
commit 59f0f06e71
7 changed files with 197 additions and 57 deletions
@@ -134,4 +134,51 @@ describe("context-window-monitor modelContextLimitsCache", () => {
})
})
})
describe("#given Anthropic provider with cached context limit and 1M mode disabled", () => {
describe("#when cached usage exceeds the Anthropic default limit", () => {
it("#then should ignore the cached limit and append the reminder from the default Anthropic limit", async () => {
// given
const modelContextLimitsCache = new Map<string, number>()
modelContextLimitsCache.set("anthropic/claude-sonnet-4-5", 500000)
const hook = createContextWindowMonitorHook({} as never, {
anthropicContext1MEnabled: false,
modelContextLimitsCache,
})
const sessionID = "ses_anthropic_default_overrides_cached_limit"
await hook.event({
event: {
type: "message.updated",
properties: {
info: {
role: "assistant",
sessionID,
providerID: "anthropic",
modelID: "claude-sonnet-4-5",
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).toContain("context remaining")
expect(output.output).toContain("200,000-token context window")
expect(output.output).not.toContain("500,000-token context window")
expect(output.output).not.toContain("1,000,000-token context window")
})
})
})
})
+10 -25
View File
@@ -1,22 +1,12 @@
import type { PluginInput } from "@opencode-ai/plugin"
import {
resolveActualContextLimit,
type ContextLimitModelCacheState,
} from "../shared/context-limit-resolver"
import { createSystemDirective, SystemDirectiveTypes } from "../shared/system-directive"
const DEFAULT_ANTHROPIC_ACTUAL_LIMIT = 200_000
const CONTEXT_WARNING_THRESHOLD = 0.70
type ModelCacheStateLike = {
anthropicContext1MEnabled: boolean
modelContextLimitsCache?: Map<string, number>
}
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_ANTHROPIC_ACTUAL_LIMIT
}
function createContextReminder(actualLimit: number): string {
const limitTokens = actualLimit.toLocaleString()
@@ -40,13 +30,9 @@ interface CachedTokenState {
tokens: TokenInfo
}
function isAnthropicProvider(providerID: string): boolean {
return providerID === "anthropic" || providerID === "google-vertex-anthropic"
}
export function createContextWindowMonitorHook(
_ctx: PluginInput,
modelCacheState?: ModelCacheStateLike,
modelCacheState?: ContextLimitModelCacheState,
) {
const remindedSessions = new Set<string>()
const tokenCache = new Map<string, CachedTokenState>()
@@ -62,12 +48,11 @@ export function createContextWindowMonitorHook(
const cached = tokenCache.get(sessionID)
if (!cached) return
const modelSpecificLimit = !isAnthropicProvider(cached.providerID)
? modelCacheState?.modelContextLimitsCache?.get(`${cached.providerID}/${cached.modelID}`)
: undefined
const actualLimit = isAnthropicProvider(cached.providerID)
? getAnthropicActualLimit(modelCacheState)
: modelSpecificLimit
const actualLimit = resolveActualContextLimit(
cached.providerID,
cached.modelID,
modelCacheState,
)
if (!actualLimit) return