fix: parse ollama cloud cost-based usage windows and credits balance (#3381)

Ollama Cloud's /settings page switched shapes for cost-based plans: parse
the 'Monthly usage $X of $Y used' row as a monthly window with a symmetric
'$X / $Y' money label that reads correctly in both used/remaining display
modes, and surface the Extra usage credits balance as a balance-only
credits_balance window matching the Codex/DeepSeek credits treatment
('Credits Balance' in the UI), omitted when the balance is $0. The VS Code
credential validation accepts the new page shape and the Settings cookie
placeholder shows the two-cookie format. Web and VS Code parsers kept in
sync per quota DOCUMENTATION.md.
This commit is contained in:
Pablo Gonzalez
2026-09-07 19:25:52 +03:00
committed by GitHub
parent 0449d65921
commit b238cd7fbc
6 changed files with 104 additions and 4 deletions
+1 -1
View File
@@ -59,7 +59,7 @@ export const validateCredential = async (provider: ManagedProvider, credential:
const response = await fetch('https://ollama.com/settings', { headers: { Cookie: credential.cookie }, redirect: 'manual', signal: AbortSignal.timeout(15_000) });
if (!response.ok || (response.status >= 300 && response.status < 400)) throw new Error('Ollama Cloud authentication failed');
const html = await response.text();
if (!/Session\s+usage|Weekly\s+usage|Premium[^0-9]*[0-9]+\s*\/\s*[0-9]+/i.test(html)) throw new Error('Ollama Cloud usage data could not be parsed');
if (!/Session\s+usage|Weekly\s+usage|Premium[^0-9]*[0-9]+\s*\/\s*[0-9]+|Monthly\s+usage/i.test(html)) throw new Error('Ollama Cloud usage data could not be parsed');
}
if (provider === 'cursor') {
if (!credential.accessToken && credential.refreshToken) {
+33
View File
@@ -1899,6 +1899,39 @@ const parseOllamaSettingsHtml = (html: string) => {
});
}
// Cost-based plans render "Monthly usage" with a dollar amount instead of
// session/weekly/premium windows; support both page shapes.
const monthlyMatch = html.match(/Monthly\s+usage[\s\S]{0,200}?\$([0-9][0-9,.]*)\s+of\s+\$([0-9][0-9,.]*)/i);
if (monthlyMatch) {
const used = toNumber(monthlyMatch[1].replace(/,/g, ''));
const total = toNumber(monthlyMatch[2].replace(/,/g, ''));
const usedPercent = total && used !== null ? Math.min(100, (used / total) * 100) : null;
windows.monthly = toUsageWindow({
usedPercent,
windowSeconds: null,
resetAt: null,
valueLabel: `$${monthlyMatch[1]} / $${monthlyMatch[2]}`,
});
}
// "Extra usage" credits block (visible when credits/auto-reload is enabled):
// a balance, not a percent. Anchor on "Balance remaining" — nearby "Add $5"
// and auto-reload copy also contain dollar amounts. Surfaced with the
// credits_balance key and OpenAI-style plain money label (the UI renders
// it as "Credits Balance"); a $0 balance is omitted rather than shown.
const balanceMatch = html.match(/Balance\s+remaining[\s\S]{0,200}?\$([0-9][0-9,.]*)/i);
if (balanceMatch) {
const balance = toNumber(balanceMatch[1].replace(/,/g, ''));
if (balance !== 0) {
windows.credits_balance = toUsageWindow({
usedPercent: null,
windowSeconds: null,
resetAt: null,
valueLabel: `$${balanceMatch[1]}`,
});
}
}
return windows;
};