2026-06-11 16:21:53 -04:00
'use strict' ;
/**
* Tests for scripts/lib/transcript-context.js (#2155)
*
* Covers transcript usage extraction, context-window detection, threshold and
* interval resolution, and the bucket math the strategic-compact hook uses.
*
* Run with: node tests/lib/transcript-context.test.js
*/
const assert = require ( 'assert' );
const fs = require ( 'fs' );
const os = require ( 'os' );
const path = require ( 'path' );
const {
STANDARD_CONTEXT_WINDOW_TOKENS ,
LARGE_CONTEXT_WINDOW_TOKENS ,
DEFAULT_CONTEXT_THRESHOLD_STANDARD ,
DEFAULT_CONTEXT_THRESHOLD_LARGE ,
DEFAULT_CONTEXT_INTERVAL_TOKENS ,
readLatestContextTokens ,
resolveContextWindowTokens ,
resolveContextThreshold ,
resolveContextInterval ,
computeContextBucket ,
2026-07-27 22:28:23 +03:00
formatWindowLabel ,
isContextWindowInferred
2026-06-11 16:21:53 -04:00
} = require ( '../../scripts/lib/transcript-context' );
console . log ( '=== Testing transcript-context.js ===\n' );
let passed = 0 ;
let failed = 0 ;
function test ( desc , fn ) {
try {
fn ();
console . log ( ` ✓ ${ desc } ` );
passed ++ ;
} catch ( e ) {
console . log ( ` ✗ ${ desc } : ${ e . message } ` );
failed ++ ;
}
}
let fixtureSeq = 0 ;
function writeTranscript ( lines ) {
fixtureSeq += 1 ;
const filePath = path . join ( os . tmpdir (), `transcript-context-test- ${ process . pid } - ${ fixtureSeq } .jsonl` );
fs . writeFileSync ( filePath , lines . join ( '\n' ) + '\n' );
return filePath ;
}
function usageRecord ( tokens , model = 'claude-sonnet-4-6' , extra = {}) {
return JSON . stringify ({
type : 'assistant' ,
message : {
model ,
usage : {
input_tokens : tokens . input || 0 ,
cache_read_input_tokens : tokens . cacheRead || 0 ,
cache_creation_input_tokens : tokens . cacheCreation || 0 ,
output_tokens : tokens . output || 0
}
},
... extra
});
}
const cleanupPaths = [];
function tracked ( filePath ) {
cleanupPaths . push ( filePath );
return filePath ;
}
// ── readLatestContextTokens ──
console . log ( 'readLatestContextTokens:' );
test ( 'sums input + cache_read + cache_creation from the latest usage record' , () => {
2026-06-18 16:49:58 -04:00
const file = tracked ( writeTranscript ([ usageRecord ({ input : 10 , cacheRead : 20 , cacheCreation : 5 }), usageRecord ({ input : 100 , cacheRead : 150000 , cacheCreation : 7000 })]));
2026-06-11 16:21:53 -04:00
const result = readLatestContextTokens ( file );
assert . ok ( result , 'Expected a usage result' );
assert . strictEqual ( result . tokens , 157100 );
});
test ( 'returns the model id alongside the token count' , () => {
2026-06-18 16:49:58 -04:00
const file = tracked ( writeTranscript ([ usageRecord ({ input : 1000 }, 'claude-opus-4-5[1m]' )]));
2026-06-11 16:21:53 -04:00
const result = readLatestContextTokens ( file );
assert . strictEqual ( result . model , 'claude-opus-4-5[1m]' );
});
test ( 'skips trailing records without usage (e.g. tool results)' , () => {
2026-06-18 16:49:58 -04:00
const file = tracked ( writeTranscript ([ usageRecord ({ input : 5000 }), JSON . stringify ({ type : 'user' , message : { content : 'tool result' } }), JSON . stringify ({ type : 'system' , subtype : 'info' })]));
2026-06-11 16:21:53 -04:00
const result = readLatestContextTokens ( file );
assert . strictEqual ( result . tokens , 5000 );
});
test ( 'skips malformed JSONL lines without throwing' , () => {
2026-06-18 16:49:58 -04:00
const file = tracked ( writeTranscript ([ usageRecord ({ input : 4200 }), '{not json at all' , '' ]));
2026-06-11 16:21:53 -04:00
const result = readLatestContextTokens ( file );
assert . strictEqual ( result . tokens , 4200 );
});
test ( 'returns null for a transcript with no usage records' , () => {
2026-06-18 16:49:58 -04:00
const file = tracked ( writeTranscript ([ JSON . stringify ({ type : 'user' , message : { content : 'hello' } })]));
2026-06-11 16:21:53 -04:00
assert . strictEqual ( readLatestContextTokens ( file ), null );
});
test ( 'returns null for a missing transcript file' , () => {
assert . strictEqual ( readLatestContextTokens ( path . join ( os . tmpdir (), 'definitely-missing.jsonl' )), null );
});
test ( 'returns null for empty or non-string paths' , () => {
assert . strictEqual ( readLatestContextTokens ( '' ), null );
assert . strictEqual ( readLatestContextTokens ( undefined ), null );
});
test ( 'ignores zero-token usage records' , () => {
2026-06-18 16:49:58 -04:00
const file = tracked ( writeTranscript ([ usageRecord ({ input : 999 }), usageRecord ({ input : 0 })]));
2026-06-11 16:21:53 -04:00
const result = readLatestContextTokens ( file );
assert . strictEqual ( result . tokens , 999 );
});
test ( 'only scans the transcript tail (latest records win on large files)' , () => {
const filler = JSON . stringify ({ type : 'system' , note : 'x' . repeat ( 512 ) });
const lines = [ usageRecord ({ input : 11 })];
for ( let i = 0 ; i < 50 ; i ++ ) lines . push ( filler );
lines . push ( usageRecord ({ input : 170000 }));
const file = tracked ( writeTranscript ( lines ));
// Tail window smaller than the file forces the truncated-tail path.
const result = readLatestContextTokens ( file , { tailBytes : 4096 });
assert . strictEqual ( result . tokens , 170000 );
});
// ── resolveContextWindowTokens ──
console . log ( '\nresolveContextWindowTokens:' );
2026-06-18 16:49:58 -04:00
// Isolation: an env-set window override (either knob) otherwise leaks into the
// default-window assertions below and fails them (#2290).
2026-08-29 14:42:59 -04:00
const originalContextWindowEnv = {
ECC_CONTEXT_WINDOW_TOKENS : process . env . ECC_CONTEXT_WINDOW_TOKENS ,
CLAUDE_CODE_AUTO_COMPACT_WINDOW : process . env . CLAUDE_CODE_AUTO_COMPACT_WINDOW ,
};
2026-06-18 16:49:58 -04:00
delete process . env . ECC_CONTEXT_WINDOW_TOKENS ;
delete process . env . CLAUDE_CODE_AUTO_COMPACT_WINDOW ;
2026-06-11 16:21:53 -04:00
test ( 'defaults to the standard 200k window' , () => {
assert . strictEqual ( resolveContextWindowTokens ( 50000 , 'claude-sonnet-4-6' ), STANDARD_CONTEXT_WINDOW_TOKENS );
});
2026-06-18 16:49:58 -04:00
test ( 'honors an explicit ECC_CONTEXT_WINDOW_TOKENS override (e.g. 400k models, #2290)' , () => {
process . env . ECC_CONTEXT_WINDOW_TOKENS = '400000' ;
try {
assert . strictEqual ( resolveContextWindowTokens ( 50000 , 'claude-opus-4-x' ), 400000 );
} finally {
delete process . env . ECC_CONTEXT_WINDOW_TOKENS ;
}
});
test ( 'honors Claude Code native CLAUDE_CODE_AUTO_COMPACT_WINDOW override' , () => {
process . env . CLAUDE_CODE_AUTO_COMPACT_WINDOW = '400000' ;
try {
assert . strictEqual ( resolveContextWindowTokens ( 50000 , 'claude-opus-4-x' ), 400000 );
} finally {
delete process . env . CLAUDE_CODE_AUTO_COMPACT_WINDOW ;
}
});
test ( 'ignores a non-positive / invalid window override' , () => {
process . env . ECC_CONTEXT_WINDOW_TOKENS = 'not-a-number' ;
try {
assert . strictEqual ( resolveContextWindowTokens ( 50000 , 'claude-sonnet-4-6' ), STANDARD_CONTEXT_WINDOW_TOKENS );
} finally {
delete process . env . ECC_CONTEXT_WINDOW_TOKENS ;
}
});
2026-06-11 16:21:53 -04:00
test ( 'detects a 1M window from the [1m] model marker' , () => {
assert . strictEqual ( resolveContextWindowTokens ( 50000 , 'claude-opus-4-5[1m]' ), LARGE_CONTEXT_WINDOW_TOKENS );
});
test ( 'detects a 1M window when observed tokens exceed 200k (marker dropped)' , () => {
assert . strictEqual ( resolveContextWindowTokens ( 220000 , 'claude-opus-4-5' ), LARGE_CONTEXT_WINDOW_TOKENS );
});
2026-07-22 12:17:11 -04:00
test ( 'recognizes claude-fable-5 as a 1M window without a [1m] marker or 200k+ tokens (#2461)' , () => {
assert . strictEqual ( resolveContextWindowTokens ( 187000 , 'claude-fable-5' ), LARGE_CONTEXT_WINDOW_TOKENS );
});
test ( 'recognizes claude-mythos-5 as a 1M window from the known-model table (#2461)' , () => {
assert . strictEqual ( resolveContextWindowTokens ( 50000 , 'claude-mythos-5' ), LARGE_CONTEXT_WINDOW_TOKENS );
});
2026-08-04 00:27:33 -04:00
test ( 'recognizes claude-opus-5 as a 1M window from the known-model table' , () => {
assert . strictEqual ( resolveContextWindowTokens ( 50000 , 'claude-opus-5' ), LARGE_CONTEXT_WINDOW_TOKENS );
});
2026-07-22 12:17:11 -04:00
test ( 'recognizes dated/prefixed variants of known large-window model ids (#2461)' , () => {
assert . strictEqual ( resolveContextWindowTokens ( 50000 , 'us.anthropic.claude-fable-5-20260115-v1:0' ), LARGE_CONTEXT_WINDOW_TOKENS );
});
test ( 'env window override still wins over the known-model table (#2461)' , () => {
process . env . ECC_CONTEXT_WINDOW_TOKENS = '400000' ;
try {
assert . strictEqual ( resolveContextWindowTokens ( 50000 , 'claude-fable-5' ), 400000 );
} finally {
delete process . env . ECC_CONTEXT_WINDOW_TOKENS ;
}
});
test ( 'does not match hypothetical smaller tiers sharing a known-family prefix (#2461)' , () => {
assert . strictEqual ( resolveContextWindowTokens ( 50000 , 'claude-fable-5-mini' ), STANDARD_CONTEXT_WINDOW_TOKENS );
assert . strictEqual ( resolveContextWindowTokens ( 50000 , 'claude-mythos-5-haiku-20260201' ), STANDARD_CONTEXT_WINDOW_TOKENS );
});
test ( 'keeps the 200k default for unknown model ids at low token counts (no false positives, #2461)' , () => {
assert . strictEqual ( resolveContextWindowTokens ( 187000 , 'claude-haiku-4-5-20251001' ), STANDARD_CONTEXT_WINDOW_TOKENS );
});
2026-06-11 16:21:53 -04:00
test ( 'treats an empty model id as standard window' , () => {
assert . strictEqual ( resolveContextWindowTokens ( 100000 , '' ), STANDARD_CONTEXT_WINDOW_TOKENS );
});
2026-07-27 22:28:23 +03:00
// ── isContextWindowInferred ──
console . log ( '\nisContextWindowInferred:' );
test ( 'flags the assumed 200k default as inferred' , () => {
assert . strictEqual ( isContextWindowInferred ( 187000 , 'claude-opus-9' ), true );
});
test ( 'an env override is a detected window, not inferred' , () => {
process . env . ECC_CONTEXT_WINDOW_TOKENS = '1000000' ;
try {
assert . strictEqual ( isContextWindowInferred ( 187000 , 'claude-opus-9' ), false );
} finally {
delete process . env . ECC_CONTEXT_WINDOW_TOKENS ;
}
});
test ( 'a [1m] marker is a detected window, not inferred' , () => {
assert . strictEqual ( isContextWindowInferred ( 187000 , 'claude-opus-4-5[1m]' ), false );
});
test ( 'a known large-window family is a detected window, not inferred' , () => {
assert . strictEqual ( isContextWindowInferred ( 187000 , 'claude-fable-5' ), false );
});
2026-08-29 14:42:59 -04:00
test ( 'tokens above the standard window still leave the exact size inferred' , () => {
assert . strictEqual ( isContextWindowInferred ( 220000 , 'claude-opus-9' ), true );
2026-07-27 22:28:23 +03:00
});
2026-08-29 14:42:59 -04:00
for ( const [ name , value ] of Object . entries ( originalContextWindowEnv )) {
if ( value === undefined ) delete process . env [ name ];
else process . env [ name ] = value ;
}
2026-06-11 16:21:53 -04:00
// ── resolveContextThreshold ──
console . log ( '\nresolveContextThreshold:' );
test ( 'defaults to 160k for the 200k window' , () => {
assert . strictEqual ( resolveContextThreshold ({}, STANDARD_CONTEXT_WINDOW_TOKENS ), DEFAULT_CONTEXT_THRESHOLD_STANDARD );
});
test ( 'defaults to 250k for the 1M window' , () => {
assert . strictEqual ( resolveContextThreshold ({}, LARGE_CONTEXT_WINDOW_TOKENS ), DEFAULT_CONTEXT_THRESHOLD_LARGE );
});
test ( 'honours COMPACT_CONTEXT_THRESHOLD override' , () => {
assert . strictEqual ( resolveContextThreshold ({ COMPACT_CONTEXT_THRESHOLD : '1234' }, STANDARD_CONTEXT_WINDOW_TOKENS ), 1234 );
});
test ( 'COMPACT_CONTEXT_THRESHOLD=0 disables the signal' , () => {
assert . strictEqual ( resolveContextThreshold ({ COMPACT_CONTEXT_THRESHOLD : '0' }, STANDARD_CONTEXT_WINDOW_TOKENS ), 0 );
});
test ( 'invalid COMPACT_CONTEXT_THRESHOLD falls back to the default' , () => {
for ( const bad of [ '-5' , 'abc' , '99999999999' ]) {
2026-06-18 16:49:58 -04:00
assert . strictEqual ( resolveContextThreshold ({ COMPACT_CONTEXT_THRESHOLD : bad }, STANDARD_CONTEXT_WINDOW_TOKENS ), DEFAULT_CONTEXT_THRESHOLD_STANDARD , `Expected fallback for ${ bad } ` );
2026-06-11 16:21:53 -04:00
}
});
// ── resolveContextInterval ──
console . log ( '\nresolveContextInterval:' );
test ( 'defaults to 60k tokens' , () => {
assert . strictEqual ( resolveContextInterval ({}), DEFAULT_CONTEXT_INTERVAL_TOKENS );
});
test ( 'honours COMPACT_CONTEXT_INTERVAL override' , () => {
assert . strictEqual ( resolveContextInterval ({ COMPACT_CONTEXT_INTERVAL : '5000' }), 5000 );
});
test ( 'invalid COMPACT_CONTEXT_INTERVAL falls back to the default' , () => {
for ( const bad of [ '0' , '-1' , 'abc' ]) {
assert . strictEqual ( resolveContextInterval ({ COMPACT_CONTEXT_INTERVAL : bad }), DEFAULT_CONTEXT_INTERVAL_TOKENS , `Expected fallback for ${ bad } ` );
}
});
// ── computeContextBucket ──
console . log ( '\ncomputeContextBucket:' );
test ( 'returns -1 below the threshold' , () => {
assert . strictEqual ( computeContextBucket ( 159999 , 160000 , 60000 ), - 1 );
});
test ( 'returns bucket 0 at the threshold' , () => {
assert . strictEqual ( computeContextBucket ( 160000 , 160000 , 60000 ), 0 );
});
test ( 'increments the bucket after each interval of growth' , () => {
assert . strictEqual ( computeContextBucket ( 219999 , 160000 , 60000 ), 0 );
assert . strictEqual ( computeContextBucket ( 220000 , 160000 , 60000 ), 1 );
assert . strictEqual ( computeContextBucket ( 280000 , 160000 , 60000 ), 2 );
});
test ( 'returns -1 when the threshold is disabled (0)' , () => {
assert . strictEqual ( computeContextBucket ( 500000 , 0 , 60000 ), - 1 );
});
test ( 'returns -1 for non-finite token counts' , () => {
assert . strictEqual ( computeContextBucket ( NaN , 160000 , 60000 ), - 1 );
});
// ── formatWindowLabel ──
console . log ( '\nformatWindowLabel:' );
test ( 'labels the standard and large windows' , () => {
assert . strictEqual ( formatWindowLabel ( STANDARD_CONTEXT_WINDOW_TOKENS ), '200k' );
assert . strictEqual ( formatWindowLabel ( LARGE_CONTEXT_WINDOW_TOKENS ), '1M' );
});
// Cleanup
for ( const filePath of cleanupPaths ) {
try {
fs . unlinkSync ( filePath );
} catch {
/* ignore */
}
}
console . log ( `\nResults: Passed: ${ passed } , Failed: ${ failed } ` );
process . exit ( failed > 0 ? 1 : 0 );