opencode-usage-report 0.6.0 → 0.6.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 +6 -0
- package/package.json +1 -1
- package/src/providers/kimi.ts +94 -38
package/README.md
CHANGED
|
@@ -302,6 +302,12 @@ so a failing check blocks the stage.
|
|
|
302
302
|
|
|
303
303
|
## Changelog
|
|
304
304
|
|
|
305
|
+
- **0.6.1** — Kimi Code adapter tracks the 2026-09 quota-model API change:
|
|
306
|
+
usage windows now come from the `usages.limit_5h` / `limit_7d` /
|
|
307
|
+
`limit_month_total` ratios (with a `limit_month_code` breakdown), and Extra
|
|
308
|
+
Usage is read from the camelCase `boosterWallet` (fixed-point `BOOSTER`
|
|
309
|
+
balance). The pre-2026-09 `usage` / `limits[]` / `totalQuota` /
|
|
310
|
+
`booster_wallet` shapes remain as fallbacks.
|
|
305
311
|
- **0.4.1** — internal: shared provider HTTP plumbing (`src/providers/http.ts`)
|
|
306
312
|
replaces the copy-pasted fetch/retry/error code; context estimates simplified;
|
|
307
313
|
reset times render as `Wed, Sep 16`.
|
package/package.json
CHANGED
package/src/providers/kimi.ts
CHANGED
|
@@ -65,12 +65,17 @@ function findLimitDetail(limits: unknown, durationMinutes: number): unknown {
|
|
|
65
65
|
return undefined;
|
|
66
66
|
}
|
|
67
67
|
|
|
68
|
-
|
|
68
|
+
/** 0-100 percent from a quota entry's `used_ratio`, or null when it is absent/invalid. */
|
|
69
|
+
function ratioPercent(value: unknown): number | null {
|
|
69
70
|
const ratio = toNumber(pick(value, "used_ratio", "usedRatio"));
|
|
71
|
+
return ratio === null ? null : ratioToPercent(ratio);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function ratioWindow(kind: UsageWindow["kind"], label: string, value: unknown): UsageWindow {
|
|
70
75
|
return {
|
|
71
76
|
kind,
|
|
72
77
|
label,
|
|
73
|
-
usedPercent:
|
|
78
|
+
usedPercent: ratioPercent(value),
|
|
74
79
|
used: null,
|
|
75
80
|
limit: null,
|
|
76
81
|
remaining: null,
|
|
@@ -88,6 +93,64 @@ function detailWindow(kind: UsageWindow["kind"], label: string, detail: unknown)
|
|
|
88
93
|
});
|
|
89
94
|
}
|
|
90
95
|
|
|
96
|
+
/**
|
|
97
|
+
* Booster-wallet amounts are fixed-point integers in micro-cents
|
|
98
|
+
* (1_000_000 → 1 cent), matching the official kimi-code parser.
|
|
99
|
+
*/
|
|
100
|
+
const FIXED_POINT_CENTS = 1_000_000;
|
|
101
|
+
|
|
102
|
+
function fixedPointToCents(value: unknown): number | null {
|
|
103
|
+
const raw = toNumber(value);
|
|
104
|
+
if (raw === null) return null;
|
|
105
|
+
const cents = raw / FIXED_POINT_CENTS;
|
|
106
|
+
if (cents > 0 && cents < 1) return 1;
|
|
107
|
+
return Math.round(cents);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function formatMoney(cents: number, currency: string): string {
|
|
111
|
+
return `$${(cents / 100).toFixed(2)} ${currency}`;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/** Currency for the wallet, taken from whichever money field carries one. */
|
|
115
|
+
function walletCurrency(wallet: unknown): string {
|
|
116
|
+
for (const key of ["monthlyChargeLimit", "monthlyUsed"]) {
|
|
117
|
+
const currency = pick(pick(wallet, key), "currency");
|
|
118
|
+
if (typeof currency === "string" && currency !== "") return currency;
|
|
119
|
+
}
|
|
120
|
+
return "USD";
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Renders the Extra Usage ("booster") wallet into `extras`. The post-2026-09
|
|
125
|
+
* quota model sends `boosterWallet.balance` as a `BOOSTER` descriptor with
|
|
126
|
+
* fixed-point micro-cents; the pre-2026-09 wire sent a scalar `booster_wallet.balance`.
|
|
127
|
+
*/
|
|
128
|
+
function parseBoosterWallet(body: unknown, extras: Record<string, string>): void {
|
|
129
|
+
const wallet = pick(body, "boosterWallet", "booster_wallet");
|
|
130
|
+
if (wallet === undefined) return;
|
|
131
|
+
|
|
132
|
+
const balance = pick(wallet, "balance");
|
|
133
|
+
const totalCents =
|
|
134
|
+
pick(balance, "type") === "BOOSTER" ? fixedPointToCents(pick(balance, "amount")) : null;
|
|
135
|
+
if (totalCents !== null && totalCents > 0) {
|
|
136
|
+
const left = fixedPointToCents(pick(balance, "amountLeft")) ?? 0;
|
|
137
|
+
extras["Booster wallet"] = formatMoney(left, walletCurrency(wallet));
|
|
138
|
+
} else if (typeof balance === "string" || typeof balance === "number") {
|
|
139
|
+
extras["Booster wallet"] = String(balance);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const monthlyUsed = pick(wallet, "monthlyUsed", "monthly_used");
|
|
143
|
+
const currency = pick(monthlyUsed, "currency");
|
|
144
|
+
const priceInCents = toNumber(pick(monthlyUsed, "priceInCents", "price_in_cents"));
|
|
145
|
+
if (
|
|
146
|
+
(typeof currency === "string" || typeof currency === "number") &&
|
|
147
|
+
String(currency) !== "" &&
|
|
148
|
+
priceInCents !== null
|
|
149
|
+
) {
|
|
150
|
+
extras["Booster used this month"] = formatMoney(priceInCents, String(currency));
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
91
154
|
function parse(body: Record<string, unknown>, cred: Credential): AdapterResult {
|
|
92
155
|
const windows: UsageWindow[] = [];
|
|
93
156
|
const extras: Record<string, string> = {};
|
|
@@ -96,9 +159,9 @@ function parse(body: Record<string, unknown>, cred: Credential): AdapterResult {
|
|
|
96
159
|
const usage = pick(body, "usage");
|
|
97
160
|
const limits = pick(body, "limits");
|
|
98
161
|
|
|
99
|
-
// Count-based windows are authoritative; the `usages.
|
|
100
|
-
// ratios are
|
|
101
|
-
//
|
|
162
|
+
// Count-based windows are authoritative when present; the `usages.limit_*`
|
|
163
|
+
// ratios are the primary wire on the post-2026-09 quota model, where the
|
|
164
|
+
// absolute `usage`/`limits[]` rows were removed.
|
|
102
165
|
const detail5h = findLimitDetail(limits, 300);
|
|
103
166
|
if (detail5h !== undefined) {
|
|
104
167
|
windows.push(detailWindow("5h", "5-hour", detail5h));
|
|
@@ -114,43 +177,36 @@ function parse(body: Record<string, unknown>, cred: Credential): AdapterResult {
|
|
|
114
177
|
if (limit7d !== undefined) windows.push(ratioWindow("weekly", "Weekly", limit7d));
|
|
115
178
|
}
|
|
116
179
|
|
|
180
|
+
// The monthly total joined the quota model; `totalQuota` is the legacy shape.
|
|
181
|
+
// `limit_month_code` is the code-typed share the official client shows as a
|
|
182
|
+
// breakdown under the monthly row.
|
|
183
|
+
const monthTotal = pick(usages, "limit_month_total");
|
|
184
|
+
if (monthTotal !== undefined) {
|
|
185
|
+
windows.push(ratioWindow("monthly", "Monthly", monthTotal));
|
|
186
|
+
const codePercent = ratioPercent(pick(usages, "limit_month_code"));
|
|
187
|
+
if (codePercent !== null) extras["Monthly code"] = `${codePercent}%`;
|
|
188
|
+
} else {
|
|
189
|
+
const totalQuota = pick(body, "totalQuota", "total_quota");
|
|
190
|
+
const quotaUsed = toNumber(pick(totalQuota, "used"));
|
|
191
|
+
if (totalQuota !== undefined && quotaUsed !== null && quotaUsed > 0) {
|
|
192
|
+
windows.push({
|
|
193
|
+
kind: "monthly",
|
|
194
|
+
label: "Monthly",
|
|
195
|
+
usedPercent: null,
|
|
196
|
+
used: quotaUsed,
|
|
197
|
+
limit: toNumber(pick(totalQuota, "limit")),
|
|
198
|
+
remaining: toNumber(pick(totalQuota, "remaining")),
|
|
199
|
+
resetsAt: toISODate(pick(totalQuota, "resetTime", "reset_time", "resetAt", "resetsAt")),
|
|
200
|
+
status: "frozen",
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
117
205
|
const level = pick(pick(body, "user"), "membership");
|
|
118
206
|
const membershipLevel = pick(level, "level");
|
|
119
207
|
if (membershipLevel !== undefined) extras["Plan"] = String(membershipLevel);
|
|
120
208
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
// `balance` is a wallet descriptor object on the live API; only render scalar values.
|
|
124
|
-
const balance = pick(wallet, "balance");
|
|
125
|
-
if (typeof balance === "string" || typeof balance === "number") {
|
|
126
|
-
extras["Booster wallet"] = String(balance);
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
const monthlyUsed = pick(wallet, "monthlyUsed", "monthly_used");
|
|
130
|
-
const currency = pick(monthlyUsed, "currency");
|
|
131
|
-
const priceInCents = toNumber(pick(monthlyUsed, "priceInCents", "price_in_cents"));
|
|
132
|
-
if (
|
|
133
|
-
(typeof currency === "string" || typeof currency === "number") &&
|
|
134
|
-
String(currency) !== "" &&
|
|
135
|
-
priceInCents !== null
|
|
136
|
-
) {
|
|
137
|
-
extras["Booster used this month"] = `$${(priceInCents / 100).toFixed(2)} ${String(currency)}`;
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
const totalQuota = pick(body, "totalQuota", "total_quota");
|
|
141
|
-
const quotaUsed = toNumber(pick(totalQuota, "used"));
|
|
142
|
-
if (totalQuota !== undefined && quotaUsed !== null && quotaUsed > 0) {
|
|
143
|
-
windows.push({
|
|
144
|
-
kind: "monthly",
|
|
145
|
-
label: "Monthly",
|
|
146
|
-
usedPercent: null,
|
|
147
|
-
used: quotaUsed,
|
|
148
|
-
limit: toNumber(pick(totalQuota, "limit")),
|
|
149
|
-
remaining: toNumber(pick(totalQuota, "remaining")),
|
|
150
|
-
resetsAt: toISODate(pick(totalQuota, "resetTime", "reset_time", "resetAt", "resetsAt")),
|
|
151
|
-
status: "frozen",
|
|
152
|
-
});
|
|
153
|
-
}
|
|
209
|
+
parseBoosterWallet(body, extras);
|
|
154
210
|
|
|
155
211
|
if (windows.length === 0) {
|
|
156
212
|
throw new AdapterError(
|