Something went wrong. Try again.
source dump of claude code forked from oppi.li/claude-code
Something went wrong. Try again.
30 kB · 882 lines
TypeScript
at main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883// Mock rate limits for testing [ANT-ONLY]// This allows testing various rate limit scenarios without hitting actual limits//// ⚠️ WARNING: This is for internal testing/demo purposes only!// The mock headers may not exactly match the API specification or real-world behavior.// Always validate against actual API responses before relying on this for production features.
import type { SubscriptionType } from '../services/oauth/types.js'import { setMockBillingAccessOverride } from '../utils/billing.js'import type { OverageDisabledReason } from './claudeAiLimits.js'
type MockHeaders = { 'anthropic-ratelimit-unified-status'?: | 'allowed' | 'allowed_warning' | 'rejected' 'anthropic-ratelimit-unified-reset'?: string 'anthropic-ratelimit-unified-representative-claim'?: | 'five_hour' | 'seven_day' | 'seven_day_opus' | 'seven_day_sonnet' 'anthropic-ratelimit-unified-overage-status'?: | 'allowed' | 'allowed_warning' | 'rejected' 'anthropic-ratelimit-unified-overage-reset'?: string 'anthropic-ratelimit-unified-overage-disabled-reason'?: OverageDisabledReason 'anthropic-ratelimit-unified-fallback'?: 'available' 'anthropic-ratelimit-unified-fallback-percentage'?: string 'retry-after'?: string // Early warning utilization headers 'anthropic-ratelimit-unified-5h-utilization'?: string 'anthropic-ratelimit-unified-5h-reset'?: string 'anthropic-ratelimit-unified-5h-surpassed-threshold'?: string 'anthropic-ratelimit-unified-7d-utilization'?: string 'anthropic-ratelimit-unified-7d-reset'?: string 'anthropic-ratelimit-unified-7d-surpassed-threshold'?: string 'anthropic-ratelimit-unified-overage-utilization'?: string 'anthropic-ratelimit-unified-overage-surpassed-threshold'?: string}
export type MockHeaderKey = | 'status' | 'reset' | 'claim' | 'overage-status' | 'overage-reset' | 'overage-disabled-reason' | 'fallback' | 'fallback-percentage' | 'retry-after' | '5h-utilization' | '5h-reset' | '5h-surpassed-threshold' | '7d-utilization' | '7d-reset' | '7d-surpassed-threshold'
export type MockScenario = | 'normal' | 'session-limit-reached' | 'approaching-weekly-limit' | 'weekly-limit-reached' | 'overage-active' | 'overage-warning' | 'overage-exhausted' | 'out-of-credits' | 'org-zero-credit-limit' | 'org-spend-cap-hit' | 'member-zero-credit-limit' | 'seat-tier-zero-credit-limit' | 'opus-limit' | 'opus-warning' | 'sonnet-limit' | 'sonnet-warning' | 'fast-mode-limit' | 'fast-mode-short-limit' | 'extra-usage-required' | 'clear'
let mockHeaders: MockHeaders = {}let mockEnabled = falselet mockHeaderless429Message: string | null = nulllet mockSubscriptionType: SubscriptionType | null = nulllet mockFastModeRateLimitDurationMs: number | null = nulllet mockFastModeRateLimitExpiresAt: number | null = null// Default subscription type for mock testingconst DEFAULT_MOCK_SUBSCRIPTION: SubscriptionType = 'max'
// Track individual exceeded limits with their reset timestype ExceededLimit = { type: 'five_hour' | 'seven_day' | 'seven_day_opus' | 'seven_day_sonnet' resetsAt: number // Unix timestamp}
let exceededLimits: ExceededLimit[] = []
// New approach: Toggle individual headersexport function setMockHeader( key: MockHeaderKey, value: string | undefined,): void { if (process.env.USER_TYPE !== 'ant') { return }
mockEnabled = true
// Special case for retry-after which doesn't have the prefix const fullKey = ( key === 'retry-after' ? 'retry-after' : `anthropic-ratelimit-unified-${key}` ) as keyof MockHeaders
if (value === undefined || value === 'clear') { delete mockHeaders[fullKey] if (key === 'claim') { exceededLimits = [] } // Update retry-after if status changed if (key === 'status' || key === 'overage-status') { updateRetryAfter() } return } else { // Handle special cases for reset times if (key === 'reset' || key === 'overage-reset') { // If user provides a number, treat it as hours from now const hours = Number(value) if (!isNaN(hours)) { value = String(Math.floor(Date.now() / 1000) + hours * 3600) } }
// Handle claims - add to exceeded limits if (key === 'claim') { const validClaims = [ 'five_hour', 'seven_day', 'seven_day_opus', 'seven_day_sonnet', ] if (validClaims.includes(value)) { // Determine reset time based on claim type let resetsAt: number if (value === 'five_hour') { resetsAt = Math.floor(Date.now() / 1000) + 5 * 3600 } else if ( value === 'seven_day' || value === 'seven_day_opus' || value === 'seven_day_sonnet' ) { resetsAt = Math.floor(Date.now() / 1000) + 7 * 24 * 3600 } else { resetsAt = Math.floor(Date.now() / 1000) + 3600 }
// Add to exceeded limits (remove if already exists) exceededLimits = exceededLimits.filter(l => l.type !== value) exceededLimits.push({ type: value as ExceededLimit['type'], resetsAt })
// Set the representative claim (furthest reset time) updateRepresentativeClaim() return } } // Widen to a string-valued record so dynamic key assignment is allowed. // MockHeaders values are string-literal unions; assigning a raw user-input // string requires widening, but this is mock/test code so it's acceptable. const headers: Partial<Record<keyof MockHeaders, string>> = mockHeaders headers[fullKey] = value
// Update retry-after if status changed if (key === 'status' || key === 'overage-status') { updateRetryAfter() } }
// If all headers are cleared, disable mocking if (Object.keys(mockHeaders).length === 0) { mockEnabled = false }}
// Helper to update retry-after based on current statefunction updateRetryAfter(): void { const status = mockHeaders['anthropic-ratelimit-unified-status'] const overageStatus = mockHeaders['anthropic-ratelimit-unified-overage-status'] const reset = mockHeaders['anthropic-ratelimit-unified-reset']
if ( status === 'rejected' && (!overageStatus || overageStatus === 'rejected') && reset ) { // Calculate seconds until reset const resetTimestamp = Number(reset) const secondsUntilReset = Math.max( 0, resetTimestamp - Math.floor(Date.now() / 1000), ) mockHeaders['retry-after'] = String(secondsUntilReset) } else { delete mockHeaders['retry-after'] }}
// Update the representative claim based on exceeded limitsfunction updateRepresentativeClaim(): void { if (exceededLimits.length === 0) { delete mockHeaders['anthropic-ratelimit-unified-representative-claim'] delete mockHeaders['anthropic-ratelimit-unified-reset'] delete mockHeaders['retry-after'] return }
// Find the limit with the furthest reset time const furthest = exceededLimits.reduce((prev, curr) => curr.resetsAt > prev.resetsAt ? curr : prev, )
// Set the representative claim (appears for both warning and rejected) mockHeaders['anthropic-ratelimit-unified-representative-claim'] = furthest.type mockHeaders['anthropic-ratelimit-unified-reset'] = String(furthest.resetsAt)
// Add retry-after if rejected and no overage available if (mockHeaders['anthropic-ratelimit-unified-status'] === 'rejected') { const overageStatus = mockHeaders['anthropic-ratelimit-unified-overage-status'] if (!overageStatus || overageStatus === 'rejected') { // Calculate seconds until reset const secondsUntilReset = Math.max( 0, furthest.resetsAt - Math.floor(Date.now() / 1000), ) mockHeaders['retry-after'] = String(secondsUntilReset) } else { // Overage is available, no retry-after delete mockHeaders['retry-after'] } } else { delete mockHeaders['retry-after'] }}
// Add function to add exceeded limit with custom reset timeexport function addExceededLimit( type: 'five_hour' | 'seven_day' | 'seven_day_opus' | 'seven_day_sonnet', hoursFromNow: number,): void { if (process.env.USER_TYPE !== 'ant') { return }
mockEnabled = true const resetsAt = Math.floor(Date.now() / 1000) + hoursFromNow * 3600
// Remove existing limit of same type exceededLimits = exceededLimits.filter(l => l.type !== type) exceededLimits.push({ type, resetsAt })
// Update status to rejected if we have exceeded limits if (exceededLimits.length > 0) { mockHeaders['anthropic-ratelimit-unified-status'] = 'rejected' }
updateRepresentativeClaim()}
// Set mock early warning utilization for time-relative thresholds// claimAbbrev: '5h' or '7d'// utilization: 0-1 (e.g., 0.92 for 92% used)// hoursFromNow: hours until reset (default: 4 for 5h, 120 for 7d)export function setMockEarlyWarning( claimAbbrev: '5h' | '7d' | 'overage', utilization: number, hoursFromNow?: number,): void { if (process.env.USER_TYPE !== 'ant') { return }
mockEnabled = true
// Clear ALL early warning headers first (5h is checked before 7d, so we need // to clear 5h headers when testing 7d to avoid 5h taking priority) clearMockEarlyWarning()
// Default hours based on claim type (early in window to trigger warning) const defaultHours = claimAbbrev === '5h' ? 4 : 5 * 24 const hours = hoursFromNow ?? defaultHours const resetsAt = Math.floor(Date.now() / 1000) + hours * 3600
mockHeaders[`anthropic-ratelimit-unified-${claimAbbrev}-utilization`] = String(utilization) mockHeaders[`anthropic-ratelimit-unified-${claimAbbrev}-reset`] = String(resetsAt) // Set the surpassed-threshold header to trigger early warning mockHeaders[ `anthropic-ratelimit-unified-${claimAbbrev}-surpassed-threshold` ] = String(utilization)
// Set status to allowed so early warning logic can upgrade it if (!mockHeaders['anthropic-ratelimit-unified-status']) { mockHeaders['anthropic-ratelimit-unified-status'] = 'allowed' }}
// Clear mock early warning headersexport function clearMockEarlyWarning(): void { delete mockHeaders['anthropic-ratelimit-unified-5h-utilization'] delete mockHeaders['anthropic-ratelimit-unified-5h-reset'] delete mockHeaders['anthropic-ratelimit-unified-5h-surpassed-threshold'] delete mockHeaders['anthropic-ratelimit-unified-7d-utilization'] delete mockHeaders['anthropic-ratelimit-unified-7d-reset'] delete mockHeaders['anthropic-ratelimit-unified-7d-surpassed-threshold']}
export function setMockRateLimitScenario(scenario: MockScenario): void { if (process.env.USER_TYPE !== 'ant') { return }
if (scenario === 'clear') { mockHeaders = {} mockHeaderless429Message = null mockEnabled = false return }
mockEnabled = true
// Set reset times for demos const fiveHoursFromNow = Math.floor(Date.now() / 1000) + 5 * 3600 const sevenDaysFromNow = Math.floor(Date.now() / 1000) + 7 * 24 * 3600
// Clear existing headers mockHeaders = {} mockHeaderless429Message = null
// Only clear exceeded limits for scenarios that explicitly set them // Overage scenarios should preserve existing exceeded limits const preserveExceededLimits = [ 'overage-active', 'overage-warning', 'overage-exhausted', ].includes(scenario) if (!preserveExceededLimits) { exceededLimits = [] }
switch (scenario) { case 'normal': mockHeaders = { 'anthropic-ratelimit-unified-status': 'allowed', 'anthropic-ratelimit-unified-reset': String(fiveHoursFromNow), } break
case 'session-limit-reached': exceededLimits = [{ type: 'five_hour', resetsAt: fiveHoursFromNow }] updateRepresentativeClaim() mockHeaders['anthropic-ratelimit-unified-status'] = 'rejected' break
case 'approaching-weekly-limit': mockHeaders = { 'anthropic-ratelimit-unified-status': 'allowed_warning', 'anthropic-ratelimit-unified-reset': String(sevenDaysFromNow), 'anthropic-ratelimit-unified-representative-claim': 'seven_day', } break
case 'weekly-limit-reached': exceededLimits = [{ type: 'seven_day', resetsAt: sevenDaysFromNow }] updateRepresentativeClaim() mockHeaders['anthropic-ratelimit-unified-status'] = 'rejected' break
case 'overage-active': { // If no limits have been exceeded yet, default to 5-hour if (exceededLimits.length === 0) { exceededLimits = [{ type: 'five_hour', resetsAt: fiveHoursFromNow }] } updateRepresentativeClaim() mockHeaders['anthropic-ratelimit-unified-status'] = 'rejected' mockHeaders['anthropic-ratelimit-unified-overage-status'] = 'allowed' // Set overage reset time (monthly) const endOfMonthActive = new Date() endOfMonthActive.setMonth(endOfMonthActive.getMonth() + 1, 1) endOfMonthActive.setHours(0, 0, 0, 0) mockHeaders['anthropic-ratelimit-unified-overage-reset'] = String( Math.floor(endOfMonthActive.getTime() / 1000), ) break }
case 'overage-warning': { // If no limits have been exceeded yet, default to 5-hour if (exceededLimits.length === 0) { exceededLimits = [{ type: 'five_hour', resetsAt: fiveHoursFromNow }] } updateRepresentativeClaim() mockHeaders['anthropic-ratelimit-unified-status'] = 'rejected' mockHeaders['anthropic-ratelimit-unified-overage-status'] = 'allowed_warning' // Overage typically resets monthly, but for demo let's say end of month const endOfMonth = new Date() endOfMonth.setMonth(endOfMonth.getMonth() + 1, 1) endOfMonth.setHours(0, 0, 0, 0) mockHeaders['anthropic-ratelimit-unified-overage-reset'] = String( Math.floor(endOfMonth.getTime() / 1000), ) break }
case 'overage-exhausted': { // If no limits have been exceeded yet, default to 5-hour if (exceededLimits.length === 0) { exceededLimits = [{ type: 'five_hour', resetsAt: fiveHoursFromNow }] } updateRepresentativeClaim() mockHeaders['anthropic-ratelimit-unified-status'] = 'rejected' mockHeaders['anthropic-ratelimit-unified-overage-status'] = 'rejected' // Both subscription and overage are exhausted // Subscription resets based on the exceeded limit, overage resets monthly const endOfMonthExhausted = new Date() endOfMonthExhausted.setMonth(endOfMonthExhausted.getMonth() + 1, 1) endOfMonthExhausted.setHours(0, 0, 0, 0) mockHeaders['anthropic-ratelimit-unified-overage-reset'] = String( Math.floor(endOfMonthExhausted.getTime() / 1000), ) break }
case 'out-of-credits': { // Out of credits - subscription limit hit, overage rejected due to insufficient credits // (wallet is empty) if (exceededLimits.length === 0) { exceededLimits = [{ type: 'five_hour', resetsAt: fiveHoursFromNow }] } updateRepresentativeClaim() mockHeaders['anthropic-ratelimit-unified-status'] = 'rejected' mockHeaders['anthropic-ratelimit-unified-overage-status'] = 'rejected' mockHeaders['anthropic-ratelimit-unified-overage-disabled-reason'] = 'out_of_credits' const endOfMonth = new Date() endOfMonth.setMonth(endOfMonth.getMonth() + 1, 1) endOfMonth.setHours(0, 0, 0, 0) mockHeaders['anthropic-ratelimit-unified-overage-reset'] = String( Math.floor(endOfMonth.getTime() / 1000), ) break }
case 'org-zero-credit-limit': { // Org service has zero credit limit - admin set org-level spend cap to $0 // Non-admin Team/Enterprise users should not see "Request extra usage" option if (exceededLimits.length === 0) { exceededLimits = [{ type: 'five_hour', resetsAt: fiveHoursFromNow }] } updateRepresentativeClaim() mockHeaders['anthropic-ratelimit-unified-status'] = 'rejected' mockHeaders['anthropic-ratelimit-unified-overage-status'] = 'rejected' mockHeaders['anthropic-ratelimit-unified-overage-disabled-reason'] = 'org_service_zero_credit_limit' const endOfMonthZero = new Date() endOfMonthZero.setMonth(endOfMonthZero.getMonth() + 1, 1) endOfMonthZero.setHours(0, 0, 0, 0) mockHeaders['anthropic-ratelimit-unified-overage-reset'] = String( Math.floor(endOfMonthZero.getTime() / 1000), ) break }
case 'org-spend-cap-hit': { // Org spend cap hit for the month - org overages temporarily disabled // Non-admin Team/Enterprise users should not see "Request extra usage" option if (exceededLimits.length === 0) { exceededLimits = [{ type: 'five_hour', resetsAt: fiveHoursFromNow }] } updateRepresentativeClaim() mockHeaders['anthropic-ratelimit-unified-status'] = 'rejected' mockHeaders['anthropic-ratelimit-unified-overage-status'] = 'rejected' mockHeaders['anthropic-ratelimit-unified-overage-disabled-reason'] = 'org_level_disabled_until' const endOfMonthHit = new Date() endOfMonthHit.setMonth(endOfMonthHit.getMonth() + 1, 1) endOfMonthHit.setHours(0, 0, 0, 0) mockHeaders['anthropic-ratelimit-unified-overage-reset'] = String( Math.floor(endOfMonthHit.getTime() / 1000), ) break }
case 'member-zero-credit-limit': { // Member has zero credit limit - admin set this user's individual limit to $0 // Non-admin Team/Enterprise users SHOULD see "Request extra usage" (admin can allocate more) if (exceededLimits.length === 0) { exceededLimits = [{ type: 'five_hour', resetsAt: fiveHoursFromNow }] } updateRepresentativeClaim() mockHeaders['anthropic-ratelimit-unified-status'] = 'rejected' mockHeaders['anthropic-ratelimit-unified-overage-status'] = 'rejected' mockHeaders['anthropic-ratelimit-unified-overage-disabled-reason'] = 'member_zero_credit_limit' const endOfMonthMember = new Date() endOfMonthMember.setMonth(endOfMonthMember.getMonth() + 1, 1) endOfMonthMember.setHours(0, 0, 0, 0) mockHeaders['anthropic-ratelimit-unified-overage-reset'] = String( Math.floor(endOfMonthMember.getTime() / 1000), ) break }
case 'seat-tier-zero-credit-limit': { // Seat tier has zero credit limit - admin set this seat tier's limit to $0 // Non-admin Team/Enterprise users SHOULD see "Request extra usage" (admin can allocate more) if (exceededLimits.length === 0) { exceededLimits = [{ type: 'five_hour', resetsAt: fiveHoursFromNow }] } updateRepresentativeClaim() mockHeaders['anthropic-ratelimit-unified-status'] = 'rejected' mockHeaders['anthropic-ratelimit-unified-overage-status'] = 'rejected' mockHeaders['anthropic-ratelimit-unified-overage-disabled-reason'] = 'seat_tier_zero_credit_limit' const endOfMonthSeatTier = new Date() endOfMonthSeatTier.setMonth(endOfMonthSeatTier.getMonth() + 1, 1) endOfMonthSeatTier.setHours(0, 0, 0, 0) mockHeaders['anthropic-ratelimit-unified-overage-reset'] = String( Math.floor(endOfMonthSeatTier.getTime() / 1000), ) break }
case 'opus-limit': { exceededLimits = [{ type: 'seven_day_opus', resetsAt: sevenDaysFromNow }] updateRepresentativeClaim() // Always send 429 rejected status - the error handler will decide whether // to show an error or return NO_RESPONSE_REQUESTED based on fallback eligibility mockHeaders['anthropic-ratelimit-unified-status'] = 'rejected' break }
case 'opus-warning': { mockHeaders = { 'anthropic-ratelimit-unified-status': 'allowed_warning', 'anthropic-ratelimit-unified-reset': String(sevenDaysFromNow), 'anthropic-ratelimit-unified-representative-claim': 'seven_day_opus', } break }
case 'sonnet-limit': { exceededLimits = [ { type: 'seven_day_sonnet', resetsAt: sevenDaysFromNow }, ] updateRepresentativeClaim() mockHeaders['anthropic-ratelimit-unified-status'] = 'rejected' break }
case 'sonnet-warning': { mockHeaders = { 'anthropic-ratelimit-unified-status': 'allowed_warning', 'anthropic-ratelimit-unified-reset': String(sevenDaysFromNow), 'anthropic-ratelimit-unified-representative-claim': 'seven_day_sonnet', } break }
case 'fast-mode-limit': { updateRepresentativeClaim() mockHeaders['anthropic-ratelimit-unified-status'] = 'rejected' // Duration in ms (> 20s threshold to trigger cooldown) mockFastModeRateLimitDurationMs = 10 * 60 * 1000 break }
case 'fast-mode-short-limit': { updateRepresentativeClaim() mockHeaders['anthropic-ratelimit-unified-status'] = 'rejected' // Duration in ms (< 20s threshold, won't trigger cooldown) mockFastModeRateLimitDurationMs = 10 * 1000 break }
case 'extra-usage-required': { // Headerless 429 — exercises the entitlement-rejection path in errors.ts mockHeaderless429Message = 'Extra usage is required for long context requests.' break }
default: break }}
export function getMockHeaderless429Message(): string | null { if (process.env.USER_TYPE !== 'ant') { return null } // Env var path for -p / SDK testing where slash commands aren't available if (process.env.CLAUDE_MOCK_HEADERLESS_429) { return process.env.CLAUDE_MOCK_HEADERLESS_429 } if (!mockEnabled) { return null } return mockHeaderless429Message}
export function getMockHeaders(): MockHeaders | null { if ( !mockEnabled || process.env.USER_TYPE !== 'ant' || Object.keys(mockHeaders).length === 0 ) { return null } return mockHeaders}
export function getMockStatus(): string { if ( !mockEnabled || (Object.keys(mockHeaders).length === 0 && !mockSubscriptionType) ) { return 'No mock headers active (using real limits)' }
const lines: string[] = [] lines.push('Active mock headers:')
// Show subscription type - either explicitly set or default const effectiveSubscription = mockSubscriptionType || DEFAULT_MOCK_SUBSCRIPTION if (mockSubscriptionType) { lines.push(` Subscription Type: ${mockSubscriptionType} (explicitly set)`) } else { lines.push(` Subscription Type: ${effectiveSubscription} (default)`) }
Object.entries(mockHeaders).forEach(([key, value]) => { if (value !== undefined) { // Format the header name nicely const formattedKey = key .replace('anthropic-ratelimit-unified-', '') .replace(/-/g, ' ') .replace(/\b\w/g, c => c.toUpperCase())
// Format timestamps as human-readable if (key.includes('reset') && value) { const timestamp = Number(value) const date = new Date(timestamp * 1000) lines.push(` ${formattedKey}: ${value} (${date.toLocaleString()})`) } else { lines.push(` ${formattedKey}: ${value}`) } } })
// Show exceeded limits if any if (exceededLimits.length > 0) { lines.push('\nExceeded limits (contributing to representative claim):') exceededLimits.forEach(limit => { const date = new Date(limit.resetsAt * 1000) lines.push(` ${limit.type}: resets at ${date.toLocaleString()}`) }) }
return lines.join('\n')}
export function clearMockHeaders(): void { mockHeaders = {} exceededLimits = [] mockSubscriptionType = null mockFastModeRateLimitDurationMs = null mockFastModeRateLimitExpiresAt = null mockHeaderless429Message = null setMockBillingAccessOverride(null) mockEnabled = false}
export function applyMockHeaders( headers: globalThis.Headers,): globalThis.Headers { const mock = getMockHeaders() if (!mock) { return headers }
// Create a new Headers object with original headers // eslint-disable-next-line eslint-plugin-n/no-unsupported-features/node-builtins const newHeaders = new globalThis.Headers(headers)
// Apply mock headers (overwriting originals) Object.entries(mock).forEach(([key, value]) => { if (value !== undefined) { newHeaders.set(key, value) } })
return newHeaders}
// Check if we should process rate limits even without subscription// This is for Ant employees testing with mocksexport function shouldProcessMockLimits(): boolean { if (process.env.USER_TYPE !== 'ant') { return false } return mockEnabled || Boolean(process.env.CLAUDE_MOCK_HEADERLESS_429)}
export function getCurrentMockScenario(): MockScenario | null { if (!mockEnabled) { return null }
// Reverse lookup the scenario from current headers if (!mockHeaders) return null
const status = mockHeaders['anthropic-ratelimit-unified-status'] const overage = mockHeaders['anthropic-ratelimit-unified-overage-status'] const claim = mockHeaders['anthropic-ratelimit-unified-representative-claim']
if (claim === 'seven_day_opus') { return status === 'rejected' ? 'opus-limit' : 'opus-warning' }
if (claim === 'seven_day_sonnet') { return status === 'rejected' ? 'sonnet-limit' : 'sonnet-warning' }
if (overage === 'rejected') return 'overage-exhausted' if (overage === 'allowed_warning') return 'overage-warning' if (overage === 'allowed') return 'overage-active'
if (status === 'rejected') { if (claim === 'five_hour') return 'session-limit-reached' if (claim === 'seven_day') return 'weekly-limit-reached' }
if (status === 'allowed_warning') { if (claim === 'seven_day') return 'approaching-weekly-limit' }
if (status === 'allowed') return 'normal'
return null}
export function getScenarioDescription(scenario: MockScenario): string { switch (scenario) { case 'normal': return 'Normal usage, no limits' case 'session-limit-reached': return 'Session rate limit exceeded' case 'approaching-weekly-limit': return 'Approaching weekly aggregate limit' case 'weekly-limit-reached': return 'Weekly aggregate limit exceeded' case 'overage-active': return 'Using extra usage (overage active)' case 'overage-warning': return 'Approaching extra usage limit' case 'overage-exhausted': return 'Both subscription and extra usage limits exhausted' case 'out-of-credits': return 'Out of extra usage credits (wallet empty)' case 'org-zero-credit-limit': return 'Org spend cap is zero (no extra usage budget)' case 'org-spend-cap-hit': return 'Org spend cap hit for the month' case 'member-zero-credit-limit': return 'Member limit is zero (admin can allocate more)' case 'seat-tier-zero-credit-limit': return 'Seat tier limit is zero (admin can allocate more)' case 'opus-limit': return 'Opus limit reached' case 'opus-warning': return 'Approaching Opus limit' case 'sonnet-limit': return 'Sonnet limit reached' case 'sonnet-warning': return 'Approaching Sonnet limit' case 'fast-mode-limit': return 'Fast mode rate limit' case 'fast-mode-short-limit': return 'Fast mode rate limit (short)' case 'extra-usage-required': return 'Headerless 429: Extra usage required for 1M context' case 'clear': return 'Clear mock headers (use real limits)' default: return 'Unknown scenario' }}
// Mock subscription type managementexport function setMockSubscriptionType( subscriptionType: SubscriptionType | null,): void { if (process.env.USER_TYPE !== 'ant') { return } mockEnabled = true mockSubscriptionType = subscriptionType}
export function getMockSubscriptionType(): SubscriptionType | null { if (!mockEnabled || process.env.USER_TYPE !== 'ant') { return null } // Return the explicitly set subscription type, or default to 'max' return mockSubscriptionType || DEFAULT_MOCK_SUBSCRIPTION}
// Export a function that checks if we should use mock subscriptionexport function shouldUseMockSubscription(): boolean { return ( mockEnabled && mockSubscriptionType !== null && process.env.USER_TYPE === 'ant' )}
// Mock billing access (admin vs non-admin)export function setMockBillingAccess(hasAccess: boolean | null): void { if (process.env.USER_TYPE !== 'ant') { return } mockEnabled = true setMockBillingAccessOverride(hasAccess)}
// Mock fast mode rate limit handlingexport function isMockFastModeRateLimitScenario(): boolean { return mockFastModeRateLimitDurationMs !== null}
export function checkMockFastModeRateLimit( isFastModeActive?: boolean,): MockHeaders | null { if (mockFastModeRateLimitDurationMs === null) { return null }
// Only throw when fast mode is active if (!isFastModeActive) { return null }
// Check if the rate limit has expired if ( mockFastModeRateLimitExpiresAt !== null && Date.now() >= mockFastModeRateLimitExpiresAt ) { clearMockHeaders() return null }
// Set expiry on first error (not when scenario is configured) if (mockFastModeRateLimitExpiresAt === null) { mockFastModeRateLimitExpiresAt = Date.now() + mockFastModeRateLimitDurationMs }
// Compute dynamic retry-after based on remaining time const remainingMs = mockFastModeRateLimitExpiresAt - Date.now() const headersToSend = { ...mockHeaders } headersToSend['retry-after'] = String( Math.max(1, Math.ceil(remainingMs / 1000)), )
return headersToSend}