Something went wrong. Try again.
source dump of claude code forked from oppi.li/claude-code
Something went wrong. Try again.
11 kB · 344 lines
TypeScript
at main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345/** * Centralized rate limit message generation * Single source of truth for all rate limit-related messages */
import { getOauthAccountInfo, getSubscriptionType, isOverageProvisioningAllowed,} from '../utils/auth.js'import { hasClaudeAiBillingAccess } from '../utils/billing.js'import { formatResetTime } from '../utils/format.js'import type { ClaudeAILimits } from './claudeAiLimits.js'
const FEEDBACK_CHANNEL_ANT = '#briarpatch-cc'
/** * All possible rate limit error message prefixes * Export this to avoid fragile string matching in UI components */export const RATE_LIMIT_ERROR_PREFIXES = [ "You've hit your", "You've used", "You're now using extra usage", "You're close to", "You're out of extra usage",] as const
/** * Check if a message is a rate limit error */export function isRateLimitErrorMessage(text: string): boolean { return RATE_LIMIT_ERROR_PREFIXES.some(prefix => text.startsWith(prefix))}
export type RateLimitMessage = { message: string severity: 'error' | 'warning'}
/** * Get the appropriate rate limit message based on limit state * Returns null if no message should be shown */export function getRateLimitMessage( limits: ClaudeAILimits, model: string,): RateLimitMessage | null { // Check overage scenarios first (when subscription is rejected but overage is available) // getUsingOverageText is rendered separately from warning. if (limits.isUsingOverage) { // Show warning if approaching overage spending limit if (limits.overageStatus === 'allowed_warning') { return { message: "You're close to your extra usage spending limit", severity: 'warning', } } return null }
// ERROR STATES - when limits are rejected if (limits.status === 'rejected') { return { message: getLimitReachedText(limits, model), severity: 'error' } }
// WARNING STATES - when approaching limits with early warning if (limits.status === 'allowed_warning') { // Only show warnings when utilization is above threshold (70%) // This prevents false warnings after week reset when API may send // allowed_warning with stale data at low usage levels const WARNING_THRESHOLD = 0.7 if ( limits.utilization !== undefined && limits.utilization < WARNING_THRESHOLD ) { return null }
// Don't warn non-billing Team/Enterprise users about approaching plan limits // if overages are enabled - they'll seamlessly roll into overage const subscriptionType = getSubscriptionType() const isTeamOrEnterprise = subscriptionType === 'team' || subscriptionType === 'enterprise' const hasExtraUsageEnabled = getOauthAccountInfo()?.hasExtraUsageEnabled === true
if ( isTeamOrEnterprise && hasExtraUsageEnabled && !hasClaudeAiBillingAccess() ) { return null }
const text = getEarlyWarningText(limits) if (text) { return { message: text, severity: 'warning' } } }
// No message needed return null}
/** * Get error message for API errors (used in errors.ts) * Returns the message string or null if no error message should be shown */export function getRateLimitErrorMessage( limits: ClaudeAILimits, model: string,): string | null { const message = getRateLimitMessage(limits, model)
// Only return error messages, not warnings if (message && message.severity === 'error') { return message.message }
return null}
/** * Get warning message for UI footer * Returns the warning message string or null if no warning should be shown */export function getRateLimitWarning( limits: ClaudeAILimits, model: string,): string | null { const message = getRateLimitMessage(limits, model)
// Only return warnings for the footer - errors are shown in AssistantTextMessages if (message && message.severity === 'warning') { return message.message }
// Don't show errors in the footer return null}
function getLimitReachedText(limits: ClaudeAILimits, model: string): string { const resetsAt = limits.resetsAt const resetTime = resetsAt ? formatResetTime(resetsAt, true) : undefined const overageResetTime = limits.overageResetsAt ? formatResetTime(limits.overageResetsAt, true) : undefined const resetMessage = resetTime ? ` · resets ${resetTime}` : ''
// if BOTH subscription (checked before this method) and overage are exhausted if (limits.overageStatus === 'rejected') { // Show the earliest reset time to indicate when user can resume let overageResetMessage = '' if (resetsAt && limits.overageResetsAt) { // Both timestamps present - use the earlier one if (resetsAt < limits.overageResetsAt) { overageResetMessage = ` · resets ${resetTime}` } else { overageResetMessage = ` · resets ${overageResetTime}` } } else if (resetTime) { overageResetMessage = ` · resets ${resetTime}` } else if (overageResetTime) { overageResetMessage = ` · resets ${overageResetTime}` }
if (limits.overageDisabledReason === 'out_of_credits') { return `You're out of extra usage${overageResetMessage}` }
return formatLimitReachedText('limit', overageResetMessage, model) }
if (limits.rateLimitType === 'seven_day_sonnet') { const subscriptionType = getSubscriptionType() const isProOrEnterprise = subscriptionType === 'pro' || subscriptionType === 'enterprise' // For pro and enterprise, Sonnet limit is the same as weekly const limit = isProOrEnterprise ? 'weekly limit' : 'Sonnet limit' return formatLimitReachedText(limit, resetMessage, model) }
if (limits.rateLimitType === 'seven_day_opus') { return formatLimitReachedText('Opus limit', resetMessage, model) }
if (limits.rateLimitType === 'seven_day') { return formatLimitReachedText('weekly limit', resetMessage, model) }
if (limits.rateLimitType === 'five_hour') { return formatLimitReachedText('session limit', resetMessage, model) }
return formatLimitReachedText('usage limit', resetMessage, model)}
function getEarlyWarningText(limits: ClaudeAILimits): string | null { let limitName: string | null = null switch (limits.rateLimitType) { case 'seven_day': limitName = 'weekly limit' break case 'five_hour': limitName = 'session limit' break case 'seven_day_opus': limitName = 'Opus limit' break case 'seven_day_sonnet': limitName = 'Sonnet limit' break case 'overage': limitName = 'extra usage' break case undefined: return null }
// utilization and resetsAt should be defined since early warning is calculated with them const used = limits.utilization ? Math.floor(limits.utilization * 100) : undefined const resetTime = limits.resetsAt ? formatResetTime(limits.resetsAt, true) : undefined
// Get upsell command based on subscription type and limit type const upsell = getWarningUpsellText(limits.rateLimitType)
if (used && resetTime) { const base = `You've used ${used}% of your ${limitName} · resets ${resetTime}` return upsell ? `${base} · ${upsell}` : base }
if (used) { const base = `You've used ${used}% of your ${limitName}` return upsell ? `${base} · ${upsell}` : base }
if (limits.rateLimitType === 'overage') { // For the "Approaching <x>" verbiage, "extra usage limit" makes more sense than "extra usage" limitName += ' limit' }
if (resetTime) { const base = `Approaching ${limitName} · resets ${resetTime}` return upsell ? `${base} · ${upsell}` : base }
const base = `Approaching ${limitName}` return upsell ? `${base} · ${upsell}` : base}
/** * Get the upsell command text for warning messages based on subscription and limit type. * Returns null if no upsell should be shown. * Only used for warnings because actual rate limit hits will see an interactive menu of options. */function getWarningUpsellText( rateLimitType: ClaudeAILimits['rateLimitType'],): string | null { const subscriptionType = getSubscriptionType() const hasExtraUsageEnabled = getOauthAccountInfo()?.hasExtraUsageEnabled === true
// 5-hour session limit warning if (rateLimitType === 'five_hour') { // Teams/Enterprise with overages disabled: prompt to request extra usage // Only show if overage provisioning is allowed for this org type (e.g., not AWS marketplace) if (subscriptionType === 'team' || subscriptionType === 'enterprise') { if (!hasExtraUsageEnabled && isOverageProvisioningAllowed()) { return '/extra-usage to request more' } // Teams/Enterprise with overages enabled or unsupported billing type don't need upsell return null }
// Pro/Max users: prompt to upgrade if (subscriptionType === 'pro' || subscriptionType === 'max') { return '/upgrade to keep using Claude Code' } }
// Overage warning (approaching spending limit) if (rateLimitType === 'overage') { if (subscriptionType === 'team' || subscriptionType === 'enterprise') { if (!hasExtraUsageEnabled && isOverageProvisioningAllowed()) { return '/extra-usage to request more' } } }
// Weekly limit warnings don't show upsell per spec return null}
/** * Get notification text for overage mode transitions * Used for transient notifications when entering overage mode */export function getUsingOverageText(limits: ClaudeAILimits): string { const resetTime = limits.resetsAt ? formatResetTime(limits.resetsAt, true) : ''
let limitName = '' if (limits.rateLimitType === 'five_hour') { limitName = 'session limit' } else if (limits.rateLimitType === 'seven_day') { limitName = 'weekly limit' } else if (limits.rateLimitType === 'seven_day_opus') { limitName = 'Opus limit' } else if (limits.rateLimitType === 'seven_day_sonnet') { const subscriptionType = getSubscriptionType() const isProOrEnterprise = subscriptionType === 'pro' || subscriptionType === 'enterprise' // For pro and enterprise, Sonnet limit is the same as weekly limitName = isProOrEnterprise ? 'weekly limit' : 'Sonnet limit' }
if (!limitName) { return 'Now using extra usage' }
const resetMessage = resetTime ? ` · Your ${limitName} resets ${resetTime}` : '' return `You're now using extra usage${resetMessage}`}
function formatLimitReachedText( limit: string, resetMessage: string, _model: string,): string { // Enhanced messaging for Ant users if (process.env.USER_TYPE === 'ant') { return `You've hit your ${limit}${resetMessage}. If you have feedback about this limit, post in ${FEEDBACK_CHANNEL_ANT}. You can reset your limits with /reset-limits` }
return `You've hit your ${limit}${resetMessage}`}