zcode-acp-server 0.40.1 → 0.42.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -3
- package/README.zh-CN.md +1 -1
- package/assets/martty-plugins/quota-dock/plugin.json +3 -3
- package/dist/bin/quota.d.ts +1 -1
- package/dist/bin/quota.d.ts.map +1 -1
- package/dist/bin/quota.js +28 -17
- package/dist/bin/quota.js.map +1 -1
- package/dist/code-fingerprint.json +2 -2
- package/dist/config/user-config.d.ts +10 -0
- package/dist/config/user-config.d.ts.map +1 -1
- package/dist/config/user-config.js +31 -4
- package/dist/config/user-config.js.map +1 -1
- package/dist/handlers/account.d.ts +27 -6
- package/dist/handlers/account.d.ts.map +1 -1
- package/dist/handlers/account.js +41 -8
- package/dist/handlers/account.js.map +1 -1
- package/dist/quota/combined.d.ts +13 -9
- package/dist/quota/combined.d.ts.map +1 -1
- package/dist/quota/combined.js +54 -29
- package/dist/quota/combined.js.map +1 -1
- package/dist/quota/format.d.ts +12 -2
- package/dist/quota/format.d.ts.map +1 -1
- package/dist/quota/format.js +29 -3
- package/dist/quota/format.js.map +1 -1
- package/dist/quota/live.d.ts.map +1 -1
- package/dist/quota/live.js +12 -6
- package/dist/quota/live.js.map +1 -1
- package/dist/quota/ollama-cloud/cache.d.ts +16 -0
- package/dist/quota/ollama-cloud/cache.d.ts.map +1 -0
- package/dist/quota/ollama-cloud/cache.js +30 -0
- package/dist/quota/ollama-cloud/cache.js.map +1 -0
- package/dist/quota/ollama-cloud/client.d.ts +36 -0
- package/dist/quota/ollama-cloud/client.d.ts.map +1 -0
- package/dist/quota/ollama-cloud/client.js +56 -0
- package/dist/quota/ollama-cloud/client.js.map +1 -0
- package/dist/quota/ollama-cloud/config.d.ts +25 -0
- package/dist/quota/ollama-cloud/config.d.ts.map +1 -0
- package/dist/quota/ollama-cloud/config.js +31 -0
- package/dist/quota/ollama-cloud/config.js.map +1 -0
- package/dist/quota/ollama-cloud/format.d.ts +30 -0
- package/dist/quota/ollama-cloud/format.d.ts.map +1 -0
- package/dist/quota/ollama-cloud/format.js +57 -0
- package/dist/quota/ollama-cloud/format.js.map +1 -0
- package/dist/quota/ollama-cloud/index.d.ts +47 -0
- package/dist/quota/ollama-cloud/index.d.ts.map +1 -0
- package/dist/quota/ollama-cloud/index.js +183 -0
- package/dist/quota/ollama-cloud/index.js.map +1 -0
- package/dist/quota/ollama-cloud/types.d.ts +53 -0
- package/dist/quota/ollama-cloud/types.d.ts.map +1 -0
- package/dist/quota/ollama-cloud/types.js +16 -0
- package/dist/quota/ollama-cloud/types.js.map +1 -0
- package/dist/quota/opencode-go/config.d.ts +11 -11
- package/dist/quota/opencode-go/config.js +11 -11
- package/dist/quota/opencode-go/index.d.ts.map +1 -1
- package/dist/quota/opencode-go/index.js +16 -7
- package/dist/quota/opencode-go/index.js.map +1 -1
- package/dist/remote/hub-server.d.ts.map +1 -1
- package/dist/remote/hub-server.js +10 -4
- package/dist/remote/hub-server.js.map +1 -1
- package/docs/CLI.md +48 -13
- package/docs/REMOTE-CLIENTS.md +18 -2
- package/package.json +1 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"format.d.ts","sourceRoot":"","sources":["../../../src/quota/ollama-cloud/format.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAKH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAehD,qEAAqE;AACrE,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,EAAE,CAAC;CAChB;AAED;;;;;;;;;GASG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,aAAa,EAAE,KAAK,UAAQ,GAAG,eAAe,CA0BrF"}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ollama Cloud usage formatting.
|
|
3
|
+
*
|
|
4
|
+
* Renders the plan's usage windows as progress bars in the same style as the
|
|
5
|
+
* GLM and Opencode Go sections, so all sections read as one card in the
|
|
6
|
+
* combined view. Which windows exist depends on the account's plan: legacy
|
|
7
|
+
* plans expose session (5h) + weekly; current credit plans expose monthly.
|
|
8
|
+
* The API exposes no reset timestamps, so the reset moments are derived at
|
|
9
|
+
* query time (window anchoring, or /api/me's billing period for monthly) and
|
|
10
|
+
* rendered as the same `MM-DD HH:MM` trailing stamp the other providers use —
|
|
11
|
+
* absent when the monthly lookup fails.
|
|
12
|
+
*/
|
|
13
|
+
import { pickOverlay, renderColorBar } from "../color.js";
|
|
14
|
+
import { formatResetTime, renderBar } from "../format.js";
|
|
15
|
+
import { roundTenth } from "../rounding.js";
|
|
16
|
+
/** Label + window metadata, in display order. Which rows appear depends on
|
|
17
|
+
* the account's plan: legacy plans carry session (5h) + weekly; current
|
|
18
|
+
* credit plans carry monthly only. */
|
|
19
|
+
const WINDOW_META = [
|
|
20
|
+
{ key: "session", label: "5h", resetKey: "sessionResetAt" },
|
|
21
|
+
{ key: "weekly", label: "Week", resetKey: "weeklyResetAt" },
|
|
22
|
+
{ key: "monthly", label: "Month", resetKey: "monthlyResetAt" },
|
|
23
|
+
];
|
|
24
|
+
/**
|
|
25
|
+
* Render the Ollama Cloud section.
|
|
26
|
+
*
|
|
27
|
+
* Used by the combined formatter. The header is always `Ollama Cloud`; body
|
|
28
|
+
* has one bar line per window. Non-success kinds return a header + a single
|
|
29
|
+
* explanatory line.
|
|
30
|
+
*
|
|
31
|
+
* When `color` is true the bar is a heat-colored 24-bit ANSI bar with the
|
|
32
|
+
* percent overlaid inside, mirroring the other providers' color layout.
|
|
33
|
+
*/
|
|
34
|
+
export function formatOcSection(result, color = false) {
|
|
35
|
+
const header = "Ollama Cloud";
|
|
36
|
+
if (result.kind !== "success") {
|
|
37
|
+
const msg = result.kind === "not_configured"
|
|
38
|
+
? "not configured (set OLLAMA_API_KEY)"
|
|
39
|
+
: result.kind === "auth_error"
|
|
40
|
+
? "auth failed — check your Ollama API key"
|
|
41
|
+
: "unavailable";
|
|
42
|
+
return { header, body: [msg] };
|
|
43
|
+
}
|
|
44
|
+
const body = WINDOW_META.filter((m) => result[m.key] !== undefined).map((m) => {
|
|
45
|
+
const fraction = result[m.key];
|
|
46
|
+
const pct = roundTenth(fraction * 100);
|
|
47
|
+
const reset = formatResetTime(result[m.resetKey]);
|
|
48
|
+
const trailing = reset ? ` · ${reset}` : "";
|
|
49
|
+
if (color) {
|
|
50
|
+
const bar = renderColorBar(pct, { overlay: pickOverlay({ usedPercent: pct }) });
|
|
51
|
+
return `${m.label.padEnd(5)} ${bar}${trailing}`;
|
|
52
|
+
}
|
|
53
|
+
return `${m.label.padEnd(5)} ${renderBar(pct)} ${String(pct).padStart(2)}%${trailing}`;
|
|
54
|
+
});
|
|
55
|
+
return { header, body };
|
|
56
|
+
}
|
|
57
|
+
//# sourceMappingURL=format.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"format.js","sourceRoot":"","sources":["../../../src/quota/ollama-cloud/format.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAC1D,OAAO,EAAE,eAAe,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAC1D,OAAO,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAG5C;;uCAEuC;AACvC,MAAM,WAAW,GAIZ;IACH,EAAE,GAAG,EAAE,SAAS,EAAE,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAE,gBAAgB,EAAE;IAC3D,EAAE,GAAG,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,eAAe,EAAE;IAC3D,EAAE,GAAG,EAAE,SAAS,EAAE,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,gBAAgB,EAAE;CAC/D,CAAC;AAQF;;;;;;;;;GASG;AACH,MAAM,UAAU,eAAe,CAAC,MAAqB,EAAE,KAAK,GAAG,KAAK;IAClE,MAAM,MAAM,GAAG,cAAc,CAAC;IAE9B,IAAI,MAAM,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;QAC9B,MAAM,GAAG,GACP,MAAM,CAAC,IAAI,KAAK,gBAAgB;YAC9B,CAAC,CAAC,qCAAqC;YACvC,CAAC,CAAC,MAAM,CAAC,IAAI,KAAK,YAAY;gBAC5B,CAAC,CAAC,yCAAyC;gBAC3C,CAAC,CAAC,aAAa,CAAC;QACtB,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC;IACjC,CAAC;IAED,MAAM,IAAI,GAAG,WAAW,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,SAAS,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE;QAC5E,MAAM,QAAQ,GAAG,MAAM,CAAC,CAAC,CAAC,GAAG,CAAE,CAAC;QAChC,MAAM,GAAG,GAAG,UAAU,CAAC,QAAQ,GAAG,GAAG,CAAC,CAAC;QACvC,MAAM,KAAK,GAAG,eAAe,CAAC,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC;QAClD,MAAM,QAAQ,GAAG,KAAK,CAAC,CAAC,CAAC,MAAM,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAC5C,IAAI,KAAK,EAAE,CAAC;YACV,MAAM,GAAG,GAAG,cAAc,CAAC,GAAG,EAAE,EAAE,OAAO,EAAE,WAAW,CAAC,EAAE,WAAW,EAAE,GAAG,EAAE,CAAC,EAAE,CAAC,CAAC;YAChF,OAAO,GAAG,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,GAAG,GAAG,QAAQ,EAAE,CAAC;QAClD,CAAC;QACD,OAAO,GAAG,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,SAAS,CAAC,GAAG,CAAC,KAAK,MAAM,CAAC,GAAG,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,QAAQ,EAAE,CAAC;IAC1F,CAAC,CAAC,CAAC;IAEH,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC;AAC1B,CAAC"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ollama Cloud usage orchestration — the entry point used by the
|
|
3
|
+
* `zcode-acp quota` CLI.
|
|
4
|
+
*
|
|
5
|
+
* Flow: credential (env + config file) → cache check → fetch → status/auth
|
|
6
|
+
* check → parse → cache write. Any thrown error degrades to `unavailable`
|
|
7
|
+
* rather than propagating, so the CLI always produces output.
|
|
8
|
+
*
|
|
9
|
+
* A missing API key yields `not_configured`, which the combined view silently
|
|
10
|
+
* skips — so users who only care about GLM see no noise.
|
|
11
|
+
*/
|
|
12
|
+
import type { OcQueryResult } from "./types.js";
|
|
13
|
+
/** Session window length: a rolling 5h bucket anchored at the Unix epoch. */
|
|
14
|
+
export declare const SESSION_WINDOW_MS: number;
|
|
15
|
+
/** Weekly window anchor: Monday 00:00 UTC (the epoch was a Thursday, so the
|
|
16
|
+
* first Monday sits 4 days in) — same inference as the pi-multi-account
|
|
17
|
+
* reference client. */
|
|
18
|
+
export declare const WEEK_ANCHOR_MS: number;
|
|
19
|
+
/**
|
|
20
|
+
* Next reset of the epoch-aligned 5h session bucket strictly after `t`.
|
|
21
|
+
* The anchoring is inferred from observed bucket behaviour (undocumented).
|
|
22
|
+
*/
|
|
23
|
+
export declare function nextSessionReset(t: number): number;
|
|
24
|
+
/** Next Monday 00:00 UTC strictly after `t` (the weekly window reset). */
|
|
25
|
+
export declare function nextWeeklyReset(t: number): number;
|
|
26
|
+
/**
|
|
27
|
+
* Next monthly anniversary of `createdMs` strictly after `now` — new credit
|
|
28
|
+
* plans reset monthly on the subscription day, so the anchor keeps the
|
|
29
|
+
* subscription's UTC time-of-day, not a made-up midnight. The day clamps to
|
|
30
|
+
* the target month's length (a 31st signup rolls to the 30th/28th).
|
|
31
|
+
*/
|
|
32
|
+
export declare function nextMonthlyAnniversary(createdMs: number, now: number): number;
|
|
33
|
+
/**
|
|
34
|
+
* Query Ollama Cloud usage and return a normalised {@link OcQueryResult}.
|
|
35
|
+
*
|
|
36
|
+
* - No key → `not_configured`.
|
|
37
|
+
* - Serves a cached result when fresh (< 10s).
|
|
38
|
+
* - HTTP 401/403 → `auth_error` (bad or revoked key).
|
|
39
|
+
* - Network/timeout/parse failure → `unavailable`.
|
|
40
|
+
*/
|
|
41
|
+
export declare function queryOcUsage(): Promise<OcQueryResult>;
|
|
42
|
+
export { clearCache, clearCache as clearOcCache } from "./cache.js";
|
|
43
|
+
export { fetchOcMe, fetchOcUsage, ME_URL, USAGE_URL } from "./client.js";
|
|
44
|
+
export { formatOcSection } from "./format.js";
|
|
45
|
+
export { loadApiKey, ENV_API_KEY } from "./config.js";
|
|
46
|
+
export type { OcQueryResult, OcUsageResponse } from "./types.js";
|
|
47
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/quota/ollama-cloud/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAMH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAEhD,6EAA6E;AAC7E,eAAO,MAAM,iBAAiB,QAAgB,CAAC;AAC/C;;wBAEwB;AACxB,eAAO,MAAM,cAAc,QAAiB,CAAC;AAG7C;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAElD;AAED,0EAA0E;AAC1E,wBAAgB,eAAe,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAGjD;AAyCD;;;;;GAKG;AACH,wBAAgB,sBAAsB,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAqB7E;AAcD;;;;;;;GAOG;AACH,wBAAsB,YAAY,IAAI,OAAO,CAAC,aAAa,CAAC,CA2D3D;AAGD,OAAO,EAAE,UAAU,EAAE,UAAU,IAAI,YAAY,EAAE,MAAM,YAAY,CAAC;AACpE,OAAO,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACzE,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAC9C,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AACtD,YAAY,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC"}
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ollama Cloud usage orchestration — the entry point used by the
|
|
3
|
+
* `zcode-acp quota` CLI.
|
|
4
|
+
*
|
|
5
|
+
* Flow: credential (env + config file) → cache check → fetch → status/auth
|
|
6
|
+
* check → parse → cache write. Any thrown error degrades to `unavailable`
|
|
7
|
+
* rather than propagating, so the CLI always produces output.
|
|
8
|
+
*
|
|
9
|
+
* A missing API key yields `not_configured`, which the combined view silently
|
|
10
|
+
* skips — so users who only care about GLM see no noise.
|
|
11
|
+
*/
|
|
12
|
+
import { log } from "../../utils.js";
|
|
13
|
+
import { getCached, setCached } from "./cache.js";
|
|
14
|
+
import { loadApiKey } from "./config.js";
|
|
15
|
+
import { fetchOcMe, fetchOcUsage } from "./client.js";
|
|
16
|
+
/** Session window length: a rolling 5h bucket anchored at the Unix epoch. */
|
|
17
|
+
export const SESSION_WINDOW_MS = 5 * 3_600_000;
|
|
18
|
+
/** Weekly window anchor: Monday 00:00 UTC (the epoch was a Thursday, so the
|
|
19
|
+
* first Monday sits 4 days in) — same inference as the pi-multi-account
|
|
20
|
+
* reference client. */
|
|
21
|
+
export const WEEK_ANCHOR_MS = 4 * 86_400_000;
|
|
22
|
+
const WEEK_MS = 7 * 86_400_000;
|
|
23
|
+
/**
|
|
24
|
+
* Next reset of the epoch-aligned 5h session bucket strictly after `t`.
|
|
25
|
+
* The anchoring is inferred from observed bucket behaviour (undocumented).
|
|
26
|
+
*/
|
|
27
|
+
export function nextSessionReset(t) {
|
|
28
|
+
return (Math.floor(t / SESSION_WINDOW_MS) + 1) * SESSION_WINDOW_MS;
|
|
29
|
+
}
|
|
30
|
+
/** Next Monday 00:00 UTC strictly after `t` (the weekly window reset). */
|
|
31
|
+
export function nextWeeklyReset(t) {
|
|
32
|
+
const next = WEEK_ANCHOR_MS + (Math.floor((t - WEEK_ANCHOR_MS) / WEEK_MS) + 1) * WEEK_MS;
|
|
33
|
+
return next > t ? next : next + WEEK_MS;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Best-effort monthly reset lookup via POST /api/me. Two shapes exist:
|
|
37
|
+
* - `SubscriptionPeriodEnd` (Go `sql.NullTime` `{Time, Valid}`) — the billing
|
|
38
|
+
* period end, when the account carries one;
|
|
39
|
+
* - `CreatedAt` — the subscription start. New credit plans (live-verified
|
|
40
|
+
* 2026-09-17: a Pro account returns only ID/CreatedAt/Email/Plan, no
|
|
41
|
+
* SubscriptionPeriodEnd) reset monthly on the subscription day, so the
|
|
42
|
+
* reset is the next monthly anniversary of that timestamp.
|
|
43
|
+
* Any failure (network, auth, shape) yields undefined — the caller then
|
|
44
|
+
* renders the monthly window without a reset stamp.
|
|
45
|
+
*/
|
|
46
|
+
async function lookupMonthlyReset(apiKey) {
|
|
47
|
+
try {
|
|
48
|
+
const resp = await fetchOcMe(apiKey);
|
|
49
|
+
if (resp.status !== 200)
|
|
50
|
+
return undefined;
|
|
51
|
+
const parsed = JSON.parse(resp.text);
|
|
52
|
+
const period = parsed.SubscriptionPeriodEnd ?? parsed.subscriptionPeriodEnd;
|
|
53
|
+
if (period !== undefined) {
|
|
54
|
+
const raw = period.Time ?? period.time;
|
|
55
|
+
const valid = period.Valid ??
|
|
56
|
+
period.valid;
|
|
57
|
+
if (valid !== false && typeof raw === "string") {
|
|
58
|
+
const ms = Date.parse(raw);
|
|
59
|
+
if (Number.isFinite(ms))
|
|
60
|
+
return ms;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
const createdRaw = parsed.CreatedAt ?? parsed.createdAt;
|
|
64
|
+
if (typeof createdRaw === "string") {
|
|
65
|
+
const created = Date.parse(createdRaw);
|
|
66
|
+
if (Number.isFinite(created))
|
|
67
|
+
return nextMonthlyAnniversary(created, Date.now());
|
|
68
|
+
}
|
|
69
|
+
return undefined;
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
return undefined;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Next monthly anniversary of `createdMs` strictly after `now` — new credit
|
|
77
|
+
* plans reset monthly on the subscription day, so the anchor keeps the
|
|
78
|
+
* subscription's UTC time-of-day, not a made-up midnight. The day clamps to
|
|
79
|
+
* the target month's length (a 31st signup rolls to the 30th/28th).
|
|
80
|
+
*/
|
|
81
|
+
export function nextMonthlyAnniversary(createdMs, now) {
|
|
82
|
+
const created = new Date(createdMs);
|
|
83
|
+
const nowDate = new Date(now);
|
|
84
|
+
const day = created.getUTCDate();
|
|
85
|
+
const h = created.getUTCHours();
|
|
86
|
+
const min = created.getUTCMinutes();
|
|
87
|
+
const sec = created.getUTCSeconds();
|
|
88
|
+
const ms = created.getUTCMilliseconds();
|
|
89
|
+
for (let year = nowDate.getUTCFullYear(); year <= nowDate.getUTCFullYear() + 1; year++) {
|
|
90
|
+
for (let month = year === nowDate.getUTCFullYear() ? nowDate.getUTCMonth() : 0; month < 12; month++) {
|
|
91
|
+
// Clamp to the month's last day (UTC months are 0-based).
|
|
92
|
+
const lastDay = new Date(Date.UTC(year, month + 1, 0)).getUTCDate();
|
|
93
|
+
const candidate = Date.UTC(year, month, Math.min(day, lastDay), h, min, sec, ms);
|
|
94
|
+
if (candidate > now)
|
|
95
|
+
return candidate;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
return Number.NaN;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Validate a usage value from the response body: a finite number in [0, 1].
|
|
102
|
+
*
|
|
103
|
+
* Values > 1 are rejected on purpose: the endpoint is undocumented, and if
|
|
104
|
+
* Ollama ever switches to percent-valued numbers, silently treating 42 as a
|
|
105
|
+
* 4200% fraction would render a nonsensical full bar. The same guard is used
|
|
106
|
+
* by the pi-multi-account reference client.
|
|
107
|
+
*/
|
|
108
|
+
function validFraction(v) {
|
|
109
|
+
return typeof v === "number" && Number.isFinite(v) && v >= 0 && v <= 1;
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Query Ollama Cloud usage and return a normalised {@link OcQueryResult}.
|
|
113
|
+
*
|
|
114
|
+
* - No key → `not_configured`.
|
|
115
|
+
* - Serves a cached result when fresh (< 10s).
|
|
116
|
+
* - HTTP 401/403 → `auth_error` (bad or revoked key).
|
|
117
|
+
* - Network/timeout/parse failure → `unavailable`.
|
|
118
|
+
*/
|
|
119
|
+
export async function queryOcUsage() {
|
|
120
|
+
const cached = getCached();
|
|
121
|
+
if (cached) {
|
|
122
|
+
log("ollama-cloud: serving cached result");
|
|
123
|
+
return cached;
|
|
124
|
+
}
|
|
125
|
+
const apiKey = loadApiKey();
|
|
126
|
+
if (!apiKey)
|
|
127
|
+
return { kind: "not_configured" };
|
|
128
|
+
let result;
|
|
129
|
+
try {
|
|
130
|
+
const resp = await fetchOcUsage(apiKey);
|
|
131
|
+
if (resp.status === 401 || resp.status === 403) {
|
|
132
|
+
result = { kind: "auth_error" };
|
|
133
|
+
}
|
|
134
|
+
else if (resp.status !== 200) {
|
|
135
|
+
// 429 = quota exhausted, 5xx = server-side; neither is a usage snapshot.
|
|
136
|
+
log(`ollama-cloud: unexpected status ${resp.status}`);
|
|
137
|
+
result = { kind: "unavailable" };
|
|
138
|
+
}
|
|
139
|
+
else {
|
|
140
|
+
const parsed = JSON.parse(resp.text);
|
|
141
|
+
const limits = parsed.limits ?? {};
|
|
142
|
+
const window = (key) => validFraction(limits[key]?.usage) ? limits[key].usage : undefined;
|
|
143
|
+
const session = window("session");
|
|
144
|
+
const weekly = window("weekly");
|
|
145
|
+
const monthly = window("monthly");
|
|
146
|
+
if (session !== undefined || weekly !== undefined || monthly !== undefined) {
|
|
147
|
+
// At least one recognised window — legacy plans carry session+weekly,
|
|
148
|
+
// current credit plans carry monthly only. Reset moments: session and
|
|
149
|
+
// weekly derive from the window anchoring; monthly needs the separate
|
|
150
|
+
// /api/me billing-period lookup.
|
|
151
|
+
const fetchedAt = Date.now();
|
|
152
|
+
result = {
|
|
153
|
+
kind: "success",
|
|
154
|
+
...(session !== undefined && { session, sessionResetAt: nextSessionReset(fetchedAt) }),
|
|
155
|
+
...(weekly !== undefined && { weekly, weeklyResetAt: nextWeeklyReset(fetchedAt) }),
|
|
156
|
+
...(monthly !== undefined && { monthly }),
|
|
157
|
+
fetchedAt,
|
|
158
|
+
};
|
|
159
|
+
if (monthly !== undefined) {
|
|
160
|
+
const monthlyResetAt = await lookupMonthlyReset(apiKey);
|
|
161
|
+
if (monthlyResetAt !== undefined)
|
|
162
|
+
result.monthlyResetAt = monthlyResetAt;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
else {
|
|
166
|
+
log("ollama-cloud: response shape not recognised (endpoint may have changed)");
|
|
167
|
+
result = { kind: "unavailable" };
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
catch (e) {
|
|
172
|
+
log(`ollama-cloud: fetch failed (${e instanceof Error ? e.message : String(e)})`);
|
|
173
|
+
result = { kind: "unavailable" };
|
|
174
|
+
}
|
|
175
|
+
setCached(result);
|
|
176
|
+
return result;
|
|
177
|
+
}
|
|
178
|
+
// Re-exports for consumers (CLI + tests).
|
|
179
|
+
export { clearCache, clearCache as clearOcCache } from "./cache.js";
|
|
180
|
+
export { fetchOcMe, fetchOcUsage, ME_URL, USAGE_URL } from "./client.js";
|
|
181
|
+
export { formatOcSection } from "./format.js";
|
|
182
|
+
export { loadApiKey, ENV_API_KEY } from "./config.js";
|
|
183
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/quota/ollama-cloud/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Type definitions for the Ollama Cloud subscription usage feature.
|
|
3
|
+
*
|
|
4
|
+
* `zcode-acp quota` queries the undocumented-but-live
|
|
5
|
+
* `GET https://ollama.com/api/usage` endpoint with the user's API key. The
|
|
6
|
+
* response carries two usage fractions (session = 5h rolling window, weekly =
|
|
7
|
+
* 7d window) in [0, 1] and NO reset timestamps — the server does not expose
|
|
8
|
+
* them, so reset moments are DERIVED client-side (see OcQueryResult) and the
|
|
9
|
+
* card shows them when available.
|
|
10
|
+
*
|
|
11
|
+
* Shape verified against live probes + the pi-multi-account reference client
|
|
12
|
+
* (2026-09); see `.zcode/scratch/research-ollama-cloud-usage.md`. Undocumented
|
|
13
|
+
* — treat parse failures as `unavailable`, never as a crash.
|
|
14
|
+
*/
|
|
15
|
+
/**
|
|
16
|
+
* Result of querying Ollama Cloud usage — a 4-state sum type mirroring
|
|
17
|
+
* {@link ../opencode-go/types.js GoQueryResult}.
|
|
18
|
+
*
|
|
19
|
+
* `not_configured` is distinct from `unavailable`: the CLI uses it to silently
|
|
20
|
+
* skip the Ollama section in default (all-provider) mode when the user has not
|
|
21
|
+
* supplied an API key, rather than printing an error.
|
|
22
|
+
*/
|
|
23
|
+
export type OcQueryResult = {
|
|
24
|
+
kind: "success";
|
|
25
|
+
/** Present windows only — the account's plan decides which exist.
|
|
26
|
+
* Fractions in [0, 1]. Legacy plans expose session+weekly; current
|
|
27
|
+
* credit plans expose monthly (with per-model request counts). */
|
|
28
|
+
session?: number;
|
|
29
|
+
weekly?: number;
|
|
30
|
+
monthly?: number;
|
|
31
|
+
/** Derived reset moments (epoch ms). session/weekly are computed from
|
|
32
|
+
* the window anchoring (epoch-aligned 5h buckets; Monday 00:00 UTC
|
|
33
|
+
* weeks) — the API itself returns no timestamps. monthly comes from
|
|
34
|
+
* POST /api/me's SubscriptionPeriodEnd when that lookup succeeds;
|
|
35
|
+
* absent otherwise. */
|
|
36
|
+
sessionResetAt?: number;
|
|
37
|
+
weeklyResetAt?: number;
|
|
38
|
+
monthlyResetAt?: number;
|
|
39
|
+
/** Epoch ms of the fetch (kept for symmetry with the other providers). */
|
|
40
|
+
fetchedAt: number;
|
|
41
|
+
} | {
|
|
42
|
+
kind: "not_configured";
|
|
43
|
+
} | {
|
|
44
|
+
kind: "auth_error";
|
|
45
|
+
} | {
|
|
46
|
+
kind: "unavailable";
|
|
47
|
+
};
|
|
48
|
+
/** Raw /api/usage fetch result — status + body text. */
|
|
49
|
+
export interface OcUsageResponse {
|
|
50
|
+
status: number;
|
|
51
|
+
text: string;
|
|
52
|
+
}
|
|
53
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/quota/ollama-cloud/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH;;;;;;;GAOG;AACH,MAAM,MAAM,aAAa,GACrB;IACE,IAAI,EAAE,SAAS,CAAC;IAChB;;uEAEmE;IACnE,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;;;4BAIwB;IACxB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,0EAA0E;IAC1E,SAAS,EAAE,MAAM,CAAC;CACnB,GACD;IAAE,IAAI,EAAE,gBAAgB,CAAA;CAAE,GAC1B;IAAE,IAAI,EAAE,YAAY,CAAA;CAAE,GACtB;IAAE,IAAI,EAAE,aAAa,CAAA;CAAE,CAAC;AAE5B,wDAAwD;AACxD,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;CACd"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Type definitions for the Ollama Cloud subscription usage feature.
|
|
3
|
+
*
|
|
4
|
+
* `zcode-acp quota` queries the undocumented-but-live
|
|
5
|
+
* `GET https://ollama.com/api/usage` endpoint with the user's API key. The
|
|
6
|
+
* response carries two usage fractions (session = 5h rolling window, weekly =
|
|
7
|
+
* 7d window) in [0, 1] and NO reset timestamps — the server does not expose
|
|
8
|
+
* them, so reset moments are DERIVED client-side (see OcQueryResult) and the
|
|
9
|
+
* card shows them when available.
|
|
10
|
+
*
|
|
11
|
+
* Shape verified against live probes + the pi-multi-account reference client
|
|
12
|
+
* (2026-09); see `.zcode/scratch/research-ollama-cloud-usage.md`. Undocumented
|
|
13
|
+
* — treat parse failures as `unavailable`, never as a crash.
|
|
14
|
+
*/
|
|
15
|
+
export {};
|
|
16
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/quota/ollama-cloud/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG"}
|
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Opencode Go credential discovery.
|
|
2
|
+
* Opencode Go credential discovery — the LEGACY `~/.pi/agent/opencode-go.json`
|
|
3
|
+
* fallback source.
|
|
3
4
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
5
|
+
* Full precedence (highest first), resolved in ../index.ts:
|
|
6
|
+
* 1. `quota.opencodeGoWorkspaceId` / `quota.opencodeGoAuthCookie` in
|
|
7
|
+
* `~/.config/zcode-acp/config.json` (our own config).
|
|
8
|
+
* 2. `OPENCODE_GO_WORKSPACE_ID` / `OPENCODE_GO_AUTH_COOKIE` env vars
|
|
7
9
|
* (best for CI / scripts / temporary overrides).
|
|
8
|
-
*
|
|
9
|
-
* (the convention used by the @beyona/pi-zai-usage Pi extension, so
|
|
10
|
-
* who already configured it there get reuse for free).
|
|
10
|
+
* 3. `~/.pi/agent/opencode-go.json` — `{ workspaceId, authCookie }`
|
|
11
|
+
* (the convention used by the @beyona/pi-zai-usage Pi extension, kept so
|
|
12
|
+
* users who already configured it there get reuse for free).
|
|
11
13
|
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
* the file while rotating the cookie via env, etc. Both fields must resolve to
|
|
15
|
-
* a valid pair — a missing/invalid one yields `not_configured`.
|
|
14
|
+
* Both fields must resolve to a valid pair — a missing/invalid one yields
|
|
15
|
+
* `not_configured`.
|
|
16
16
|
*/
|
|
17
17
|
/** Env var names — documented in the CLI help and README. */
|
|
18
18
|
export declare const ENV_WORKSPACE_ID = "OPENCODE_GO_WORKSPACE_ID";
|
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Opencode Go credential discovery.
|
|
2
|
+
* Opencode Go credential discovery — the LEGACY `~/.pi/agent/opencode-go.json`
|
|
3
|
+
* fallback source.
|
|
3
4
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
5
|
+
* Full precedence (highest first), resolved in ../index.ts:
|
|
6
|
+
* 1. `quota.opencodeGoWorkspaceId` / `quota.opencodeGoAuthCookie` in
|
|
7
|
+
* `~/.config/zcode-acp/config.json` (our own config).
|
|
8
|
+
* 2. `OPENCODE_GO_WORKSPACE_ID` / `OPENCODE_GO_AUTH_COOKIE` env vars
|
|
7
9
|
* (best for CI / scripts / temporary overrides).
|
|
8
|
-
*
|
|
9
|
-
* (the convention used by the @beyona/pi-zai-usage Pi extension, so
|
|
10
|
-
* who already configured it there get reuse for free).
|
|
10
|
+
* 3. `~/.pi/agent/opencode-go.json` — `{ workspaceId, authCookie }`
|
|
11
|
+
* (the convention used by the @beyona/pi-zai-usage Pi extension, kept so
|
|
12
|
+
* users who already configured it there get reuse for free).
|
|
11
13
|
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
* the file while rotating the cookie via env, etc. Both fields must resolve to
|
|
15
|
-
* a valid pair — a missing/invalid one yields `not_configured`.
|
|
14
|
+
* Both fields must resolve to a valid pair — a missing/invalid one yields
|
|
15
|
+
* `not_configured`.
|
|
16
16
|
*/
|
|
17
17
|
import { readFileSync } from "node:fs";
|
|
18
18
|
import path from "node:path";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/quota/opencode-go/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/quota/opencode-go/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAQH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAwChD;;;;;;;;GAQG;AACH,wBAAsB,YAAY,IAAI,OAAO,CAAC,aAAa,CAAC,CA6C3D;AAGD,OAAO,EAAE,UAAU,EAAE,UAAU,IAAI,YAAY,EAAE,MAAM,YAAY,CAAC;AACpE,OAAO,EAAE,gBAAgB,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC7D,OAAO,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAClE,OAAO,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAC9D,OAAO,EAAE,cAAc,EAAE,WAAW,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAC7F,YAAY,EAAE,aAAa,EAAE,QAAQ,EAAE,WAAW,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC"}
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
* combined view silently skips (vs. `unavailable`, which renders an error
|
|
11
11
|
* line) — so users who only care about GLM see no noise.
|
|
12
12
|
*/
|
|
13
|
+
import { loadUserConfig } from "../../config/user-config.js";
|
|
13
14
|
import { log } from "../../utils.js";
|
|
14
15
|
import { getCached, setCached } from "./cache.js";
|
|
15
16
|
import { ENV_AUTH_COOKIE, ENV_WORKSPACE_ID, readConfigFile } from "./config.js";
|
|
@@ -21,16 +22,24 @@ const COOKIE_PREFIX = "Fe26.2**";
|
|
|
21
22
|
/**
|
|
22
23
|
* Resolve & validate credentials.
|
|
23
24
|
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
* `
|
|
25
|
+
* Three sources, merged field-by-field with the standard user-config
|
|
26
|
+
* precedence (highest first):
|
|
27
|
+
* 1. `quota.opencodeGoWorkspaceId` / `quota.opencodeGoAuthCookie` in
|
|
28
|
+
* `~/.config/zcode-acp/config.json` (our own config — preferred home).
|
|
29
|
+
* 2. `OPENCODE_GO_WORKSPACE_ID` / `OPENCODE_GO_AUTH_COOKIE` env vars.
|
|
30
|
+
* 3. `~/.pi/agent/opencode-go.json` (legacy — the @beyona/pi-zai-usage Pi
|
|
31
|
+
* extension convention, kept so existing setups keep working).
|
|
32
|
+
*
|
|
33
|
+
* A field present in a higher-precedence source overrides the same field
|
|
34
|
+
* below it; a field present only lower down still counts. Returns `null`
|
|
35
|
+
* when the resolved pair is incomplete or malformed — `queryGoUsage` maps
|
|
36
|
+
* that to `not_configured`.
|
|
29
37
|
*/
|
|
30
38
|
function loadCredentials() {
|
|
39
|
+
const own = loadUserConfig().quota;
|
|
31
40
|
const file = readConfigFile();
|
|
32
|
-
const workspaceId = process.env[ENV_WORKSPACE_ID] ?? file.workspaceId;
|
|
33
|
-
const authCookie = process.env[ENV_AUTH_COOKIE] ?? file.authCookie;
|
|
41
|
+
const workspaceId = own?.opencodeGoWorkspaceId ?? process.env[ENV_WORKSPACE_ID] ?? file.workspaceId;
|
|
42
|
+
const authCookie = own?.opencodeGoAuthCookie ?? process.env[ENV_AUTH_COOKIE] ?? file.authCookie;
|
|
34
43
|
if (!workspaceId || !authCookie)
|
|
35
44
|
return null;
|
|
36
45
|
if (!RE_WORKSPACE.test(workspaceId)) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/quota/opencode-go/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAE,GAAG,EAAE,MAAM,gBAAgB,CAAC;AACrC,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAClD,OAAO,EAAE,eAAe,EAAE,gBAAgB,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAChF,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAC/C,OAAO,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAG9C,wDAAwD;AACxD,MAAM,YAAY,GAAG,oBAAoB,CAAC;AAC1C,MAAM,aAAa,GAAG,UAAU,CAAC;AAEjC
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/quota/opencode-go/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hub-server.d.ts","sourceRoot":"","sources":["../../src/remote/hub-server.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAEH,OAAO,EAAS,KAAK,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAmC9D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;
|
|
1
|
+
{"version":3,"file":"hub-server.d.ts","sourceRoot":"","sources":["../../src/remote/hub-server.ts"],"names":[],"mappings":"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"}
|
|
@@ -43,8 +43,9 @@ import { readCodeFingerprint } from "./code-fingerprint.js";
|
|
|
43
43
|
import { remoteEnabledLive, remoteTerminalPrefs } from "./config.js";
|
|
44
44
|
import { accountUsageStats } from "../handlers/account.js";
|
|
45
45
|
import { BOOT_RESUME_TRIGGER } from "../handlers/session.js";
|
|
46
|
-
import { composeQuotaDock, formatGoDockSegment, formatQuotaDock } from "../quota/format.js";
|
|
46
|
+
import { composeQuotaDock, formatGoDockSegment, formatOcDockSegment, formatQuotaDock, } from "../quota/format.js";
|
|
47
47
|
import { queryQuota } from "../quota/index.js";
|
|
48
|
+
import { queryOcUsage } from "../quota/ollama-cloud/index.js";
|
|
48
49
|
import { queryGoUsage } from "../quota/opencode-go/index.js";
|
|
49
50
|
import { listKnownWorkspaces } from "../tasks-index.js";
|
|
50
51
|
const HEARTBEAT_TIMEOUT_MS = 30_000;
|
|
@@ -512,9 +513,14 @@ function getQuotaDock() {
|
|
|
512
513
|
}
|
|
513
514
|
return Promise.all([
|
|
514
515
|
queryQuota().then(formatQuotaDock),
|
|
515
|
-
queryGoUsage()
|
|
516
|
-
|
|
517
|
-
|
|
516
|
+
queryGoUsage()
|
|
517
|
+
.then(formatGoDockSegment)
|
|
518
|
+
.catch(() => null),
|
|
519
|
+
queryOcUsage()
|
|
520
|
+
.then(formatOcDockSegment)
|
|
521
|
+
.catch(() => null),
|
|
522
|
+
]).then(([glm, go, oc]) => {
|
|
523
|
+
dockCache = { formatted: composeQuotaDock(glm, go, oc), at: Date.now() };
|
|
518
524
|
return { formatted: dockCache.formatted, fetchedAt: dockCache.at };
|
|
519
525
|
});
|
|
520
526
|
}
|