source dump of claude code
at main 92 lines 3.2 kB view raw
1/** 2 * Read tool output limits. Two caps apply to text reads: 3 * 4 * | limit | default | checks | cost | on overflow | 5 * |---------------|---------|---------------------------|---------------|-----------------| 6 * | maxSizeBytes | 256 KB | TOTAL FILE SIZE (not out) | 1 stat | throws pre-read | 7 * | maxTokens | 25000 | actual output tokens | API roundtrip | throws post-read| 8 * 9 * Known mismatch: maxSizeBytes gates on total file size, not the slice. 10 * Tested truncating instead of throwing for explicit-limit reads that 11 * exceed the byte cap (#21841, Mar 2026). Reverted: tool error rate 12 * dropped but mean tokens rose — the throw path yields a ~100-byte error 13 * tool-result while truncation yields ~25K tokens of content at the cap. 14 */ 15import memoize from 'lodash-es/memoize.js' 16import { getFeatureValue_CACHED_MAY_BE_STALE } from 'src/services/analytics/growthbook.js' 17import { MAX_OUTPUT_SIZE } from 'src/utils/file.js' 18export const DEFAULT_MAX_OUTPUT_TOKENS = 25000 19 20/** 21 * Env var override for max output tokens. Returns undefined when unset/invalid 22 * so the caller can fall through to the next precedence tier. 23 */ 24function getEnvMaxTokens(): number | undefined { 25 const override = process.env.CLAUDE_CODE_FILE_READ_MAX_OUTPUT_TOKENS 26 if (override) { 27 const parsed = parseInt(override, 10) 28 if (!isNaN(parsed) && parsed > 0) { 29 return parsed 30 } 31 } 32 return undefined 33} 34 35export type FileReadingLimits = { 36 maxTokens: number 37 maxSizeBytes: number 38 includeMaxSizeInPrompt?: boolean 39 targetedRangeNudge?: boolean 40} 41 42/** 43 * Default limits for Read tool when the ToolUseContext doesn't supply an 44 * override. Memoized so the GrowthBook value is fixed at first call — avoids 45 * the cap changing mid-session as the flag refreshes in the background. 46 * 47 * Precedence for maxTokens: env var > GrowthBook > DEFAULT_MAX_OUTPUT_TOKENS. 48 * (Env var is a user-set override, should beat experiment infrastructure.) 49 * 50 * Defensive: each field is individually validated; invalid values fall 51 * through to the hardcoded defaults (no route to cap=0). 52 */ 53export const getDefaultFileReadingLimits = memoize((): FileReadingLimits => { 54 const override = 55 getFeatureValue_CACHED_MAY_BE_STALE<Partial<FileReadingLimits> | null>( 56 'tengu_amber_wren', 57 {}, 58 ) 59 60 const maxSizeBytes = 61 typeof override?.maxSizeBytes === 'number' && 62 Number.isFinite(override.maxSizeBytes) && 63 override.maxSizeBytes > 0 64 ? override.maxSizeBytes 65 : MAX_OUTPUT_SIZE 66 67 const envMaxTokens = getEnvMaxTokens() 68 const maxTokens = 69 envMaxTokens ?? 70 (typeof override?.maxTokens === 'number' && 71 Number.isFinite(override.maxTokens) && 72 override.maxTokens > 0 73 ? override.maxTokens 74 : DEFAULT_MAX_OUTPUT_TOKENS) 75 76 const includeMaxSizeInPrompt = 77 typeof override?.includeMaxSizeInPrompt === 'boolean' 78 ? override.includeMaxSizeInPrompt 79 : undefined 80 81 const targetedRangeNudge = 82 typeof override?.targetedRangeNudge === 'boolean' 83 ? override.targetedRangeNudge 84 : undefined 85 86 return { 87 maxSizeBytes, 88 maxTokens, 89 includeMaxSizeInPrompt, 90 targetedRangeNudge, 91 } 92})