Files
ccs/tests/unit/shared/extended-context-utils.test.ts
T
Kenneth Wong 7ae7a5eba6 fix(extended-context): manage ANTHROPIC_DEFAULT_MODEL and keep the overlay in sync
Claude Code reads ANTHROPIC_DEFAULT_MODEL as its lowest-priority startup
model (after --model, ANTHROPIC_MODEL and the settings `model` field). It is
a plain model id, so behind a proxy a bare value is clamped to 200k exactly
like the tier keys. Add it to EXTRA_EXTENDED_CONTEXT_MODEL_ENV_KEYS so
--1m/--no-1m and the saved preference toggle it with the rest.

The `--settings` launch overlay only carried the routing keys plus the five
Anthropic tier keys. Claude Code applies the settings `env` block on top of
the process environment, so a bare CLAUDE_CODE_SUBAGENT_MODEL or
ANTHROPIC_DEFAULT_MODEL persisted on disk silently undid the resolved
--1m result (and a saved [1m] undid --no-1m). Overlay the extended-context
extra keys too.

Verified with a stub Claude binary reading the overlay file:

  ccs claude --no-1m -> ANTHROPIC_DEFAULT_MODEL=claude-opus-5, CLAUDE_CODE_SUBAGENT_MODEL=claude-sonnet-5
  ccs claude --1m    -> both keys suffixed with [1m]
2026-09-04 08:34:33 +08:00

122 lines
4.3 KiB
TypeScript

import { describe, expect, it } from 'bun:test';
import {
ANTHROPIC_MODEL_ENV_KEYS,
EXTENDED_CONTEXT_MODEL_ENV_KEYS,
applyExtendedContextPreferenceToAnthropicModels,
hasAnthropicExtendedContextEnabled,
isAnthropicModelEnvKey,
isExtendedContextModelEnvKey,
} from '../../../src/shared/extended-context-utils';
describe('Anthropic model env keys', () => {
it('includes all five tier mapping keys', () => {
expect([...ANTHROPIC_MODEL_ENV_KEYS]).toEqual([
'ANTHROPIC_MODEL',
'ANTHROPIC_DEFAULT_OPUS_MODEL',
'ANTHROPIC_DEFAULT_SONNET_MODEL',
'ANTHROPIC_DEFAULT_HAIKU_MODEL',
'ANTHROPIC_DEFAULT_FABLE_MODEL',
]);
});
it('recognizes the fable tier key like the other tiers', () => {
expect(isAnthropicModelEnvKey('ANTHROPIC_DEFAULT_FABLE_MODEL')).toBe(true);
expect(isAnthropicModelEnvKey('ANTHROPIC_DEFAULT_HAIKU_MODEL')).toBe(true);
expect(isAnthropicModelEnvKey('ANTHROPIC_BASE_URL')).toBe(false);
});
});
describe('extended-context model env keys', () => {
it('covers the Anthropic tiers plus the subagent and startup-default model keys', () => {
expect([...EXTENDED_CONTEXT_MODEL_ENV_KEYS]).toEqual([
...ANTHROPIC_MODEL_ENV_KEYS,
'CLAUDE_CODE_SUBAGENT_MODEL',
'ANTHROPIC_DEFAULT_MODEL',
]);
expect(isExtendedContextModelEnvKey('CLAUDE_CODE_SUBAGENT_MODEL')).toBe(true);
expect(isExtendedContextModelEnvKey('ANTHROPIC_DEFAULT_MODEL')).toBe(true);
expect(isAnthropicModelEnvKey('CLAUDE_CODE_SUBAGENT_MODEL')).toBe(false);
expect(isAnthropicModelEnvKey('ANTHROPIC_DEFAULT_MODEL')).toBe(false);
});
});
describe('applyExtendedContextPreferenceToAnthropicModels', () => {
it('writes [1m] into every managed key, the fable tier included', () => {
// Behind a proxy base URL Claude Code clamps a bare Fable id to 200k, so the
// fable tier needs the suffix exactly like the opus/sonnet tiers do.
const env = applyExtendedContextPreferenceToAnthropicModels(
{
ANTHROPIC_MODEL: 'claude-fable-5-1',
ANTHROPIC_DEFAULT_OPUS_MODEL: 'claude-opus-5',
ANTHROPIC_DEFAULT_FABLE_MODEL: 'claude-fable-5-1',
CLAUDE_CODE_SUBAGENT_MODEL: 'claude-fable-5-1',
},
true
);
expect(env).toEqual({
ANTHROPIC_MODEL: 'claude-fable-5-1[1m]',
ANTHROPIC_DEFAULT_OPUS_MODEL: 'claude-opus-5[1m]',
ANTHROPIC_DEFAULT_FABLE_MODEL: 'claude-fable-5-1[1m]',
CLAUDE_CODE_SUBAGENT_MODEL: 'claude-fable-5-1[1m]',
});
});
it('strips every managed key when the preference is off', () => {
const env = applyExtendedContextPreferenceToAnthropicModels(
{
ANTHROPIC_MODEL: 'claude-opus-5[1m]',
ANTHROPIC_DEFAULT_MODEL: 'claude-opus-5[1m]',
ANTHROPIC_DEFAULT_FABLE_MODEL: 'claude-fable-5-1[1m]',
CLAUDE_CODE_SUBAGENT_MODEL: 'claude-fable-5-1[1m]',
},
false
);
expect(env).toEqual({
ANTHROPIC_MODEL: 'claude-opus-5',
ANTHROPIC_DEFAULT_MODEL: 'claude-opus-5',
ANTHROPIC_DEFAULT_FABLE_MODEL: 'claude-fable-5-1',
CLAUDE_CODE_SUBAGENT_MODEL: 'claude-fable-5-1',
});
});
it('suffixes the startup-default model key when the preference is on', () => {
const env = applyExtendedContextPreferenceToAnthropicModels(
{ ANTHROPIC_DEFAULT_MODEL: 'claude-opus-5' },
true
);
expect(env).toEqual({ ANTHROPIC_DEFAULT_MODEL: 'claude-opus-5[1m]' });
});
it('honors a caller compatibility predicate', () => {
const env = applyExtendedContextPreferenceToAnthropicModels(
{
ANTHROPIC_MODEL: 'claude-opus-5',
ANTHROPIC_DEFAULT_HAIKU_MODEL: 'claude-haiku-4-5-20251001',
},
true,
{ supportsExtendedContext: (modelId) => !modelId.startsWith('claude-haiku-') }
);
expect(env).toEqual({
ANTHROPIC_MODEL: 'claude-opus-5[1m]',
ANTHROPIC_DEFAULT_HAIKU_MODEL: 'claude-haiku-4-5-20251001',
});
});
it('reads saved intent from any managed key', () => {
expect(
hasAnthropicExtendedContextEnabled({ CLAUDE_CODE_SUBAGENT_MODEL: 'claude-fable-5-1[1m]' })
).toBe(true);
expect(
hasAnthropicExtendedContextEnabled({ ANTHROPIC_DEFAULT_FABLE_MODEL: 'claude-fable-5-1[1m]' })
).toBe(true);
expect(
hasAnthropicExtendedContextEnabled({ ANTHROPIC_DEFAULT_FABLE_MODEL: 'claude-fable-5-1' })
).toBe(false);
});
});