@narumitw/pi-usage 0.51.0 → 0.52.1
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 +19 -0
- package/dist/index.ts +2613 -0
- package/dist/index.ts.map +7 -0
- package/package.json +17 -11
- package/src/format.ts +20 -0
- package/src/index.ts +1 -0
- package/src/providers/opencode-zen.ts +83 -0
- package/src/query.ts +24 -0
- package/src/types.ts +4 -0
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { sanitizeDisplayText } from "../core.js";
|
|
2
|
+
import type { OpenCodeZenPayload, UsageBucket, UsageReport } from "../types.js";
|
|
3
|
+
|
|
4
|
+
const ZEN_WINDOWS = [
|
|
5
|
+
{ key: "rolling", label: "Rolling" },
|
|
6
|
+
{ key: "weekly", label: "Weekly" },
|
|
7
|
+
{ key: "monthly", label: "Monthly" },
|
|
8
|
+
] as const;
|
|
9
|
+
|
|
10
|
+
export function normalizeOpenCodeZenPayload(
|
|
11
|
+
payload: OpenCodeZenPayload,
|
|
12
|
+
capturedAt: number,
|
|
13
|
+
): UsageReport {
|
|
14
|
+
const usage = asObject(payload.usage);
|
|
15
|
+
if (!usage) throw new Error("OpenCode Zen usage response was not an object.");
|
|
16
|
+
|
|
17
|
+
const buckets: UsageBucket[] = [];
|
|
18
|
+
const notes: string[] = [];
|
|
19
|
+
for (const window of ZEN_WINDOWS) {
|
|
20
|
+
const raw = asObject(usage[window.key]);
|
|
21
|
+
if (!raw) continue;
|
|
22
|
+
const status = asString(raw.status);
|
|
23
|
+
if (status !== "ok" && status !== "rate-limited") {
|
|
24
|
+
notes.push(`${window.label} window unavailable (${status ?? "unknown status"}).`);
|
|
25
|
+
continue;
|
|
26
|
+
}
|
|
27
|
+
const used = asNonnegativeNumber(raw.percent);
|
|
28
|
+
if (used === undefined) continue;
|
|
29
|
+
const resetsAt = asEpochSeconds(raw.resetsAt);
|
|
30
|
+
buckets.push({
|
|
31
|
+
id: window.key,
|
|
32
|
+
label: `${window.label} window`,
|
|
33
|
+
used,
|
|
34
|
+
remaining: 100 - clampPercent(used),
|
|
35
|
+
limit: 100,
|
|
36
|
+
unit: "percent",
|
|
37
|
+
...(resetsAt !== undefined ? { resetsAt } : {}),
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
if (buckets.length === 0) {
|
|
41
|
+
throw new Error("OpenCode Zen usage endpoint returned no displayable usage data.");
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return {
|
|
45
|
+
providerId: "opencode-go",
|
|
46
|
+
providerName: "OpenCode Go",
|
|
47
|
+
capturedAt,
|
|
48
|
+
source: "opencode-zen-usage",
|
|
49
|
+
semantics: {
|
|
50
|
+
kind: "consumer-subscription",
|
|
51
|
+
label: "OpenCode Zen plan usage",
|
|
52
|
+
},
|
|
53
|
+
buckets,
|
|
54
|
+
metrics: [],
|
|
55
|
+
...(notes.length > 0 ? { notes } : {}),
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function asObject(value: unknown): Record<string, unknown> | undefined {
|
|
60
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return undefined;
|
|
61
|
+
return value as Record<string, unknown>;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function asString(value: unknown): string | undefined {
|
|
65
|
+
if (typeof value !== "string") return undefined;
|
|
66
|
+
return sanitizeDisplayText(value, 80) || undefined;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function asNonnegativeNumber(value: unknown): number | undefined {
|
|
70
|
+
if (typeof value !== "number" || !Number.isFinite(value) || value < 0) return undefined;
|
|
71
|
+
return value;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function asEpochSeconds(value: unknown): number | undefined {
|
|
75
|
+
if (typeof value !== "string" || !value.trim()) return undefined;
|
|
76
|
+
const parsed = Date.parse(value);
|
|
77
|
+
if (Number.isNaN(parsed)) return undefined;
|
|
78
|
+
return Math.floor(parsed / 1000);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function clampPercent(value: number): number {
|
|
82
|
+
return Math.min(100, Math.max(0, value));
|
|
83
|
+
}
|
package/src/query.ts
CHANGED
|
@@ -3,10 +3,12 @@ import { type ExtensionContext, readStoredCredential } from "@earendil-works/pi-
|
|
|
3
3
|
import { errorMessage, fingerprintResolvedAuth, redactUsageError } from "./core.js";
|
|
4
4
|
import { normalizeCodexBackendPayload } from "./providers/codex.js";
|
|
5
5
|
import { normalizeGitHubCopilotUsagePayload } from "./providers/github-copilot.js";
|
|
6
|
+
import { normalizeOpenCodeZenPayload } from "./providers/opencode-zen.js";
|
|
6
7
|
import { normalizeOpenRouterKeyPayload } from "./providers/openrouter.js";
|
|
7
8
|
import type {
|
|
8
9
|
CodexBackendPayload,
|
|
9
10
|
GitHubCopilotUsagePayload,
|
|
11
|
+
OpenCodeZenPayload,
|
|
10
12
|
OpenRouterKeyPayload,
|
|
11
13
|
PiModel,
|
|
12
14
|
ResolvedUsageAuth,
|
|
@@ -74,6 +76,21 @@ export const SUPPORTED_ADAPTERS: readonly UsageProviderAdapter[] = [
|
|
|
74
76
|
return normalizeOpenRouterKeyPayload(payload as OpenRouterKeyPayload, Date.now());
|
|
75
77
|
},
|
|
76
78
|
},
|
|
79
|
+
{
|
|
80
|
+
id: "opencode-go",
|
|
81
|
+
displayName: "OpenCode Go",
|
|
82
|
+
semantics: { kind: "consumer-subscription", label: "OpenCode Zen plan usage" },
|
|
83
|
+
async query(auth, signal, timeoutMs) {
|
|
84
|
+
const payload = await fetchProviderJson(
|
|
85
|
+
opencodeUsageUrl(auth.model.baseUrl),
|
|
86
|
+
auth,
|
|
87
|
+
signal,
|
|
88
|
+
timeoutMs,
|
|
89
|
+
"OpenCode Zen usage endpoint",
|
|
90
|
+
);
|
|
91
|
+
return normalizeOpenCodeZenPayload(payload as OpenCodeZenPayload, Date.now());
|
|
92
|
+
},
|
|
93
|
+
},
|
|
77
94
|
];
|
|
78
95
|
|
|
79
96
|
export function adapterForProvider(
|
|
@@ -393,6 +410,7 @@ function hasOfficialUrlOrigin(value: string, providerId: string): boolean {
|
|
|
393
410
|
const url = new URL(value);
|
|
394
411
|
if (providerId === "openai-codex") return url.origin === "https://chatgpt.com";
|
|
395
412
|
if (providerId === "openrouter") return url.origin === "https://openrouter.ai";
|
|
413
|
+
if (providerId === "opencode-go") return url.origin === "https://opencode.ai";
|
|
396
414
|
if (providerId === "github-copilot") {
|
|
397
415
|
return (
|
|
398
416
|
url.protocol === "https:" && /^api\.[a-z0-9-]+\.githubcopilot\.com$/u.test(url.hostname)
|
|
@@ -418,6 +436,12 @@ function hasHeader(headers: Record<string, string>, name: string): boolean {
|
|
|
418
436
|
return Object.keys(headers).some((key) => key.toLowerCase() === name.toLowerCase());
|
|
419
437
|
}
|
|
420
438
|
|
|
439
|
+
function opencodeUsageUrl(baseUrl: string | undefined): string {
|
|
440
|
+
const base = baseUrl?.trim().replace(/\/+$/u, "");
|
|
441
|
+
if (!base) throw new Error("OpenCode Go model base URL is unavailable.");
|
|
442
|
+
return `${base}/usage`;
|
|
443
|
+
}
|
|
444
|
+
|
|
421
445
|
function isAbortError(error: unknown): boolean {
|
|
422
446
|
return error instanceof Error && error.name === "AbortError";
|
|
423
447
|
}
|