Compare commits

...

1 Commits

Author SHA1 Message Date
diegosouzapw
27d643a9da fix(sse): parse Ollama Cloud's current usage markup (#12749)
extractOllamaUsagePercent() only inspected the data-usage-track element's
own opening tag. Ollama's settings page now writes the aria-label as
"$X of $Y used" (no % sign) and moves the width:NN% style onto a
nested child div, so both extraction paths silently returned null and
usage/quota never populated.

Add a $X-of-$Y aria-label fallback (used/total*100) and widen the
width-style lookup to search the whole data-usage-track block instead
of only its own opening tag, while keeping the old NN%-used /
same-tag-style markup working.
2026-09-10 15:19:26 -03:00
3 changed files with 105 additions and 8 deletions

View File

@@ -0,0 +1 @@
- fix(sse): parse Ollama Cloud's current usage markup (`$X of $Y used` aria-label, nested width style) (#12749)

View File

@@ -77,16 +77,36 @@ function normalizeOllamaCloudCookie(value: string): string {
: trimmed;
}
function clampPercent(pct: number): number | null {
return Number.isFinite(pct) && pct >= 0 && pct <= 100 ? pct : null;
}
function extractAriaLabelPercent(tagHeader: string): number | null {
const directMatch = tagHeader.match(/(\d+(?:\.\d+)?)%\s*used/);
if (directMatch) return clampPercent(toNumber(directMatch[1], Number.NaN));
const ratioMatch = tagHeader.match(/\$\s*([0-9.]+)\s*of\s*\$\s*([0-9.]+)\s*used/i);
if (!ratioMatch) return null;
const used = toNumber(ratioMatch[1], Number.NaN);
const total = toNumber(ratioMatch[2], Number.NaN);
if (!Number.isFinite(used) || !Number.isFinite(total) || total <= 0) return null;
return clampPercent((used / total) * 100);
}
function extractWidthStylePercent(html: string): number | null {
const styleMatches = html.matchAll(/style="([^"]*)"/g);
for (const match of styleMatches) {
const pct = toNumber(match[1].match(/(?:^|;)\s*width\s*:\s*([0-9.]+)%/)?.[1], Number.NaN);
const clamped = clampPercent(pct);
if (clamped !== null) return clamped;
}
return null;
}
function extractOllamaUsagePercent(trackHtml: string): number | null {
const tagHeader = trackHtml.match(/^[^>]*/)?.[0] ?? "";
const ariaMatch = tagHeader.match(/(\d+(?:\.\d+)?)%\s*used/);
if (ariaMatch) {
const pct = toNumber(ariaMatch[1], Number.NaN);
if (Number.isFinite(pct) && pct >= 0 && pct <= 100) return pct;
}
const style = tagHeader.match(/style="([^"]*)"/)?.[1] ?? "";
const pct = toNumber(style.match(/(?:^|;)\s*width\s*:\s*([0-9.]+)%/)?.[1], Number.NaN);
return Number.isFinite(pct) && pct >= 0 && pct <= 100 ? pct : null;
const ariaPercent = extractAriaLabelPercent(tagHeader);
if (ariaPercent !== null) return ariaPercent;
return extractWidthStylePercent(trackHtml);
}
function parseOllamaCloudSettingsHtml(html: string): OllamaCloudUsage | null {

View File

@@ -189,3 +189,79 @@ test("getUsageForProvider reports expired Ollama Cloud cookies on redirect", asy
else process.env.OLLAMA_USAGE_COOKIE = originalCookie;
}
});
test("getUsageForProvider parses the current $X-of-$Y aria-label with nested width style (#12749)", async () => {
const originalFetch = globalThis.fetch;
const originalCookie = process.env.OLLAMA_USAGE_COOKIE;
const originalOmniCookie = process.env.OMNIROUTE_OLLAMA_USAGE_COOKIE;
delete process.env.OLLAMA_USAGE_COOKIE;
process.env.OMNIROUTE_OLLAMA_USAGE_COOKIE = "__Secure-session=test-cookie";
globalThis.fetch = async () =>
new Response(
[
'<span class="capitalize">pro</span>',
'<div class="relative h-3 overflow-hidden rounded-full bg-neutral-200" data-usage-track aria-label="Monthly usage $60.01 of $60 used">',
'<div class="flex h-full overflow-hidden bg-neutral-950" style="width: 100%; background: #ef4444;"></div>',
'<span class="local-time" data-time="2026-06-22T15:00:00.000Z"></span>',
"</div>",
].join(""),
{ status: 200, headers: { "content-type": "text/html" } }
);
try {
const result = (await usage.getUsageForProvider({
id: "ollama-cloud-new-markup",
provider: "ollama-cloud",
apiKey: "ollama-chat-key",
})) as { message?: string; quotas?: Record<string, { used: number }> };
assert.ok(
result.quotas && Object.keys(result.quotas).length > 0,
`expected quotas, got message: ${result.message}`
);
assert.equal(result.quotas!.session.used, 100);
} finally {
globalThis.fetch = originalFetch;
if (originalCookie === undefined) delete process.env.OLLAMA_USAGE_COOKIE;
else process.env.OLLAMA_USAGE_COOKIE = originalCookie;
if (originalOmniCookie === undefined) delete process.env.OMNIROUTE_OLLAMA_USAGE_COOKIE;
else process.env.OMNIROUTE_OLLAMA_USAGE_COOKIE = originalOmniCookie;
}
});
test("getUsageForProvider still finds width style on a nested child when no aria-label percent exists (#12749)", async () => {
const originalFetch = globalThis.fetch;
const originalCookie = process.env.OLLAMA_USAGE_COOKIE;
const originalOmniCookie = process.env.OMNIROUTE_OLLAMA_USAGE_COOKIE;
delete process.env.OLLAMA_USAGE_COOKIE;
process.env.OMNIROUTE_OLLAMA_USAGE_COOKIE = "__Secure-session=test-cookie";
globalThis.fetch = async () =>
new Response(
[
'<div class="relative h-3" data-usage-track aria-label="Weekly usage $12 of $60 used">',
'<div class="flex h-full" style="width: 20%;"></div>',
'<span class="local-time" data-time="2026-06-29T15:00:00.000Z"></span>',
"</div>",
].join(""),
{ status: 200, headers: { "content-type": "text/html" } }
);
try {
const result = (await usage.getUsageForProvider({
id: "ollama-cloud-nested-width",
provider: "ollama-cloud",
apiKey: "ollama-chat-key",
})) as { quotas?: Record<string, { used: number }> };
// The aria-label ratio ($12 of $60 = 20%) is used, matching the nested style width fallback.
assert.equal(result.quotas!.session.used, 20);
} finally {
globalThis.fetch = originalFetch;
if (originalCookie === undefined) delete process.env.OLLAMA_USAGE_COOKIE;
else process.env.OLLAMA_USAGE_COOKIE = originalCookie;
if (originalOmniCookie === undefined) delete process.env.OMNIROUTE_OLLAMA_USAGE_COOKIE;
else process.env.OMNIROUTE_OLLAMA_USAGE_COOKIE = originalOmniCookie;
}
});