dsh-subscriptions 0.1.1 → 0.1.3
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 +115 -4
- package/icon.svg +28 -0
- package/lib/client/CursorCard.js +15 -11
- package/lib/client/ExternalUsageCards.d.ts +11 -1
- package/lib/client/ExternalUsageCards.js +28 -13
- package/lib/client/SubscriptionUsageBadge.d.ts +25 -4
- package/lib/client/SubscriptionUsageBadge.js +115 -28
- package/lib/client/SubscriptionsSection.d.ts +7 -2
- package/lib/client/SubscriptionsSection.js +9 -13
- package/lib/client/UsageBadgeDisplaySetting.js +19 -2
- package/lib/client/UsageMeter.d.ts +8 -0
- package/lib/client/UsageMeter.js +56 -0
- package/lib/client/host-contract.d.ts +1 -1
- package/lib/client/host-contract.js +1 -0
- package/lib/client/locales.d.ts +50 -0
- package/lib/client/locales.js +52 -2
- package/lib/client/subscription-card-styles.js +0 -5
- package/lib/client/usage-badge-preferences.d.ts +7 -2
- package/lib/client/usage-badge-preferences.js +3 -2
- package/lib/client/usage-color-preferences.d.ts +6 -0
- package/lib/client/usage-color-preferences.js +38 -0
- package/lib/client/usage-pace.d.ts +42 -0
- package/lib/client/usage-pace.js +71 -0
- package/lib/client.js +489 -132
- package/lib/client.js.map +1 -1
- package/lib/index.js +252 -35
- package/lib/providers/claude.js +7 -0
- package/lib/providers/codex.js +5 -0
- package/lib/providers/common.d.ts +10 -0
- package/lib/providers/cursor-usage.js +6 -2
- package/lib/providers/external-usage-controller.d.ts +1 -1
- package/lib/providers/external-usage-controller.js +8 -3
- package/lib/providers/external-usage.d.ts +1 -1
- package/lib/providers/external-usage.js +62 -2
- package/lib/providers/grok.js +3 -1
- package/lib/providers/minimax-usage.d.ts +24 -0
- package/lib/providers/minimax-usage.js +85 -0
- package/lib/providers/pool-usage.js +6 -4
- package/locale/en.json +6 -0
- package/locale/zh.json +6 -0
- package/package.json +7 -3
package/lib/index.js
CHANGED
|
@@ -1031,8 +1031,121 @@ async function setDefaultAccount(provider, account, path = authFilePath()) {
|
|
|
1031
1031
|
});
|
|
1032
1032
|
}
|
|
1033
1033
|
|
|
1034
|
+
//#endregion
|
|
1035
|
+
//#region src/providers/minimax-usage.ts
|
|
1036
|
+
/**
|
|
1037
|
+
* Used percentage from the legacy counts, read as the official CLI does when no
|
|
1038
|
+
* explicit percentage exists: the count is the quota remaining. Undefined
|
|
1039
|
+
* unless the total is a positive finite number, so a zero total never yields a
|
|
1040
|
+
* bar.
|
|
1041
|
+
*/
|
|
1042
|
+
function usedFromCounts(total, count) {
|
|
1043
|
+
return typeof total === "number" && Number.isFinite(total) && total > 0 && typeof count === "number" ? (1 - count / total) * 100 : void 0;
|
|
1044
|
+
}
|
|
1045
|
+
/**
|
|
1046
|
+
* Read MiniMax subscription quota (M Plan and Token Plan), not the balance API
|
|
1047
|
+
* that `sk-api-*` pay-as-you-go keys use.
|
|
1048
|
+
*
|
|
1049
|
+
* Each `model_remains` row can yield an interval window and a weekly window.
|
|
1050
|
+
* A window needs a finite share: the explicit `*_remaining_percent` (the weekly
|
|
1051
|
+
* one scaled by `weekly_boost_permille`), else the legacy counts. Time-based
|
|
1052
|
+
* plans report zero counts beside the percentage for standard models, so the
|
|
1053
|
+
* percentage stands on its own and a zero total does not hide the window.
|
|
1054
|
+
* Windows that cannot be a finite bar are omitted rather than approximated:
|
|
1055
|
+
* status 3 (unlimited, or no quota in the plan), a weekly pool boosted past
|
|
1056
|
+
* 100% remaining, a percentage outside 0-100, and a row with neither a
|
|
1057
|
+
* percentage nor positive counts. Start and end times are kept only when both
|
|
1058
|
+
* are valid millisecond bounds.
|
|
1059
|
+
* @param key - subscription key for the region; never copied into a result or error.
|
|
1060
|
+
* @param region - `global` or `cn`, which selects the fixed quota host.
|
|
1061
|
+
* @param http - fetch implementation (injectable for tests).
|
|
1062
|
+
* @param signal - caller cancellation from the RPC transport.
|
|
1063
|
+
* @returns the finite windows in the order the API lists them.
|
|
1064
|
+
* @throws when the key is pay-as-you-go, the endpoint or its business status
|
|
1065
|
+
* fails, the body has no `model_remains`, or no window is a finite bar.
|
|
1066
|
+
*/
|
|
1067
|
+
async function fetchMiniMaxUsage(key, region, http$1 = fetch, signal) {
|
|
1068
|
+
if (key.startsWith("sk-api-")) throw new Error("MiniMax requires a subscription key, not a pay-as-you-go key");
|
|
1069
|
+
const response = await http$1(`https://www.minimax.${region === "cn" ? "cn" : "io"}/v1/token_plan/remains`, {
|
|
1070
|
+
headers: {
|
|
1071
|
+
authorization: `Bearer ${key}`,
|
|
1072
|
+
"content-type": "application/json"
|
|
1073
|
+
},
|
|
1074
|
+
...signal === void 0 ? {} : { signal }
|
|
1075
|
+
});
|
|
1076
|
+
if (!response.ok) throw new Error(`MiniMax usage endpoint returned HTTP ${response.status}`);
|
|
1077
|
+
const body = await response.json();
|
|
1078
|
+
if (body?.base_resp?.status_code !== void 0 && body.base_resp.status_code !== 0 || !Array.isArray(body?.model_remains)) throw new Error("MiniMax usage response failed or has no quota records");
|
|
1079
|
+
const windows = [];
|
|
1080
|
+
for (const raw of body.model_remains) {
|
|
1081
|
+
if (raw === null || typeof raw !== "object") continue;
|
|
1082
|
+
const row = raw;
|
|
1083
|
+
if (typeof row.model_name !== "string") continue;
|
|
1084
|
+
for (const [prefix, startKey, endKey, fallback] of [[
|
|
1085
|
+
"current_interval",
|
|
1086
|
+
"start_time",
|
|
1087
|
+
"end_time",
|
|
1088
|
+
"session"
|
|
1089
|
+
], [
|
|
1090
|
+
"current_weekly",
|
|
1091
|
+
"weekly_start_time",
|
|
1092
|
+
"weekly_end_time",
|
|
1093
|
+
"weekly"
|
|
1094
|
+
]]) {
|
|
1095
|
+
if (row[`${prefix}_status`] === 3) continue;
|
|
1096
|
+
const remaining = row[`${prefix}_remaining_percent`];
|
|
1097
|
+
const boost = prefix === "current_weekly" ? row.weekly_boost_permille ?? 1e3 : 1e3;
|
|
1098
|
+
if (typeof boost !== "number" || !Number.isFinite(boost) || boost <= 0) continue;
|
|
1099
|
+
const usedPercent = typeof remaining === "number" ? 100 - remaining * boost / 1e3 : usedFromCounts(row[`${prefix}_total_count`], row[`${prefix}_usage_count`]);
|
|
1100
|
+
if (usedPercent === void 0 || !Number.isFinite(usedPercent) || usedPercent < 0 || usedPercent > 100) continue;
|
|
1101
|
+
const start = row[startKey];
|
|
1102
|
+
const end = row[endKey];
|
|
1103
|
+
const validBounds = typeof start === "number" && Number.isFinite(start) && start > 0 && typeof end === "number" && Number.isFinite(end) && end > start;
|
|
1104
|
+
windows.push({
|
|
1105
|
+
kind: fallback === "session" && validBounds && end - start !== 18e6 ? "other" : fallback,
|
|
1106
|
+
scope: row.model_name,
|
|
1107
|
+
usedPercent,
|
|
1108
|
+
...validBounds ? {
|
|
1109
|
+
startsAt: start,
|
|
1110
|
+
resetsAt: end
|
|
1111
|
+
} : {}
|
|
1112
|
+
});
|
|
1113
|
+
}
|
|
1114
|
+
}
|
|
1115
|
+
if (!windows.length) throw new Error("MiniMax has no supported finite quota windows");
|
|
1116
|
+
return {
|
|
1117
|
+
supported: true,
|
|
1118
|
+
plan: "MiniMax",
|
|
1119
|
+
windows
|
|
1120
|
+
};
|
|
1121
|
+
}
|
|
1122
|
+
|
|
1034
1123
|
//#endregion
|
|
1035
1124
|
//#region src/providers/external-usage.ts
|
|
1125
|
+
function numeric(value) {
|
|
1126
|
+
const n = typeof value === "string" && value.trim() !== "" ? Number(value) : value;
|
|
1127
|
+
return typeof n === "number" && Number.isFinite(n) ? n : void 0;
|
|
1128
|
+
}
|
|
1129
|
+
function kimiReset(value) {
|
|
1130
|
+
for (const key of [
|
|
1131
|
+
"reset_at",
|
|
1132
|
+
"resetAt",
|
|
1133
|
+
"reset_time",
|
|
1134
|
+
"resetTime"
|
|
1135
|
+
]) {
|
|
1136
|
+
const reset = resetTime$1(value[key]);
|
|
1137
|
+
if (reset !== void 0) return reset;
|
|
1138
|
+
}
|
|
1139
|
+
for (const key of [
|
|
1140
|
+
"reset_in",
|
|
1141
|
+
"resetIn",
|
|
1142
|
+
"ttl",
|
|
1143
|
+
"window"
|
|
1144
|
+
]) {
|
|
1145
|
+
const seconds = numeric(value[key]);
|
|
1146
|
+
if (seconds !== void 0 && seconds > 0) return Date.now() + seconds * 1e3;
|
|
1147
|
+
}
|
|
1148
|
+
}
|
|
1036
1149
|
function record$3(value) {
|
|
1037
1150
|
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
1038
1151
|
}
|
|
@@ -1085,6 +1198,13 @@ async function fetchOpenCodeGoUsage(apiKey, http$1 = fetch, signal) {
|
|
|
1085
1198
|
kind,
|
|
1086
1199
|
...scope === void 0 ? {} : { scope },
|
|
1087
1200
|
usedPercent,
|
|
1201
|
+
...field === "rolling" ? {
|
|
1202
|
+
fixedWindow: true,
|
|
1203
|
+
windowDurationMs: 18e6
|
|
1204
|
+
} : field === "weekly" ? {
|
|
1205
|
+
fixedWindow: true,
|
|
1206
|
+
windowDurationMs: 6048e5
|
|
1207
|
+
} : {},
|
|
1088
1208
|
...resetsAt === void 0 ? {} : { resetsAt }
|
|
1089
1209
|
});
|
|
1090
1210
|
}
|
|
@@ -1095,12 +1215,45 @@ async function fetchOpenCodeGoUsage(apiKey, http$1 = fetch, signal) {
|
|
|
1095
1215
|
plan: "OpenCode Go"
|
|
1096
1216
|
};
|
|
1097
1217
|
}
|
|
1098
|
-
/** Read
|
|
1218
|
+
/** Read official CLI count windows and retain compatibility with older ratio pools. */
|
|
1099
1219
|
async function fetchKimiCodeUsage(apiKey, http$1 = fetch, signal) {
|
|
1100
1220
|
const body = record$3(await getUsage("https://api.kimi.com/coding/v1/usages", apiKey, http$1, signal));
|
|
1101
1221
|
if (body === void 0) throw new Error("Kimi Code usage response is not an object");
|
|
1222
|
+
const modern = [];
|
|
1223
|
+
const rows = [...record$3(body.usage) ? [{ detail: body.usage }] : [], ...Array.isArray(body.limits) ? body.limits : []];
|
|
1224
|
+
for (const raw of rows) {
|
|
1225
|
+
const item = record$3(raw);
|
|
1226
|
+
if (!item) continue;
|
|
1227
|
+
const detail = record$3(item.detail) ?? item;
|
|
1228
|
+
const limit = numeric(detail.limit);
|
|
1229
|
+
const used = numeric(detail.used) ?? (limit !== void 0 && numeric(detail.remaining) !== void 0 ? limit - numeric(detail.remaining) : void 0);
|
|
1230
|
+
if (limit === void 0 || limit <= 0 || used === void 0) continue;
|
|
1231
|
+
const usedPercent = percent(used / limit * 100);
|
|
1232
|
+
if (usedPercent === void 0) continue;
|
|
1233
|
+
const window = record$3(item.window) ?? item;
|
|
1234
|
+
const duration = numeric(window.duration ?? item.duration ?? detail.duration);
|
|
1235
|
+
const unit = String(window.timeUnit ?? item.timeUnit ?? detail.timeUnit ?? "").toUpperCase();
|
|
1236
|
+
const multiplier = unit.includes("MINUTE") ? 6e4 : unit.includes("HOUR") ? 36e5 : unit.includes("DAY") ? 864e5 : unit.includes("SECOND") ? 1e3 : void 0;
|
|
1237
|
+
const durationMs$1 = duration === void 0 || duration <= 0 || multiplier === void 0 ? void 0 : duration * multiplier;
|
|
1238
|
+
const kind = durationMs$1 === 18e6 ? "session" : durationMs$1 === 6048e5 ? "weekly" : "other";
|
|
1239
|
+
const resetsAt = kimiReset(detail);
|
|
1240
|
+
const scope = detail.name ?? detail.title ?? item.name ?? item.title;
|
|
1241
|
+
modern.push({
|
|
1242
|
+
kind,
|
|
1243
|
+
usedPercent,
|
|
1244
|
+
...typeof scope === "string" ? { scope } : {},
|
|
1245
|
+
...resetsAt === void 0 ? {} : { resetsAt },
|
|
1246
|
+
...durationMs$1 === void 0 ? {} : { windowDurationMs: durationMs$1 },
|
|
1247
|
+
...kind === "weekly" ? { fixedWindow: true } : {}
|
|
1248
|
+
});
|
|
1249
|
+
}
|
|
1250
|
+
if (modern.length) return {
|
|
1251
|
+
supported: true,
|
|
1252
|
+
windows: modern,
|
|
1253
|
+
plan: "Kimi Code"
|
|
1254
|
+
};
|
|
1102
1255
|
const usages = record$3(body.usages);
|
|
1103
|
-
if (usages === void 0) throw new Error("Kimi Code usage response has no
|
|
1256
|
+
if (usages === void 0) throw new Error("Kimi Code usage response has no valid windows");
|
|
1104
1257
|
const windows = [];
|
|
1105
1258
|
for (const [field, kind, scope] of [
|
|
1106
1259
|
[
|
|
@@ -1127,6 +1280,10 @@ async function fetchKimiCodeUsage(apiKey, http$1 = fetch, signal) {
|
|
|
1127
1280
|
kind,
|
|
1128
1281
|
...scope === void 0 ? {} : { scope },
|
|
1129
1282
|
usedPercent: ratio * 100,
|
|
1283
|
+
...field === "limit_7d" ? {
|
|
1284
|
+
fixedWindow: true,
|
|
1285
|
+
windowDurationMs: 6048e5
|
|
1286
|
+
} : {},
|
|
1130
1287
|
...resetsAt === void 0 ? {} : { resetsAt }
|
|
1131
1288
|
});
|
|
1132
1289
|
}
|
|
@@ -1139,10 +1296,17 @@ async function fetchKimiCodeUsage(apiKey, http$1 = fetch, signal) {
|
|
|
1139
1296
|
|
|
1140
1297
|
//#endregion
|
|
1141
1298
|
//#region src/providers/external-usage-controller.ts
|
|
1142
|
-
const EXTERNAL_USAGE_SOURCES = [
|
|
1299
|
+
const EXTERNAL_USAGE_SOURCES = [
|
|
1300
|
+
"opencode-go",
|
|
1301
|
+
"kimi-code",
|
|
1302
|
+
"minimax",
|
|
1303
|
+
"minimax-cn"
|
|
1304
|
+
];
|
|
1143
1305
|
const DEFAULT_REFS = {
|
|
1144
1306
|
"opencode-go": "OPENCODE_GO_API_KEY",
|
|
1145
|
-
"kimi-code": "KIMI_CODING_API_KEY"
|
|
1307
|
+
"kimi-code": "KIMI_CODING_API_KEY",
|
|
1308
|
+
"minimax": "MINIMAX_API_KEY",
|
|
1309
|
+
"minimax-cn": "MINIMAX_CN_API_KEY"
|
|
1146
1310
|
};
|
|
1147
1311
|
var ExternalUsageController = class {
|
|
1148
1312
|
refs;
|
|
@@ -1155,18 +1319,16 @@ var ExternalUsageController = class {
|
|
|
1155
1319
|
};
|
|
1156
1320
|
}
|
|
1157
1321
|
async status() {
|
|
1158
|
-
const
|
|
1322
|
+
const values = await Promise.all(EXTERNAL_USAGE_SOURCES.map(async (source) => {
|
|
1159
1323
|
const value = await this.resolveCredential(this.refs[source]);
|
|
1160
1324
|
return typeof value?.value === "string" && value.value.length > 0;
|
|
1161
1325
|
}));
|
|
1162
|
-
return {
|
|
1163
|
-
"opencode-go": { configured: go ?? false },
|
|
1164
|
-
"kimi-code": { configured: kimi ?? false }
|
|
1165
|
-
};
|
|
1326
|
+
return Object.fromEntries(EXTERNAL_USAGE_SOURCES.map((source, i) => [source, { configured: values[i] ?? false }]));
|
|
1166
1327
|
}
|
|
1167
1328
|
async usage(source, signal) {
|
|
1168
1329
|
const credential = await this.resolveCredential(this.refs[source]);
|
|
1169
1330
|
if (credential?.value === void 0 || credential.value.length === 0) throw new Error(`${source} API key is not configured`);
|
|
1331
|
+
if (source === "minimax" || source === "minimax-cn") return fetchMiniMaxUsage(credential.value, source === "minimax-cn" ? "cn" : "global", this.http, signal);
|
|
1170
1332
|
return source === "opencode-go" ? fetchOpenCodeGoUsage(credential.value, this.http, signal) : fetchKimiCodeUsage(credential.value, this.http, signal);
|
|
1171
1333
|
}
|
|
1172
1334
|
};
|
|
@@ -1766,6 +1928,12 @@ async function fetchCursorUsage(accessToken, http$1 = fetch, signal) {
|
|
|
1766
1928
|
const resetValue = summary?.billingCycleEnd;
|
|
1767
1929
|
const parsedReset = typeof resetValue === "string" ? Date.parse(resetValue) : NaN;
|
|
1768
1930
|
const resetsAt = Number.isFinite(parsedReset) ? parsedReset : void 0;
|
|
1931
|
+
const startValue = summary?.billingCycleStart;
|
|
1932
|
+
const parsedStart = typeof startValue === "string" ? Date.parse(startValue) : NaN;
|
|
1933
|
+
const timing = {
|
|
1934
|
+
...resetsAt === void 0 ? {} : { resetsAt },
|
|
1935
|
+
...Number.isFinite(parsedStart) ? { startsAt: parsedStart } : {}
|
|
1936
|
+
};
|
|
1769
1937
|
const windows = [];
|
|
1770
1938
|
for (const [field, scope] of [
|
|
1771
1939
|
["totalPercentUsed", "Included"],
|
|
@@ -1778,11 +1946,14 @@ async function fetchCursorUsage(accessToken, http$1 = fetch, signal) {
|
|
|
1778
1946
|
kind: "other",
|
|
1779
1947
|
scope,
|
|
1780
1948
|
usedPercent,
|
|
1781
|
-
...
|
|
1949
|
+
...timing
|
|
1782
1950
|
});
|
|
1783
1951
|
}
|
|
1784
1952
|
const legacy = legacyRequestWindow(legacyResult.status === "fulfilled" ? legacyResult.value : void 0, resetsAt);
|
|
1785
|
-
if (legacy !== void 0) windows.push(
|
|
1953
|
+
if (legacy !== void 0) windows.push({
|
|
1954
|
+
...legacy,
|
|
1955
|
+
...timing
|
|
1956
|
+
});
|
|
1786
1957
|
if (windows.length === 0 && summary?.isUnlimited !== true) throw new Error("Cursor usage response has no quota windows");
|
|
1787
1958
|
const membershipType = summary?.membershipType;
|
|
1788
1959
|
return {
|
|
@@ -4521,8 +4692,8 @@ function resetInstantFromValue(value, now) {
|
|
|
4521
4692
|
if (typeof value !== "string") return void 0;
|
|
4522
4693
|
const trimmed = value.trim();
|
|
4523
4694
|
if (trimmed.length === 0) return void 0;
|
|
4524
|
-
const numeric = Number(trimmed);
|
|
4525
|
-
if (Number.isFinite(numeric)) return resetInstantFromNumber(numeric, now);
|
|
4695
|
+
const numeric$1 = Number(trimmed);
|
|
4696
|
+
if (Number.isFinite(numeric$1)) return resetInstantFromNumber(numeric$1, now);
|
|
4526
4697
|
const duration = durationMs(trimmed);
|
|
4527
4698
|
if (duration !== void 0) return now + duration;
|
|
4528
4699
|
const parsed = Date.parse(trimmed);
|
|
@@ -6548,6 +6719,16 @@ function claudeResetsAt(value) {
|
|
|
6548
6719
|
const parsed = Date.parse(value);
|
|
6549
6720
|
return Number.isFinite(parsed) ? parsed : void 0;
|
|
6550
6721
|
}
|
|
6722
|
+
/** Claude session and weekly buckets reset as whole five-hour/seven-day windows. */
|
|
6723
|
+
function claudeWindowTiming(kind) {
|
|
6724
|
+
return kind === "session" ? {
|
|
6725
|
+
windowDurationMs: 300 * 6e4,
|
|
6726
|
+
fixedWindow: true
|
|
6727
|
+
} : kind === "weekly" ? {
|
|
6728
|
+
windowDurationMs: 10080 * 6e4,
|
|
6729
|
+
fixedWindow: true
|
|
6730
|
+
} : {};
|
|
6731
|
+
}
|
|
6551
6732
|
/** Map one legacy `{utilization, resets_at}` bucket; undefined when null or unusable. */
|
|
6552
6733
|
function claudeLegacyWindow(value, kind, scope) {
|
|
6553
6734
|
if (typeof value !== "object" || value === null) return void 0;
|
|
@@ -6557,6 +6738,7 @@ function claudeLegacyWindow(value, kind, scope) {
|
|
|
6557
6738
|
return {
|
|
6558
6739
|
kind,
|
|
6559
6740
|
...scope === void 0 ? {} : { scope },
|
|
6741
|
+
...claudeWindowTiming(kind),
|
|
6560
6742
|
usedPercent: bucket.utilization,
|
|
6561
6743
|
...resetsAt === void 0 ? {} : { resetsAt }
|
|
6562
6744
|
};
|
|
@@ -6575,6 +6757,7 @@ function claudeLimitsWindows(value) {
|
|
|
6575
6757
|
windows.push({
|
|
6576
6758
|
kind,
|
|
6577
6759
|
...typeof scope === "string" && scope.length > 0 ? { scope } : {},
|
|
6760
|
+
...claudeWindowTiming(kind),
|
|
6578
6761
|
usedPercent: entry.percent,
|
|
6579
6762
|
...resetsAt === void 0 ? {} : { resetsAt }
|
|
6580
6763
|
});
|
|
@@ -7575,6 +7758,10 @@ function codexUsageWindow(value, fallbackKind) {
|
|
|
7575
7758
|
else if (typeof window.reset_after_seconds === "number" && window.reset_after_seconds > 0) resetsAt = Date.now() + window.reset_after_seconds * 1e3;
|
|
7576
7759
|
return {
|
|
7577
7760
|
kind: codexWindowKind(window, fallbackKind),
|
|
7761
|
+
...typeof window.limit_window_seconds === "number" && Number.isFinite(window.limit_window_seconds) && window.limit_window_seconds > 0 ? {
|
|
7762
|
+
windowDurationMs: window.limit_window_seconds * 1e3,
|
|
7763
|
+
fixedWindow: true
|
|
7764
|
+
} : {},
|
|
7578
7765
|
usedPercent: window.used_percent,
|
|
7579
7766
|
...resetsAt === void 0 ? {} : { resetsAt }
|
|
7580
7767
|
};
|
|
@@ -9106,13 +9293,19 @@ var PoolUsageTracker = class {
|
|
|
9106
9293
|
const entry = this.entries.get(key);
|
|
9107
9294
|
if (entry !== void 0 && Date.now() - entry.at < (entry.cooldownMs ?? this.ttlMs)) if (entry.snapshot !== void 0) {
|
|
9108
9295
|
if (!force) return entry.snapshot;
|
|
9109
|
-
} else if (entry.lastSnapshot !== void 0) return
|
|
9296
|
+
} else if (entry.lastSnapshot !== void 0) return {
|
|
9297
|
+
...entry.lastSnapshot,
|
|
9298
|
+
stale: true
|
|
9299
|
+
};
|
|
9110
9300
|
else throw entry.error;
|
|
9111
9301
|
try {
|
|
9112
9302
|
return await this.refresh(key, fetcher);
|
|
9113
9303
|
} catch (error) {
|
|
9114
9304
|
const failed = this.entries.get(key);
|
|
9115
|
-
if (failed?.snapshot === void 0 && failed?.lastSnapshot !== void 0) return
|
|
9305
|
+
if (failed?.snapshot === void 0 && failed?.lastSnapshot !== void 0) return {
|
|
9306
|
+
...failed.lastSnapshot,
|
|
9307
|
+
stale: true
|
|
9308
|
+
};
|
|
9116
9309
|
throw error;
|
|
9117
9310
|
}
|
|
9118
9311
|
}
|
|
@@ -9137,10 +9330,16 @@ var PoolUsageTracker = class {
|
|
|
9137
9330
|
if (pending === void 0) {
|
|
9138
9331
|
const prior = this.entries.get(key);
|
|
9139
9332
|
const lastSnapshot = prior?.snapshot ?? prior?.lastSnapshot;
|
|
9140
|
-
pending = fetcher().then((
|
|
9333
|
+
pending = fetcher().then((value) => {
|
|
9334
|
+
const at = Date.now();
|
|
9335
|
+
const snapshot = {
|
|
9336
|
+
...value,
|
|
9337
|
+
observedAt: at,
|
|
9338
|
+
stale: false
|
|
9339
|
+
};
|
|
9141
9340
|
this.entries.set(key, {
|
|
9142
9341
|
snapshot,
|
|
9143
|
-
at
|
|
9342
|
+
at
|
|
9144
9343
|
});
|
|
9145
9344
|
return snapshot;
|
|
9146
9345
|
}, (error) => {
|
|
@@ -9445,10 +9644,12 @@ async function fetchGrokUsage(session, fetchFn = hostFetch, signal) {
|
|
|
9445
9644
|
const kind = config.currentPeriod?.type === "USAGE_PERIOD_TYPE_WEEKLY" ? "weekly" : "other";
|
|
9446
9645
|
const resetsAt = grokResetsAt(config.currentPeriod?.end);
|
|
9447
9646
|
const usedPercent = typeof config.creditUsagePercent === "number" && Number.isFinite(config.creditUsagePercent) ? config.creditUsagePercent : 0;
|
|
9647
|
+
const startsAt = grokResetsAt(config.currentPeriod?.start);
|
|
9448
9648
|
windows.push({
|
|
9449
9649
|
kind,
|
|
9450
9650
|
usedPercent,
|
|
9451
|
-
...resetsAt === void 0 ? {} : { resetsAt }
|
|
9651
|
+
...resetsAt === void 0 ? {} : { resetsAt },
|
|
9652
|
+
...startsAt === void 0 ? {} : { startsAt }
|
|
9452
9653
|
});
|
|
9453
9654
|
} else if (typeof config.monthlyLimit?.val === "number" && config.monthlyLimit.val > 0) {
|
|
9454
9655
|
const used = typeof config.used?.val === "number" ? config.used.val : 0;
|
|
@@ -12999,16 +13200,29 @@ const Config = z.object({
|
|
|
12999
13200
|
const DEFAULT_MODELS = {
|
|
13000
13201
|
codex: [
|
|
13001
13202
|
{
|
|
13002
|
-
id: "gpt-
|
|
13003
|
-
name: "GPT-
|
|
13203
|
+
id: "gpt-6-astra",
|
|
13204
|
+
name: "GPT-6-Astra",
|
|
13205
|
+
contextWindow: 272e3
|
|
13206
|
+
},
|
|
13207
|
+
{
|
|
13208
|
+
id: "gpt-5.6-sol",
|
|
13209
|
+
name: "GPT-5.6-Sol",
|
|
13210
|
+
contextWindow: 272e3
|
|
13211
|
+
},
|
|
13212
|
+
{
|
|
13213
|
+
id: "gpt-5.6-terra",
|
|
13214
|
+
name: "GPT-5.6-Terra",
|
|
13215
|
+
contextWindow: 272e3
|
|
13004
13216
|
},
|
|
13005
13217
|
{
|
|
13006
|
-
id: "gpt-5.
|
|
13007
|
-
name: "GPT-5.
|
|
13218
|
+
id: "gpt-5.6-luna",
|
|
13219
|
+
name: "GPT-5.6-Luna",
|
|
13220
|
+
contextWindow: 272e3
|
|
13008
13221
|
},
|
|
13009
13222
|
{
|
|
13010
|
-
id: "gpt-5.
|
|
13011
|
-
name: "GPT-5.
|
|
13223
|
+
id: "gpt-5.5",
|
|
13224
|
+
name: "GPT-5.5",
|
|
13225
|
+
contextWindow: 272e3
|
|
13012
13226
|
}
|
|
13013
13227
|
],
|
|
13014
13228
|
claude: [
|
|
@@ -13058,23 +13272,26 @@ const DEFAULT_MODELS = {
|
|
|
13058
13272
|
],
|
|
13059
13273
|
copilot: [
|
|
13060
13274
|
{
|
|
13061
|
-
id: "gpt-
|
|
13062
|
-
name: "GPT-
|
|
13063
|
-
inputModalities: ["text", "image"]
|
|
13275
|
+
id: "gpt-6-sol",
|
|
13276
|
+
name: "GPT-6 Sol",
|
|
13277
|
+
inputModalities: ["text", "image"],
|
|
13278
|
+
wire: "responses"
|
|
13064
13279
|
},
|
|
13065
13280
|
{
|
|
13066
|
-
id: "gpt-
|
|
13067
|
-
name: "GPT-
|
|
13068
|
-
inputModalities: ["text", "image"]
|
|
13281
|
+
id: "gpt-6-luna",
|
|
13282
|
+
name: "GPT-6 Luna",
|
|
13283
|
+
inputModalities: ["text", "image"],
|
|
13284
|
+
wire: "responses"
|
|
13069
13285
|
},
|
|
13070
13286
|
{
|
|
13071
|
-
id: "
|
|
13072
|
-
name: "
|
|
13073
|
-
inputModalities: ["text", "image"]
|
|
13287
|
+
id: "gpt-5.5",
|
|
13288
|
+
name: "GPT-5.5",
|
|
13289
|
+
inputModalities: ["text", "image"],
|
|
13290
|
+
wire: "responses"
|
|
13074
13291
|
},
|
|
13075
13292
|
{
|
|
13076
|
-
id: "
|
|
13077
|
-
name: "
|
|
13293
|
+
id: "claude-haiku-4.5",
|
|
13294
|
+
name: "Claude Haiku 4.5",
|
|
13078
13295
|
inputModalities: ["text", "image"]
|
|
13079
13296
|
}
|
|
13080
13297
|
],
|
package/lib/providers/claude.js
CHANGED
|
@@ -256,6 +256,11 @@ function claudeResetsAt(value) {
|
|
|
256
256
|
const parsed = Date.parse(value);
|
|
257
257
|
return Number.isFinite(parsed) ? parsed : undefined;
|
|
258
258
|
}
|
|
259
|
+
/** Claude session and weekly buckets reset as whole five-hour/seven-day windows. */
|
|
260
|
+
function claudeWindowTiming(kind) {
|
|
261
|
+
return kind === 'session' ? { windowDurationMs: 5 * 60 * 60_000, fixedWindow: true }
|
|
262
|
+
: kind === 'weekly' ? { windowDurationMs: 7 * 24 * 60 * 60_000, fixedWindow: true } : {};
|
|
263
|
+
}
|
|
259
264
|
/** Map one legacy `{utilization, resets_at}` bucket; undefined when null or unusable. */
|
|
260
265
|
function claudeLegacyWindow(value, kind, scope) {
|
|
261
266
|
if (typeof value !== 'object' || value === null)
|
|
@@ -267,6 +272,7 @@ function claudeLegacyWindow(value, kind, scope) {
|
|
|
267
272
|
return {
|
|
268
273
|
kind,
|
|
269
274
|
...scope === undefined ? {} : { scope },
|
|
275
|
+
...claudeWindowTiming(kind),
|
|
270
276
|
usedPercent: bucket.utilization,
|
|
271
277
|
...resetsAt === undefined ? {} : { resetsAt },
|
|
272
278
|
};
|
|
@@ -290,6 +296,7 @@ function claudeLimitsWindows(value) {
|
|
|
290
296
|
windows.push({
|
|
291
297
|
kind,
|
|
292
298
|
...typeof scope === 'string' && scope.length > 0 ? { scope } : {},
|
|
299
|
+
...claudeWindowTiming(kind),
|
|
293
300
|
usedPercent: entry.percent,
|
|
294
301
|
...resetsAt === undefined ? {} : { resetsAt },
|
|
295
302
|
});
|
package/lib/providers/codex.js
CHANGED
|
@@ -256,6 +256,11 @@ function codexUsageWindow(value, fallbackKind) {
|
|
|
256
256
|
}
|
|
257
257
|
return {
|
|
258
258
|
kind: codexWindowKind(window, fallbackKind),
|
|
259
|
+
...typeof window.limit_window_seconds === 'number' && Number.isFinite(window.limit_window_seconds)
|
|
260
|
+
&& window.limit_window_seconds > 0 ? {
|
|
261
|
+
// wham reports the duration and reset of this same quota window.
|
|
262
|
+
windowDurationMs: window.limit_window_seconds * 1000, fixedWindow: true,
|
|
263
|
+
} : {},
|
|
259
264
|
usedPercent: window.used_percent,
|
|
260
265
|
...resetsAt === undefined ? {} : { resetsAt },
|
|
261
266
|
};
|
|
@@ -193,9 +193,19 @@ export interface UsageWindow {
|
|
|
193
193
|
usedPercent: number;
|
|
194
194
|
/** Epoch milliseconds at which the window resets, when the provider discloses it. */
|
|
195
195
|
resetsAt?: number;
|
|
196
|
+
/** Epoch milliseconds of the billing/window start, when disclosed. */
|
|
197
|
+
startsAt?: number;
|
|
198
|
+
/** Reported duration; not sufficient to establish fixed-window semantics. */
|
|
199
|
+
windowDurationMs?: number;
|
|
200
|
+
/** Provider-verified fixed interval; permits deriving start from end and duration. */
|
|
201
|
+
fixedWindow?: boolean;
|
|
196
202
|
}
|
|
197
203
|
/** Subscription usage of one provider, as served by the `usage` RPC endpoint. */
|
|
198
204
|
export interface ProviderUsage {
|
|
205
|
+
/** Original successful observation time, preserved across cache reads. */
|
|
206
|
+
observedAt?: number;
|
|
207
|
+
/** Last refresh failed; cached numbers are display-only until recovery. */
|
|
208
|
+
stale?: boolean;
|
|
199
209
|
/** False when the provider has no usage endpoint (grok); windows are absent then. */
|
|
200
210
|
supported: boolean;
|
|
201
211
|
/** Usage windows in display order. */
|
|
@@ -86,6 +86,10 @@ export async function fetchCursorUsage(accessToken, http = fetch, signal) {
|
|
|
86
86
|
const resetValue = summary?.billingCycleEnd;
|
|
87
87
|
const parsedReset = typeof resetValue === 'string' ? Date.parse(resetValue) : NaN;
|
|
88
88
|
const resetsAt = Number.isFinite(parsedReset) ? parsedReset : undefined;
|
|
89
|
+
const startValue = summary?.billingCycleStart;
|
|
90
|
+
const parsedStart = typeof startValue === 'string' ? Date.parse(startValue) : NaN;
|
|
91
|
+
const timing = { ...(resetsAt === undefined ? {} : { resetsAt }),
|
|
92
|
+
...(Number.isFinite(parsedStart) ? { startsAt: parsedStart } : {}) };
|
|
89
93
|
const windows = [];
|
|
90
94
|
for (const [field, scope] of [
|
|
91
95
|
['totalPercentUsed', 'Included'],
|
|
@@ -95,11 +99,11 @@ export async function fetchCursorUsage(accessToken, http = fetch, signal) {
|
|
|
95
99
|
const usedPercent = number(plan?.[field]);
|
|
96
100
|
if (usedPercent === undefined || usedPercent < 0 || usedPercent > 100)
|
|
97
101
|
continue;
|
|
98
|
-
windows.push({ kind: 'other', scope, usedPercent, ...
|
|
102
|
+
windows.push({ kind: 'other', scope, usedPercent, ...timing });
|
|
99
103
|
}
|
|
100
104
|
const legacy = legacyRequestWindow(legacyResult.status === 'fulfilled' ? legacyResult.value : undefined, resetsAt);
|
|
101
105
|
if (legacy !== undefined)
|
|
102
|
-
windows.push(legacy);
|
|
106
|
+
windows.push({ ...legacy, ...timing });
|
|
103
107
|
if (windows.length === 0 && summary?.isUnlimited !== true) {
|
|
104
108
|
throw new Error('Cursor usage response has no quota windows');
|
|
105
109
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { ProviderUsage } from './common.js';
|
|
2
2
|
/** Usage-only sources whose model routes are supplied by the DSH base install. */
|
|
3
|
-
export type ExternalUsageSource = 'opencode-go' | 'kimi-code';
|
|
3
|
+
export type ExternalUsageSource = 'opencode-go' | 'kimi-code' | 'minimax' | 'minimax-cn';
|
|
4
4
|
export declare const EXTERNAL_USAGE_SOURCES: readonly ExternalUsageSource[];
|
|
5
5
|
export interface ExternalUsageStatus {
|
|
6
6
|
configured: boolean;
|
|
@@ -1,8 +1,11 @@
|
|
|
1
|
+
import { fetchMiniMaxUsage } from './minimax-usage.js';
|
|
1
2
|
import { fetchKimiCodeUsage, fetchOpenCodeGoUsage } from './external-usage.js';
|
|
2
|
-
export const EXTERNAL_USAGE_SOURCES = ['opencode-go', 'kimi-code'];
|
|
3
|
+
export const EXTERNAL_USAGE_SOURCES = ['opencode-go', 'kimi-code', 'minimax', 'minimax-cn'];
|
|
3
4
|
const DEFAULT_REFS = {
|
|
4
5
|
'opencode-go': 'OPENCODE_GO_API_KEY',
|
|
5
6
|
'kimi-code': 'KIMI_CODING_API_KEY',
|
|
7
|
+
'minimax': 'MINIMAX_API_KEY',
|
|
8
|
+
'minimax-cn': 'MINIMAX_CN_API_KEY',
|
|
6
9
|
};
|
|
7
10
|
export class ExternalUsageController {
|
|
8
11
|
resolveCredential;
|
|
@@ -14,17 +17,19 @@ export class ExternalUsageController {
|
|
|
14
17
|
this.refs = { ...DEFAULT_REFS, ...refs };
|
|
15
18
|
}
|
|
16
19
|
async status() {
|
|
17
|
-
const
|
|
20
|
+
const values = await Promise.all(EXTERNAL_USAGE_SOURCES.map(async (source) => {
|
|
18
21
|
const value = await this.resolveCredential(this.refs[source]);
|
|
19
22
|
return typeof value?.value === 'string' && value.value.length > 0;
|
|
20
23
|
}));
|
|
21
|
-
return
|
|
24
|
+
return Object.fromEntries(EXTERNAL_USAGE_SOURCES.map((source, i) => [source, { configured: values[i] ?? false }]));
|
|
22
25
|
}
|
|
23
26
|
async usage(source, signal) {
|
|
24
27
|
const credential = await this.resolveCredential(this.refs[source]);
|
|
25
28
|
if (credential?.value === undefined || credential.value.length === 0) {
|
|
26
29
|
throw new Error(`${source} API key is not configured`);
|
|
27
30
|
}
|
|
31
|
+
if (source === 'minimax' || source === 'minimax-cn')
|
|
32
|
+
return fetchMiniMaxUsage(credential.value, source === 'minimax-cn' ? 'cn' : 'global', this.http, signal);
|
|
28
33
|
return source === 'opencode-go'
|
|
29
34
|
? fetchOpenCodeGoUsage(credential.value, this.http, signal)
|
|
30
35
|
: fetchKimiCodeUsage(credential.value, this.http, signal);
|
|
@@ -3,6 +3,6 @@ import type { ProviderUsage } from './common.js';
|
|
|
3
3
|
type HttpFetch = typeof fetch;
|
|
4
4
|
/** Read the three account-wide OpenCode Go windows from its Go gateway. */
|
|
5
5
|
export declare function fetchOpenCodeGoUsage(apiKey: string, http?: HttpFetch, signal?: AbortSignal): Promise<ProviderUsage>;
|
|
6
|
-
/** Read
|
|
6
|
+
/** Read official CLI count windows and retain compatibility with older ratio pools. */
|
|
7
7
|
export declare function fetchKimiCodeUsage(apiKey: string, http?: HttpFetch, signal?: AbortSignal): Promise<ProviderUsage>;
|
|
8
8
|
export {};
|