perf(shared): cache context window usage per session

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
YeonGyu-Kim
2026-04-28 17:59:25 +09:00
parent 938b609a91
commit ece8fd4f25
3 changed files with 150 additions and 8 deletions
+77 -5
View File
@@ -24,6 +24,66 @@ interface MessageWrapper {
info: { role: string } & Partial<AssistantMessageInfo>;
}
type ContextWindowUsage = {
usedTokens: number;
remainingTokens: number;
usagePercentage: number;
}
type ContextWindowUsageClient = Pick<PluginInput["client"], "session">
const usageCacheByClient = new WeakMap<object, Map<string, Map<string, Promise<ContextWindowUsage | null>>>>()
function createModelCacheKey(modelCacheState?: ContextLimitModelCacheState): string {
if (!modelCacheState) {
return "default"
}
const cachedLimits = modelCacheState.modelContextLimitsCache
? [...modelCacheState.modelContextLimitsCache.entries()]
.sort(([leftKey], [rightKey]) => leftKey.localeCompare(rightKey))
.map(([modelKey, limit]) => `${modelKey}:${limit}`)
.join(",")
: ""
return `${modelCacheState.anthropicContext1MEnabled ? "1m" : "200k"}|${cachedLimits}`
}
function getUsageCache(
client: ContextWindowUsageClient,
modelCacheState?: ContextLimitModelCacheState,
): Map<string, Promise<ContextWindowUsage | null>> {
let cacheByModelState = usageCacheByClient.get(client)
if (!cacheByModelState) {
cacheByModelState = new Map()
usageCacheByClient.set(client, cacheByModelState)
}
const modelCacheKey = createModelCacheKey(modelCacheState)
let cache = cacheByModelState.get(modelCacheKey)
if (!cache) {
cache = new Map()
cacheByModelState.set(modelCacheKey, cache)
}
return cache
}
export function invalidateContextWindowUsageCache(ctx: PluginInput, sessionID?: string): void {
const cacheByModelState = usageCacheByClient.get(ctx.client)
if (!cacheByModelState) {
return
}
for (const cache of cacheByModelState.values()) {
if (sessionID) {
cache.delete(sessionID)
} else {
cache.clear()
}
}
}
export interface TruncationResult {
result: string;
truncated: boolean;
@@ -112,11 +172,23 @@ export async function getContextWindowUsage(
ctx: PluginInput,
sessionID: string,
modelCacheState?: ContextLimitModelCacheState,
): Promise<{
usedTokens: number;
remainingTokens: number;
usagePercentage: number;
} | null> {
): Promise<ContextWindowUsage | null> {
const cache = getUsageCache(ctx.client, modelCacheState)
const cached = cache.get(sessionID)
if (cached) {
return cached
}
const usagePromise = fetchContextWindowUsage(ctx, sessionID, modelCacheState)
cache.set(sessionID, usagePromise)
return usagePromise
}
async function fetchContextWindowUsage(
ctx: PluginInput,
sessionID: string,
modelCacheState?: ContextLimitModelCacheState,
): Promise<ContextWindowUsage | null> {
try {
const response = await ctx.client.session.messages({
path: { id: sessionID },