mirror of
https://github.com/tiennm99/ccs.git
synced 2026-10-11 03:13:12 +00:00
Claude Code resolves ANTHROPIC_DEFAULT_FABLE_MODEL through a resolver that strips the [1m] suffix before use, unlike the opus/sonnet resolvers which pass their env value through untouched. The stripped value is then treated as an env-supplied default and held to the standard 200k window instead of the model's native 1M, so writing [1m] into that key costs the long context window rather than granting it. Measured against a Claude Code 2.1.259 session (`/context` on a live proxy endpoint): ANTHROPIC_MODEL=claude-fable-5-1[1m] -> 1m window ANTHROPIC_MODEL=claude-fable-5-1 -> 200k window ANTHROPIC_DEFAULT_FABLE_MODEL=claude-fable-5-1[1m] -> 200k window The fix is key-scoped rather than model-scoped, since the suffix is what grants 1M on every other key. Adds envKeyAcceptsExtendedContextSuffix() as the single place recording which keys reject the suffix, enforces it as a floor inside applyExtendedContextPreferenceToAnthropicModels() so no caller can bypass it, and strips a previously saved suffix from that key on the next launch through the auto path. Also extends the preference to CLAUDE_CODE_SUBAGENT_MODEL. Subagent windows come from the same resolver as the main loop (the Explore inheritCap bounds the model tier, not the context window), so a bare subagent model id sits at 200k while the suffixed form gets 1M. The new key list is kept separate from ANTHROPIC_MODEL_ENV_KEYS, which also drives routing, model-id normalization and profile validation. Claude-Session: https://claude.ai/code/session_01NPkafQjVf4pSwPBwBistGk
111 lines
3.9 KiB
TypeScript
111 lines
3.9 KiB
TypeScript
import { describe, expect, it } from 'bun:test';
|
|
|
|
import {
|
|
ANTHROPIC_MODEL_ENV_KEYS,
|
|
EXTENDED_CONTEXT_MODEL_ENV_KEYS,
|
|
applyExtendedContextPreferenceToAnthropicModels,
|
|
envKeyAcceptsExtendedContextSuffix,
|
|
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 model key', () => {
|
|
expect([...EXTENDED_CONTEXT_MODEL_ENV_KEYS]).toEqual([
|
|
...ANTHROPIC_MODEL_ENV_KEYS,
|
|
'CLAUDE_CODE_SUBAGENT_MODEL',
|
|
]);
|
|
expect(isExtendedContextModelEnvKey('CLAUDE_CODE_SUBAGENT_MODEL')).toBe(true);
|
|
expect(isAnthropicModelEnvKey('CLAUDE_CODE_SUBAGENT_MODEL')).toBe(false);
|
|
});
|
|
|
|
it('marks the fable tier key as suffix-stripping', () => {
|
|
expect(envKeyAcceptsExtendedContextSuffix('ANTHROPIC_DEFAULT_FABLE_MODEL')).toBe(false);
|
|
expect(envKeyAcceptsExtendedContextSuffix('ANTHROPIC_MODEL')).toBe(true);
|
|
expect(envKeyAcceptsExtendedContextSuffix('ANTHROPIC_DEFAULT_OPUS_MODEL')).toBe(true);
|
|
expect(envKeyAcceptsExtendedContextSuffix('CLAUDE_CODE_SUBAGENT_MODEL')).toBe(true);
|
|
});
|
|
});
|
|
|
|
describe('applyExtendedContextPreferenceToAnthropicModels', () => {
|
|
it('never writes [1m] into the fable tier key and strips a saved one', () => {
|
|
const env = applyExtendedContextPreferenceToAnthropicModels(
|
|
{
|
|
ANTHROPIC_MODEL: 'claude-fable-5-1',
|
|
ANTHROPIC_DEFAULT_OPUS_MODEL: 'claude-opus-5',
|
|
ANTHROPIC_DEFAULT_FABLE_MODEL: 'claude-fable-5-1[1m]',
|
|
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',
|
|
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_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_FABLE_MODEL: 'claude-fable-5-1',
|
|
CLAUDE_CODE_SUBAGENT_MODEL: 'claude-fable-5-1',
|
|
});
|
|
});
|
|
|
|
it('honors a caller compatibility predicate on top of the key guard', () => {
|
|
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 the subagent key too', () => {
|
|
expect(
|
|
hasAnthropicExtendedContextEnabled({ CLAUDE_CODE_SUBAGENT_MODEL: 'claude-fable-5-1[1m]' })
|
|
).toBe(true);
|
|
expect(
|
|
hasAnthropicExtendedContextEnabled({ ANTHROPIC_DEFAULT_FABLE_MODEL: 'claude-fable-5-1' })
|
|
).toBe(false);
|
|
});
|
|
});
|