source dump of claude code
at main 144 lines 4.4 kB view raw
1/** 2 * Facade for rate limit header processing 3 * This isolates mock logic from production code 4 */ 5 6import { APIError } from '@anthropic-ai/sdk' 7import { 8 applyMockHeaders, 9 checkMockFastModeRateLimit, 10 getMockHeaderless429Message, 11 getMockHeaders, 12 isMockFastModeRateLimitScenario, 13 shouldProcessMockLimits, 14} from './mockRateLimits.js' 15 16/** 17 * Process headers, applying mocks if /mock-limits command is active 18 */ 19export function processRateLimitHeaders( 20 headers: globalThis.Headers, 21): globalThis.Headers { 22 // Only apply mocks for Ant employees using /mock-limits command 23 if (shouldProcessMockLimits()) { 24 return applyMockHeaders(headers) 25 } 26 return headers 27} 28 29/** 30 * Check if we should process rate limits (either real subscriber or /mock-limits command) 31 */ 32export function shouldProcessRateLimits(isSubscriber: boolean): boolean { 33 return isSubscriber || shouldProcessMockLimits() 34} 35 36/** 37 * Check if mock rate limits should throw a 429 error 38 * Returns the error to throw, or null if no error should be thrown 39 * @param currentModel The model being used for the current request 40 * @param isFastModeActive Whether fast mode is currently active (for fast-mode-only mocks) 41 */ 42export function checkMockRateLimitError( 43 currentModel: string, 44 isFastModeActive?: boolean, 45): APIError | null { 46 if (!shouldProcessMockLimits()) { 47 return null 48 } 49 50 const headerlessMessage = getMockHeaderless429Message() 51 if (headerlessMessage) { 52 return new APIError( 53 429, 54 { error: { type: 'rate_limit_error', message: headerlessMessage } }, 55 headerlessMessage, 56 // eslint-disable-next-line eslint-plugin-n/no-unsupported-features/node-builtins 57 new globalThis.Headers(), 58 ) 59 } 60 61 const mockHeaders = getMockHeaders() 62 if (!mockHeaders) { 63 return null 64 } 65 66 // Check if we should throw a 429 error 67 // Only throw if: 68 // 1. Status is rejected AND 69 // 2. Either no overage headers OR overage is also rejected 70 // 3. For Opus-specific limits, only throw if actually using an Opus model 71 const status = mockHeaders['anthropic-ratelimit-unified-status'] 72 const overageStatus = 73 mockHeaders['anthropic-ratelimit-unified-overage-status'] 74 const rateLimitType = 75 mockHeaders['anthropic-ratelimit-unified-representative-claim'] 76 77 // Check if this is an Opus-specific rate limit 78 const isOpusLimit = rateLimitType === 'seven_day_opus' 79 80 // Check if current model is an Opus model (handles all variants including aliases) 81 const isUsingOpus = currentModel.includes('opus') 82 83 // For Opus limits, only throw 429 if actually using Opus 84 // This simulates the real API behavior where fallback to Sonnet succeeds 85 if (isOpusLimit && !isUsingOpus) { 86 return null 87 } 88 89 // Check for mock fast mode rate limits (handles expiry, countdown, etc.) 90 if (isMockFastModeRateLimitScenario()) { 91 const fastModeHeaders = checkMockFastModeRateLimit(isFastModeActive) 92 if (fastModeHeaders === null) { 93 return null 94 } 95 // Create a mock 429 error with the fast mode headers 96 const error = new APIError( 97 429, 98 { error: { type: 'rate_limit_error', message: 'Rate limit exceeded' } }, 99 'Rate limit exceeded', 100 // eslint-disable-next-line eslint-plugin-n/no-unsupported-features/node-builtins 101 new globalThis.Headers( 102 Object.entries(fastModeHeaders).filter(([_, v]) => v !== undefined) as [ 103 string, 104 string, 105 ][], 106 ), 107 ) 108 return error 109 } 110 111 const shouldThrow429 = 112 status === 'rejected' && (!overageStatus || overageStatus === 'rejected') 113 114 if (shouldThrow429) { 115 // Create a mock 429 error with the appropriate headers 116 const error = new APIError( 117 429, 118 { error: { type: 'rate_limit_error', message: 'Rate limit exceeded' } }, 119 'Rate limit exceeded', 120 // eslint-disable-next-line eslint-plugin-n/no-unsupported-features/node-builtins 121 new globalThis.Headers( 122 Object.entries(mockHeaders).filter(([_, v]) => v !== undefined) as [ 123 string, 124 string, 125 ][], 126 ), 127 ) 128 return error 129 } 130 131 return null 132} 133 134/** 135 * Check if this is a mock 429 error that shouldn't be retried 136 */ 137export function isMockRateLimitError(error: APIError): boolean { 138 return shouldProcessMockLimits() && error.status === 429 139} 140 141/** 142 * Check if /mock-limits command is currently active (for UI purposes) 143 */ 144export { shouldProcessMockLimits }