import { readAuthFile } from '../../opencode/auth.js'; import { getAuthEntry, normalizeAuthEntry, buildResult, toUsageWindow, toNumber, formatMoney, asObject, asNonEmptyString } from '../utils/index.js'; export const providerId = 'hyper'; export const providerName = 'Charm Hyper'; export const aliases = ['hyper']; const HYPER_QUOTA_URL = 'https://hyper.charm.land/v1/credits'; const CREDIT_TO_USD = 0.05; const getApiKey = (auth) => { const entry = normalizeAuthEntry(getAuthEntry(auth, aliases)); return asNonEmptyString(entry?.key) ?? asNonEmptyString(entry?.token); }; export const isConfigured = (auth = readAuthFile()) => Boolean(getApiKey(auth)); export const fetchQuota = async ({ readAuth = readAuthFile, fetchImpl = fetch } = {}) => { const apiKey = getApiKey(readAuth()); if (!apiKey) { return buildResult({ providerId, providerName, ok: false, configured: false, error: 'Not configured' }); } const timeoutSignal = AbortSignal.timeout(15_000); try { const response = await fetchImpl(HYPER_QUOTA_URL, { method: 'GET', headers: { Authorization: `Bearer ${apiKey}`, 'Accept-Encoding': 'identity' }, signal: timeoutSignal }); if (!response.ok) { return buildResult({ providerId, providerName, ok: false, configured: true, error: response.status === 401 || response.status === 403 ? 'Session expired — please re-authenticate with Charm Hyper' : `API error: ${response.status}` }); } const payload = asObject(await response.json()); const rawBalance = payload?.balance; const balance = toNumber(asNonEmptyString(rawBalance) ?? (Number.isFinite(rawBalance) ? rawBalance : null)); if (balance === null) { return buildResult({ providerId, providerName, ok: false, configured: true, error: 'No quota data in response' }); } const creditsLabel = Number.isInteger(balance) ? String(balance) : formatMoney(balance); const windows = { credits_balance: toUsageWindow({ usedPercent: null, windowSeconds: null, resetAt: null, valueLabel: `$${formatMoney(balance * CREDIT_TO_USD)}` }), credits: toUsageWindow({ usedPercent: null, windowSeconds: null, resetAt: null, valueLabel: creditsLabel }) }; return buildResult({ providerId, providerName, ok: true, configured: true, usage: { windows } }); } catch (error) { const isTimeout = error instanceof DOMException && ( error.name === 'TimeoutError' || (error.name === 'AbortError' && timeoutSignal.aborted) ); const isParseError = error instanceof SyntaxError; return buildResult({ providerId, providerName, ok: false, configured: true, error: isTimeout ? 'Request timed out' : isParseError ? 'Invalid response from provider' : (error instanceof Error ? error.message : 'Request failed') }); } };