Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/readme/providers.md
Original file line number Diff line number Diff line change
Expand Up @@ -562,7 +562,7 @@ Per-API-key costs remain unsupported until Xiaomi exposes endpoint and schema ev

### Ollama Cloud

Ollama Cloud calls `https://ollama.com/api/usage` and reports session and weekly quota plus per-model request counts. Create an Ollama API key, then set:
Ollama Cloud calls `https://ollama.com/api/usage` and reports session and weekly usage fractions. The API-key response does not include quota reset timestamps or per-model request rows. Create an Ollama API key, then set:

```bash
export OLLAMA_API_KEY="your-api-key"
Expand Down
59 changes: 4 additions & 55 deletions src/lib/ollama-cloud.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,17 @@
/**
* Ollama Cloud usage API client.
*
* Fetches session and weekly usage fractions plus per-model request counts
* from the authenticated Ollama Cloud usage endpoint.
* Fetches session and weekly usage fractions from the authenticated Ollama
* Cloud usage endpoint.
*/

import { sanitizeSingleLineDisplayText } from "./display-sanitize.js";
import { fetchWithTimeout } from "./http.js";
import { resolveOllamaCloudApiKey } from "./ollama-cloud-config.js";
import type { OllamaCloudModelUsage, OllamaCloudResult, OllamaCloudWindow } from "./types.js";
import type { OllamaCloudResult, OllamaCloudWindow } from "./types.js";

const OLLAMA_CLOUD_USAGE_URL = "https://ollama.com/api/usage";
const MAX_RESPONSE_BYTES = 256 * 1024;
const MAX_MODEL_ROWS = 100;

type JsonRecord = Record<string, unknown>;

Expand Down Expand Up @@ -81,47 +80,6 @@ function parseWindow(value: unknown): OllamaCloudWindow | undefined {
};
}

function parseModels(value: unknown, rowErrors: string[]): OllamaCloudModelUsage[] {
if (!Array.isArray(value)) {
rowErrors.push("Models: expected an array");
return [];
}

const models: OllamaCloudModelUsage[] = [];
const seenModels = new Set<string>();

for (const candidate of value) {
if (!isRecord(candidate)) {
rowErrors.push("Models: ignored an invalid row");
continue;
}

const model =
typeof candidate.model === "string"
? sanitizeRemoteSingleLineText(candidate.model).slice(0, 160)
: "";
const requests = candidate.requests;

if (!model) {
rowErrors.push("Models: ignored a row without a model name");
continue;
}
if (typeof requests !== "number" || !Number.isSafeInteger(requests) || requests < 0) {
rowErrors.push(`Models: ignored invalid request count for ${model}`);
continue;
}
if (seenModels.has(model)) {
rowErrors.push(`Models: ignored duplicate model ${model}`);
continue;
}

seenModels.add(model);
models.push({ model, requests });
}

return models.sort((left, right) => left.model.localeCompare(right.model));
}

function parseOllamaCloudUsage(payload: unknown): OllamaCloudResult {
if (!isRecord(payload)) {
return {
Expand All @@ -130,13 +88,6 @@ function parseOllamaCloudUsage(payload: unknown): OllamaCloudResult {
};
}

if (Array.isArray(payload.models) && payload.models.length > MAX_MODEL_ROWS) {
return {
success: false,
error: `Ollama Cloud usage API returned more than ${MAX_MODEL_ROWS} model rows`,
};
}

const rowErrors: string[] = [];
const limits = isRecord(payload.limits) ? payload.limits : undefined;
const session = parseWindow(limits?.session);
Expand All @@ -152,8 +103,7 @@ function parseOllamaCloudUsage(payload: unknown): OllamaCloudResult {
rowErrors.push("Limits: expected an object");
}

const models = parseModels(payload.models, rowErrors);
if (!session && !weekly && models.length === 0) {
if (!session && !weekly) {
return {
success: false,
error: "Ollama Cloud usage API returned no usable usage data",
Expand All @@ -164,7 +114,6 @@ function parseOllamaCloudUsage(payload: unknown): OllamaCloudResult {
success: true,
...(session ? { session } : {}),
...(weekly ? { weekly } : {}),
models,
...(rowErrors.length > 0 ? { rowErrors } : {}),
};
}
Expand Down
3 changes: 1 addition & 2 deletions src/lib/provider-registration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -359,8 +359,7 @@ export const QUOTA_PROVIDER_REGISTRATION_SOURCE = [
authentication: "opencode_auth_api_key",
authFallbacks: ["env_api_key", "global_opencode_config"],
quota: "remote_api",
notes:
"Queries the Ollama Cloud usage API; reports session and weekly quota plus model request counts",
notes: "Queries the Ollama Cloud usage API; reports session and weekly usage fractions",
},
},
{
Expand Down
10 changes: 1 addition & 9 deletions src/lib/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -870,12 +870,6 @@ export interface OllamaCloudWindow {
percentRemaining: number;
}

/** Per-model request count from the Ollama Cloud usage API */
export interface OllamaCloudModelUsage {
model: string;
requests: number;
}

/** Result from the Ollama Cloud usage API */
export type OllamaCloudResult =
| {
Expand All @@ -884,9 +878,7 @@ export type OllamaCloudResult =
session?: OllamaCloudWindow;
/** Weekly usage window, when present */
weekly?: OllamaCloudWindow;
/** Valid per-model request counts */
models: OllamaCloudModelUsage[];
/** Independent response rows that could not be used */
/** Independent response fields that could not be used */
rowErrors?: string[];
}
| QuotaError
Expand Down
23 changes: 1 addition & 22 deletions src/providers/ollama-cloud.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
/**
* Ollama Cloud provider wrapper.
*
* Queries the Ollama Cloud usage API and reports session/weekly quota plus
* provider-reported per-model request counts.
* Queries the Ollama Cloud usage API and reports session/weekly quota.
*/

import type {
Expand Down Expand Up @@ -32,10 +31,6 @@ const REMOTE_API_ACCOUNTING = {

type OllamaCloudSuccess = Extract<OllamaCloudResult, { success: true }>;

function formatRequestCount(requests: number): string {
return `${requests} ${requests === 1 ? "request" : "requests"}`;
}

function mapOllamaCloudSuccess(result: OllamaCloudSuccess): QuotaProviderResult {
const entries: QuotaToastEntry[] = [];

Expand Down Expand Up @@ -65,21 +60,6 @@ function mapOllamaCloudSuccess(result: OllamaCloudSuccess): QuotaProviderResult
});
}

for (const model of result.models) {
entries.push({
kind: "value",
accounting: {
resultType: "usage",
...REMOTE_API_ACCOUNTING,
},
name: `${OLLAMA_CLOUD_PROVIDER_LABEL} ${model.model}`,
group: OLLAMA_CLOUD_PROVIDER_LABEL,
label: `${model.model}:`,
metricLabel: model.model,
value: formatRequestCount(model.requests),
});
}

const errors = (result.rowErrors ?? []).map((message) => ({
label: OLLAMA_CLOUD_PROVIDER_LABEL,
message,
Expand All @@ -95,7 +75,6 @@ function mapOllamaCloudSuccess(result: OllamaCloudSuccess): QuotaProviderResult
...statusDetailsFromRecord({
session_usage_fraction: result.session?.usageFraction.toString(),
weekly_usage_fraction: result.weekly?.usageFraction.toString(),
model_rows: result.models.length.toString(),
}),
...(result.rowErrors ?? []).map((message, index) => ({
key: `live_error_${index + 1}`,
Expand Down
6 changes: 0 additions & 6 deletions tests/helpers/provider-assertions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -298,12 +298,6 @@ export const PROVIDER_ACCOUNTING_LEDGER: Record<string, Array<QuotaToastEntry["a
ownership: "maintained",
authority: "provider_reported",
},
{
resultType: "usage",
acquisitionMethod: "remote_api",
ownership: "maintained",
authority: "provider_reported",
},
],
"quota-providers": [
{
Expand Down
69 changes: 18 additions & 51 deletions tests/lib.ollama-cloud.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,13 +48,15 @@ function mockResponse(params: {

const usagePayload = {
limits: {
session: { usage: 0.25 },
weekly: { usage: 0.405 },
session: {
usage: 0.25,
models: [{ name: "glm-5.3-flash", request_count: 57 }],
},
weekly: {
usage: 0.405,
models: [{ name: "glm-5.3-flash", request_count: 162 }],
},
},
models: [
{ model: "qwen3-coder:480b", requests: 12 },
{ model: "deepseek-v3.1:671b", requests: 1 },
],
};

describe("queryOllamaCloudQuota", () => {
Expand Down Expand Up @@ -108,10 +110,11 @@ describe("queryOllamaCloudQuota", () => {
);
});

it("maps usage fractions and sorts model request counts", async () => {
it("maps usage fractions from a real nested Ollama response", async () => {
mockResponse({ ok: true, status: 200, json: usagePayload });

await expect(queryOllamaCloudQuota()).resolves.toEqual({
const out = await queryOllamaCloudQuota();
expect(out).toEqual({
success: true,
session: {
usageFraction: 0.25,
Expand All @@ -123,58 +126,38 @@ describe("queryOllamaCloudQuota", () => {
usagePercent: 40.5,
percentRemaining: 59.5,
},
models: [
{ model: "deepseek-v3.1:671b", requests: 1 },
{ model: "qwen3-coder:480b", requests: 12 },
],
});
expect(out?.success ? out.rowErrors : undefined).toBeUndefined();
});

it("preserves zero and fully-used fraction boundaries", () => {
expect(
_parseOllamaCloudUsage({
limits: { session: { usage: 0 }, weekly: { usage: 1 } },
models: [],
limits: {
session: { usage: 0 },
weekly: { usage: 1 },
},
}),
).toEqual({
success: true,
session: { usageFraction: 0, usagePercent: 0, percentRemaining: 100 },
weekly: { usageFraction: 1, usagePercent: 100, percentRemaining: 0 },
models: [],
});
});

it("keeps valid rows and reports invalid independent rows", () => {
it("reports invalid independent usage windows", () => {
const out = _parseOllamaCloudUsage({
limits: {
session: { usage: 0.2 },
weekly: { usage: 1.5 },
},
models: [
{ model: "valid-model", requests: 0 },
{ model: "negative", requests: -1 },
{ model: "decimal", requests: 1.5 },
{ model: "valid-model", requests: 3 },
{ model: " unsafe\nmodel\u202e\u001b[31m ", requests: 2 },
null,
],
});

expect(out).toMatchObject({
success: true,
session: { usageFraction: 0.2, usagePercent: 20, percentRemaining: 80 },
models: [
{ model: "unsafe model", requests: 2 },
{ model: "valid-model", requests: 0 },
],
});
expect(out && out.success ? out.rowErrors : []).toEqual([
"Weekly: ignored invalid usage fraction",
"Models: ignored invalid request count for negative",
"Models: ignored invalid request count for decimal",
"Models: ignored duplicate model valid-model",
"Models: ignored an invalid row",
]);
expect(out?.success ? out.rowErrors : []).toEqual(["Weekly: ignored invalid usage fraction"]);
});

it.each([null, [], "invalid"])("rejects an invalid root payload: %j", (payload) => {
Expand All @@ -184,26 +167,10 @@ describe("queryOllamaCloudQuota", () => {
});
});

it("rejects more than 100 model rows", () => {
expect(
_parseOllamaCloudUsage({
limits: { session: { usage: 0.1 } },
models: Array.from({ length: 101 }, (_, index) => ({
model: `model-${index}`,
requests: index,
})),
}),
).toEqual({
success: false,
error: "Ollama Cloud usage API returned more than 100 model rows",
});
});

it("rejects an object with no usable usage data", () => {
expect(
_parseOllamaCloudUsage({
limits: { session: { usage: -1 }, weekly: { usage: Number.NaN } },
models: [{ model: "bad", requests: -1 }],
}),
).toEqual({
success: false,
Expand Down
3 changes: 1 addition & 2 deletions tests/lib.provider-metadata.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -197,8 +197,7 @@ describe("provider-metadata", () => {
authentication: "opencode_auth_api_key",
authFallbacks: ["env_api_key", "global_opencode_config"],
quota: "remote_api",
notes:
"Queries the Ollama Cloud usage API; reports session and weekly quota plus model request counts",
notes: "Queries the Ollama Cloud usage API; reports session and weekly usage fractions",
},
{
id: "quota-providers",
Expand Down
14 changes: 2 additions & 12 deletions tests/providers.ollama-cloud.surfaces.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,15 +28,6 @@ const data: QuotaRenderData = {
label: "Weekly:",
percentRemaining: 60,
},
{
kind: "value",
accounting: { resultType: "usage", ...accounting },
name: "Ollama Cloud qwen3",
group: "Ollama Cloud",
label: "qwen3:",
metricLabel: "qwen3",
value: "12 requests",
},
],
errors: [],
};
Expand All @@ -60,9 +51,8 @@ describe("Ollama Cloud four-surface formatting", () => {
expect(output).toContain("75%");
}

expect(command).toContain("qwen3");
for (const output of [command, toast, sidebar]) {
expect(output).toContain("12 requests");
for (const output of [command, toast, sidebar, compact]) {
expect(output).not.toContain("requests");
}
});
});
Loading