fix(cliproxy): preserve explicit Claude long-context intent

- centralize Anthropic model-key [1m] handling in shared helpers

- keep dashboard preset and editor writes consistent across all Claude tiers

- add explicit api create and help guidance for --1m/--no-1m

Refs #789
This commit is contained in:
Tam Nhu Tran
2026-03-26 16:36:04 -04:00
parent 63b291785c
commit c05189b4b1
12 changed files with 329 additions and 134 deletions
+71
View File
@@ -4,6 +4,16 @@
/** Extended context suffix recognized by Claude Code. */
export const EXTENDED_CONTEXT_SUFFIX = '[1m]';
export const ANTHROPIC_MODEL_ENV_KEYS = [
'ANTHROPIC_MODEL',
'ANTHROPIC_DEFAULT_OPUS_MODEL',
'ANTHROPIC_DEFAULT_SONNET_MODEL',
'ANTHROPIC_DEFAULT_HAIKU_MODEL',
] as const;
export type AnthropicModelEnvKey = (typeof ANTHROPIC_MODEL_ENV_KEYS)[number];
const ANTHROPIC_MODEL_ENV_KEY_SET = new Set<string>(ANTHROPIC_MODEL_ENV_KEYS);
/** Check if model is a native Gemini model (auto-enabled behavior). */
export function isNativeGeminiModel(modelId: string): boolean {
@@ -27,3 +37,64 @@ export function stripExtendedContextSuffix(model: string): string {
if (!model) return model;
return hasExtendedContextSuffix(model) ? model.slice(0, -EXTENDED_CONTEXT_SUFFIX.length) : model;
}
/** True when key belongs to Anthropic model mapping state. */
export function isAnthropicModelEnvKey(key: string): key is AnthropicModelEnvKey {
return ANTHROPIC_MODEL_ENV_KEY_SET.has(key);
}
/** Strip transient config suffixes so model IDs can be checked against catalogs. */
export function stripModelConfigurationSuffixes(modelId: string): string {
return stripExtendedContextSuffix(modelId.trim()).replace(/\([^)]+\)$/, '');
}
/** Whether any saved Anthropic model mapping explicitly requests [1m]. */
export function hasAnthropicExtendedContextEnabled(
env: Partial<Record<string, string | undefined>>
): boolean {
return ANTHROPIC_MODEL_ENV_KEYS.some((key) => {
const value = env[key];
return typeof value === 'string' && hasExtendedContextSuffix(value);
});
}
/** Apply or strip [1m] across Anthropic model mappings while honoring compatibility. */
export function applyExtendedContextPreferenceToAnthropicModels<
T extends Record<string, string | undefined>,
>(
env: T,
enabled: boolean,
options: {
supportsExtendedContext?: (modelId: string, key: AnthropicModelEnvKey) => boolean;
} = {}
): T {
const nextEnv: Record<string, string | undefined> = { ...env };
for (const key of ANTHROPIC_MODEL_ENV_KEYS) {
const value = nextEnv[key];
if (typeof value !== 'string' || value.trim().length === 0) {
continue;
}
const modelId = stripModelConfigurationSuffixes(value);
const supported = options.supportsExtendedContext?.(modelId, key) ?? true;
nextEnv[key] =
enabled && supported ? applyExtendedContextSuffix(value) : stripExtendedContextSuffix(value);
}
return nextEnv as T;
}
/** Detect Claude model identifiers, regardless of transient [1m]/(thinking) suffixes. */
export function isClaudeModelId(modelId: string): boolean {
return stripModelConfigurationSuffixes(modelId).toLowerCase().startsWith('claude-');
}
/**
* Conservative Claude long-context support heuristic for generic API profile flows.
* Haiku stays plain; Opus/Sonnet default to opt-in [1m].
*/
export function likelySupportsClaudeExtendedContext(modelId: string): boolean {
const baseModel = stripModelConfigurationSuffixes(modelId).toLowerCase();
return baseModel.startsWith('claude-') && !baseModel.startsWith('claude-haiku-');
}