"use client"; import { useState, useEffect, useCallback, useRef, useMemo } from "react"; import ProviderIcon from "@/shared/components/ProviderIcon"; import QuotaTable from "./QuotaTable"; import Toggle from "@/shared/components/Toggle"; import Tooltip from "@/shared/components/Tooltip"; import { parseQuotaData, calculatePercentage, getConnectionLabel, getConnectionQuotaRemaining, sortVisibleConnections, buildLoadingState, filterQuotaStateByConnections, getConnectionsEmptyMessage, getPageSizeLabel, getConnectionsPaginationSummary, getSafePagination, getSafeTotals, shouldResetPage, getPaginationPageValue, getProviderOptions, reconcileConnectionsPage, getQuotaCache, setQuotaCache, QUOTA_CACHE_KEY, REFRESH_INTERVAL_MS, CLAUDE_REFRESH_INTERVAL_MS, DEPLETED_QUOTA_THRESHOLD, AUTO_REFRESH_STORAGE_KEY, CONNECTIONS_PAGE_SIZE, ACCOUNT_PAGE_SIZE_OPTIONS, ACCOUNT_PAGE_SIZE_MAX, ACCOUNT_FILTER_OPTIONS, QUOTA_SORT_OPTIONS, } from "./utils"; import Card from "@/shared/components/Card"; import { ConfirmModal, EditConnectionModal } from "@/shared/components"; import { USAGE_SUPPORTED_PROVIDERS } from "@/shared/constants/providers"; import { useCopyToClipboard } from "@/shared/hooks/useCopyToClipboard"; // Maps the stored providerSpecificData.authMethod to a human label for Kiro. // Values come from the Kiro connect flows: builder-id/idc (device code), // google/github (social), imported (refresh-token paste), api_key (headless). const KIRO_METHOD_LABELS = { "builder-id": "AWS Builder ID", idc: "IAM Identity Center", google: "Google", github: "GitHub", imported: "Imported Token", api_key: "API Key", }; function kiroMethodLabel(conn) { const m = conn.providerSpecificData?.authMethod; if (m && KIRO_METHOD_LABELS[m]) return KIRO_METHOD_LABELS[m]; return conn.authType === "api_key" ? "API Key" : "OAuth"; } function getConnectionSecondaryLabel(connection) { if (connection.name?.trim() && connection.email?.trim() && connection.name.trim() !== connection.email.trim()) { return connection.email.trim(); } if (connection.name?.trim() && connection.displayName?.trim() && connection.name.trim() !== connection.displayName.trim()) { return connection.displayName.trim(); } return null; } // Region is stored for builder-id/idc/api_key flows; social and imported flows // omit it, so fall back to the region segment of the profileArn // (arn:aws:codewhisperer::...). function kiroRegion(conn) { const r = conn.providerSpecificData?.region; if (r) return r; const arn = conn.providerSpecificData?.profileArn; const seg = typeof arn === "string" ? arn.split(":")[3] : ""; return seg || ""; } function getCodexResetCreditCount(quota) { const value = quota?.raw?.resetCredits?.availableCount; const count = typeof value === "number" ? value : Number(value); return Number.isFinite(count) ? Math.max(0, count) : 0; } export default function ProviderLimits() { const { copied, copy } = useCopyToClipboard(); const [connections, setConnections] = useState([]); const [quotaData, setQuotaData] = useState({}); const [loading, setLoading] = useState({}); const [errors, setErrors] = useState({}); const [autoRefresh, setAutoRefresh] = useState(true); const [autoPingMap, setAutoPingMap] = useState({}); const [lastUpdated, setLastUpdated] = useState(null); const [hasHydratedAutoRefresh, setHasHydratedAutoRefresh] = useState(false); const [refreshingAll, setRefreshingAll] = useState(false); const [countdown, setCountdown] = useState(60); const [connectionsLoading, setConnectionsLoading] = useState(true); const [deletingId, setDeletingId] = useState(null); const [togglingId, setTogglingId] = useState(null); const [resettingLimitId, setResettingLimitId] = useState(null); const [resetConfirmState, setResetConfirmState] = useState(null); const [showEditModal, setShowEditModal] = useState(false); const [selectedConnection, setSelectedConnection] = useState(null); const [proxyPools, setProxyPools] = useState([]); const [providerFilter, setProviderFilter] = useState("all"); const [providerOptions, setProviderOptions] = useState([]); const [accountFilter, setAccountFilter] = useState("all"); const [quotaSortMode, setQuotaSortMode] = useState("default"); const [expiringFirst, setExpiringFirst] = useState(false); const [providerMenuOpen, setProviderMenuOpen] = useState(false); const [bulkToggling, setBulkToggling] = useState(false); const [page, setPage] = useState(1); const [pageSize, setPageSize] = useState(CONNECTIONS_PAGE_SIZE); const [customPageSizeInput, setCustomPageSizeInput] = useState( String(CONNECTIONS_PAGE_SIZE), ); const [pagination, setPagination] = useState({ page: 1, pageSize: CONNECTIONS_PAGE_SIZE, total: 0, totalPages: 1, }); const [totals, setTotals] = useState({ eligibleConnections: 0, providerFilteredConnections: 0, }); const intervalRef = useRef(null); const countdownRef = useRef(null); const tickCountRef = useRef(0); const fetchConnections = useCallback( async (targetPage = page) => { try { const params = new URLSearchParams({ page: String(targetPage), pageSize: String(pageSize), accountStatus: accountFilter, sort: "priority", }); if (providerFilter !== "all") { params.set("provider", providerFilter); } const response = await fetch( `/api/providers/client?${params.toString()}`, ); if (!response.ok) throw new Error("Failed to fetch connections"); const data = await response.json(); const connectionList = data.connections || []; const nextPagination = getSafePagination(data.pagination, pageSize); const nextTotals = getSafeTotals(data.totals, connectionList.length); setConnections(connectionList); setProviderOptions(getProviderOptions(data.providerOptions)); setPagination(nextPagination); setTotals(nextTotals); setPage(getPaginationPageValue(data.pagination, targetPage)); return connectionList; } catch (error) { console.error("Error fetching connections:", error); setConnections([]); setProviderOptions([]); setPagination({ page: 1, pageSize, total: 0, totalPages: 1 }); setTotals({ eligibleConnections: 0, providerFilteredConnections: 0 }); return []; } }, [accountFilter, expiringFirst, page, pageSize, providerFilter], ); // Fetch quota for a specific connection const fetchQuota = useCallback(async (connectionId, provider) => { setLoading((prev) => ({ ...prev, [connectionId]: true })); setErrors((prev) => ({ ...prev, [connectionId]: null })); try { console.log( `[ProviderLimits] Fetching quota for ${provider} (${connectionId})`, ); const response = await fetch(`/api/usage/${connectionId}`); if (!response.ok) { const errorData = await response.json().catch(() => ({})); const errorMsg = errorData.error || response.statusText; // Handle different error types gracefully if (response.status === 404) { // Connection not found - skip silently console.warn( `[ProviderLimits] Connection not found for ${provider}, skipping`, ); return; } if (response.status === 401) { // Auth error - show message instead of throwing console.warn( `[ProviderLimits] Auth error for ${provider}:`, errorMsg, ); const quotaEntry = { quotas: [], message: errorMsg, }; setQuotaData((prev) => ({ ...prev, [connectionId]: quotaEntry, })); setQuotaCache(connectionId, quotaEntry); return; } throw new Error(`HTTP ${response.status}: ${errorMsg}`); } const data = await response.json(); console.log(`[ProviderLimits] Got quota for ${provider}:`, data); // Parse quota data using provider-specific parser const parsedQuotas = parseQuotaData(provider, data); const quotaEntry = { quotas: parsedQuotas, plan: data.plan || null, message: data.message || null, raw: data, }; setQuotaData((prev) => ({ ...prev, [connectionId]: quotaEntry, })); setQuotaCache(connectionId, quotaEntry); } catch (error) { console.error( `[ProviderLimits] Error fetching quota for ${provider} (${connectionId}):`, error, ); setErrors((prev) => ({ ...prev, [connectionId]: error.message || "Failed to fetch quota", })); } finally { setLoading((prev) => ({ ...prev, [connectionId]: false })); } }, []); // Refresh quota for a specific provider const refreshProvider = useCallback( async (connectionId, provider) => { await fetchQuota(connectionId, provider); setLastUpdated(new Date()); }, [fetchQuota], ); const handleResetCodexLimit = useCallback( async (connectionId, provider) => { if (provider !== "codex" || resettingLimitId) return; setResettingLimitId(connectionId); setErrors((prev) => ({ ...prev, [connectionId]: null })); try { const response = await fetch(`/api/usage/${connectionId}/codex-reset-credits`, { method: "POST" }); const result = await response.json().catch(() => ({})); if (!response.ok) { throw new Error(result.message || result.error || result.code || "Failed to reset Codex limit"); } await fetchQuota(connectionId, provider); setLastUpdated(new Date()); } catch (error) { setErrors((prev) => ({ ...prev, [connectionId]: error.message || "Failed to reset Codex limit" })); } finally { setResettingLimitId(null); } }, [fetchQuota, resettingLimitId], ); const handleDeleteConnection = useCallback( async (id) => { if (!confirm("Delete this connection?")) return; setDeletingId(id); try { const res = await fetch(`/api/providers/${id}`, { method: "DELETE" }); if (res.ok) { setQuotaData((prev) => { const next = { ...prev }; delete next[id]; return next; }); setLoading((prev) => { const next = { ...prev }; delete next[id]; return next; }); setErrors((prev) => { const next = { ...prev }; delete next[id]; return next; }); if (typeof window !== "undefined") { try { const cache = getQuotaCache(); if (cache[id]) { delete cache[id]; window.localStorage.setItem( QUOTA_CACHE_KEY, JSON.stringify(cache), ); } } catch (e) { console.error("Error deleting cache entry:", e); } } await reconcileConnectionsPage(fetchConnections, page); } } catch (error) { console.error("Error deleting connection:", error); } finally { setDeletingId(null); } }, [fetchConnections, page], ); const handleToggleConnectionActive = useCallback( async (id, isActive) => { setTogglingId(id); try { const res = await fetch(`/api/providers/${id}`, { method: "PUT", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ isActive }), }); if (res.ok) { setQuotaData((prev) => { const next = { ...prev }; return next; }); await reconcileConnectionsPage(fetchConnections, page); } } catch (error) { console.error("Error updating connection status:", error); } finally { setTogglingId(null); } }, [fetchConnections, page], ); const handleUpdateConnection = useCallback( async (formData) => { if (!selectedConnection?.id) return; const connectionId = selectedConnection.id; const provider = selectedConnection.provider; try { const res = await fetch(`/api/providers/${connectionId}`, { method: "PUT", headers: { "Content-Type": "application/json" }, body: JSON.stringify(formData), }); if (res.ok) { await fetchConnections(); setShowEditModal(false); setSelectedConnection(null); if (USAGE_SUPPORTED_PROVIDERS.includes(provider)) { await fetchQuota(connectionId, provider); } } } catch (error) { console.error("Error saving connection:", error); } }, [selectedConnection, fetchConnections, fetchQuota], ); useEffect(() => { let cancelled = false; fetch("/api/proxy-pools?isActive=true", { cache: "no-store" }) .then((res) => res.json()) .then((data) => { if (!cancelled && data?.proxyPools) { setProxyPools(data.proxyPools); } }) .catch(() => {}); return () => { cancelled = true; }; }, []); const refreshAll = useCallback(async (force = false) => { if (refreshingAll) return; setRefreshingAll(true); setCountdown(60); // Throttle Claude: poll its quota every Nth auto-tick (manual force bypasses) const tick = (tickCountRef.current += 1); const claudeEvery = Math.round(CLAUDE_REFRESH_INTERVAL_MS / REFRESH_INTERVAL_MS); const shouldFetch = (conn) => force || conn.provider !== "claude" || tick % claudeEvery === 0; try { const visibleConnections = await fetchConnections(page); setLoading(buildLoadingState(visibleConnections)); setErrors((prev) => filterQuotaStateByConnections(prev, visibleConnections), ); setQuotaData((prev) => filterQuotaStateByConnections(prev, visibleConnections), ); await Promise.all( visibleConnections .filter(shouldFetch) .map((conn) => fetchQuota(conn.id, conn.provider)), ); setLastUpdated(new Date()); } catch (error) { console.error("Error refreshing all providers:", error); } finally { setRefreshingAll(false); } }, [refreshingAll, fetchConnections, fetchQuota, page]); useEffect(() => { const initializeData = async () => { setConnectionsLoading(true); const visibleConnections = await fetchConnections(page); setConnectionsLoading(false); // Always fetch fresh quota on mount, no cache display setLoading(buildLoadingState(visibleConnections)); setErrors((prev) => filterQuotaStateByConnections(prev, visibleConnections), ); setQuotaData((prev) => filterQuotaStateByConnections(prev, visibleConnections), ); await Promise.all( visibleConnections.map((conn) => fetchQuota(conn.id, conn.provider)), ); setLastUpdated(new Date()); }; initializeData(); }, [fetchConnections, fetchQuota, page]); useEffect(() => { if (typeof window === "undefined") return; const stored = window.localStorage.getItem(AUTO_REFRESH_STORAGE_KEY); setAutoRefresh(stored === null ? true : stored === "true"); setHasHydratedAutoRefresh(true); }, []); // Persist auto-refresh preference useEffect(() => { if (typeof window === "undefined" || !hasHydratedAutoRefresh) return; window.localStorage.setItem(AUTO_REFRESH_STORAGE_KEY, String(autoRefresh)); }, [autoRefresh, hasHydratedAutoRefresh]); // Load Claude auto-ping per-connection map useEffect(() => { fetch("/api/settings", { cache: "no-store" }) .then((r) => (r.ok ? r.json() : {})) .then((s) => setAutoPingMap(s?.claudeAutoPing?.connections || {})) .catch(() => {}); }, []); const toggleAutoPing = useCallback(async (connectionId, on) => { const next = { ...autoPingMap, [connectionId]: on }; setAutoPingMap(next); try { const r = await fetch("/api/settings", { cache: "no-store" }); const s = r.ok ? await r.json() : {}; const cfg = { ...(s.claudeAutoPing || {}), connections: next }; await fetch("/api/settings", { method: "PATCH", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ claudeAutoPing: cfg }), }); } catch { setAutoPingMap(autoPingMap); } }, [autoPingMap]); // Auto-refresh interval useEffect(() => { if (!hasHydratedAutoRefresh || !autoRefresh) { if (intervalRef.current) { clearInterval(intervalRef.current); intervalRef.current = null; } if (countdownRef.current) { clearInterval(countdownRef.current); countdownRef.current = null; } return; } // Main refresh interval intervalRef.current = setInterval(() => { refreshAll(); }, REFRESH_INTERVAL_MS); // Countdown interval countdownRef.current = setInterval(() => { setCountdown((prev) => { if (prev <= 1) return 60; return prev - 1; }); }, 1000); return () => { if (intervalRef.current) clearInterval(intervalRef.current); if (countdownRef.current) clearInterval(countdownRef.current); }; }, [autoRefresh, refreshAll, hasHydratedAutoRefresh]); // Pause auto-refresh when tab is hidden (Page Visibility API) useEffect(() => { const handleVisibilityChange = () => { if (document.hidden) { if (intervalRef.current) { clearInterval(intervalRef.current); intervalRef.current = null; } if (countdownRef.current) { clearInterval(countdownRef.current); countdownRef.current = null; } } else if (autoRefresh && hasHydratedAutoRefresh) { // Resume auto-refresh when tab becomes visible intervalRef.current = setInterval(() => refreshAll(), REFRESH_INTERVAL_MS); countdownRef.current = setInterval(() => { setCountdown((prev) => (prev <= 1 ? 60 : prev - 1)); }, 1000); } }; document.addEventListener("visibilitychange", handleVisibilityChange); return () => { document.removeEventListener("visibilitychange", handleVisibilityChange); }; }, [autoRefresh, refreshAll, hasHydratedAutoRefresh]); const sortedConnections = useMemo( () => sortVisibleConnections( connections, quotaData, expiringFirst, providerFilter, quotaSortMode, ), [connections, quotaData, expiringFirst, providerFilter, quotaSortMode], ); // Connection is depleted when any quota entry hit the threshold const isConnectionDepleted = (conn) => { const quotas = quotaData[conn.id]?.quotas; if (!quotas?.length) return false; return quotas.some((q) => { if (!q.total || q.total <= 0) return false; return calculatePercentage(q.used, q.total) <= DEPLETED_QUOTA_THRESHOLD; }); }; const bulkSetActive = useCallback( async (targetIds, isActive) => { if (!targetIds.length || bulkToggling) return; setBulkToggling(true); try { await Promise.all( targetIds.map((id) => fetch(`/api/providers/${id}`, { method: "PUT", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ isActive }), }), ), ); await reconcileConnectionsPage(fetchConnections, page); } catch (error) { console.error("Error bulk toggling connections:", error); } finally { setBulkToggling(false); } }, [bulkToggling, fetchConnections, page], ); const handleDisableDepleted = () => { const ids = sortedConnections .filter((c) => (c.isActive ?? true) && isConnectionDepleted(c)) .map((c) => c.id); bulkSetActive(ids, false); }; const handleEnableAvailable = () => { const ids = sortedConnections .filter((c) => !(c.isActive ?? true) && !isConnectionDepleted(c)) .map((c) => c.id); bulkSetActive(ids, true); }; const selectedProviderLabel = providerFilter === "all" ? "All providers" : providerFilter; const hasEligibleConnections = totals.eligibleConnections > 0; const hasVisibleConnections = sortedConnections.length > 0; const emptyState = getConnectionsEmptyMessage( totals, providerFilter, accountFilter, ); const connectionsPageSummary = getConnectionsPaginationSummary(pagination); const isCustomPageSize = !ACCOUNT_PAGE_SIZE_OPTIONS.includes(pageSize); const pageSizeLabel = getPageSizeLabel(pageSize, isCustomPageSize); if (!connectionsLoading && !hasEligibleConnections) { return (
cloud_off

No Providers Connected

Connect to providers with OAuth to track your API quota limits and usage.

); } if (!connectionsLoading && !hasVisibleConnections) { return (
{emptyState.icon}

{emptyState.title}

{emptyState.description}

); } return (
{/* Header Controls */}
{providerMenuOpen && ( <>
{providerOptions.map((provider) => ( ))}
)}
{providerFilter === "codex" && ( )} {/* Bulk: disable depleted */} {/* Bulk: enable available */} {/* Auto-refresh toggle */} {/* Refresh all button */}
{/* Provider cards: 2 columns, compact */} {expiringFirst && (
Expiring-first currently reorders accounts inside the current page. Cross-page ordering still follows backend pagination.
)}
{sortedConnections.map((conn) => { const quota = quotaData[conn.id]; const isLoading = loading[conn.id]; const error = errors[conn.id]; // Use table layout for all providers const isInactive = conn.isActive === false; const isCodex = conn.provider === "codex"; const resetCreditCount = getCodexResetCreditCount(quota); const isResettingLimit = resettingLimitId === conn.id; const rowBusy = deletingId === conn.id || togglingId === conn.id || isResettingLimit; return (

{conn.provider}

{getConnectionLabel(conn) ? (

{getConnectionLabel(conn)}

) : null} {getConnectionSecondaryLabel(conn) ? (

{getConnectionSecondaryLabel(conn)}

) : null} {isCodex && (

Reset eligible: {resetCreditCount}

)} {conn.provider === "kiro" && (
{kiroMethodLabel(conn)} {kiroRegion(conn) && ( {kiroRegion(conn)} )} {isInactive ? "disabled" : conn.testStatus || "unknown"} {conn.providerSpecificData?.profileArn && ( )}
)}
{isCodex && (
0 ? "border-primary/30 bg-primary/5 text-primary" : "border-black/10 bg-black/[0.02] text-text-muted dark:border-white/10 dark:bg-white/[0.03]" }`} > restart_alt {resetCreditCount}
)} {isCodex && resetCreditCount > 0 && ( )} {conn.provider === "claude" && conn.authType === "oauth" && ( )}
handleToggleConnectionActive(conn.id, nextActive) } />
{isLoading ? (
progress_activity
) : error ? (
error

{error}

) : quota?.message ? (

{quota.message}

) : ( )}
); })}
{connectionsPageSummary}
setCustomPageSizeInput(event.target.value)} onBlur={() => { const parsedValue = Number.parseInt(customPageSizeInput, 10); if (!Number.isFinite(parsedValue)) { setCustomPageSizeInput(String(pageSize)); return; } const nextPageSize = Math.min(ACCOUNT_PAGE_SIZE_MAX, Math.max(1, parsedValue)); setPage(1); setPageSize(nextPageSize); setCustomPageSizeInput(String(nextPageSize)); }} onKeyDown={(event) => { if (event.key !== "Enter") return; const parsedValue = Number.parseInt(customPageSizeInput, 10); if (!Number.isFinite(parsedValue)) { setCustomPageSizeInput(String(pageSize)); return; } const nextPageSize = Math.min(ACCOUNT_PAGE_SIZE_MAX, Math.max(1, parsedValue)); setPage(1); setPageSize(nextPageSize); setCustomPageSizeInput(String(nextPageSize)); }} className="h-8 w-20 rounded-lg border border-black/10 bg-black/[0.02] px-2 text-xs text-text-primary outline-none transition-colors hover:bg-black/5 dark:border-white/10 dark:bg-white/[0.03] dark:hover:bg-white/10" aria-label="Custom accounts per page" placeholder="Custom" /> Page {pagination.page} / {pagination.totalPages}
{ if (!resettingLimitId) setResetConfirmState(null); }} onConfirm={async () => { const connection = resetConfirmState?.connection; if (!connection) return; await handleResetCodexLimit(connection.id, connection.provider); setResetConfirmState(null); }} title="Reset Codex limit?" message={`Use 1 Codex reset credit for ${getConnectionLabel(resetConfirmState?.connection || {}) || "this account"}. This cannot be undone. Remaining credits: ${resetConfirmState?.resetCreditCount ?? 0}.`} confirmText="Reset limit" cancelText="Cancel" variant="danger" loading={Boolean(resettingLimitId)} /> { setShowEditModal(false); setSelectedConnection(null); }} />
); }