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:
@@ -35,6 +35,37 @@ export const parseOllamaSettingsHtml = (html) => {
|
||||
valueLabel: `${used ?? 0} / ${total ?? 0}`
|
||||
});
|
||||
}
|
||||
// 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;
|
||||
};
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { describe, expect, it } from 'bun:test';
|
||||
import { fetchOllamaCloudUsage } from './ollama-cloud.js';
|
||||
import { parseOllamaSettingsHtml, fetchOllamaCloudUsage } from './ollama-cloud.js';
|
||||
|
||||
describe('Ollama Cloud quota provider', () => {
|
||||
it('rejects redirects without forwarding credentials', async () => {
|
||||
@@ -9,4 +9,36 @@ describe('Ollama Cloud quota provider', () => {
|
||||
it('rejects successful pages without usage data', async () => {
|
||||
await expect(fetchOllamaCloudUsage({ cookie: 'session=secret' }, async () => new Response('<html></html>'))).rejects.toThrow('could not be parsed');
|
||||
});
|
||||
|
||||
it('parses session/weekly/premium windows', () => {
|
||||
const windows = parseOllamaSettingsHtml('<p>Session usage: 42%</p><p>Weekly usage: 7%</p><p>Premium 120 / 1000</p>');
|
||||
expect(windows.session.usedPercent).toBe(42);
|
||||
expect(windows.weekly.usedPercent).toBe(7);
|
||||
expect(windows.premium.usedPercent).toBe(12);
|
||||
expect(windows.premium.valueLabel).toBe('120 / 1000');
|
||||
});
|
||||
|
||||
it('parses the cost-based monthly usage shape', () => {
|
||||
const html = '<span class="text-sm">Monthly usage</span>\n <span class="text-sm "\n >$4.39 of $60 used</span\n >';
|
||||
const windows = parseOllamaSettingsHtml(html);
|
||||
expect(windows.monthly.usedPercent).toBeCloseTo(7.3, 1);
|
||||
expect(windows.monthly.valueLabel).toBe('$4.39 / $60');
|
||||
expect(windows.monthly.remainingPercent).toBeCloseTo(92.7, 1);
|
||||
});
|
||||
|
||||
it('parses the extra-usage credits balance', () => {
|
||||
const html = '<div><span>Balance remaining</span><span>$12.50</span></div><button>Add $5</button>';
|
||||
const windows = parseOllamaSettingsHtml(html);
|
||||
expect(windows.credits_balance.usedPercent).toBeNull();
|
||||
expect(windows.credits_balance.valueLabel).toBe('$12.50');
|
||||
});
|
||||
|
||||
it('omits the credits balance when it is zero', () => {
|
||||
const html = '<div><span>Balance remaining</span><span>$0.00</span></div>';
|
||||
expect(parseOllamaSettingsHtml(html).credits_balance).toBeUndefined();
|
||||
});
|
||||
|
||||
it('ignores nearby dollar amounts that are not the balance', () => {
|
||||
expect(parseOllamaSettingsHtml('<p>Add $5 to your balance when it hits $0</p>').credits_balance).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user