/** * Claude usage handler */ import { proxyAwareFetch } from "../../utils/proxyFetch.js"; import { ANTHROPIC_API_VERSION } from "../../providers/shared.js"; import { U, parseResetTime } from "./shared.js"; // Claude API config (urls from registry, apiVersion is header logic kept here) const CLAUDE_CONFIG = { oauthUsageUrl: U("claude").oauthUrl, usageUrl: U("claude").orgUrl, settingsUrl: U("claude").settingsUrl, apiVersion: ANTHROPIC_API_VERSION, }; /** * Claude Usage - Primary: OAuth endpoint, Fallback: legacy settings/org endpoint */ export async function getClaudeUsage(accessToken, proxyOptions = null) { try { // Primary: OAuth usage endpoint (Claude Code consumer OAuth tokens) const oauthResponse = await proxyAwareFetch(CLAUDE_CONFIG.oauthUsageUrl, { method: "GET", headers: { "Authorization": `Bearer ${accessToken}`, "anthropic-beta": "oauth-2025-04-20", "anthropic-version": CLAUDE_CONFIG.apiVersion, }, }, proxyOptions); if (oauthResponse.ok) { const data = await oauthResponse.json(); const quotas = {}; // utilization = % USED (e.g. 87 means 87% used, 13% remaining) const hasUtilization = (window) => window && typeof window === "object" && typeof window.utilization === "number"; const createQuotaObject = (window) => { const used = window.utilization; const remaining = Math.max(0, 100 - used); return { used, total: 100, remaining, remainingPercentage: remaining, resetAt: parseResetTime(window.resets_at), unlimited: false, }; }; if (hasUtilization(data.five_hour)) { quotas["session (5h)"] = createQuotaObject(data.five_hour); } if (hasUtilization(data.seven_day)) { quotas["weekly (7d)"] = createQuotaObject(data.seven_day); } // Parse model-specific weekly windows (e.g. seven_day_sonnet, seven_day_opus) for (const [key, value] of Object.entries(data)) { if (key.startsWith("seven_day_") && key !== "seven_day" && hasUtilization(value)) { const modelName = key.replace("seven_day_", ""); quotas[`weekly ${modelName} (7d)`] = createQuotaObject(value); } } return { plan: "Claude Code", extraUsage: data.extra_usage ?? null, quotas, }; } // Fallback: legacy settings + org usage endpoint console.warn(`[Claude Usage] OAuth endpoint returned ${oauthResponse.status}, falling back to legacy`); return await getClaudeUsageLegacy(accessToken, proxyOptions); } catch (error) { return { message: `Claude connected. Unable to fetch usage: ${error.message}` }; } } /** * Legacy Claude usage for API key / org admin users */ async function getClaudeUsageLegacy(accessToken, proxyOptions = null) { try { const settingsResponse = await proxyAwareFetch(CLAUDE_CONFIG.settingsUrl, { method: "GET", headers: { "Authorization": `Bearer ${accessToken}`, "anthropic-version": CLAUDE_CONFIG.apiVersion, }, }, proxyOptions); if (settingsResponse.ok) { const settings = await settingsResponse.json(); if (settings.organization_id) { const usageResponse = await proxyAwareFetch( CLAUDE_CONFIG.usageUrl.replace("{org_id}", settings.organization_id), { method: "GET", headers: { "Authorization": `Bearer ${accessToken}`, "anthropic-version": CLAUDE_CONFIG.apiVersion, }, }, proxyOptions ); if (usageResponse.ok) { const usage = await usageResponse.json(); return { plan: settings.plan || "Unknown", organization: settings.organization_name, quotas: usage, }; } } return { plan: settings.plan || "Unknown", organization: settings.organization_name, message: "Claude connected. Usage details require admin access.", }; } return { message: "Claude connected. Usage API requires admin permissions." }; } catch (error) { return { message: `Claude connected. Unable to fetch usage: ${error.message}` }; } }