feat(quota): add NanoGPT quota provider support (#424)
* feat: add NanoGPT quota provider and usage fetch Detect NanoGPT as a configured quota provider. Expose daily and monthly usage windows with reset times. Return configured and ok state based on API key presence. * feat: fetch NanoGPT quota and detect config Detect NanoGPT in configured providers from auth. Fetch and expose daily and monthly usage windows for NanoGPT. * feat: add NanoGPT quota provider option
This commit is contained in:
@@ -200,6 +200,11 @@ export const listConfiguredQuotaProviders = () => {
|
||||
configured.add('openrouter');
|
||||
}
|
||||
|
||||
const nanopgAuth = normalizeAuthEntry(getAuthEntry(auth, ['nano-gpt', 'nanogpt', 'nano_gpt']));
|
||||
if (nanopgAuth?.key || nanopgAuth?.token) {
|
||||
configured.add('nano-gpt');
|
||||
}
|
||||
|
||||
const copilotAuth = normalizeAuthEntry(getAuthEntry(auth, ['github-copilot', 'copilot']));
|
||||
if (copilotAuth?.access || copilotAuth?.token) {
|
||||
configured.add('github-copilot');
|
||||
@@ -1195,6 +1200,111 @@ export const fetchZaiQuota = async () => {
|
||||
}
|
||||
};
|
||||
|
||||
const NANO_GPT_DAILY_WINDOW_SECONDS = 86400;
|
||||
|
||||
export const fetchNanoGptQuota = async () => {
|
||||
const auth = readAuthFile();
|
||||
const entry = normalizeAuthEntry(getAuthEntry(auth, ['nano-gpt', 'nanogpt', 'nano_gpt']));
|
||||
const apiKey = entry?.key ?? entry?.token;
|
||||
|
||||
if (!apiKey) {
|
||||
return buildResult({
|
||||
providerId: 'nano-gpt',
|
||||
providerName: 'NanoGPT',
|
||||
ok: false,
|
||||
configured: false,
|
||||
error: 'Not configured'
|
||||
});
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch('https://nano-gpt.com/api/subscription/v1/usage', {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
Authorization: `Bearer ${apiKey}`,
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
return buildResult({
|
||||
providerId: 'nano-gpt',
|
||||
providerName: 'NanoGPT',
|
||||
ok: false,
|
||||
configured: true,
|
||||
error: `API error: ${response.status}`
|
||||
});
|
||||
}
|
||||
|
||||
const payload = await response.json();
|
||||
const windows = {};
|
||||
const period = payload?.period ?? null;
|
||||
const daily = payload?.daily ?? null;
|
||||
const monthly = payload?.monthly ?? null;
|
||||
const state = payload?.state ?? 'active';
|
||||
|
||||
if (daily) {
|
||||
let usedPercent = null;
|
||||
const percentUsed = daily?.percentUsed;
|
||||
if (typeof percentUsed === 'number') {
|
||||
usedPercent = Math.max(0, Math.min(100, percentUsed * 100));
|
||||
} else {
|
||||
const used = toNumber(daily?.used);
|
||||
const limit = toNumber(daily?.limit ?? daily?.limits?.daily);
|
||||
if (used !== null && limit !== null && limit > 0) {
|
||||
usedPercent = Math.max(0, Math.min(100, (used / limit) * 100));
|
||||
}
|
||||
}
|
||||
const resetAt = toTimestamp(daily?.resetAt);
|
||||
const valueLabel = state !== 'active' ? `(${state})` : null;
|
||||
windows['daily'] = toUsageWindow({
|
||||
usedPercent,
|
||||
windowSeconds: NANO_GPT_DAILY_WINDOW_SECONDS,
|
||||
resetAt,
|
||||
valueLabel
|
||||
});
|
||||
}
|
||||
|
||||
if (monthly) {
|
||||
let usedPercent = null;
|
||||
const percentUsed = monthly?.percentUsed;
|
||||
if (typeof percentUsed === 'number') {
|
||||
usedPercent = Math.max(0, Math.min(100, percentUsed * 100));
|
||||
} else {
|
||||
const used = toNumber(monthly?.used);
|
||||
const limit = toNumber(monthly?.limit ?? monthly?.limits?.monthly);
|
||||
if (used !== null && limit !== null && limit > 0) {
|
||||
usedPercent = Math.max(0, Math.min(100, (used / limit) * 100));
|
||||
}
|
||||
}
|
||||
const resetAt = toTimestamp(monthly?.resetAt ?? period?.currentPeriodEnd);
|
||||
const valueLabel = state !== 'active' ? `(${state})` : null;
|
||||
windows['monthly'] = toUsageWindow({
|
||||
usedPercent,
|
||||
windowSeconds: null,
|
||||
resetAt,
|
||||
valueLabel
|
||||
});
|
||||
}
|
||||
|
||||
return buildResult({
|
||||
providerId: 'nano-gpt',
|
||||
providerName: 'NanoGPT',
|
||||
ok: true,
|
||||
configured: true,
|
||||
usage: { windows }
|
||||
});
|
||||
} catch (error) {
|
||||
return buildResult({
|
||||
providerId: 'nano-gpt',
|
||||
providerName: 'NanoGPT',
|
||||
ok: false,
|
||||
configured: true,
|
||||
error: error instanceof Error ? error.message : 'Request failed'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
export const fetchQuotaForProvider = async (providerId) => {
|
||||
switch (providerId) {
|
||||
case 'claude':
|
||||
@@ -1209,6 +1319,8 @@ export const fetchQuotaForProvider = async (providerId) => {
|
||||
return fetchGoogleQuota();
|
||||
case 'kimi-for-coding':
|
||||
return fetchKimiQuota();
|
||||
case 'nano-gpt':
|
||||
return fetchNanoGptQuota();
|
||||
case 'openrouter':
|
||||
return fetchOpenRouterQuota();
|
||||
case 'zai-coding-plan':
|
||||
|
||||
Reference in New Issue
Block a user