119 lines
3.2 KiB
JavaScript
119 lines
3.2 KiB
JavaScript
import { readAuthFile } from '../../opencode/auth.js';
|
|
import {
|
|
getAuthEntry,
|
|
normalizeAuthEntry,
|
|
buildResult,
|
|
toUsageWindow,
|
|
toNumber,
|
|
formatMoney
|
|
} from '../utils/index.js';
|
|
|
|
export const providerId = 'deepseek';
|
|
export const providerName = 'DeepSeek';
|
|
const aliases = ['deepseek'];
|
|
const DEEPSEEK_QUOTA_URL = 'https://api.deepseek.com/user/balance';
|
|
|
|
export const isConfigured = () => {
|
|
const auth = readAuthFile();
|
|
const entry = normalizeAuthEntry(getAuthEntry(auth, aliases));
|
|
return Boolean(entry?.key || entry?.token);
|
|
};
|
|
|
|
export const fetchQuota = async () => {
|
|
const auth = readAuthFile();
|
|
const entry = normalizeAuthEntry(getAuthEntry(auth, aliases));
|
|
const apiKey = entry?.key ?? entry?.token;
|
|
|
|
if (!apiKey) {
|
|
return buildResult({
|
|
providerId,
|
|
providerName,
|
|
ok: false,
|
|
configured: false,
|
|
error: 'Not configured'
|
|
});
|
|
}
|
|
|
|
const timeoutSignal = AbortSignal.timeout(15_000);
|
|
|
|
try {
|
|
const response = await fetch(DEEPSEEK_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 DeepSeek'
|
|
: `API error: ${response.status}`
|
|
});
|
|
}
|
|
|
|
const payload = await response.json();
|
|
const balanceInfos = Array.isArray(payload?.balance_infos) ? payload.balance_infos : [];
|
|
const balanceInfo = balanceInfos.find((info) => info?.currency === 'USD')
|
|
?? balanceInfos.find((info) => info?.currency === 'CNY')
|
|
?? null;
|
|
const rawBalance = balanceInfo?.total_balance;
|
|
const totalBalance = (typeof rawBalance === 'number' || (typeof rawBalance === 'string' && rawBalance.trim() !== ''))
|
|
? toNumber(rawBalance)
|
|
: null;
|
|
|
|
if (totalBalance === null) {
|
|
return buildResult({
|
|
providerId,
|
|
providerName,
|
|
ok: false,
|
|
configured: true,
|
|
error: 'No quota data in response'
|
|
});
|
|
}
|
|
|
|
const isCny = balanceInfo?.currency === 'CNY';
|
|
const symbol = isCny ? '¥' : '$';
|
|
const valueLabel = `${symbol}${formatMoney(totalBalance)}`;
|
|
|
|
const windows = {
|
|
credits_balance: toUsageWindow({
|
|
usedPercent: null,
|
|
windowSeconds: null,
|
|
resetAt: null,
|
|
valueLabel
|
|
})
|
|
};
|
|
|
|
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')
|
|
});
|
|
}
|
|
};
|