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
+14 -32
View File
@@ -1,27 +1,13 @@
import type { PluginInput } from "@opencode-ai/plugin";
import {
resolveActualContextLimit,
type ContextLimitModelCacheState,
} from "./context-limit-resolver"
import { normalizeSDKResponse } from "./normalize-sdk-response"
const DEFAULT_ANTHROPIC_ACTUAL_LIMIT = 200_000;
const CHARS_PER_TOKEN_ESTIMATE = 4;
const DEFAULT_TARGET_MAX_TOKENS = 50_000;
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 isAnthropicProvider(providerID: string): boolean {
return providerID === "anthropic" || providerID === "google-vertex-anthropic";
}
interface AssistantMessageInfo {
role: "assistant";
providerID?: string;
@@ -125,7 +111,7 @@ export function truncateToTokenLimit(
export async function getContextWindowUsage(
ctx: PluginInput,
sessionID: string,
modelCacheState?: ModelCacheStateLike,
modelCacheState?: ContextLimitModelCacheState,
): Promise<{
usedTokens: number;
remainingTokens: number;
@@ -148,18 +134,14 @@ export async function getContextWindowUsage(
const lastTokens = lastAssistant?.tokens;
if (!lastAssistant || !lastTokens) return null;
const modelSpecificLimit =
lastAssistant.providerID !== undefined &&
lastAssistant.modelID !== undefined &&
!isAnthropicProvider(lastAssistant.providerID)
? modelCacheState?.modelContextLimitsCache?.get(
`${lastAssistant.providerID}/${lastAssistant.modelID}`,
)
: undefined;
const actualLimit =
lastAssistant.providerID !== undefined && isAnthropicProvider(lastAssistant.providerID)
? getAnthropicActualLimit(modelCacheState)
: modelSpecificLimit ?? null;
lastAssistant.providerID !== undefined
? resolveActualContextLimit(
lastAssistant.providerID,
lastAssistant.modelID ?? "",
modelCacheState,
)
: null;
if (!actualLimit) return null;
@@ -184,7 +166,7 @@ export async function dynamicTruncate(
sessionID: string,
output: string,
options: TruncationOptions = {},
modelCacheState?: ModelCacheStateLike,
modelCacheState?: ContextLimitModelCacheState,
): Promise<TruncationResult> {
if (typeof output !== 'string') {
return { result: String(output ?? ''), truncated: false };
@@ -219,7 +201,7 @@ export async function dynamicTruncate(
export function createDynamicTruncator(
ctx: PluginInput,
modelCacheState?: ModelCacheStateLike,
modelCacheState?: ContextLimitModelCacheState,
) {
return {
truncate: (