feat: add Ollama Cloud quota provider (#544)

* feat: add Ollama Cloud quota provider

Adds ollama-cloud quota provider that reads auth cookie from
~/.config/ollama-quota/cookie and fetches usage from ollama.com/settings.
Returns 3 windows: session, weekly, and premium (X/Y). Reuses Ollama
logo via provider alias.

* docs(quota): add ollama-cloud provider entry to DOCUMENTATION.md
This commit is contained in:
Henry Moran
2026-02-28 01:38:28 +02:00
committed by GitHub
parent 2fb0aa9c96
commit 654d49067f
8 changed files with 128 additions and 2 deletions
+1
View File
@@ -22,6 +22,7 @@ const LOGO_ALIAS = new Map<string, string>([
['gemini', 'google'],
['evroc-ai', 'evroc'],
['evrocai', 'evroc'],
['ollama-cloud', 'ollama'],
]);
const normalizeProviderId = (providerId: string | null | undefined) => {
@@ -16,6 +16,7 @@ export const QUOTA_PROVIDERS: QuotaProviderMeta[] = [
{ id: 'zai-coding-plan', name: 'z.ai' },
{ id: 'minimax-cn-coding-plan', name: 'MiniMax Coding Plan (minimaxi.com)' },
{ id: 'minimax-coding-plan', name: 'MiniMax Coding Plan (minimax.io)' },
{ id: 'ollama-cloud', name: 'Ollama Cloud' },
];
export const QUOTA_PROVIDER_MAP = QUOTA_PROVIDERS.reduce<
+1
View File
@@ -35,6 +35,7 @@ export const formatWindowLabel = (label: string): string => {
if (label === 'daily') return 'Daily';
if (label === 'monthly') return 'Monthly Limit';
if (label === 'credits') return 'Credits';
if (label === 'session') return 'Session';
if (label === 'premium') return 'Premium Interactions';
if (label === 'chat') return 'Chat Requests';
if (label === 'completions') return 'Completions';
+2 -1
View File
@@ -10,7 +10,8 @@ export type QuotaProviderId =
| 'openrouter'
| 'zai-coding-plan'
| 'minimax-coding-plan'
| 'minimax-cn-coding-plan';
| 'minimax-cn-coding-plan'
| 'ollama-cloud';
export interface UsageWindow {
usedPercent: number | null;
@@ -27,6 +27,7 @@ These provider IDs are currently dispatchable via `fetchQuotaForProvider(provide
| `zai-coding-plan` | z.ai | `providers/zai.js` | `zai-coding-plan`, `zai`, `z.ai` |
| `minimax-coding-plan` | MiniMax Coding Plan (minimax.io) | `providers/minimax-coding-plan.js` | `minimax-coding-plan` |
| `minimax-cn-coding-plan` | MiniMax Coding Plan (minimaxi.com) | `providers/minimax-cn-coding-plan.js` | `minimax-cn-coding-plan` |
| `ollama-cloud` | Ollama Cloud | `providers/ollama-cloud.js` | Cookie file at `~/.config/ollama-quota/cookie` (raw session cookie string) |
## Internal-only provider module
- `providers/openai.js` exists for logic parity/reuse but is intentionally not registered for dispatcher ID routing.
+2 -1
View File
@@ -19,5 +19,6 @@ export {
fetchZaiQuota,
fetchNanoGptQuota,
fetchMinimaxCodingPlanQuota,
fetchMinimaxCnCodingPlanQuota
fetchMinimaxCnCodingPlanQuota,
fetchOllamaCloudQuota
} from './providers/index.js';
@@ -18,6 +18,7 @@ import * as openrouter from './openrouter.js';
import * as zai from './zai.js';
import * as minimaxCodingPlan from './minimax-coding-plan.js';
import * as minimaxCnCodingPlan from './minimax-cn-coding-plan.js';
import * as ollamaCloud from './ollama-cloud.js';
const registry = {
claude: {
@@ -85,6 +86,12 @@ const registry = {
providerName: minimaxCnCodingPlan.providerName,
isConfigured: minimaxCnCodingPlan.isConfigured,
fetchQuota: minimaxCnCodingPlan.fetchQuota
},
'ollama-cloud': {
providerId: ollamaCloud.providerId,
providerName: ollamaCloud.providerName,
isConfigured: ollamaCloud.isConfigured,
fetchQuota: ollamaCloud.fetchQuota
}
};
@@ -142,3 +149,4 @@ export const fetchZaiQuota = zai.fetchQuota;
export const fetchNanoGptQuota = nanogpt.fetchQuota;
export const fetchMinimaxCodingPlanQuota = minimaxCodingPlan.fetchQuota;
export const fetchMinimaxCnCodingPlanQuota = minimaxCnCodingPlan.fetchQuota;
export const fetchOllamaCloudQuota = ollamaCloud.fetchQuota;
@@ -0,0 +1,112 @@
import { homedir } from 'os';
import { readFileSync, existsSync } from 'fs';
import { join } from 'path';
import { buildResult, toUsageWindow, toNumber } from '../utils/index.js';
const COOKIE_PATH = join(homedir(), '.config', 'ollama-quota', 'cookie');
export const providerId = 'ollama-cloud';
export const providerName = 'Ollama Cloud';
export const aliases = ['ollama-cloud', 'ollamacloud'];
const readCookieFile = () => {
try {
if (!existsSync(COOKIE_PATH)) return null;
const content = readFileSync(COOKIE_PATH, 'utf-8');
const trimmed = content.trim();
return trimmed || null;
} catch {
return null;
}
};
const parseOllamaSettingsHtml = (html) => {
const windows = {};
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 isConfigured = () => {
const cookie = readCookieFile();
return Boolean(cookie);
};
export const fetchQuota = async () => {
const cookie = readCookieFile();
if (!cookie) {
return buildResult({
providerId,
providerName,
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,
providerName,
ok: false,
configured: true,
error: `API error: ${response.status}`
});
}
const html = await response.text();
const windows = parseOllamaSettingsHtml(html);
return buildResult({
providerId,
providerName,
ok: true,
configured: true,
usage: { windows }
});
} catch (error) {
return buildResult({
providerId,
providerName,
ok: false,
configured: true,
error: error instanceof Error ? error.message : 'Request failed'
});
}
};