Something went wrong. Try again.
source dump of claude code forked from oppi.li/claude-code
Something went wrong. Try again.
4.9 kB · 137 lines
TypeScript
at main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138import axios from 'axios'import { getOauthConfig } from '../../constants/oauth.js'import { getOauthAccountInfo } from '../../utils/auth.js'import { getGlobalConfig, saveGlobalConfig } from '../../utils/config.js'import { logError } from '../../utils/log.js'import { isEssentialTrafficOnly } from '../../utils/privacyLevel.js'import { getOAuthHeaders, prepareApiRequest } from '../../utils/teleport/api.js'
export type OverageCreditGrantInfo = { available: boolean eligible: boolean granted: boolean amount_minor_units: number | null currency: string | null}
type CachedGrantEntry = { info: OverageCreditGrantInfo timestamp: number}
const CACHE_TTL_MS = 60 * 60 * 1000 // 1 hour
/** * Fetch the current user's overage credit grant eligibility from the backend. * The backend resolves tier-specific amounts and role-based claim permission, * so the CLI just reads the response without replicating that logic. */async function fetchOverageCreditGrant(): Promise<OverageCreditGrantInfo | null> { try { const { accessToken, orgUUID } = await prepareApiRequest() const url = `${getOauthConfig().BASE_API_URL}/api/oauth/organizations/${orgUUID}/overage_credit_grant` const response = await axios.get<OverageCreditGrantInfo>(url, { headers: getOAuthHeaders(accessToken), }) return response.data } catch (err) { logError(err) return null }}
/** * Get cached grant info. Returns null if no cache or cache is stale. * Callers should render nothing (not block) when this returns null — * refreshOverageCreditGrantCache fires lazily to populate it. */export function getCachedOverageCreditGrant(): OverageCreditGrantInfo | null { const orgId = getOauthAccountInfo()?.organizationUuid if (!orgId) return null const cached = getGlobalConfig().overageCreditGrantCache?.[orgId] if (!cached) return null if (Date.now() - cached.timestamp > CACHE_TTL_MS) return null return cached.info}
/** * Drop the current org's cached entry so the next read refetches. * Leaves other orgs' entries intact. */export function invalidateOverageCreditGrantCache(): void { const orgId = getOauthAccountInfo()?.organizationUuid if (!orgId) return const cache = getGlobalConfig().overageCreditGrantCache if (!cache || !(orgId in cache)) return saveGlobalConfig(prev => { const next = { ...prev.overageCreditGrantCache } delete next[orgId] return { ...prev, overageCreditGrantCache: next } })}
/** * Fetch and cache grant info. Fire-and-forget; call when an upsell surface * is about to render and the cache is empty. */export async function refreshOverageCreditGrantCache(): Promise<void> { if (isEssentialTrafficOnly()) return const orgId = getOauthAccountInfo()?.organizationUuid if (!orgId) return const info = await fetchOverageCreditGrant() if (!info) return // Skip rewriting info if grant data is unchanged — avoids config write // amplification (inc-4552 pattern). Still refresh the timestamp so the // TTL-based staleness check in getCachedOverageCreditGrant doesn't keep // re-triggering API calls on every component mount. saveGlobalConfig(prev => { // Derive from prev (lock-fresh) rather than a pre-lock getGlobalConfig() // read — saveConfigWithLock re-reads config from disk under the file lock, // so another CLI instance may have written between any outer read and lock // acquire. const prevCached = prev.overageCreditGrantCache?.[orgId] const existing = prevCached?.info const dataUnchanged = existing && existing.available === info.available && existing.eligible === info.eligible && existing.granted === info.granted && existing.amount_minor_units === info.amount_minor_units && existing.currency === info.currency // When data is unchanged and timestamp is still fresh, skip the write entirely if ( dataUnchanged && prevCached && Date.now() - prevCached.timestamp <= CACHE_TTL_MS ) { return prev } const entry: CachedGrantEntry = { info: dataUnchanged ? existing : info, timestamp: Date.now(), } return { ...prev, overageCreditGrantCache: { ...prev.overageCreditGrantCache, [orgId]: entry, }, } })}
/** * Format the grant amount for display. Returns null if amount isn't available * (not eligible, or currency we don't know how to format). */export function formatGrantAmount(info: OverageCreditGrantInfo): string | null { if (info.amount_minor_units == null || !info.currency) return null // For now only USD; backend may expand later if (info.currency.toUpperCase() === 'USD') { const dollars = info.amount_minor_units / 100 return Number.isInteger(dollars) ? `$${dollars}` : `$${dollars.toFixed(2)}` } return null}
export type { CachedGrantEntry as OverageCreditGrantCacheEntry }