fix(context-window-monitor): use model-specific context limits
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
@@ -0,0 +1,90 @@
|
|||||||
|
/// <reference types="bun-types" />
|
||||||
|
|
||||||
|
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<string, number>()
|
||||||
|
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<string, number>()
|
||||||
|
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")
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -7,6 +7,7 @@ const CONTEXT_WARNING_THRESHOLD = 0.70
|
|||||||
|
|
||||||
type ModelCacheStateLike = {
|
type ModelCacheStateLike = {
|
||||||
anthropicContext1MEnabled: boolean
|
anthropicContext1MEnabled: boolean
|
||||||
|
modelContextLimitsCache?: Map<string, number>
|
||||||
}
|
}
|
||||||
|
|
||||||
function getAnthropicActualLimit(modelCacheState?: ModelCacheStateLike): number {
|
function getAnthropicActualLimit(modelCacheState?: ModelCacheStateLike): number {
|
||||||
@@ -32,6 +33,7 @@ interface TokenInfo {
|
|||||||
|
|
||||||
interface CachedTokenState {
|
interface CachedTokenState {
|
||||||
providerID: string
|
providerID: string
|
||||||
|
modelID: string
|
||||||
tokens: TokenInfo
|
tokens: TokenInfo
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -57,13 +59,19 @@ export function createContextWindowMonitorHook(
|
|||||||
const cached = tokenCache.get(sessionID)
|
const cached = tokenCache.get(sessionID)
|
||||||
if (!cached) return
|
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 lastTokens = cached.tokens
|
||||||
const totalInputTokens = (lastTokens?.input ?? 0) + (lastTokens?.cache?.read ?? 0)
|
const totalInputTokens = (lastTokens?.input ?? 0) + (lastTokens?.cache?.read ?? 0)
|
||||||
|
|
||||||
const actualUsagePercentage =
|
const actualUsagePercentage = totalInputTokens / actualLimit
|
||||||
totalInputTokens / getAnthropicActualLimit(modelCacheState)
|
|
||||||
|
|
||||||
if (actualUsagePercentage < CONTEXT_WARNING_THRESHOLD) return
|
if (actualUsagePercentage < CONTEXT_WARNING_THRESHOLD) return
|
||||||
|
|
||||||
@@ -95,6 +103,7 @@ export function createContextWindowMonitorHook(
|
|||||||
role?: string
|
role?: string
|
||||||
sessionID?: string
|
sessionID?: string
|
||||||
providerID?: string
|
providerID?: string
|
||||||
|
modelID?: string
|
||||||
finish?: boolean
|
finish?: boolean
|
||||||
tokens?: TokenInfo
|
tokens?: TokenInfo
|
||||||
} | undefined
|
} | undefined
|
||||||
@@ -104,6 +113,7 @@ export function createContextWindowMonitorHook(
|
|||||||
|
|
||||||
tokenCache.set(info.sessionID, {
|
tokenCache.set(info.sessionID, {
|
||||||
providerID: info.providerID,
|
providerID: info.providerID,
|
||||||
|
modelID: info.modelID ?? "",
|
||||||
tokens: info.tokens,
|
tokens: info.tokens,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user