Files

119 lines
3.1 KiB
JavaScript
Raw Permalink Normal View History

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')
});
}
};