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:
Nelson Pires
2026-02-14 23:56:42 +02:00
committed by GitHub
parent 08f6bef405
commit e3deaeb314
4 changed files with 226 additions and 0 deletions
+112
View File
@@ -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':