All files / webview/lib quotaErrors.ts

100% Statements 28/28
95% Branches 19/20
100% Functions 3/3
100% Lines 28/28

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77                  26x   26x   1x 1x     25x   1x 1x 1x 1x     24x   20x 20x     4x 1x     3x 1x     2x 1x     1x       42x 42x       65x 65x 25x                 40x 40x 1x                 39x    
export type QuotaError = {
  kind: 'requests' | 'tokens'
  used: number
  limit: number
  window: string
  resetAt: Date | null
}
 
function computeResetAt(window: string): Date | null {
  const now = new Date()
 
  if (window === 'monthly') {
    // 1st of next calendar month, 00:00 UTC
    const d = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth() + 1, 1, 0, 0, 0, 0))
    return d
  }
 
  if (window === 'weekly') {
    // Next Monday 00:00 UTC (dayOfWeek: 0=Sun, 1=Mon ... 6=Sat)
    const day = now.getUTCDay() // 0=Sun
    const daysUntilMonday = day === 0 ? 1 : 8 - day
    const d = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate() + daysUntilMonday))
    return d
  }
 
  if (window === 'daily') {
    // Next midnight UTC
    const d = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate() + 1))
    return d
  }
 
  if (window === '1m') {
    return new Date(now.getTime() + 60_000)
  }
 
  if (window === '5m') {
    return new Date(now.getTime() + 5 * 60_000)
  }
 
  if (window === '1h') {
    return new Date(now.getTime() + 60 * 60_000)
  }
 
  return null
}
 
export function isTooManyRequestsError(error: string): boolean {
  const lowerErr = error.toLowerCase()
  return lowerErr.includes('429') || lowerErr.includes('unavailable')
}
 
export function parseQuotaError(error: string): QuotaError | null {
  const tokenMatch = error.match(/user token quota exceeded: (\d+)\/(\d+) tokens used \(window: (\w+)\)/)
  if (tokenMatch) {
    return {
      kind: 'tokens',
      used: Number(tokenMatch[1]),
      limit: Number(tokenMatch[2]),
      window: tokenMatch[3],
      resetAt: computeResetAt(tokenMatch[3]),
    }
  }
 
  const requestMatch = error.match(/user request limit exceeded: (\d+)\/(\d+) requests \(window: (\w+)\)/)
  if (requestMatch) {
    return {
      kind: 'requests',
      used: Number(requestMatch[1]),
      limit: Number(requestMatch[2]),
      window: requestMatch[3],
      resetAt: computeResetAt(requestMatch[3]),
    }
  }
 
  return null
}