mirror of
https://github.com/tiennm99/ccs.git
synced 2026-10-13 22:15:33 +00:00
fix(extended-context): never write [1m] into the Fable tier key
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
This commit is contained in:
1 parent
3583b544d0
commit
2b780ceae3
6 files changed
+179
-9
No files matched your search
@@ -2,7 +2,12 @@ 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', () => {
|
||||
@@ -22,3 +27,84 @@ describe('Anthropic model env keys', () => {
|
||||
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);
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user