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:
@@ -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<
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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'
|
||||
});
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user