Download src/services/api/overageCreditGrant.ts from chenbhao/codev: direct link, hf CLI and curl.
- Browser
- Download file 4.91 kB
-
https://huggingface.co/chenbhao/codev/resolve/afbc7edaae4e6bb0102ba024b49ce8be89d2264c/src/services/api/overageCreditGrant.ts
- Command line
-
hf download hf://chenbhao/codev@afbc7edaae4e6bb0102ba024b49ce8be89d2264c/src/services/api/overageCreditGrant.ts
-
curl -L -o overageCreditGrant.ts https://huggingface.co/chenbhao/codev/resolve/afbc7edaae4e6bb0102ba024b49ce8be89d2264c/src/services/api/overageCreditGrant.ts
4.91 kB
| import 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 } | |