Files
openchamber/packages/vscode/src/quotaProviders.ts
T
gsxdsm a264393f1b feat: extend quota providers and add usage dropdown (#254) (#284)
* feat: extend quota providers and add usage dropdown

Add new quota providers:
- Claude (Anthropic API)
- Codex (OpenAI/ChatGPT)
- GitHub Copilot (base + add-on)
- Kimi for Coding
- OpenRouter

UI enhancements:
- Add rate limits dropdown in header with timer icon
- Desktop: sticky header with Used/Remaining toggle, refresh button
- Mobile: full-screen dropdown with same controls
- Per-provider sticky headers with provider logos
- Auto-refresh on open if no data exists

Usage settings page:
- Provider icon next to header
- Show in dropdown toggle per provider
- Global display selector (Usage vs Quota remaining)
- Auto-refresh controls in sidebar

Settings persistence:
- Add usageDisplayMode and usageDropdownProviders settings
- Server-side sanitization for new settings

Provider logo aliases:
- codex -> openai, claude -> anthropic

Bug fixes:
- Fix React error #310 by calling hooks before early returns
- Add aria-describedby to SettingsWindow dialog
- Remove horizontal scroll from dropdown

* fix: Remove duplicate github copilot declarations
2026-02-04 11:13:23 +02:00

1103 lines
33 KiB
TypeScript

import fs from 'node:fs';
import path from 'node:path';
import os from 'node:os';
type AuthEntry = Record<string, unknown> | string;
type AuthFile = Record<string, AuthEntry>;
type UsageWindow = {
usedPercent: number | null;
remainingPercent: number | null;
windowSeconds: number | null;
resetAfterSeconds: number | null;
resetAt: number | null;
resetAtFormatted: string | null;
resetAfterFormatted: string | null;
valueLabel?: string | null;
};
type ProviderUsage = {
windows: Record<string, UsageWindow>;
models?: Record<string, ProviderUsage>;
};
type OpenAiUsagePayload = {
rate_limit?: {
primary_window?: {
used_percent?: number;
limit_window_seconds?: number;
reset_at?: number;
};
secondary_window?: {
used_percent?: number;
limit_window_seconds?: number;
reset_at?: number;
};
};
credits?: {
balance?: number | string;
unlimited?: boolean;
};
};
type GoogleModelsPayload = {
models?: Record<string, {
quotaInfo?: {
remainingFraction?: number;
resetTime?: string;
};
}>;
};
type ZaiLimit = {
type?: string;
number?: number;
unit?: number;
nextResetTime?: number;
percentage?: number;
};
type ZaiPayload = {
data?: {
limits?: ZaiLimit[];
};
};
export type ProviderResult = {
providerId: string;
providerName: string;
ok: boolean;
configured: boolean;
usage: ProviderUsage | null;
fetchedAt: number;
error?: string;
};
const OPENCODE_CONFIG_DIR = path.join(os.homedir(), '.config', 'opencode');
const OPENCODE_DATA_DIR = path.join(os.homedir(), '.local', 'share', 'opencode');
const AUTH_FILE = path.join(OPENCODE_DATA_DIR, 'auth.json');
const ANTIGRAVITY_ACCOUNTS_PATHS = [
path.join(OPENCODE_CONFIG_DIR, 'antigravity-accounts.json'),
path.join(OPENCODE_DATA_DIR, 'antigravity-accounts.json'),
];
const GOOGLE_CLIENT_ID =
'1071006060591-tmhssin2h21lcre235vtolojh4g403ep.apps.googleusercontent.com';
const GOOGLE_CLIENT_SECRET = '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';
const DEFAULT_PROJECT_ID = 'rising-fact-p41fc';
const GOOGLE_WINDOW_SECONDS = 5 * 60 * 60;
const GOOGLE_ENDPOINTS = [
'https://daily-cloudcode-pa.sandbox.googleapis.com',
'https://autopush-cloudcode-pa.sandbox.googleapis.com',
'https://cloudcode-pa.googleapis.com',
];
const GOOGLE_HEADERS = {
'User-Agent': 'antigravity/1.11.5 windows/amd64',
'X-Goog-Api-Client': 'google-cloud-sdk vscode_cloudshelleditor/0.1',
'Client-Metadata':
'{"ideType":"IDE_UNSPECIFIED","platform":"PLATFORM_UNSPECIFIED","pluginType":"GEMINI"}',
};
const ZAI_TOKEN_WINDOW_SECONDS: Record<number, number> = { 3: 3600 };
const readAuthFile = (): AuthFile => {
if (!fs.existsSync(AUTH_FILE)) {
return {};
}
try {
const content = fs.readFileSync(AUTH_FILE, 'utf8');
const trimmed = content.trim();
if (!trimmed) {
return {};
}
return JSON.parse(trimmed) as AuthFile;
} catch (error) {
console.error('Failed to read auth file:', error);
throw new Error('Failed to read OpenCode auth configuration');
}
};
const readJsonFile = (filePath: string): Record<string, unknown> | null => {
if (!fs.existsSync(filePath)) {
return null;
}
try {
const raw = fs.readFileSync(filePath, 'utf8');
const trimmed = raw.trim();
if (!trimmed) return null;
const parsed = JSON.parse(trimmed);
if (!parsed || typeof parsed !== 'object') return null;
return parsed as Record<string, unknown>;
} catch (error) {
console.warn(`Failed to read JSON file: ${filePath}`, error);
return null;
}
};
const getAuthEntry = (auth: AuthFile, aliases: string[]) => {
for (const alias of aliases) {
if (auth[alias]) {
return auth[alias];
}
}
return null;
};
const normalizeAuthEntry = (entry: AuthEntry | null) => {
if (!entry) return null;
if (typeof entry === 'string') {
return { token: entry } as Record<string, unknown>;
}
if (typeof entry === 'object') {
return entry;
}
return null;
};
const toNumber = (value: unknown): number | null => {
if (typeof value === 'number' && Number.isFinite(value)) {
return value;
}
if (typeof value === 'string') {
const parsed = Number(value);
return Number.isFinite(parsed) ? parsed : null;
}
return null;
};
const toTimestamp = (value: unknown): number | null => {
if (!value) return null;
if (typeof value === 'number') {
return value < 1_000_000_000_000 ? value * 1000 : value;
}
if (typeof value === 'string') {
const parsed = Date.parse(value);
return Number.isNaN(parsed) ? null : parsed;
}
return null;
};
const formatResetTime = (timestamp: number) => {
try {
const resetDate = new Date(timestamp);
const now = new Date();
const isToday = resetDate.toDateString() === now.toDateString();
if (isToday) {
// Same day: show time only (e.g., "9:56 PM")
return resetDate.toLocaleTimeString(undefined, {
hour: 'numeric',
minute: '2-digit',
});
}
// Different day: show date + weekday + time (e.g., "Feb 2, Sun 9:56 PM")
return resetDate.toLocaleString(undefined, {
month: 'short',
day: 'numeric',
weekday: 'short',
hour: 'numeric',
minute: '2-digit',
});
} catch {
return null;
}
};
const calculateResetAfterSeconds = (resetAt: number | null) => {
if (!resetAt) return null;
const delta = Math.floor((resetAt - Date.now()) / 1000);
return delta < 0 ? 0 : delta;
};
const toUsageWindow = (data: { usedPercent: number | null; windowSeconds: number | null; resetAt: number | null; valueLabel?: string | null }) => {
const resetAfterSeconds = calculateResetAfterSeconds(data.resetAt);
const resetFormatted = data.resetAt ? formatResetTime(data.resetAt) : null;
return {
usedPercent: data.usedPercent,
remainingPercent: data.usedPercent !== null ? Math.max(0, 100 - data.usedPercent) : null,
windowSeconds: data.windowSeconds ?? null,
resetAfterSeconds,
resetAt: data.resetAt,
resetAtFormatted: resetFormatted,
resetAfterFormatted: resetFormatted,
...(data.valueLabel ? { valueLabel: data.valueLabel } : {}),
} satisfies UsageWindow;
};
const buildResult = (data: {
providerId: string;
providerName: string;
ok: boolean;
configured: boolean;
usage?: ProviderUsage | null;
error?: string;
}): ProviderResult => ({
providerId: data.providerId,
providerName: data.providerName,
ok: data.ok,
configured: data.configured,
usage: data.usage ?? null,
...(data.error ? { error: data.error } : {}),
fetchedAt: Date.now(),
});
const formatMoney = (value: number | null) => {
if (value === null || !Number.isFinite(value)) return null;
return value.toFixed(2);
};
const durationToLabel = (duration?: number, unit?: string) => {
if (!duration || !unit) return 'limit';
if (unit === 'TIME_UNIT_MINUTE') return `${duration}m`;
if (unit === 'TIME_UNIT_HOUR') return `${duration}h`;
if (unit === 'TIME_UNIT_DAY') return `${duration}d`;
return 'limit';
};
const durationToSeconds = (duration?: number, unit?: string) => {
if (!duration || !unit) return null;
if (unit === 'TIME_UNIT_MINUTE') return duration * 60;
if (unit === 'TIME_UNIT_HOUR') return duration * 3600;
if (unit === 'TIME_UNIT_DAY') return duration * 86400;
return null;
};
export const listConfiguredQuotaProviders = () => {
const auth = readAuthFile();
const configured = new Set<string>();
const anthropicAuth = normalizeAuthEntry(getAuthEntry(auth, ['anthropic', 'claude']));
if (anthropicAuth && ((anthropicAuth as Record<string, unknown>).access || (anthropicAuth as Record<string, unknown>).token)) {
configured.add('claude');
}
const openaiAuth = normalizeAuthEntry(getAuthEntry(auth, ['openai', 'codex', 'chatgpt']));
if (openaiAuth && ((openaiAuth as Record<string, unknown>).access || (openaiAuth as Record<string, unknown>).token)) {
configured.add('codex');
}
const googleAuth = normalizeAuthEntry(getAuthEntry(auth, ['google', 'antigravity']));
if (googleAuth && ((googleAuth as Record<string, unknown>).access || (googleAuth as Record<string, unknown>).token || (googleAuth as Record<string, unknown>).refresh)) {
configured.add('google');
}
const zaiAuth = normalizeAuthEntry(getAuthEntry(auth, ['zai-coding-plan', 'zai', 'z.ai']));
if (zaiAuth && ((zaiAuth as Record<string, unknown>).key || (zaiAuth as Record<string, unknown>).token)) {
configured.add('zai-coding-plan');
}
const kimiAuth = normalizeAuthEntry(getAuthEntry(auth, ['kimi-for-coding', 'kimi']));
if (kimiAuth && ((kimiAuth as Record<string, unknown>).key || (kimiAuth as Record<string, unknown>).token)) {
configured.add('kimi-for-coding');
}
const openrouterAuth = normalizeAuthEntry(getAuthEntry(auth, ['openrouter']));
if (openrouterAuth && ((openrouterAuth as Record<string, unknown>).key || (openrouterAuth as Record<string, unknown>).token)) {
configured.add('openrouter');
}
const copilotAuth = normalizeAuthEntry(getAuthEntry(auth, ['github-copilot', 'copilot']));
if (copilotAuth && ((copilotAuth as Record<string, unknown>).access || (copilotAuth as Record<string, unknown>).token)) {
configured.add('github-copilot');
configured.add('github-copilot-addon');
}
for (const filePath of ANTIGRAVITY_ACCOUNTS_PATHS) {
const data = readJsonFile(filePath);
const accounts = data?.accounts;
if (Array.isArray(accounts) && accounts.length > 0) {
configured.add('google');
break;
}
}
return Array.from(configured);
};
export const fetchCodexQuota = async (): Promise<ProviderResult> => {
const auth = readAuthFile();
const entry = normalizeAuthEntry(getAuthEntry(auth, ['openai', 'codex', 'chatgpt'])) as Record<string, unknown> | null;
const accessToken = (entry?.access as string | undefined) ?? (entry?.token as string | undefined);
const accountId = entry?.accountId as string | undefined;
if (!accessToken) {
return buildResult({
providerId: 'codex',
providerName: 'Codex',
ok: false,
configured: false,
error: 'Not configured',
});
}
try {
const response = await fetch('https://chatgpt.com/backend-api/wham/usage', {
method: 'GET',
headers: {
Authorization: `Bearer ${accessToken}`,
'Content-Type': 'application/json',
...(accountId ? { 'ChatGPT-Account-Id': accountId } : {}),
},
});
if (!response.ok) {
return buildResult({
providerId: 'codex',
providerName: 'Codex',
ok: false,
configured: true,
error: `API error: ${response.status}`,
});
}
const payload = await response.json() as OpenAiUsagePayload;
const primary = payload?.rate_limit?.primary_window ?? null;
const secondary = payload?.rate_limit?.secondary_window ?? null;
const credits = payload?.credits ?? null;
const windows: Record<string, UsageWindow> = {};
if (primary) {
windows['5h'] = toUsageWindow({
usedPercent: toNumber(primary.used_percent),
windowSeconds: toNumber(primary.limit_window_seconds),
resetAt: toTimestamp(primary.reset_at),
});
}
if (secondary) {
windows['weekly'] = toUsageWindow({
usedPercent: toNumber(secondary.used_percent),
windowSeconds: toNumber(secondary.limit_window_seconds),
resetAt: toTimestamp(secondary.reset_at),
});
}
if (credits) {
const balance = toNumber(credits.balance);
const unlimited = Boolean(credits.unlimited);
const valueLabel = unlimited
? 'Unlimited'
: balance !== null
? `$${formatMoney(balance)} remaining`
: null;
windows.credits = toUsageWindow({
usedPercent: null,
windowSeconds: null,
resetAt: null,
valueLabel,
});
}
return buildResult({
providerId: 'codex',
providerName: 'Codex',
ok: true,
configured: true,
usage: { windows },
});
} catch (error) {
return buildResult({
providerId: 'codex',
providerName: 'Codex',
ok: false,
configured: true,
error: error instanceof Error ? error.message : 'Request failed',
});
}
};
const resolveGoogleAuth = () => {
const auth = readAuthFile();
const entry = normalizeAuthEntry(getAuthEntry(auth, ['google', 'antigravity'])) as Record<string, unknown> | null;
if (entry) {
const accessToken = (entry.access as string | undefined) ?? (entry.token as string | undefined);
let refreshToken = entry.refresh as string | undefined;
let projectId: string | undefined;
if (refreshToken && refreshToken.includes('|')) {
const [first, second] = refreshToken.split('|');
refreshToken = first;
projectId = second;
}
return {
accessToken,
refreshToken,
expires: entry.expires as number | undefined,
projectId,
};
}
for (const filePath of ANTIGRAVITY_ACCOUNTS_PATHS) {
const data = readJsonFile(filePath);
const accounts = data?.accounts;
if (Array.isArray(accounts) && accounts.length > 0) {
const index = typeof (data as Record<string, unknown>)?.activeIndex === 'number'
? (data as Record<string, unknown>).activeIndex as number
: 0;
const account = (accounts[index] as Record<string, unknown> | undefined) ?? (accounts[0] as Record<string, unknown> | undefined);
if (account?.refreshToken) {
return {
refreshToken: account.refreshToken as string,
projectId: (account.projectId as string | undefined) ?? (account.managedProjectId as string | undefined),
email: account.email as string | undefined,
};
}
}
}
return null;
};
const refreshGoogleAccessToken = async (refreshToken: string) => {
const response = await fetch('https://oauth2.googleapis.com/token', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({
client_id: GOOGLE_CLIENT_ID,
client_secret: GOOGLE_CLIENT_SECRET,
refresh_token: refreshToken,
grant_type: 'refresh_token',
}),
});
if (!response.ok) {
return null;
}
const data = await response.json() as Record<string, unknown>;
return typeof data?.access_token === 'string' ? data.access_token : null;
};
const fetchGoogleModels = async (accessToken: string, projectId?: string) => {
const body = projectId ? { project: projectId } : {};
for (const endpoint of GOOGLE_ENDPOINTS) {
const controller = typeof AbortController !== 'undefined' ? new AbortController() : null;
const timeout = controller ? setTimeout(() => controller.abort(), 15000) : null;
try {
const response = await fetch(`${endpoint}/v1internal:fetchAvailableModels`, {
method: 'POST',
headers: {
Authorization: `Bearer ${accessToken}`,
'Content-Type': 'application/json',
...GOOGLE_HEADERS,
},
body: JSON.stringify(body),
signal: controller?.signal,
});
if (response.ok) {
return await response.json() as Record<string, unknown>;
}
} catch {
// fall through
} finally {
if (timeout) {
clearTimeout(timeout);
}
}
}
return null;
};
export const fetchGoogleQuota = async (): Promise<ProviderResult> => {
const auth = resolveGoogleAuth();
if (!auth) {
return buildResult({
providerId: 'google',
providerName: 'Google',
ok: false,
configured: false,
error: 'Not configured',
});
}
const now = Date.now();
let accessToken: string | undefined = auth.accessToken;
if (!accessToken || (typeof auth.expires === 'number' && auth.expires <= now)) {
if (!auth.refreshToken) {
return buildResult({
providerId: 'google',
providerName: 'Google',
ok: false,
configured: true,
error: 'Missing refresh token',
});
}
accessToken = (await refreshGoogleAccessToken(auth.refreshToken)) ?? undefined;
}
if (!accessToken) {
return buildResult({
providerId: 'google',
providerName: 'Google',
ok: false,
configured: true,
error: 'Failed to refresh OAuth token',
});
}
const projectId = auth.projectId ?? DEFAULT_PROJECT_ID;
const payload = await fetchGoogleModels(accessToken, projectId);
if (!payload || typeof payload !== 'object') {
return buildResult({
providerId: 'google',
providerName: 'Google',
ok: false,
configured: true,
error: 'Failed to fetch models',
});
}
const models: Record<string, ProviderUsage> = {};
const payloadModels = (payload as GoogleModelsPayload).models ?? {};
for (const [modelName, modelData] of Object.entries(payloadModels)) {
const quotaInfo = modelData?.quotaInfo;
const remainingFraction = quotaInfo?.remainingFraction;
const remainingPercent = typeof remainingFraction === 'number'
? Math.round(remainingFraction * 100)
: null;
const usedPercent = remainingPercent !== null ? Math.max(0, 100 - remainingPercent) : null;
const resetAt = quotaInfo?.resetTime
? new Date(quotaInfo.resetTime).getTime()
: null;
models[modelName] = {
windows: {
'5h': toUsageWindow({
usedPercent,
windowSeconds: GOOGLE_WINDOW_SECONDS,
resetAt,
}),
},
};
}
return buildResult({
providerId: 'google',
providerName: 'Google',
ok: true,
configured: true,
usage: {
windows: {},
models: Object.keys(models).length ? models : undefined,
},
});
};
export const fetchClaudeQuota = async (): Promise<ProviderResult> => {
const auth = readAuthFile();
const entry = normalizeAuthEntry(getAuthEntry(auth, ['anthropic', 'claude'])) as Record<string, unknown> | null;
const accessToken = (entry?.access as string | undefined) ?? (entry?.token as string | undefined);
if (!accessToken) {
return buildResult({
providerId: 'claude',
providerName: 'Claude',
ok: false,
configured: false,
error: 'Not configured',
});
}
try {
const response = await fetch('https://api.anthropic.com/api/oauth/usage', {
method: 'GET',
headers: {
Authorization: `Bearer ${accessToken}`,
'anthropic-beta': 'oauth-2025-04-20',
},
});
if (!response.ok) {
return buildResult({
providerId: 'claude',
providerName: 'Claude',
ok: false,
configured: true,
error: `API error: ${response.status}`,
});
}
const payload = await response.json() as Record<string, unknown>;
const windows: Record<string, UsageWindow> = {};
const fiveHour = (payload as Record<string, unknown>).five_hour as Record<string, unknown> | undefined;
const sevenDay = (payload as Record<string, unknown>).seven_day as Record<string, unknown> | undefined;
const sevenDaySonnet = (payload as Record<string, unknown>).seven_day_sonnet as Record<string, unknown> | undefined;
const sevenDayOpus = (payload as Record<string, unknown>).seven_day_opus as Record<string, unknown> | undefined;
if (fiveHour) {
windows['5h'] = toUsageWindow({
usedPercent: toNumber(fiveHour.utilization),
windowSeconds: null,
resetAt: toTimestamp(fiveHour.resets_at),
});
}
if (sevenDay) {
windows['7d'] = toUsageWindow({
usedPercent: toNumber(sevenDay.utilization),
windowSeconds: null,
resetAt: toTimestamp(sevenDay.resets_at),
});
}
if (sevenDaySonnet) {
windows['7d-sonnet'] = toUsageWindow({
usedPercent: toNumber(sevenDaySonnet.utilization),
windowSeconds: null,
resetAt: toTimestamp(sevenDaySonnet.resets_at),
});
}
if (sevenDayOpus) {
windows['7d-opus'] = toUsageWindow({
usedPercent: toNumber(sevenDayOpus.utilization),
windowSeconds: null,
resetAt: toTimestamp(sevenDayOpus.resets_at),
});
}
return buildResult({
providerId: 'claude',
providerName: 'Claude',
ok: true,
configured: true,
usage: { windows },
});
} catch (error) {
return buildResult({
providerId: 'claude',
providerName: 'Claude',
ok: false,
configured: true,
error: error instanceof Error ? error.message : 'Request failed',
});
}
};
const buildCopilotWindows = (payload: Record<string, unknown>) => {
const quota = (payload.quota_snapshots as Record<string, unknown>) ?? {};
const resetAt = toTimestamp(payload.quota_reset_date);
const windows: Record<string, UsageWindow> = {};
const addWindow = (label: string, snapshot?: Record<string, unknown>) => {
if (!snapshot) return;
const entitlement = toNumber(snapshot.entitlement);
const remaining = toNumber(snapshot.remaining);
const usedPercent = entitlement && remaining !== null
? Math.max(0, Math.min(100, 100 - (remaining / entitlement) * 100))
: null;
const valueLabel = entitlement !== null && remaining !== null
? `${remaining.toFixed(0)} / ${entitlement.toFixed(0)} left`
: null;
windows[label] = toUsageWindow({
usedPercent,
windowSeconds: null,
resetAt,
valueLabel,
});
};
addWindow('chat', quota.chat as Record<string, unknown> | undefined);
addWindow('completions', quota.completions as Record<string, unknown> | undefined);
addWindow('premium', quota.premium_interactions as Record<string, unknown> | undefined);
return windows;
};
export const fetchCopilotQuota = async (): Promise<ProviderResult> => {
const auth = readAuthFile();
const entry = normalizeAuthEntry(getAuthEntry(auth, ['github-copilot', 'copilot'])) as Record<string, unknown> | null;
const accessToken = (entry?.access as string | undefined) ?? (entry?.token as string | undefined);
if (!accessToken) {
return buildResult({
providerId: 'github-copilot',
providerName: 'GitHub Copilot',
ok: false,
configured: false,
error: 'Not configured',
});
}
try {
const response = await fetch('https://api.github.com/copilot_internal/user', {
method: 'GET',
headers: {
Authorization: `token ${accessToken}`,
Accept: 'application/json',
'Editor-Version': 'vscode/1.96.2',
'X-Github-Api-Version': '2025-04-01',
},
});
if (!response.ok) {
return buildResult({
providerId: 'github-copilot',
providerName: 'GitHub Copilot',
ok: false,
configured: true,
error: `API error: ${response.status}`,
});
}
const payload = await response.json() as Record<string, unknown>;
return buildResult({
providerId: 'github-copilot',
providerName: 'GitHub Copilot',
ok: true,
configured: true,
usage: { windows: buildCopilotWindows(payload) },
});
} catch (error) {
return buildResult({
providerId: 'github-copilot',
providerName: 'GitHub Copilot',
ok: false,
configured: true,
error: error instanceof Error ? error.message : 'Request failed',
});
}
};
export const fetchCopilotAddonQuota = async (): Promise<ProviderResult> => {
const auth = readAuthFile();
const entry = normalizeAuthEntry(getAuthEntry(auth, ['github-copilot', 'copilot'])) as Record<string, unknown> | null;
const accessToken = (entry?.access as string | undefined) ?? (entry?.token as string | undefined);
if (!accessToken) {
return buildResult({
providerId: 'github-copilot-addon',
providerName: 'GitHub Copilot Add-on',
ok: false,
configured: false,
error: 'Not configured',
});
}
try {
const response = await fetch('https://api.github.com/copilot_internal/user', {
method: 'GET',
headers: {
Authorization: `token ${accessToken}`,
Accept: 'application/json',
'Editor-Version': 'vscode/1.96.2',
'X-Github-Api-Version': '2025-04-01',
},
});
if (!response.ok) {
return buildResult({
providerId: 'github-copilot-addon',
providerName: 'GitHub Copilot Add-on',
ok: false,
configured: true,
error: `API error: ${response.status}`,
});
}
const payload = await response.json() as Record<string, unknown>;
const windows = buildCopilotWindows(payload);
const premium = windows.premium ? { premium: windows.premium } : windows;
return buildResult({
providerId: 'github-copilot-addon',
providerName: 'GitHub Copilot Add-on',
ok: true,
configured: true,
usage: { windows: premium },
});
} catch (error) {
return buildResult({
providerId: 'github-copilot-addon',
providerName: 'GitHub Copilot Add-on',
ok: false,
configured: true,
error: error instanceof Error ? error.message : 'Request failed',
});
}
};
export const fetchKimiQuota = async (): Promise<ProviderResult> => {
const auth = readAuthFile();
const entry = normalizeAuthEntry(getAuthEntry(auth, ['kimi-for-coding', 'kimi'])) as Record<string, unknown> | null;
const apiKey = (entry?.key as string | undefined) ?? (entry?.token as string | undefined);
if (!apiKey) {
return buildResult({
providerId: 'kimi-for-coding',
providerName: 'Kimi for Coding',
ok: false,
configured: false,
error: 'Not configured',
});
}
try {
const response = await fetch('https://api.kimi.com/coding/v1/usages', {
method: 'GET',
headers: {
Authorization: `Bearer ${apiKey}`,
'Content-Type': 'application/json',
},
});
if (!response.ok) {
return buildResult({
providerId: 'kimi-for-coding',
providerName: 'Kimi for Coding',
ok: false,
configured: true,
error: `API error: ${response.status}`,
});
}
const payload = await response.json() as Record<string, unknown>;
const windows: Record<string, UsageWindow> = {};
const usage = payload.usage as Record<string, unknown> | undefined;
if (usage) {
const limit = toNumber(usage.limit);
const remaining = toNumber(usage.remaining);
const usedPercent = limit && remaining !== null
? Math.max(0, Math.min(100, 100 - (remaining / limit) * 100))
: null;
windows.weekly = toUsageWindow({
usedPercent,
windowSeconds: null,
resetAt: toTimestamp(usage.resetTime),
});
}
const limits = Array.isArray(payload.limits) ? payload.limits : [];
for (const limit of limits) {
const window = (limit as Record<string, unknown>)?.window as Record<string, unknown> | undefined;
const detail = (limit as Record<string, unknown>)?.detail as Record<string, unknown> | undefined;
const rawLabel = durationToLabel(window?.duration as number | undefined, window?.timeUnit as string | undefined);
const windowSeconds = durationToSeconds(window?.duration as number | undefined, window?.timeUnit as string | undefined);
const label = windowSeconds === 5 * 60 * 60 ? `Rate Limit (${rawLabel})` : rawLabel;
const total = toNumber(detail?.limit);
const remaining = toNumber(detail?.remaining);
const usedPercent = total && remaining !== null
? Math.max(0, Math.min(100, 100 - (remaining / total) * 100))
: null;
windows[label] = toUsageWindow({
usedPercent,
windowSeconds,
resetAt: toTimestamp(detail?.resetTime),
});
}
return buildResult({
providerId: 'kimi-for-coding',
providerName: 'Kimi for Coding',
ok: true,
configured: true,
usage: { windows },
});
} catch (error) {
return buildResult({
providerId: 'kimi-for-coding',
providerName: 'Kimi for Coding',
ok: false,
configured: true,
error: error instanceof Error ? error.message : 'Request failed',
});
}
};
export const fetchOpenRouterQuota = async (): Promise<ProviderResult> => {
const auth = readAuthFile();
const entry = normalizeAuthEntry(getAuthEntry(auth, ['openrouter'])) as Record<string, unknown> | null;
const apiKey = (entry?.key as string | undefined) ?? (entry?.token as string | undefined);
if (!apiKey) {
return buildResult({
providerId: 'openrouter',
providerName: 'OpenRouter',
ok: false,
configured: false,
error: 'Not configured',
});
}
try {
const response = await fetch('https://openrouter.ai/api/v1/credits', {
method: 'GET',
headers: {
Authorization: `Bearer ${apiKey}`,
'Content-Type': 'application/json',
},
});
if (!response.ok) {
return buildResult({
providerId: 'openrouter',
providerName: 'OpenRouter',
ok: false,
configured: true,
error: `API error: ${response.status}`,
});
}
const payload = await response.json() as Record<string, unknown>;
const credits = payload.data as Record<string, unknown> | undefined;
const totalCredits = toNumber(credits?.total_credits);
const totalUsage = toNumber(credits?.total_usage);
const remaining = totalCredits !== null && totalUsage !== null
? Math.max(0, totalCredits - totalUsage)
: null;
const usedPercent = totalCredits && totalUsage !== null
? Math.max(0, Math.min(100, (totalUsage / totalCredits) * 100))
: null;
const valueLabel = remaining !== null ? `$${formatMoney(remaining)} remaining` : null;
return buildResult({
providerId: 'openrouter',
providerName: 'OpenRouter',
ok: true,
configured: true,
usage: {
windows: {
credits: toUsageWindow({
usedPercent,
windowSeconds: null,
resetAt: null,
valueLabel,
}),
},
},
});
} catch (error) {
return buildResult({
providerId: 'openrouter',
providerName: 'OpenRouter',
ok: false,
configured: true,
error: error instanceof Error ? error.message : 'Request failed',
});
}
};
const normalizeTimestamp = (value: unknown) => {
if (typeof value !== 'number') return null;
return value < 1_000_000_000_000 ? value * 1000 : value;
};
const resolveWindowSeconds = (limit: Record<string, unknown> | undefined) => {
if (!limit || typeof limit.number !== 'number') return null;
const unitSeconds = ZAI_TOKEN_WINDOW_SECONDS[Number(limit.unit)];
if (!unitSeconds) return null;
return unitSeconds * limit.number;
};
const resolveWindowLabel = (windowSeconds: number | null) => {
if (!windowSeconds) return 'tokens';
if (windowSeconds % 86400 === 0) {
const days = windowSeconds / 86400;
return days === 7 ? 'weekly' : `${days}d`;
}
if (windowSeconds % 3600 === 0) {
return `${windowSeconds / 3600}h`;
}
return `${windowSeconds}s`;
};
export const fetchZaiQuota = async (): Promise<ProviderResult> => {
const auth = readAuthFile();
const entry = normalizeAuthEntry(getAuthEntry(auth, ['zai-coding-plan', 'zai', 'z.ai'])) as Record<string, unknown> | null;
const apiKey = (entry?.key as string | undefined) ?? (entry?.token as string | undefined);
if (!apiKey) {
return buildResult({
providerId: 'zai-coding-plan',
providerName: 'z.ai',
ok: false,
configured: false,
error: 'Not configured',
});
}
try {
const response = await fetch('https://api.z.ai/api/monitor/usage/quota/limit', {
method: 'GET',
headers: {
Authorization: `Bearer ${apiKey}`,
'Content-Type': 'application/json',
},
});
if (!response.ok) {
return buildResult({
providerId: 'zai-coding-plan',
providerName: 'z.ai',
ok: false,
configured: true,
error: `API error: ${response.status}`,
});
}
const payload = await response.json() as ZaiPayload;
const limits = Array.isArray(payload?.data?.limits) ? payload.data.limits : [];
const tokensLimit = limits.find((limit: Record<string, unknown>) => limit?.type === 'TOKENS_LIMIT');
const windowSeconds = resolveWindowSeconds(tokensLimit as Record<string, unknown> | undefined);
const windowLabel = resolveWindowLabel(windowSeconds);
const resetAt = tokensLimit?.nextResetTime ? normalizeTimestamp(tokensLimit.nextResetTime) : null;
const usedPercent = typeof tokensLimit?.percentage === 'number' ? tokensLimit.percentage : null;
const windows: Record<string, UsageWindow> = {};
if (tokensLimit) {
windows[windowLabel] = toUsageWindow({
usedPercent,
windowSeconds,
resetAt,
});
}
return buildResult({
providerId: 'zai-coding-plan',
providerName: 'z.ai',
ok: true,
configured: true,
usage: { windows },
});
} catch (error) {
return buildResult({
providerId: 'zai-coding-plan',
providerName: 'z.ai',
ok: false,
configured: true,
error: error instanceof Error ? error.message : 'Request failed',
});
}
};
export const fetchQuotaForProvider = async (providerId: string): Promise<ProviderResult> => {
switch (providerId) {
case 'claude':
return fetchClaudeQuota();
case 'codex':
return fetchCodexQuota();
case 'github-copilot':
return fetchCopilotQuota();
case 'github-copilot-addon':
return fetchCopilotAddonQuota();
case 'google':
return fetchGoogleQuota();
case 'kimi-for-coding':
return fetchKimiQuota();
case 'openrouter':
return fetchOpenRouterQuota();
case 'zai-coding-plan':
return fetchZaiQuota();
default:
return buildResult({
providerId,
providerName: providerId,
ok: false,
configured: false,
error: 'Unsupported provider',
});
}
};