Files
openchamber/packages/web/server/lib/quota-providers.js
T
Nelson Pires 392cd888a8 Add support for GitHub Copilot quota provider (#271)
* feat(quota): add github-copilot to QuotaProviderId type

* feat(quota): add GitHub Copilot provider metadata

* feat(quota): add premium_interactions window label mapping

* feat(quota): implement GitHub Copilot quota provider for web

* feat(quota): implement GitHub Copilot quota provider for VS Code

* feat(quota): implement GitHub Copilot quota provider for desktop
2026-02-03 13:03:27 +02:00

616 lines
17 KiB
JavaScript

import fs from 'fs';
import path from 'path';
import os from 'os';
import { readAuthFile } from './opencode-auth.js';
const OPENCODE_CONFIG_DIR = path.join(os.homedir(), '.config', 'opencode');
const OPENCODE_DATA_DIR = path.join(os.homedir(), '.local', 'share', 'opencode');
const ANTIGRAVITY_ACCOUNTS_PATHS = [
path.join(OPENCODE_CONFIG_DIR, 'antigravity-accounts.json'),
path.join(OPENCODE_DATA_DIR, 'antigravity-accounts.json')
];
const readJsonFile = (filePath) => {
if (!fs.existsSync(filePath)) {
return null;
}
try {
const raw = fs.readFileSync(filePath, 'utf8');
const trimmed = raw.trim();
if (!trimmed) return null;
return JSON.parse(trimmed);
} catch (error) {
console.warn(`Failed to read JSON file: ${filePath}`, error);
return null;
}
};
const getAuthEntry = (auth, aliases) => {
for (const alias of aliases) {
if (auth[alias]) {
return auth[alias];
}
}
return null;
};
const normalizeAuthEntry = (entry) => {
if (!entry) return null;
if (typeof entry === 'string') {
return { token: entry };
}
if (typeof entry === 'object') {
return entry;
}
return null;
};
const formatResetTime = (timestamp) => {
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) => {
if (!resetAt) return null;
const delta = Math.floor((resetAt - Date.now()) / 1000);
return delta < 0 ? 0 : delta;
};
const toUsageWindow = ({ usedPercent, windowSeconds, resetAt }) => {
const resetAfterSeconds = calculateResetAfterSeconds(resetAt);
const resetFormatted = resetAt ? formatResetTime(resetAt) : null;
return {
usedPercent,
remainingPercent: usedPercent !== null ? Math.max(0, 100 - usedPercent) : null,
windowSeconds: windowSeconds ?? null,
resetAfterSeconds,
resetAt,
resetAtFormatted: resetFormatted,
resetAfterFormatted: resetFormatted
};
};
const buildResult = ({ providerId, providerName, ok, configured, usage, error }) => ({
providerId,
providerName,
ok,
configured,
usage: usage ?? null,
...(error ? { error } : {}),
fetchedAt: Date.now()
});
export const listConfiguredQuotaProviders = () => {
const auth = readAuthFile();
const configured = new Set();
const openaiAuth = normalizeAuthEntry(getAuthEntry(auth, ['openai', 'codex', 'chatgpt']));
if (openaiAuth?.access || openaiAuth?.token) {
configured.add('openai');
}
const googleAuth = normalizeAuthEntry(getAuthEntry(auth, ['google', 'antigravity']));
if (googleAuth?.access || googleAuth?.token || googleAuth?.refresh) {
configured.add('google');
}
const zaiAuth = normalizeAuthEntry(getAuthEntry(auth, ['zai-coding-plan', 'zai', 'z.ai']));
if (zaiAuth?.key || zaiAuth?.token) {
configured.add('zai-coding-plan');
}
const githubCopilotAuth = normalizeAuthEntry(getAuthEntry(auth, ['github-copilot']));
if (githubCopilotAuth?.access || githubCopilotAuth?.token) {
configured.add('github-copilot');
}
for (const filePath of ANTIGRAVITY_ACCOUNTS_PATHS) {
const data = readJsonFile(filePath);
if (Array.isArray(data?.accounts) && data.accounts.length > 0) {
configured.add('google');
break;
}
}
return Array.from(configured);
};
export const fetchOpenaiQuota = async () => {
const auth = readAuthFile();
const entry = normalizeAuthEntry(getAuthEntry(auth, ['openai', 'codex', 'chatgpt']));
const accessToken = entry?.access ?? entry?.token;
if (!accessToken) {
return buildResult({
providerId: 'openai',
providerName: 'OpenAI',
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'
}
});
if (!response.ok) {
return buildResult({
providerId: 'openai',
providerName: 'OpenAI',
ok: false,
configured: true,
error: `API error: ${response.status}`
});
}
const payload = await response.json();
const primary = payload?.rate_limit?.primary_window ?? null;
const secondary = payload?.rate_limit?.secondary_window ?? null;
const windows = {};
if (primary) {
windows['5h'] = toUsageWindow({
usedPercent: primary.used_percent ?? null,
windowSeconds: primary.limit_window_seconds ?? null,
resetAt: primary.reset_at ? primary.reset_at * 1000 : null
});
}
if (secondary) {
windows['weekly'] = toUsageWindow({
usedPercent: secondary.used_percent ?? null,
windowSeconds: secondary.limit_window_seconds ?? null,
resetAt: secondary.reset_at ? secondary.reset_at * 1000 : null
});
}
return buildResult({
providerId: 'openai',
providerName: 'OpenAI',
ok: true,
configured: true,
usage: { windows }
});
} catch (error) {
return buildResult({
providerId: 'openai',
providerName: 'OpenAI',
ok: false,
configured: true,
error: error instanceof Error ? error.message : 'Request failed'
});
}
};
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 resolveGoogleAuth = () => {
const auth = readAuthFile();
const entry = normalizeAuthEntry(getAuthEntry(auth, ['google', 'antigravity']));
if (entry) {
const accessToken = entry.access ?? entry.token;
let refreshToken = entry.refresh;
let projectId = undefined;
if (refreshToken && refreshToken.includes('|')) {
const parts = refreshToken.split('|');
refreshToken = parts[0];
projectId = parts[1];
}
return {
accessToken,
refreshToken,
expires: entry.expires,
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.activeIndex === 'number' ? data.activeIndex : 0;
const account = accounts[index] ?? accounts[0];
if (account?.refreshToken) {
return {
refreshToken: account.refreshToken,
projectId: account.projectId ?? account.managedProjectId,
email: account.email
};
}
}
}
return null;
};
const refreshGoogleAccessToken = async (refreshToken) => {
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();
return typeof data?.access_token === 'string' ? data.access_token : null;
};
const fetchGoogleModels = async (accessToken, projectId) => {
const body = projectId ? { project: projectId } : {};
for (const endpoint of GOOGLE_ENDPOINTS) {
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: AbortSignal.timeout(15000)
});
if (response.ok) {
return await response.json();
}
} catch {
continue;
}
}
return null;
};
export const fetchGoogleQuota = async () => {
const auth = resolveGoogleAuth();
if (!auth) {
return buildResult({
providerId: 'google',
providerName: 'Google',
ok: false,
configured: false,
error: 'Not configured'
});
}
const now = Date.now();
let accessToken = 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);
}
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) {
return buildResult({
providerId: 'google',
providerName: 'Google',
ok: false,
configured: true,
error: 'Failed to fetch models'
});
}
const models = {};
for (const [modelName, modelData] of Object.entries(payload.models ?? {})) {
const remainingFraction = modelData?.quotaInfo?.remainingFraction;
const remainingPercent = typeof remainingFraction === 'number'
? Math.round(remainingFraction * 100)
: null;
const usedPercent = remainingPercent !== null ? Math.max(0, 100 - remainingPercent) : null;
const resetAt = modelData?.quotaInfo?.resetTime
? new Date(modelData.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
}
});
};
const normalizeTimestamp = (value) => {
if (typeof value !== 'number') return null;
return value < 1_000_000_000_000 ? value * 1000 : value;
};
const ZAI_TOKEN_WINDOW_SECONDS = { 3: 3600 };
const resolveWindowSeconds = (limit) => {
if (!limit || !limit.number) return null;
const unitSeconds = ZAI_TOKEN_WINDOW_SECONDS[limit.unit];
if (!unitSeconds) return null;
return unitSeconds * limit.number;
};
const resolveWindowLabel = (windowSeconds) => {
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 () => {
const auth = readAuthFile();
const entry = normalizeAuthEntry(getAuthEntry(auth, ['zai-coding-plan', 'zai', 'z.ai']));
const apiKey = entry?.key ?? entry?.token;
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();
const limits = Array.isArray(payload?.data?.limits) ? payload.data.limits : [];
const tokensLimit = limits.find((limit) => limit?.type === 'TOKENS_LIMIT');
const windowSeconds = resolveWindowSeconds(tokensLimit);
const windowLabel = resolveWindowLabel(windowSeconds);
const resetAt = tokensLimit?.nextResetTime ? normalizeTimestamp(tokensLimit.nextResetTime) : null;
const usedPercent = typeof tokensLimit?.percentage === 'number' ? tokensLimit.percentage : null;
const windows = {};
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 fetchGitHubCopilotQuota = async () => {
const auth = readAuthFile();
const entry = normalizeAuthEntry(getAuthEntry(auth, ['github-copilot']));
const accessToken = entry?.access ?? entry?.token;
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: `Bearer ${accessToken}`,
Accept: 'application/vnd.github+json',
'User-Agent': 'OpenChamber'
}
});
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();
const snapshots = payload?.quota_snapshots ?? {};
const premiumInteractions = snapshots?.premium_interactions ?? null;
// Parse reset date
let resetAt = null;
const resetDateUtc = payload?.quota_reset_date_utc;
const resetDate = payload?.quota_reset_date;
if (resetDateUtc) {
resetAt = new Date(resetDateUtc).getTime();
} else if (resetDate) {
// Use the date as UTC midnight
resetAt = new Date(`${resetDate}T00:00:00Z`).getTime();
}
const windows = {};
if (premiumInteractions) {
let usedPercent = null;
if (premiumInteractions.unlimited === true) {
usedPercent = null;
} else if (typeof premiumInteractions.percent_remaining === 'number') {
usedPercent = 100 - premiumInteractions.percent_remaining;
} else if (
typeof premiumInteractions.entitlement === 'number' &&
premiumInteractions.entitlement > 0
) {
const remaining =
typeof premiumInteractions.remaining === 'number'
? premiumInteractions.remaining
: typeof premiumInteractions.quota_remaining === 'number'
? premiumInteractions.quota_remaining
: null;
if (remaining !== null) {
usedPercent = ((premiumInteractions.entitlement - remaining) / premiumInteractions.entitlement) * 100;
}
}
windows['premium_interactions'] = toUsageWindow({
usedPercent,
windowSeconds: null,
resetAt
});
}
return buildResult({
providerId: 'github-copilot',
providerName: 'GitHub Copilot',
ok: true,
configured: true,
usage: { windows }
});
} catch (error) {
return buildResult({
providerId: 'github-copilot',
providerName: 'GitHub Copilot',
ok: false,
configured: true,
error: error instanceof Error ? error.message : 'Request failed'
});
}
};
export const fetchQuotaForProvider = async (providerId) => {
switch (providerId) {
case 'openai':
return fetchOpenaiQuota();
case 'google':
return fetchGoogleQuota();
case 'zai-coding-plan':
return fetchZaiQuota();
case 'github-copilot':
return fetchGitHubCopilotQuota();
default:
return buildResult({
providerId,
providerName: providerId,
ok: false,
configured: false,
error: 'Unsupported provider'
});
}
};