source dump of claude code
at main 329 lines 12 kB view raw
1// biome-ignore-all assist/source/organizeImports: ANT-ONLY import markers must not be reordered 2import { isUltrathinkEnabled } from './thinking.js' 3import { getInitialSettings } from './settings/settings.js' 4import { isProSubscriber, isMaxSubscriber, isTeamSubscriber } from './auth.js' 5import { getFeatureValue_CACHED_MAY_BE_STALE } from 'src/services/analytics/growthbook.js' 6import { getAPIProvider } from './model/providers.js' 7import { get3PModelCapabilityOverride } from './model/modelSupportOverrides.js' 8import { isEnvTruthy } from './envUtils.js' 9import type { EffortLevel } from 'src/entrypoints/sdk/runtimeTypes.js' 10 11export type { EffortLevel } 12 13export const EFFORT_LEVELS = [ 14 'low', 15 'medium', 16 'high', 17 'max', 18] as const satisfies readonly EffortLevel[] 19 20export type EffortValue = EffortLevel | number 21 22// @[MODEL LAUNCH]: Add the new model to the allowlist if it supports the effort parameter. 23export function modelSupportsEffort(model: string): boolean { 24 const m = model.toLowerCase() 25 if (isEnvTruthy(process.env.CLAUDE_CODE_ALWAYS_ENABLE_EFFORT)) { 26 return true 27 } 28 const supported3P = get3PModelCapabilityOverride(model, 'effort') 29 if (supported3P !== undefined) { 30 return supported3P 31 } 32 // Supported by a subset of Claude 4 models 33 if (m.includes('opus-4-6') || m.includes('sonnet-4-6')) { 34 return true 35 } 36 // Exclude any other known legacy models (haiku, older opus/sonnet variants) 37 if (m.includes('haiku') || m.includes('sonnet') || m.includes('opus')) { 38 return false 39 } 40 41 // IMPORTANT: Do not change the default effort support without notifying 42 // the model launch DRI and research. This is a sensitive setting that can 43 // greatly affect model quality and bashing. 44 45 // Default to true for unknown model strings on 1P. 46 // Do not default to true for 3P as they have different formats for their 47 // model strings (ex. anthropics/claude-code#30795) 48 return getAPIProvider() === 'firstParty' 49} 50 51// @[MODEL LAUNCH]: Add the new model to the allowlist if it supports 'max' effort. 52// Per API docs, 'max' is Opus 4.6 only for public models — other models return an error. 53export function modelSupportsMaxEffort(model: string): boolean { 54 const supported3P = get3PModelCapabilityOverride(model, 'max_effort') 55 if (supported3P !== undefined) { 56 return supported3P 57 } 58 if (model.toLowerCase().includes('opus-4-6')) { 59 return true 60 } 61 if (process.env.USER_TYPE === 'ant' && resolveAntModel(model)) { 62 return true 63 } 64 return false 65} 66 67export function isEffortLevel(value: string): value is EffortLevel { 68 return (EFFORT_LEVELS as readonly string[]).includes(value) 69} 70 71export function parseEffortValue(value: unknown): EffortValue | undefined { 72 if (value === undefined || value === null || value === '') { 73 return undefined 74 } 75 if (typeof value === 'number' && isValidNumericEffort(value)) { 76 return value 77 } 78 const str = String(value).toLowerCase() 79 if (isEffortLevel(str)) { 80 return str 81 } 82 const numericValue = parseInt(str, 10) 83 if (!isNaN(numericValue) && isValidNumericEffort(numericValue)) { 84 return numericValue 85 } 86 return undefined 87} 88 89/** 90 * Numeric values are model-default only and not persisted. 91 * 'max' is session-scoped for external users (ants can persist it). 92 * Write sites call this before saving to settings so the Zod schema 93 * (which only accepts string levels) never rejects a write. 94 */ 95export function toPersistableEffort( 96 value: EffortValue | undefined, 97): EffortLevel | undefined { 98 if (value === 'low' || value === 'medium' || value === 'high') { 99 return value 100 } 101 if (value === 'max' && process.env.USER_TYPE === 'ant') { 102 return value 103 } 104 return undefined 105} 106 107export function getInitialEffortSetting(): EffortLevel | undefined { 108 // toPersistableEffort filters 'max' for non-ants on read, so a manually 109 // edited settings.json doesn't leak session-scoped max into a fresh session. 110 return toPersistableEffort(getInitialSettings().effortLevel) 111} 112 113/** 114 * Decide what effort level (if any) to persist when the user selects a model 115 * in ModelPicker. Keeps an explicit prior /effort choice sticky even when it 116 * matches the picked model's default, while letting purely-default and 117 * session-ephemeral effort (CLI --effort, EffortCallout default) fall through 118 * to undefined so it follows future model-default changes. 119 * 120 * priorPersisted must come from userSettings on disk 121 * (getSettingsForSource('userSettings')?.effortLevel), NOT merged settings 122 * (project/policy layers would leak into the user's global settings.json) 123 * and NOT AppState.effortValue (includes session-scoped sources that 124 * deliberately do not write to settings.json). 125 */ 126export function resolvePickerEffortPersistence( 127 picked: EffortLevel | undefined, 128 modelDefault: EffortLevel, 129 priorPersisted: EffortLevel | undefined, 130 toggledInPicker: boolean, 131): EffortLevel | undefined { 132 const hadExplicit = priorPersisted !== undefined || toggledInPicker 133 return hadExplicit || picked !== modelDefault ? picked : undefined 134} 135 136export function getEffortEnvOverride(): EffortValue | null | undefined { 137 const envOverride = process.env.CLAUDE_CODE_EFFORT_LEVEL 138 return envOverride?.toLowerCase() === 'unset' || 139 envOverride?.toLowerCase() === 'auto' 140 ? null 141 : parseEffortValue(envOverride) 142} 143 144/** 145 * Resolve the effort value that will actually be sent to the API for a given 146 * model, following the full precedence chain: 147 * env CLAUDE_CODE_EFFORT_LEVEL → appState.effortValue → model default 148 * 149 * Returns undefined when no effort parameter should be sent (env set to 150 * 'unset', or no default exists for the model). 151 */ 152export function resolveAppliedEffort( 153 model: string, 154 appStateEffortValue: EffortValue | undefined, 155): EffortValue | undefined { 156 const envOverride = getEffortEnvOverride() 157 if (envOverride === null) { 158 return undefined 159 } 160 const resolved = 161 envOverride ?? appStateEffortValue ?? getDefaultEffortForModel(model) 162 // API rejects 'max' on non-Opus-4.6 models — downgrade to 'high'. 163 if (resolved === 'max' && !modelSupportsMaxEffort(model)) { 164 return 'high' 165 } 166 return resolved 167} 168 169/** 170 * Resolve the effort level to show the user. Wraps resolveAppliedEffort 171 * with the 'high' fallback (what the API uses when no effort param is sent). 172 * Single source of truth for the status bar and /effort output (CC-1088). 173 */ 174export function getDisplayedEffortLevel( 175 model: string, 176 appStateEffort: EffortValue | undefined, 177): EffortLevel { 178 const resolved = resolveAppliedEffort(model, appStateEffort) ?? 'high' 179 return convertEffortValueToLevel(resolved) 180} 181 182/** 183 * Build the ` with {level} effort` suffix shown in Logo/Spinner. 184 * Returns empty string if the user hasn't explicitly set an effort value. 185 * Delegates to resolveAppliedEffort() so the displayed level matches what 186 * the API actually receives (including max→high clamp for non-Opus models). 187 */ 188export function getEffortSuffix( 189 model: string, 190 effortValue: EffortValue | undefined, 191): string { 192 if (effortValue === undefined) return '' 193 const resolved = resolveAppliedEffort(model, effortValue) 194 if (resolved === undefined) return '' 195 return ` with ${convertEffortValueToLevel(resolved)} effort` 196} 197 198export function isValidNumericEffort(value: number): boolean { 199 return Number.isInteger(value) 200} 201 202export function convertEffortValueToLevel(value: EffortValue): EffortLevel { 203 if (typeof value === 'string') { 204 // Runtime guard: value may come from remote config (GrowthBook) where 205 // TypeScript types can't help us. Coerce unknown strings to 'high' 206 // rather than passing them through unchecked. 207 return isEffortLevel(value) ? value : 'high' 208 } 209 if (process.env.USER_TYPE === 'ant' && typeof value === 'number') { 210 if (value <= 50) return 'low' 211 if (value <= 85) return 'medium' 212 if (value <= 100) return 'high' 213 return 'max' 214 } 215 return 'high' 216} 217 218/** 219 * Get user-facing description for effort levels 220 * 221 * @param level The effort level to describe 222 * @returns Human-readable description 223 */ 224export function getEffortLevelDescription(level: EffortLevel): string { 225 switch (level) { 226 case 'low': 227 return 'Quick, straightforward implementation with minimal overhead' 228 case 'medium': 229 return 'Balanced approach with standard implementation and testing' 230 case 'high': 231 return 'Comprehensive implementation with extensive testing and documentation' 232 case 'max': 233 return 'Maximum capability with deepest reasoning (Opus 4.6 only)' 234 } 235} 236 237/** 238 * Get user-facing description for effort values (both string and numeric) 239 * 240 * @param value The effort value to describe 241 * @returns Human-readable description 242 */ 243export function getEffortValueDescription(value: EffortValue): string { 244 if (process.env.USER_TYPE === 'ant' && typeof value === 'number') { 245 return `[ANT-ONLY] Numeric effort value of ${value}` 246 } 247 248 if (typeof value === 'string') { 249 return getEffortLevelDescription(value) 250 } 251 return 'Balanced approach with standard implementation and testing' 252} 253 254export type OpusDefaultEffortConfig = { 255 enabled: boolean 256 dialogTitle: string 257 dialogDescription: string 258} 259 260const OPUS_DEFAULT_EFFORT_CONFIG_DEFAULT: OpusDefaultEffortConfig = { 261 enabled: true, 262 dialogTitle: 'We recommend medium effort for Opus', 263 dialogDescription: 264 'Effort determines how long Claude thinks for when completing your task. We recommend medium effort for most tasks to balance speed and intelligence and maximize rate limits. Use ultrathink to trigger high effort when needed.', 265} 266 267export function getOpusDefaultEffortConfig(): OpusDefaultEffortConfig { 268 const config = getFeatureValue_CACHED_MAY_BE_STALE( 269 'tengu_grey_step2', 270 OPUS_DEFAULT_EFFORT_CONFIG_DEFAULT, 271 ) 272 return { 273 ...OPUS_DEFAULT_EFFORT_CONFIG_DEFAULT, 274 ...config, 275 } 276} 277 278// @[MODEL LAUNCH]: Update the default effort levels for new models 279export function getDefaultEffortForModel( 280 model: string, 281): EffortValue | undefined { 282 if (process.env.USER_TYPE === 'ant') { 283 const config = getAntModelOverrideConfig() 284 const isDefaultModel = 285 config?.defaultModel !== undefined && 286 model.toLowerCase() === config.defaultModel.toLowerCase() 287 if (isDefaultModel && config?.defaultModelEffortLevel) { 288 return config.defaultModelEffortLevel 289 } 290 const antModel = resolveAntModel(model) 291 if (antModel) { 292 if (antModel.defaultEffortLevel) { 293 return antModel.defaultEffortLevel 294 } 295 if (antModel.defaultEffortValue !== undefined) { 296 return antModel.defaultEffortValue 297 } 298 } 299 // Always default ants to undefined/high 300 return undefined 301 } 302 303 // IMPORTANT: Do not change the default effort level without notifying 304 // the model launch DRI and research. Default effort is a sensitive setting 305 // that can greatly affect model quality and bashing. 306 307 // Default effort on Opus 4.6 to medium for Pro. 308 // Max/Team also get medium when the tengu_grey_step2 config is enabled. 309 if (model.toLowerCase().includes('opus-4-6')) { 310 if (isProSubscriber()) { 311 return 'medium' 312 } 313 if ( 314 getOpusDefaultEffortConfig().enabled && 315 (isMaxSubscriber() || isTeamSubscriber()) 316 ) { 317 return 'medium' 318 } 319 } 320 321 // When ultrathink feature is on, default effort to medium (ultrathink bumps to high) 322 if (isUltrathinkEnabled() && modelSupportsEffort(model)) { 323 return 'medium' 324 } 325 326 // Fallback to undefined, which means we don't set an effort level. This 327 // should resolve to high effort level in the API. 328 return undefined 329}