From 390db58cc8896ee0328464b9ace130906671b016 Mon Sep 17 00:00:00 2001 From: Bohdan Triapitsyn Date: Mon, 27 Apr 2026 18:43:20 +0300 Subject: [PATCH] fix: add VS Code quota support for MiniMax and Ollama (fix for #1051) Adds MiniMax coding plan quota support Adds Ollama Cloud quota support Detects configured providers in VS Code --- packages/vscode/src/quotaProviders.ts | 258 ++++++++++++++++++++++++++ 1 file changed, 258 insertions(+) diff --git a/packages/vscode/src/quotaProviders.ts b/packages/vscode/src/quotaProviders.ts index fd051e73..86f144bd 100644 --- a/packages/vscode/src/quotaProviders.ts +++ b/packages/vscode/src/quotaProviders.ts @@ -114,6 +114,7 @@ export type ProviderResult = { 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 OLLAMA_CLOUD_COOKIE_PATH = path.join(os.homedir(), '.config', 'ollama-quota', 'cookie'); const ANTIGRAVITY_ACCOUNTS_PATHS = [ @@ -209,6 +210,19 @@ const readJsonFile = (filePath: string): Record | null => { } }; +const readTextFile = (filePath: string): string | null => { + if (!fs.existsSync(filePath)) { + return null; + } + try { + const content = fs.readFileSync(filePath, 'utf8').trim(); + return content || null; + } catch (error) { + console.warn(`Failed to read text file: ${filePath}`, error); + return null; + } +}; + const getAuthEntry = (auth: AuthFile, aliases: string[]) => { for (const alias of aliases) { if (auth[alias]) { @@ -395,6 +409,16 @@ export const listConfiguredQuotaProviders = () => { configured.add('kimi-for-coding'); } + const minimaxAuth = normalizeAuthEntry(getAuthEntry(auth, ['minimax-coding-plan'])); + if (minimaxAuth && ((minimaxAuth as Record).key || (minimaxAuth as Record).token)) { + configured.add('minimax-coding-plan'); + } + + const minimaxCnAuth = normalizeAuthEntry(getAuthEntry(auth, ['minimax-cn-coding-plan'])); + if (minimaxCnAuth && ((minimaxCnAuth as Record).key || (minimaxCnAuth as Record).token)) { + configured.add('minimax-cn-coding-plan'); + } + const openrouterAuth = normalizeAuthEntry(getAuthEntry(auth, ['openrouter'])); if (openrouterAuth && ((openrouterAuth as Record).key || (openrouterAuth as Record).token)) { configured.add('openrouter'); @@ -411,6 +435,10 @@ export const listConfiguredQuotaProviders = () => { configured.add('github-copilot-addon'); } + if (readTextFile(OLLAMA_CLOUD_COOKIE_PATH)) { + configured.add('ollama-cloud'); + } + return Array.from(configured); }; @@ -1127,6 +1155,230 @@ export const fetchKimiQuota = async (): Promise => { } }; +const fetchMiniMaxQuota = async (data: { + providerId: 'minimax-coding-plan' | 'minimax-cn-coding-plan'; + providerName: string; + endpoint: string; + usageFieldsAreRemaining: boolean; +}): Promise => { + const auth = readAuthFile(); + const entry = normalizeAuthEntry(getAuthEntry(auth, [data.providerId])) as Record | null; + const apiKey = (entry?.key as string | undefined) ?? (entry?.token as string | undefined); + + if (!apiKey) { + return buildResult({ + providerId: data.providerId, + providerName: data.providerName, + ok: false, + configured: false, + error: 'Not configured', + }); + } + + try { + const response = await fetch(data.endpoint, { + method: 'GET', + headers: { + Authorization: `Bearer ${apiKey}`, + 'Content-Type': 'application/json', + }, + }); + + if (!response.ok) { + return buildResult({ + providerId: data.providerId, + providerName: data.providerName, + ok: false, + configured: true, + error: `API error: ${response.status}`, + }); + } + + const payload = await response.json() as Record; + const baseResp = asObject(payload.base_resp); + const statusCode = toNumber(baseResp?.status_code); + if (baseResp && statusCode !== 0) { + return buildResult({ + providerId: data.providerId, + providerName: data.providerName, + ok: false, + configured: true, + error: asNonEmptyString(baseResp.status_msg) ?? `API error: ${statusCode}`, + }); + } + + const modelRemains = Array.isArray(payload.model_remains) ? payload.model_remains : []; + const firstModel = asObject(modelRemains[0]); + if (!firstModel) { + return buildResult({ + providerId: data.providerId, + providerName: data.providerName, + ok: false, + configured: true, + error: 'No model quota data available', + }); + } + + const intervalTotal = toNumber(firstModel.current_interval_total_count); + const intervalUsage = toNumber(firstModel.current_interval_usage_count); + const intervalStartAt = toTimestamp(firstModel.start_time); + const intervalResetAt = toTimestamp(firstModel.end_time); + const weeklyTotal = toNumber(firstModel.current_weekly_total_count); + const weeklyUsage = toNumber(firstModel.current_weekly_usage_count); + const weeklyStartAt = toTimestamp(firstModel.weekly_start_time); + const weeklyResetAt = toTimestamp(firstModel.weekly_end_time); + + const intervalUsed = data.usageFieldsAreRemaining && intervalTotal !== null && intervalUsage !== null + ? intervalTotal - intervalUsage + : intervalUsage; + const weeklyUsed = data.usageFieldsAreRemaining && weeklyTotal !== null && weeklyUsage !== null + ? weeklyTotal - weeklyUsage + : weeklyUsage; + + const intervalUsedPercent = intervalTotal !== null && intervalTotal > 0 && intervalUsed !== null + ? Math.max(0, Math.min(100, (intervalUsed / intervalTotal) * 100)) + : null; + const intervalWindowSeconds = intervalStartAt && intervalResetAt && intervalResetAt > intervalStartAt + ? Math.floor((intervalResetAt - intervalStartAt) / 1000) + : null; + const weeklyUsedPercent = weeklyTotal !== null && weeklyTotal > 0 && weeklyUsed !== null + ? Math.max(0, Math.min(100, (weeklyUsed / weeklyTotal) * 100)) + : null; + const weeklyWindowSeconds = weeklyStartAt && weeklyResetAt && weeklyResetAt > weeklyStartAt + ? Math.floor((weeklyResetAt - weeklyStartAt) / 1000) + : null; + + return buildResult({ + providerId: data.providerId, + providerName: data.providerName, + ok: true, + configured: true, + usage: { + windows: { + '5h': toUsageWindow({ + usedPercent: intervalUsedPercent, + windowSeconds: intervalWindowSeconds, + resetAt: intervalResetAt, + }), + weekly: toUsageWindow({ + usedPercent: weeklyUsedPercent, + windowSeconds: weeklyWindowSeconds, + resetAt: weeklyResetAt, + }), + }, + }, + }); + } catch (error) { + return buildResult({ + providerId: data.providerId, + providerName: data.providerName, + ok: false, + configured: true, + error: error instanceof Error ? error.message : 'Request failed', + }); + } +}; + +export const fetchMiniMaxCodingPlanQuota = () => fetchMiniMaxQuota({ + providerId: 'minimax-coding-plan', + providerName: 'MiniMax Coding Plan (minimax.io)', + endpoint: 'https://api.minimax.io/v1/api/openplatform/coding_plan/remains', + usageFieldsAreRemaining: false, +}); + +export const fetchMiniMaxCnCodingPlanQuota = () => fetchMiniMaxQuota({ + providerId: 'minimax-cn-coding-plan', + providerName: 'MiniMax Coding Plan (minimaxi.com)', + endpoint: 'https://www.minimaxi.com/v1/api/openplatform/coding_plan/remains', + usageFieldsAreRemaining: true, +}); + +const parseOllamaSettingsHtml = (html: string) => { + const windows: Record = {}; + const sessionMatch = html.match(/Session\s+usage[^0-9]*([0-9.]+)%/i); + if (sessionMatch) { + windows.session = toUsageWindow({ + usedPercent: toNumber(sessionMatch[1]), + windowSeconds: null, + resetAt: null, + }); + } + + const weeklyMatch = html.match(/Weekly\s+usage[^0-9]*([0-9.]+)%/i); + if (weeklyMatch) { + windows.weekly = toUsageWindow({ + usedPercent: toNumber(weeklyMatch[1]), + windowSeconds: null, + resetAt: null, + }); + } + + const premiumMatch = html.match(/Premium[^0-9]*([0-9]+)\s*\/\s*([0-9]+)/i); + if (premiumMatch) { + const used = toNumber(premiumMatch[1]); + const total = toNumber(premiumMatch[2]); + const usedPercent = total && used !== null ? Math.min(100, (used / total) * 100) : null; + windows.premium = toUsageWindow({ + usedPercent, + windowSeconds: null, + resetAt: null, + valueLabel: `${used ?? 0} / ${total ?? 0}`, + }); + } + + return windows; +}; + +export const fetchOllamaCloudQuota = async (): Promise => { + const cookie = readTextFile(OLLAMA_CLOUD_COOKIE_PATH); + + if (!cookie) { + return buildResult({ + providerId: 'ollama-cloud', + providerName: 'Ollama Cloud', + ok: false, + configured: false, + error: 'Not configured', + }); + } + + try { + const response = await fetch('https://ollama.com/settings', { + method: 'GET', + headers: { + Cookie: cookie, + 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36', + }, + }); + + if (!response.ok) { + return buildResult({ + providerId: 'ollama-cloud', + providerName: 'Ollama Cloud', + ok: false, + configured: true, + error: `API error: ${response.status}`, + }); + } + + return buildResult({ + providerId: 'ollama-cloud', + providerName: 'Ollama Cloud', + ok: true, + configured: true, + usage: { windows: parseOllamaSettingsHtml(await response.text()) }, + }); + } catch (error) { + return buildResult({ + providerId: 'ollama-cloud', + providerName: 'Ollama Cloud', + ok: false, + configured: true, + error: error instanceof Error ? error.message : 'Request failed', + }); + } +}; + export const fetchOpenRouterQuota = async (): Promise => { const auth = readAuthFile(); const entry = normalizeAuthEntry(getAuthEntry(auth, ['openrouter'])) as Record | null; @@ -1502,6 +1754,12 @@ export const fetchQuotaForProvider = async (providerId: string): Promise