@narumitw/pi-usage 0.54.0 → 0.57.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 +103 -64
- package/dist/index.ts +329 -110
- package/dist/index.ts.map +4 -4
- package/package.json +9 -10
- package/src/format.ts +94 -7
- package/src/index.ts +2 -0
- package/src/providers/deepseek.ts +85 -0
- package/src/query.ts +58 -10
- package/src/settings.ts +10 -5
- package/src/types.ts +5 -0
- package/src/usage-helpers.ts +2 -2
- package/src/usage-settings-ui.ts +10 -11
- package/src/usage.ts +121 -85
package/dist/index.ts
CHANGED
|
@@ -449,10 +449,81 @@ function clampPercent(value) {
|
|
|
449
449
|
return Math.min(100, Math.max(0, value));
|
|
450
450
|
}
|
|
451
451
|
|
|
452
|
+
// src/providers/deepseek.ts
|
|
453
|
+
var CURRENCIES = ["CNY", "USD"];
|
|
454
|
+
var BALANCE_FIELDS = [
|
|
455
|
+
["total", "Total balance", "total_balance"],
|
|
456
|
+
["granted", "Granted balance", "granted_balance"],
|
|
457
|
+
["topped-up", "Topped-up balance", "topped_up_balance"]
|
|
458
|
+
];
|
|
459
|
+
function normalizeDeepSeekBalancePayload(payload, capturedAt) {
|
|
460
|
+
if (typeof payload.is_available !== "boolean") {
|
|
461
|
+
throw new Error("DeepSeek API balance response availability was not a boolean.");
|
|
462
|
+
}
|
|
463
|
+
if (!Array.isArray(payload.balance_infos) || payload.balance_infos.length === 0) {
|
|
464
|
+
throw new Error("DeepSeek API balance response returned no balance information.");
|
|
465
|
+
}
|
|
466
|
+
const balances = /* @__PURE__ */ new Map();
|
|
467
|
+
for (const raw of payload.balance_infos) {
|
|
468
|
+
const balance = asObject2(raw);
|
|
469
|
+
if (!balance) throw new Error("DeepSeek API balance row was not an object.");
|
|
470
|
+
const currency = deepSeekCurrency(balance.currency);
|
|
471
|
+
if (!currency) throw new Error("DeepSeek API balance row returned an unsupported currency.");
|
|
472
|
+
if (balances.has(currency)) {
|
|
473
|
+
throw new Error(`DeepSeek API balance response repeated ${currency}.`);
|
|
474
|
+
}
|
|
475
|
+
for (const [, label, field] of BALANCE_FIELDS) {
|
|
476
|
+
if (!decimalAmount(balance[field])) {
|
|
477
|
+
throw new Error(`DeepSeek API balance ${label.toLowerCase()} was not a valid amount.`);
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
balances.set(currency, balance);
|
|
481
|
+
}
|
|
482
|
+
const metrics = [
|
|
483
|
+
{
|
|
484
|
+
id: "api-availability",
|
|
485
|
+
label: "API calls",
|
|
486
|
+
value: payload.is_available ? "available" : "unavailable"
|
|
487
|
+
}
|
|
488
|
+
];
|
|
489
|
+
for (const currency of CURRENCIES) {
|
|
490
|
+
const balance = balances.get(currency);
|
|
491
|
+
if (!balance) continue;
|
|
492
|
+
for (const [id, label, field] of BALANCE_FIELDS) {
|
|
493
|
+
metrics.push({
|
|
494
|
+
id: `${currency.toLowerCase()}-${id}`,
|
|
495
|
+
label,
|
|
496
|
+
value: balance[field],
|
|
497
|
+
unit: "currency",
|
|
498
|
+
currency
|
|
499
|
+
});
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
return {
|
|
503
|
+
providerId: "deepseek",
|
|
504
|
+
providerName: "DeepSeek",
|
|
505
|
+
capturedAt,
|
|
506
|
+
source: "deepseek-balance",
|
|
507
|
+
semantics: { kind: "api-key", label: "DeepSeek API balance" },
|
|
508
|
+
buckets: [],
|
|
509
|
+
metrics
|
|
510
|
+
};
|
|
511
|
+
}
|
|
512
|
+
function asObject2(value) {
|
|
513
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
|
|
514
|
+
return value;
|
|
515
|
+
}
|
|
516
|
+
function deepSeekCurrency(value) {
|
|
517
|
+
return CURRENCIES.find((currency) => currency === value);
|
|
518
|
+
}
|
|
519
|
+
function decimalAmount(value) {
|
|
520
|
+
return typeof value === "string" && value.length <= 64 && /^(?:0|[1-9]\d*)(?:\.\d+)?$/u.test(value);
|
|
521
|
+
}
|
|
522
|
+
|
|
452
523
|
// src/providers/github-copilot.ts
|
|
453
524
|
function normalizeGitHubCopilotUsagePayload(payload, capturedAt) {
|
|
454
|
-
const snapshots =
|
|
455
|
-
const premium =
|
|
525
|
+
const snapshots = asObject3(payload.quota_snapshots);
|
|
526
|
+
const premium = asObject3(snapshots?.premium_interactions);
|
|
456
527
|
const metrics = [];
|
|
457
528
|
let semanticsLabel;
|
|
458
529
|
let bucket;
|
|
@@ -493,8 +564,8 @@ function normalizeGitHubCopilotUsagePayload(payload, capturedAt) {
|
|
|
493
564
|
};
|
|
494
565
|
}
|
|
495
566
|
} else {
|
|
496
|
-
const limited =
|
|
497
|
-
const monthly =
|
|
567
|
+
const limited = asObject3(payload.limited_user_quotas);
|
|
568
|
+
const monthly = asObject3(payload.monthly_quotas);
|
|
498
569
|
const remaining = asNonnegativeNumber(limited?.chat);
|
|
499
570
|
const entitlement = asNonnegativeNumber(monthly?.chat);
|
|
500
571
|
if (remaining === void 0 || entitlement === void 0) {
|
|
@@ -533,7 +604,7 @@ function resetTimestamp(payload) {
|
|
|
533
604
|
const milliseconds = Date.parse(raw);
|
|
534
605
|
return Number.isNaN(milliseconds) ? {} : { resetsAt: Math.floor(milliseconds / 1e3) };
|
|
535
606
|
}
|
|
536
|
-
function
|
|
607
|
+
function asObject3(value) {
|
|
537
608
|
if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
|
|
538
609
|
return value;
|
|
539
610
|
}
|
|
@@ -556,7 +627,7 @@ var DAILY_WINDOW_MINUTES = 1440;
|
|
|
556
627
|
var WEEKLY_WINDOW_MINUTES = 10080;
|
|
557
628
|
var FIXED_POINT_UNITS_PER_CENT = 1e6;
|
|
558
629
|
function normalizeKimiCodingUsagePayload(payload, capturedAt) {
|
|
559
|
-
const root =
|
|
630
|
+
const root = asObject4(payload);
|
|
560
631
|
if (!root) throw new Error("Kimi Coding usage response was not an object.");
|
|
561
632
|
const candidates = [];
|
|
562
633
|
let omittedWindow = false;
|
|
@@ -565,7 +636,7 @@ function normalizeKimiCodingUsagePayload(payload, capturedAt) {
|
|
|
565
636
|
else if (root.usage !== void 0) omittedWindow = true;
|
|
566
637
|
if (Array.isArray(root.limits)) {
|
|
567
638
|
for (const raw of root.limits) {
|
|
568
|
-
const item =
|
|
639
|
+
const item = asObject4(raw);
|
|
569
640
|
const windowMinutes = parseWindowMinutes(item?.window);
|
|
570
641
|
const label = sanitizedLabel(item?.name);
|
|
571
642
|
const bucket = windowMinutes === void 0 ? void 0 : parseUsageRow(item?.detail, windowMinutes, label ?? defaultWindowLabel(windowMinutes));
|
|
@@ -602,7 +673,7 @@ function normalizeKimiCodingUsagePayload(payload, capturedAt) {
|
|
|
602
673
|
};
|
|
603
674
|
}
|
|
604
675
|
function parseUsageRow(value, windowMinutes, label) {
|
|
605
|
-
const row =
|
|
676
|
+
const row = asObject4(value);
|
|
606
677
|
if (!row) return void 0;
|
|
607
678
|
const used = asNonnegativeInteger2(row.used);
|
|
608
679
|
const limit = asNonnegativeInteger2(row.limit);
|
|
@@ -620,7 +691,7 @@ function parseUsageRow(value, windowMinutes, label) {
|
|
|
620
691
|
};
|
|
621
692
|
}
|
|
622
693
|
function parseWindowMinutes(value) {
|
|
623
|
-
const window =
|
|
694
|
+
const window = asObject4(value);
|
|
624
695
|
if (!window) return void 0;
|
|
625
696
|
const duration = asPositiveInteger(window.duration);
|
|
626
697
|
if (duration === void 0) return void 0;
|
|
@@ -630,8 +701,8 @@ function parseWindowMinutes(value) {
|
|
|
630
701
|
return Number.isSafeInteger(minutes) ? minutes : void 0;
|
|
631
702
|
}
|
|
632
703
|
function parseBoosterWallet(value) {
|
|
633
|
-
const wallet =
|
|
634
|
-
const balance =
|
|
704
|
+
const wallet = asObject4(value);
|
|
705
|
+
const balance = asObject4(wallet?.balance);
|
|
635
706
|
if (!wallet || !balance || balance.type !== "BOOSTER") return [];
|
|
636
707
|
const totalRaw = asPositiveInteger(balance.amount);
|
|
637
708
|
if (totalRaw === void 0) return [];
|
|
@@ -682,7 +753,7 @@ function parseBoosterWallet(value) {
|
|
|
682
753
|
return metrics;
|
|
683
754
|
}
|
|
684
755
|
function parseMoney(value) {
|
|
685
|
-
const money =
|
|
756
|
+
const money = asObject4(value);
|
|
686
757
|
if (!money) return void 0;
|
|
687
758
|
const cents = asNonnegativeInteger2(money.priceInCents);
|
|
688
759
|
if (cents === void 0) return void 0;
|
|
@@ -696,7 +767,7 @@ function fixedPointToMajor(value) {
|
|
|
696
767
|
const major = roundedCents / 100;
|
|
697
768
|
return Number.isSafeInteger(roundedCents) && Number.isFinite(major) ? major : void 0;
|
|
698
769
|
}
|
|
699
|
-
function
|
|
770
|
+
function asObject4(value) {
|
|
700
771
|
if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
|
|
701
772
|
return value;
|
|
702
773
|
}
|
|
@@ -770,12 +841,12 @@ var ZEN_WINDOWS = [
|
|
|
770
841
|
{ key: "monthly", label: "Monthly" }
|
|
771
842
|
];
|
|
772
843
|
function normalizeOpenCodeZenPayload(payload, capturedAt) {
|
|
773
|
-
const usage =
|
|
844
|
+
const usage = asObject5(payload.usage);
|
|
774
845
|
if (!usage) throw new Error("OpenCode Zen usage response was not an object.");
|
|
775
846
|
const buckets = [];
|
|
776
847
|
const notes = [];
|
|
777
848
|
for (const window of ZEN_WINDOWS) {
|
|
778
|
-
const raw =
|
|
849
|
+
const raw = asObject5(usage[window.key]);
|
|
779
850
|
if (!raw) continue;
|
|
780
851
|
const status = asString3(raw.status);
|
|
781
852
|
if (status !== "ok" && status !== "rate-limited") {
|
|
@@ -812,7 +883,7 @@ function normalizeOpenCodeZenPayload(payload, capturedAt) {
|
|
|
812
883
|
...notes.length > 0 ? { notes } : {}
|
|
813
884
|
};
|
|
814
885
|
}
|
|
815
|
-
function
|
|
886
|
+
function asObject5(value) {
|
|
816
887
|
if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
|
|
817
888
|
return value;
|
|
818
889
|
}
|
|
@@ -836,7 +907,7 @@ function clampPercent2(value) {
|
|
|
836
907
|
|
|
837
908
|
// src/providers/openrouter.ts
|
|
838
909
|
function normalizeOpenRouterKeyPayload(payload, capturedAt) {
|
|
839
|
-
const data =
|
|
910
|
+
const data = asObject6(payload.data);
|
|
840
911
|
if (!data) throw new Error("OpenRouter key response data was not an object.");
|
|
841
912
|
const limit = asNonnegativeNumber3(data.limit);
|
|
842
913
|
const remaining = asNonnegativeNumber3(data.limit_remaining);
|
|
@@ -882,7 +953,7 @@ function addUsageMetric(metrics, id, label, value) {
|
|
|
882
953
|
if (amount === void 0) return;
|
|
883
954
|
metrics.push({ id, label, value: amount, unit: "usd" });
|
|
884
955
|
}
|
|
885
|
-
function
|
|
956
|
+
function asObject6(value) {
|
|
886
957
|
if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
|
|
887
958
|
return value;
|
|
888
959
|
}
|
|
@@ -1060,13 +1131,13 @@ function isRecord2(value) {
|
|
|
1060
1131
|
var FIVE_HOUR_WINDOW_MINUTES2 = 300;
|
|
1061
1132
|
var WEEKLY_WINDOW_MINUTES2 = 10080;
|
|
1062
1133
|
function normalizeZaiQuotaPayload(providerId, providerName, payload, capturedAt) {
|
|
1063
|
-
const data =
|
|
1134
|
+
const data = asObject7(payload.data);
|
|
1064
1135
|
if (!data) throw new Error("Z.AI quota response data was not an object.");
|
|
1065
1136
|
const limits = Array.isArray(data.limits) ? data.limits : [];
|
|
1066
1137
|
const buckets = [];
|
|
1067
1138
|
const metrics = [];
|
|
1068
1139
|
for (const raw of limits) {
|
|
1069
|
-
const limit =
|
|
1140
|
+
const limit = asObject7(raw);
|
|
1070
1141
|
if (!limit) continue;
|
|
1071
1142
|
const type = asString5(limit.type);
|
|
1072
1143
|
const unit = asNonnegativeNumber4(limit.unit);
|
|
@@ -1138,7 +1209,7 @@ function addCountBucket(buckets, limit, id, label, windowMinutes) {
|
|
|
1138
1209
|
function addUsageDetailMetrics(metrics, value) {
|
|
1139
1210
|
if (!Array.isArray(value)) return;
|
|
1140
1211
|
for (const raw of value) {
|
|
1141
|
-
const detail =
|
|
1212
|
+
const detail = asObject7(raw);
|
|
1142
1213
|
if (!detail) continue;
|
|
1143
1214
|
const label = asString5(detail.modelCode);
|
|
1144
1215
|
const usage = asNonnegativeNumber4(detail.usage);
|
|
@@ -1146,7 +1217,7 @@ function addUsageDetailMetrics(metrics, value) {
|
|
|
1146
1217
|
metrics.push({ id: `mcp-${kebabCase(label)}`, label, value: usage, unit: "count" });
|
|
1147
1218
|
}
|
|
1148
1219
|
}
|
|
1149
|
-
function
|
|
1220
|
+
function asObject7(value) {
|
|
1150
1221
|
if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
|
|
1151
1222
|
return value;
|
|
1152
1223
|
}
|
|
@@ -1172,6 +1243,7 @@ function clampPercent3(value) {
|
|
|
1172
1243
|
|
|
1173
1244
|
// src/query.ts
|
|
1174
1245
|
var CODEX_USAGE_URL = "https://chatgpt.com/backend-api/wham/usage";
|
|
1246
|
+
var DEEPSEEK_BALANCE_URL = "https://api.deepseek.com/user/balance";
|
|
1175
1247
|
var GITHUB_COPILOT_USAGE_URL = "https://api.github.com/copilot_internal/user";
|
|
1176
1248
|
var OPENROUTER_KEY_URL = "https://openrouter.ai/api/v1/key";
|
|
1177
1249
|
var OPENCODE_GO_USAGE_URL = "https://opencode.ai/zen/go/v1/usage";
|
|
@@ -1205,6 +1277,27 @@ var SUPPORTED_ADAPTERS = [
|
|
|
1205
1277
|
return normalizeCodexBackendPayload(payload, Date.now());
|
|
1206
1278
|
}
|
|
1207
1279
|
},
|
|
1280
|
+
{
|
|
1281
|
+
id: "deepseek",
|
|
1282
|
+
displayName: "DeepSeek",
|
|
1283
|
+
semantics: { kind: "api-key", label: "DeepSeek API balance" },
|
|
1284
|
+
async query(auth, signal, timeoutMs, guard) {
|
|
1285
|
+
if (!guard) throw new Error("DeepSeek API balance requires request-boundary revalidation.");
|
|
1286
|
+
const startedAt = Date.now();
|
|
1287
|
+
await guard();
|
|
1288
|
+
const remainingMs = timeoutMs - (Date.now() - startedAt);
|
|
1289
|
+
if (remainingMs <= 0) throw new Error("Timed out while revalidating DeepSeek runtime auth.");
|
|
1290
|
+
const payload = await fetchProviderJson(
|
|
1291
|
+
DEEPSEEK_BALANCE_URL,
|
|
1292
|
+
auth,
|
|
1293
|
+
signal,
|
|
1294
|
+
remainingMs,
|
|
1295
|
+
"DeepSeek API balance endpoint",
|
|
1296
|
+
{ redirect: "error" }
|
|
1297
|
+
);
|
|
1298
|
+
return normalizeDeepSeekBalancePayload(payload, Date.now());
|
|
1299
|
+
}
|
|
1300
|
+
},
|
|
1208
1301
|
{
|
|
1209
1302
|
id: "github-copilot",
|
|
1210
1303
|
displayName: "GitHub Copilot",
|
|
@@ -1273,7 +1366,6 @@ var SUPPORTED_ADAPTERS = [
|
|
|
1273
1366
|
id: "zai",
|
|
1274
1367
|
displayName: "Z.AI",
|
|
1275
1368
|
semantics: { kind: "consumer-subscription", label: "GLM Coding Plan usage" },
|
|
1276
|
-
publishesStatusline: false,
|
|
1277
1369
|
async query(auth, signal, timeoutMs) {
|
|
1278
1370
|
const payload = await fetchProviderJson(
|
|
1279
1371
|
zaiMonitorUrl(auth.model.baseUrl),
|
|
@@ -1289,7 +1381,6 @@ var SUPPORTED_ADAPTERS = [
|
|
|
1289
1381
|
id: "zai-coding-cn",
|
|
1290
1382
|
displayName: "Z.AI Coding CN",
|
|
1291
1383
|
semantics: { kind: "consumer-subscription", label: "GLM Coding Plan usage" },
|
|
1292
|
-
publishesStatusline: false,
|
|
1293
1384
|
async query(auth, signal, timeoutMs) {
|
|
1294
1385
|
const payload = await fetchProviderJson(
|
|
1295
1386
|
zaiMonitorUrl(auth.model.baseUrl),
|
|
@@ -1351,11 +1442,11 @@ var XAI_ADAPTER = {
|
|
|
1351
1442
|
return normalizeXaiBillingPayload(billingPayload, userPayload.subscriptionTier, Date.now());
|
|
1352
1443
|
}
|
|
1353
1444
|
};
|
|
1354
|
-
function usageAdapters(
|
|
1355
|
-
return
|
|
1445
|
+
function usageAdapters() {
|
|
1446
|
+
return [...SUPPORTED_ADAPTERS, XAI_ADAPTER];
|
|
1356
1447
|
}
|
|
1357
|
-
function adapterForProvider(providerId
|
|
1358
|
-
return usageAdapters(
|
|
1448
|
+
function adapterForProvider(providerId) {
|
|
1449
|
+
return usageAdapters().find((adapter) => adapter.id === providerId);
|
|
1359
1450
|
}
|
|
1360
1451
|
function isStaleExtensionContextError(error) {
|
|
1361
1452
|
return error instanceof Error && error.message.includes("This extension ctx is stale after session replacement or reload");
|
|
@@ -1372,11 +1463,14 @@ async function resolveUsageAuth(ctx, adapter, salt = AUTH_FINGERPRINT_SALT, cred
|
|
|
1372
1463
|
if (!model) return void 0;
|
|
1373
1464
|
const registry = ctx.modelRegistry;
|
|
1374
1465
|
let modelAuth;
|
|
1375
|
-
|
|
1376
|
-
|
|
1466
|
+
const currentModel = ctx.model?.provider === adapter.id ? ctx.model : void 0;
|
|
1467
|
+
const resolveCurrentModelAuth = async () => {
|
|
1468
|
+
if (!currentModel || typeof registry.getApiKeyAndHeaders !== "function") return void 0;
|
|
1469
|
+
const result = await registry.getApiKeyAndHeaders(currentModel);
|
|
1377
1470
|
if (!result.ok) throw new Error(redactUsageError(result.error));
|
|
1378
|
-
|
|
1379
|
-
}
|
|
1471
|
+
return authorizationFrom(result) ? result : void 0;
|
|
1472
|
+
};
|
|
1473
|
+
if (adapter.id !== "deepseek") modelAuth = await resolveCurrentModelAuth();
|
|
1380
1474
|
if (typeof registry.getProviderAuth !== "function") {
|
|
1381
1475
|
throw new Error("pi-usage requires Pi 0.81.0 or newer to validate resolved provider auth.");
|
|
1382
1476
|
}
|
|
@@ -1386,6 +1480,7 @@ async function resolveUsageAuth(ctx, adapter, salt = AUTH_FINGERPRINT_SALT, cred
|
|
|
1386
1480
|
`${adapter.displayName} usage cannot send a proxy-resolved credential to the official usage endpoint.`
|
|
1387
1481
|
);
|
|
1388
1482
|
}
|
|
1483
|
+
if (adapter.id === "deepseek") modelAuth = await resolveCurrentModelAuth();
|
|
1389
1484
|
const auth = modelAuth ?? providerResult?.auth;
|
|
1390
1485
|
if (!auth) return void 0;
|
|
1391
1486
|
if (adapter.id === "github-copilot") {
|
|
@@ -1406,6 +1501,26 @@ async function resolveUsageAuth(ctx, adapter, salt = AUTH_FINGERPRINT_SALT, cred
|
|
|
1406
1501
|
if (!offered.ok) throw new Error("xAI OAuth credential discovery failed closed.");
|
|
1407
1502
|
return resolveXaiUsageAuth(auth, model, salt, offered.candidates);
|
|
1408
1503
|
}
|
|
1504
|
+
if (adapter.id === "deepseek") {
|
|
1505
|
+
const resolvedAuthorization = authorizationFrom(auth);
|
|
1506
|
+
const access = bearerToken(resolvedAuthorization);
|
|
1507
|
+
if (!access) throw new Error("DeepSeek API balance requires Bearer authentication.");
|
|
1508
|
+
const authorization2 = `Bearer ${access}`;
|
|
1509
|
+
const headers2 = { Authorization: authorization2 };
|
|
1510
|
+
return {
|
|
1511
|
+
apiKey: access,
|
|
1512
|
+
headers: headers2,
|
|
1513
|
+
fingerprint: fingerprintResolvedAuth({ headers: headers2 }, salt),
|
|
1514
|
+
secrets: [
|
|
1515
|
+
access,
|
|
1516
|
+
auth.apiKey,
|
|
1517
|
+
headerValue(auth.headers, "Authorization"),
|
|
1518
|
+
resolvedAuthorization,
|
|
1519
|
+
authorization2
|
|
1520
|
+
].filter((value) => Boolean(value)),
|
|
1521
|
+
model
|
|
1522
|
+
};
|
|
1523
|
+
}
|
|
1409
1524
|
const authorization = authorizationFrom(auth);
|
|
1410
1525
|
if (!authorization) return void 0;
|
|
1411
1526
|
const headers = { Authorization: authorization };
|
|
@@ -1563,7 +1678,7 @@ function resolveXaiUsageAuth(auth, model, salt, candidates) {
|
|
|
1563
1678
|
const matches = [];
|
|
1564
1679
|
for (const candidate of candidates) {
|
|
1565
1680
|
try {
|
|
1566
|
-
const credential =
|
|
1681
|
+
const credential = asObject8(candidate);
|
|
1567
1682
|
if (credential?.type !== "oauth") continue;
|
|
1568
1683
|
sawOAuth = true;
|
|
1569
1684
|
if (credential.access !== resolvedAccess) continue;
|
|
@@ -1617,7 +1732,7 @@ function resolveGitHubCopilotUsageAuth(auth, model, salt, candidates, standalone
|
|
|
1617
1732
|
const matches = /* @__PURE__ */ new Map();
|
|
1618
1733
|
for (const candidate of candidates) {
|
|
1619
1734
|
try {
|
|
1620
|
-
const credential =
|
|
1735
|
+
const credential = asObject8(candidate);
|
|
1621
1736
|
if (credential?.type !== "oauth") continue;
|
|
1622
1737
|
sawOAuth = true;
|
|
1623
1738
|
const storedAccess2 = typeof credential.access === "string" && credential.access ? credential.access : void 0;
|
|
@@ -1679,7 +1794,7 @@ function bearerToken(authorization) {
|
|
|
1679
1794
|
const match = /^Bearer\s+(.+)$/iu.exec(authorization ?? "");
|
|
1680
1795
|
return match?.[1];
|
|
1681
1796
|
}
|
|
1682
|
-
function
|
|
1797
|
+
function asObject8(value) {
|
|
1683
1798
|
if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
|
|
1684
1799
|
return value;
|
|
1685
1800
|
}
|
|
@@ -1698,6 +1813,7 @@ function hasOfficialUrlOrigin(value, providerId) {
|
|
|
1698
1813
|
try {
|
|
1699
1814
|
const url = new URL(value);
|
|
1700
1815
|
if (providerId === "openai-codex") return url.origin === "https://chatgpt.com";
|
|
1816
|
+
if (providerId === "deepseek") return url.origin === "https://api.deepseek.com";
|
|
1701
1817
|
if (providerId === "openrouter") return url.origin === "https://openrouter.ai";
|
|
1702
1818
|
if (providerId === "opencode-go") return url.origin === "https://opencode.ai";
|
|
1703
1819
|
if (providerId === "kimi-coding") return url.origin === "https://api.kimi.com";
|
|
@@ -1885,7 +2001,7 @@ function normalizeCodexResetCreditsPayload(payload) {
|
|
|
1885
2001
|
if (rawCredits !== void 0 && !Array.isArray(rawCredits)) {
|
|
1886
2002
|
throw new Error("Codex reset credits response returned invalid credits.");
|
|
1887
2003
|
}
|
|
1888
|
-
const options = (rawCredits ?? []).map(
|
|
2004
|
+
const options = (rawCredits ?? []).map(asObject9).filter((credit) => Boolean(credit)).filter((credit) => credit.status === "available" && credit.reset_type === "codex_rate_limits").map(normalizeResetOption).sort(
|
|
1889
2005
|
(left, right) => (left.expiresAt ?? Number.MAX_SAFE_INTEGER) - (right.expiresAt ?? Number.MAX_SAFE_INTEGER)
|
|
1890
2006
|
).slice(0, Math.min(availableCount, MAX_RESET_OPTIONS));
|
|
1891
2007
|
if (availableCount > 0 && options.length === 0) {
|
|
@@ -1900,7 +2016,7 @@ function selectCodexResetCredential(candidates, resolvedAccess, resolvedAccountI
|
|
|
1900
2016
|
const matches = /* @__PURE__ */ new Map();
|
|
1901
2017
|
for (const candidate of candidates) {
|
|
1902
2018
|
try {
|
|
1903
|
-
const credential =
|
|
2019
|
+
const credential = asObject9(candidate);
|
|
1904
2020
|
if (credential?.type !== "oauth") continue;
|
|
1905
2021
|
sawOAuth = true;
|
|
1906
2022
|
const storedAccess = asNonemptyString(credential.access);
|
|
@@ -1939,7 +2055,7 @@ function codexAccountIdFromAccessToken(access) {
|
|
|
1939
2055
|
const parts = access.split(".");
|
|
1940
2056
|
if (parts.length !== 3 || !parts[1]) return void 0;
|
|
1941
2057
|
const payload = JSON.parse(Buffer.from(parts[1], "base64url").toString("utf8"));
|
|
1942
|
-
const claims =
|
|
2058
|
+
const claims = asObject9(asObject9(payload)?.["https://api.openai.com/auth"]);
|
|
1943
2059
|
return validHeaderValue(claims?.chatgpt_account_id);
|
|
1944
2060
|
} catch {
|
|
1945
2061
|
return void 0;
|
|
@@ -1971,7 +2087,7 @@ function normalizeResetOption(credit) {
|
|
|
1971
2087
|
function isCodexResetOutcomeCode(value) {
|
|
1972
2088
|
return value === "reset" || value === "nothing_to_reset" || value === "no_credit" || value === "already_redeemed";
|
|
1973
2089
|
}
|
|
1974
|
-
function
|
|
2090
|
+
function asObject9(value) {
|
|
1975
2091
|
if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
|
|
1976
2092
|
return value;
|
|
1977
2093
|
}
|
|
@@ -2012,10 +2128,12 @@ var BAR_SEGMENTS = 20;
|
|
|
2012
2128
|
var VALUE_COLUMN = 29;
|
|
2013
2129
|
function formatUsageReport(report, displayState) {
|
|
2014
2130
|
const stateLabel = displayState === "current" ? "Current" : "Configured";
|
|
2015
|
-
const
|
|
2131
|
+
const title = report.providerId === "deepseek" ? "DeepSeek API Balance" : `${report.providerName} Usage`;
|
|
2132
|
+
const lines = [`${title} \xB7 ${stateLabel}`];
|
|
2016
2133
|
if (report.accountLabel) lines.push(`Account: ${report.accountLabel}`);
|
|
2017
2134
|
lines.push(`Semantics: ${report.semantics.label}`, "");
|
|
2018
2135
|
if (report.providerId === "openai-codex") formatCodexReport(lines, report);
|
|
2136
|
+
else if (report.providerId === "deepseek") formatDeepSeekReport(lines, report);
|
|
2019
2137
|
else if (report.providerId === "github-copilot") formatGitHubCopilotReport(lines, report);
|
|
2020
2138
|
else if (report.providerId === "openrouter") formatOpenRouterReport(lines, report);
|
|
2021
2139
|
else if (report.providerId === "opencode-go") formatOpenCodeZenReport(lines, report);
|
|
@@ -2029,8 +2147,11 @@ function formatUsageReport(report, displayState) {
|
|
|
2029
2147
|
}
|
|
2030
2148
|
return lines.join("\n").trimEnd();
|
|
2031
2149
|
}
|
|
2032
|
-
function formatUsageStatusline(report, model) {
|
|
2033
|
-
if (report.providerId === "openai-codex")
|
|
2150
|
+
function formatUsageStatusline(report, model, now = Date.now(), showCodexResetCountdown = true) {
|
|
2151
|
+
if (report.providerId === "openai-codex") {
|
|
2152
|
+
return formatCodexStatusline(report, model, now, showCodexResetCountdown);
|
|
2153
|
+
}
|
|
2154
|
+
if (report.providerId === "deepseek") return formatDeepSeekStatusline(report);
|
|
2034
2155
|
if (report.providerId === "github-copilot") return formatGitHubCopilotStatusline(report);
|
|
2035
2156
|
if (report.providerId === "openrouter") {
|
|
2036
2157
|
const limit = report.buckets.find((bucket) => bucket.id === "key-limit");
|
|
@@ -2040,6 +2161,9 @@ function formatUsageStatusline(report, model) {
|
|
|
2040
2161
|
}
|
|
2041
2162
|
if (report.providerId === "opencode-go") return formatOpenCodeZenStatusline(report);
|
|
2042
2163
|
if (report.providerId === "kimi-coding") return formatKimiCodingStatusline(report);
|
|
2164
|
+
if (report.providerId === "zai" || report.providerId === "zai-coding-cn") {
|
|
2165
|
+
return formatZaiStatusline(report);
|
|
2166
|
+
}
|
|
2043
2167
|
return void 0;
|
|
2044
2168
|
}
|
|
2045
2169
|
function formatProviderStates(states) {
|
|
@@ -2073,6 +2197,31 @@ function formatCodexReport(lines, report) {
|
|
|
2073
2197
|
}
|
|
2074
2198
|
}
|
|
2075
2199
|
}
|
|
2200
|
+
function formatDeepSeekReport(lines, report) {
|
|
2201
|
+
const availability = report.metrics.find((metric) => metric.id === "api-availability");
|
|
2202
|
+
lines.push(
|
|
2203
|
+
`${"API calls:".padEnd(VALUE_COLUMN)}${availability?.value === "available" ? "Available" : "Unavailable"}`
|
|
2204
|
+
);
|
|
2205
|
+
for (const currency of ["CNY", "USD"]) {
|
|
2206
|
+
const metrics = report.metrics.filter((metric) => metric.currency === currency);
|
|
2207
|
+
if (metrics.length === 0) continue;
|
|
2208
|
+
lines.push("", `${currency} balance:`);
|
|
2209
|
+
for (const metric of metrics) {
|
|
2210
|
+
lines.push(`${`${metric.label}:`.padEnd(VALUE_COLUMN)}${currency} ${metric.value}`);
|
|
2211
|
+
}
|
|
2212
|
+
}
|
|
2213
|
+
}
|
|
2214
|
+
function formatDeepSeekStatusline(report) {
|
|
2215
|
+
const availability = report.metrics.find((metric) => metric.id === "api-availability");
|
|
2216
|
+
if (availability?.value !== "available") return "deepseek API unavailable";
|
|
2217
|
+
const totals = ["CNY", "USD"].flatMap((currency) => {
|
|
2218
|
+
const metric = report.metrics.find(
|
|
2219
|
+
(candidate) => candidate.id === `${currency.toLowerCase()}-total`
|
|
2220
|
+
);
|
|
2221
|
+
return metric ? [`${currency} ${metric.value}`] : [];
|
|
2222
|
+
});
|
|
2223
|
+
return totals.length > 0 ? `deepseek ${totals.join(" \xB7 ")}` : "deepseek balance unavailable";
|
|
2224
|
+
}
|
|
2076
2225
|
function formatGitHubCopilotReport(lines, report) {
|
|
2077
2226
|
const quota = findGitHubCopilotQuota(report);
|
|
2078
2227
|
if (!quota || quota.limit === void 0 || quota.remaining === void 0) {
|
|
@@ -2187,6 +2336,21 @@ function formatKimiCodingStatusline(report) {
|
|
|
2187
2336
|
}
|
|
2188
2337
|
return parts.length > 1 ? parts.join(" ") : void 0;
|
|
2189
2338
|
}
|
|
2339
|
+
function formatZaiStatusline(report) {
|
|
2340
|
+
const selected = [
|
|
2341
|
+
report.buckets.find((bucket) => bucket.id === "five-hour"),
|
|
2342
|
+
report.buckets.find((bucket) => bucket.id === "weekly")
|
|
2343
|
+
];
|
|
2344
|
+
const parts = ["zai"];
|
|
2345
|
+
for (const bucket of selected) {
|
|
2346
|
+
if (!bucket?.limit || bucket.remaining === void 0) continue;
|
|
2347
|
+
const fallback = bucket.id === "weekly" ? "weekly" : "5h";
|
|
2348
|
+
parts.push(
|
|
2349
|
+
`${percentRemaining(bucket)}% ${formatWindowLabel(bucket.windowMinutes, fallback, true)}`
|
|
2350
|
+
);
|
|
2351
|
+
}
|
|
2352
|
+
return parts.length > 1 ? parts.join(" ") : void 0;
|
|
2353
|
+
}
|
|
2190
2354
|
function formatCurrencyMetric(metric) {
|
|
2191
2355
|
if (typeof metric.value !== "number") return String(metric.value);
|
|
2192
2356
|
if (!metric.currency) return "unavailable";
|
|
@@ -2258,7 +2422,7 @@ function formatGenericReport(lines, report) {
|
|
|
2258
2422
|
);
|
|
2259
2423
|
}
|
|
2260
2424
|
}
|
|
2261
|
-
function formatCodexStatusline(report, model) {
|
|
2425
|
+
function formatCodexStatusline(report, model, now = Date.now(), showResetCountdown = true) {
|
|
2262
2426
|
const group = selectCodexGroup(report, model);
|
|
2263
2427
|
if (!group) return formatCodexCreditsStatus(report);
|
|
2264
2428
|
const buckets = report.buckets.filter((bucket) => (bucket.groupId ?? bucket.id) === group);
|
|
@@ -2268,10 +2432,15 @@ function formatCodexStatusline(report, model) {
|
|
|
2268
2432
|
];
|
|
2269
2433
|
for (const bucket of buckets) {
|
|
2270
2434
|
if (bucket.remaining === void 0) continue;
|
|
2435
|
+
const percent = `${clampPercent4(bucket.remaining).toFixed(0)}%`;
|
|
2271
2436
|
const fallback = bucket.id.endsWith(":secondary") ? "weekly" : "5h";
|
|
2272
|
-
|
|
2273
|
-
|
|
2274
|
-
|
|
2437
|
+
const window = formatWindowLabel(bucket.windowMinutes, fallback, true);
|
|
2438
|
+
if (!showResetCountdown) {
|
|
2439
|
+
parts.push(`${percent} ${window}`);
|
|
2440
|
+
continue;
|
|
2441
|
+
}
|
|
2442
|
+
const reset = formatResetCountdown(bucket.resetsAt, now);
|
|
2443
|
+
parts.push(`${percent} ${reset ? `\u21BB ${reset}` : window}`);
|
|
2275
2444
|
}
|
|
2276
2445
|
return parts.length > 1 ? parts.join(" ") : formatCodexCreditsStatus(report);
|
|
2277
2446
|
}
|
|
@@ -2352,6 +2521,23 @@ function formatWindowLabel(minutes, fallback, compact) {
|
|
|
2352
2521
|
if (minutes % 60 === 0) return `${minutes / 60}h`;
|
|
2353
2522
|
return `${minutes}m`;
|
|
2354
2523
|
}
|
|
2524
|
+
function formatResetCountdown(resetsAt, now) {
|
|
2525
|
+
if (resetsAt === void 0 || !Number.isFinite(resetsAt) || !Number.isFinite(now))
|
|
2526
|
+
return void 0;
|
|
2527
|
+
const totalMinutes = Math.max(0, Math.ceil((resetsAt * 1e3 - now) / 6e4));
|
|
2528
|
+
const days = Math.floor(totalMinutes / 1440);
|
|
2529
|
+
const hours = Math.floor(totalMinutes % 1440 / 60);
|
|
2530
|
+
const minutes = totalMinutes % 60;
|
|
2531
|
+
if (days > 0) {
|
|
2532
|
+
return [
|
|
2533
|
+
`${String(days)}d`,
|
|
2534
|
+
hours > 0 ? `${String(hours)}h` : minutes > 0 ? `${String(minutes)}m` : ""
|
|
2535
|
+
].filter(Boolean).join("");
|
|
2536
|
+
}
|
|
2537
|
+
if (hours > 0)
|
|
2538
|
+
return [`${String(hours)}h`, minutes > 0 ? `${String(minutes)}m` : ""].filter(Boolean).join("");
|
|
2539
|
+
return `${String(minutes)}m`;
|
|
2540
|
+
}
|
|
2355
2541
|
function formatMetricValue(value, unit) {
|
|
2356
2542
|
if (unit === "usd" && typeof value === "number") return formatUsd(value);
|
|
2357
2543
|
return String(value);
|
|
@@ -2384,7 +2570,7 @@ var USAGE_SETTINGS_FILE = "pi-usage.json";
|
|
|
2384
2570
|
var MAX_USAGE_SETTINGS_BYTES = 64 * 1024;
|
|
2385
2571
|
var DEFAULT_USAGE_SETTINGS = Object.freeze({
|
|
2386
2572
|
codexFastMode: false,
|
|
2387
|
-
|
|
2573
|
+
codexStatusResetCountdown: true
|
|
2388
2574
|
});
|
|
2389
2575
|
function usageSettingsPath() {
|
|
2390
2576
|
return join(getAgentDir(), USAGE_SETTINGS_FILE);
|
|
@@ -2394,12 +2580,12 @@ function normalizeUsageSettings(value) {
|
|
|
2394
2580
|
if (Object.hasOwn(value, "codexFastMode") && typeof value.codexFastMode !== "boolean") {
|
|
2395
2581
|
return void 0;
|
|
2396
2582
|
}
|
|
2397
|
-
if (Object.hasOwn(value, "
|
|
2583
|
+
if (Object.hasOwn(value, "codexStatusResetCountdown") && typeof value.codexStatusResetCountdown !== "boolean") {
|
|
2398
2584
|
return void 0;
|
|
2399
2585
|
}
|
|
2400
2586
|
return {
|
|
2401
2587
|
codexFastMode: typeof value.codexFastMode === "boolean" ? value.codexFastMode : DEFAULT_USAGE_SETTINGS.codexFastMode,
|
|
2402
|
-
|
|
2588
|
+
codexStatusResetCountdown: typeof value.codexStatusResetCountdown === "boolean" ? value.codexStatusResetCountdown : DEFAULT_USAGE_SETTINGS.codexStatusResetCountdown
|
|
2403
2589
|
};
|
|
2404
2590
|
}
|
|
2405
2591
|
async function loadUsageSettings(path = usageSettingsPath(), signal) {
|
|
@@ -2684,8 +2870,8 @@ function isAbortError2(error) {
|
|
|
2684
2870
|
}
|
|
2685
2871
|
|
|
2686
2872
|
// src/usage-helpers.ts
|
|
2687
|
-
function configuredAdapters(ctx
|
|
2688
|
-
return usageAdapters(
|
|
2873
|
+
function configuredAdapters(ctx) {
|
|
2874
|
+
return usageAdapters().filter(
|
|
2689
2875
|
(adapter) => adapter.id === ctx.model?.provider || providerIsConfigured(ctx, adapter.id)
|
|
2690
2876
|
);
|
|
2691
2877
|
}
|
|
@@ -2750,10 +2936,10 @@ async function showUsageSettings(ctx, settingsRuntime, parentSignal, isCurrent,
|
|
|
2750
2936
|
values: [OFF, ON]
|
|
2751
2937
|
},
|
|
2752
2938
|
{
|
|
2753
|
-
id: "
|
|
2754
|
-
label: "
|
|
2755
|
-
description: "
|
|
2756
|
-
currentValue: state.
|
|
2939
|
+
id: "codexStatusResetCountdown",
|
|
2940
|
+
label: "Codex reset countdown",
|
|
2941
|
+
description: "Show time remaining until each Codex usage limit resets.",
|
|
2942
|
+
currentValue: state.settings.codexStatusResetCountdown ? ON : OFF,
|
|
2757
2943
|
values: [OFF, ON]
|
|
2758
2944
|
}
|
|
2759
2945
|
];
|
|
@@ -2777,8 +2963,7 @@ async function showUsageSettings(ctx, settingsRuntime, parentSignal, isCurrent,
|
|
|
2777
2963
|
saveQueue = saveQueue.then(async () => {
|
|
2778
2964
|
const previous = settingsRuntime.get().settings[settingId];
|
|
2779
2965
|
if (settingsRuntime.get().kind === "invalid") {
|
|
2780
|
-
|
|
2781
|
-
settingsList.updateValue(id, displayValue(settingId, effectivePrevious));
|
|
2966
|
+
settingsList.updateValue(id, displayValue(previous));
|
|
2782
2967
|
if (!signal.aborted && isCurrent()) {
|
|
2783
2968
|
ctx.ui.notify("Repair pi-usage.json and reload before changing settings.", "error");
|
|
2784
2969
|
tui.requestRender();
|
|
@@ -2789,17 +2974,17 @@ async function showUsageSettings(ctx, settingsRuntime, parentSignal, isCurrent,
|
|
|
2789
2974
|
await settingsRuntime.update({ [settingId]: requested }, signal);
|
|
2790
2975
|
} catch (error) {
|
|
2791
2976
|
if (signal.aborted || !isCurrent()) return;
|
|
2792
|
-
settingsList.updateValue(id, displayValue(
|
|
2977
|
+
settingsList.updateValue(id, displayValue(previous));
|
|
2793
2978
|
ctx.ui.notify(`Could not save pi-usage.json: ${errorMessage(error)}`, "error");
|
|
2794
2979
|
tui.requestRender();
|
|
2795
2980
|
return;
|
|
2796
2981
|
}
|
|
2797
2982
|
if (previous !== requested) {
|
|
2798
2983
|
changed = true;
|
|
2799
|
-
onApplied(settingId
|
|
2984
|
+
onApplied(settingId);
|
|
2800
2985
|
}
|
|
2801
2986
|
if (signal.aborted || !isCurrent()) return;
|
|
2802
|
-
settingsList.updateValue(id, displayValue(
|
|
2987
|
+
settingsList.updateValue(id, displayValue(requested));
|
|
2803
2988
|
tui.requestRender();
|
|
2804
2989
|
});
|
|
2805
2990
|
},
|
|
@@ -2823,12 +3008,13 @@ async function showUsageSettings(ctx, settingsRuntime, parentSignal, isCurrent,
|
|
|
2823
3008
|
};
|
|
2824
3009
|
});
|
|
2825
3010
|
}
|
|
2826
|
-
function displayValue(
|
|
3011
|
+
function displayValue(enabled) {
|
|
2827
3012
|
return enabled ? ON : OFF;
|
|
2828
3013
|
}
|
|
2829
3014
|
|
|
2830
3015
|
// src/usage.ts
|
|
2831
3016
|
var CACHE_TTL_MS = 5 * 60 * 1e3;
|
|
3017
|
+
var STATUS_COUNTDOWN_REFRESH_MS = 60 * 1e3;
|
|
2832
3018
|
var DEFAULT_TIMEOUT_MS = 15e3;
|
|
2833
3019
|
var ALL_PROVIDER_CONCURRENCY = 2;
|
|
2834
3020
|
var FAILURE_BACKOFF_MS = 3e4;
|
|
@@ -2854,19 +3040,22 @@ function usageExtension(pi, dependencies = {}) {
|
|
|
2854
3040
|
let sessionActive = false;
|
|
2855
3041
|
let statusGeneration = 0;
|
|
2856
3042
|
let sessionGeneration = 0;
|
|
2857
|
-
let xaiSettingsGeneration = 0;
|
|
2858
3043
|
let statusRefreshTimer;
|
|
3044
|
+
let statusCountdownTimer;
|
|
2859
3045
|
let statusController;
|
|
2860
3046
|
let fastRuntime;
|
|
2861
|
-
const
|
|
2862
|
-
const state = settingsRuntime.get();
|
|
2863
|
-
return state.kind !== "invalid" && state.settings.xaiUsage;
|
|
2864
|
-
};
|
|
2865
|
-
const activeAdapterForProvider = (providerId) => adapterForProvider(providerId, xaiUsageEnabled());
|
|
2866
|
-
const clearStatusTimer = () => {
|
|
3047
|
+
const clearStatusRefreshTimer = () => {
|
|
2867
3048
|
if (statusRefreshTimer) clearTimeout(statusRefreshTimer);
|
|
2868
3049
|
statusRefreshTimer = void 0;
|
|
2869
3050
|
};
|
|
3051
|
+
const clearStatusCountdownTimer = () => {
|
|
3052
|
+
if (statusCountdownTimer) clearTimeout(statusCountdownTimer);
|
|
3053
|
+
statusCountdownTimer = void 0;
|
|
3054
|
+
};
|
|
3055
|
+
const clearStatusTimers = () => {
|
|
3056
|
+
clearStatusRefreshTimer();
|
|
3057
|
+
clearStatusCountdownTimer();
|
|
3058
|
+
};
|
|
2870
3059
|
const safeSetStatus = (ctx, value) => {
|
|
2871
3060
|
try {
|
|
2872
3061
|
ctx.ui.setStatus(STATUS_KEY, value);
|
|
@@ -2880,11 +3069,11 @@ function usageExtension(pi, dependencies = {}) {
|
|
|
2880
3069
|
statusGeneration += 1;
|
|
2881
3070
|
statusController?.abort();
|
|
2882
3071
|
statusController = void 0;
|
|
2883
|
-
|
|
3072
|
+
clearStatusTimers();
|
|
2884
3073
|
safeSetStatus(ctx, void 0);
|
|
2885
3074
|
};
|
|
2886
3075
|
const scheduleStatusRefresh = (ctx, model) => {
|
|
2887
|
-
|
|
3076
|
+
clearStatusRefreshTimer();
|
|
2888
3077
|
const generation = statusGeneration;
|
|
2889
3078
|
statusRefreshTimer = setTimeout(() => {
|
|
2890
3079
|
statusRefreshTimer = void 0;
|
|
@@ -2894,13 +3083,14 @@ function usageExtension(pi, dependencies = {}) {
|
|
|
2894
3083
|
statusRefreshTimer.unref?.();
|
|
2895
3084
|
};
|
|
2896
3085
|
const publishStatus = (ctx, outcome, model, shouldSchedule) => {
|
|
2897
|
-
|
|
2898
|
-
|
|
3086
|
+
clearStatusCountdownTimer();
|
|
3087
|
+
if (adapterForProvider(model.provider)?.publishesStatusline === false) {
|
|
3088
|
+
clearStatusRefreshTimer();
|
|
2899
3089
|
safeSetStatus(ctx, void 0);
|
|
2900
3090
|
return;
|
|
2901
3091
|
}
|
|
2902
3092
|
if (outcome.state.status === "unsupported") {
|
|
2903
|
-
|
|
3093
|
+
clearStatusRefreshTimer();
|
|
2904
3094
|
safeSetStatus(ctx, void 0);
|
|
2905
3095
|
return;
|
|
2906
3096
|
}
|
|
@@ -2913,10 +3103,30 @@ function usageExtension(pi, dependencies = {}) {
|
|
|
2913
3103
|
}
|
|
2914
3104
|
return;
|
|
2915
3105
|
}
|
|
2916
|
-
const
|
|
3106
|
+
const showCodexResetCountdown = outcome.state.report.providerId === "openai-codex" && settingsRuntime.get().settings.codexStatusResetCountdown;
|
|
3107
|
+
const now = Date.now();
|
|
3108
|
+
const rawValue = formatUsageStatusline(
|
|
3109
|
+
outcome.state.report,
|
|
3110
|
+
model,
|
|
3111
|
+
now,
|
|
3112
|
+
showCodexResetCountdown
|
|
3113
|
+
);
|
|
2917
3114
|
const value = rawValue ? fastRuntime.decorateStatus(model, rawValue) : void 0;
|
|
2918
3115
|
if (!safeSetStatus(ctx, value)) return;
|
|
2919
3116
|
if (shouldSchedule && sessionActive) scheduleStatusRefresh(ctx, model);
|
|
3117
|
+
if (sessionActive && showCodexResetCountdown && outcome.state.report.buckets.some(
|
|
3118
|
+
(bucket) => bucket.resetsAt !== void 0 && Number.isFinite(bucket.resetsAt) && bucket.resetsAt * 1e3 > now
|
|
3119
|
+
)) {
|
|
3120
|
+
const generation = statusGeneration;
|
|
3121
|
+
statusCountdownTimer = setTimeout(() => {
|
|
3122
|
+
statusCountdownTimer = void 0;
|
|
3123
|
+
if (!sessionActive || generation !== statusGeneration || modelIdentity(ctx.model) !== modelIdentity(model)) {
|
|
3124
|
+
return;
|
|
3125
|
+
}
|
|
3126
|
+
publishStatus(ctx, outcome, model, false);
|
|
3127
|
+
}, STATUS_COUNTDOWN_REFRESH_MS);
|
|
3128
|
+
statusCountdownTimer.unref?.();
|
|
3129
|
+
}
|
|
2920
3130
|
};
|
|
2921
3131
|
const invalidateProviderState = (providerId) => {
|
|
2922
3132
|
cache.clearProvider(providerId);
|
|
@@ -2938,10 +3148,8 @@ function usageExtension(pi, dependencies = {}) {
|
|
|
2938
3148
|
}
|
|
2939
3149
|
activeCurrentIdentity = nextIdentity;
|
|
2940
3150
|
};
|
|
2941
|
-
const queryAdapterState = async (ctx, adapter, displayState, force, signal) => {
|
|
2942
|
-
const startedAt = Date.now();
|
|
3151
|
+
const queryAdapterState = async (ctx, adapter, displayState, force, signal, authRetry = 0, deadlineAt = Date.now() + DEFAULT_TIMEOUT_MS) => {
|
|
2943
3152
|
const expectedSessionGeneration = sessionGeneration;
|
|
2944
|
-
const expectedXaiSettingsGeneration = xaiSettingsGeneration;
|
|
2945
3153
|
const expectedSessionId = ctx.sessionManager.getSessionId();
|
|
2946
3154
|
const expectedModelIdentity = modelIdentity(ctx.model);
|
|
2947
3155
|
let auth;
|
|
@@ -2949,7 +3157,7 @@ function usageExtension(pi, dependencies = {}) {
|
|
|
2949
3157
|
auth = await awaitWithDeadline(
|
|
2950
3158
|
resolveUsageAuth(ctx, adapter, void 0, credentialReader, credentialCandidates),
|
|
2951
3159
|
signal,
|
|
2952
|
-
|
|
3160
|
+
Math.max(1, deadlineAt - Date.now()),
|
|
2953
3161
|
`resolving ${adapter.displayName} runtime auth`
|
|
2954
3162
|
);
|
|
2955
3163
|
} catch (error) {
|
|
@@ -2967,9 +3175,9 @@ function usageExtension(pi, dependencies = {}) {
|
|
|
2967
3175
|
}
|
|
2968
3176
|
};
|
|
2969
3177
|
}
|
|
2970
|
-
|
|
2971
|
-
|
|
2972
|
-
|
|
3178
|
+
const requiresRequestBoundaryGuard = adapter.id === "deepseek" || adapter.id === "xai";
|
|
3179
|
+
const requestContextChanged = () => expectedSessionGeneration !== sessionGeneration || ctx.sessionManager.getSessionId() !== expectedSessionId || modelIdentity(ctx.model) !== expectedModelIdentity;
|
|
3180
|
+
if (requiresRequestBoundaryGuard && requestContextChanged()) throw abortError();
|
|
2973
3181
|
if (!auth) {
|
|
2974
3182
|
if (displayState === "current") {
|
|
2975
3183
|
transitionCurrentIdentity(`${adapter.id}:unavailable`, adapter.id);
|
|
@@ -3019,19 +3227,23 @@ function usageExtension(pi, dependencies = {}) {
|
|
|
3019
3227
|
querySequence += 1;
|
|
3020
3228
|
const queryId = querySequence;
|
|
3021
3229
|
setBoundedMap(latestQueries, failureKey, queryId, MAX_ACCOUNT_STATES);
|
|
3230
|
+
let deepSeekAuthChanged = false;
|
|
3022
3231
|
try {
|
|
3023
|
-
const remainingMs = Math.max(1,
|
|
3024
|
-
const guard =
|
|
3025
|
-
if (signal.aborted ||
|
|
3026
|
-
throw abortError();
|
|
3027
|
-
}
|
|
3232
|
+
const remainingMs = Math.max(1, deadlineAt - Date.now());
|
|
3233
|
+
const guard = requiresRequestBoundaryGuard ? async () => {
|
|
3234
|
+
if (signal.aborted || requestContextChanged()) throw abortError();
|
|
3028
3235
|
const revalidated = await awaitWithDeadline(
|
|
3029
3236
|
resolveUsageAuth(ctx, adapter, void 0, credentialReader, credentialCandidates),
|
|
3030
3237
|
signal,
|
|
3031
|
-
Math.max(1,
|
|
3032
|
-
|
|
3238
|
+
Math.max(1, deadlineAt - Date.now()),
|
|
3239
|
+
`revalidating ${adapter.displayName} runtime auth`
|
|
3033
3240
|
);
|
|
3034
|
-
if (signal.aborted ||
|
|
3241
|
+
if (signal.aborted || requestContextChanged()) throw abortError();
|
|
3242
|
+
if (revalidated?.fingerprint !== auth.fingerprint) {
|
|
3243
|
+
if (adapter.id === "deepseek") {
|
|
3244
|
+
deepSeekAuthChanged = true;
|
|
3245
|
+
throw new Error("DeepSeek runtime credential changed during the balance query.");
|
|
3246
|
+
}
|
|
3035
3247
|
throw abortError();
|
|
3036
3248
|
}
|
|
3037
3249
|
} : void 0;
|
|
@@ -3053,6 +3265,18 @@ function usageExtension(pi, dependencies = {}) {
|
|
|
3053
3265
|
};
|
|
3054
3266
|
} catch (error) {
|
|
3055
3267
|
if (isStaleExtensionContextError(error) || isAbortError3(error)) throw error;
|
|
3268
|
+
if (deepSeekAuthChanged && authRetry === 0 && !signal.aborted && !requestContextChanged() && Date.now() < deadlineAt) {
|
|
3269
|
+
if (latestQueries.get(failureKey) === queryId) latestQueries.delete(failureKey);
|
|
3270
|
+
return queryAdapterState(
|
|
3271
|
+
ctx,
|
|
3272
|
+
adapter,
|
|
3273
|
+
displayState,
|
|
3274
|
+
true,
|
|
3275
|
+
signal,
|
|
3276
|
+
authRetry + 1,
|
|
3277
|
+
deadlineAt
|
|
3278
|
+
);
|
|
3279
|
+
}
|
|
3056
3280
|
const message = errorMessage(error);
|
|
3057
3281
|
const now = Date.now();
|
|
3058
3282
|
for (const [key, failure] of failureBackoff) {
|
|
@@ -3079,7 +3303,7 @@ function usageExtension(pi, dependencies = {}) {
|
|
|
3079
3303
|
}
|
|
3080
3304
|
};
|
|
3081
3305
|
const queryCurrentState = async (ctx, model, force, signal) => {
|
|
3082
|
-
const adapter =
|
|
3306
|
+
const adapter = adapterForProvider(model?.provider);
|
|
3083
3307
|
if (!adapter) {
|
|
3084
3308
|
const providerId = model?.provider ?? "none";
|
|
3085
3309
|
transitionCurrentIdentity(`unsupported:${providerId}`, providerId);
|
|
@@ -3089,14 +3313,14 @@ function usageExtension(pi, dependencies = {}) {
|
|
|
3089
3313
|
providerName: providerDisplayName(ctx, providerId),
|
|
3090
3314
|
displayState: "current",
|
|
3091
3315
|
status: "unsupported",
|
|
3092
|
-
message:
|
|
3316
|
+
message: model ? `Usage reporting is not supported for ${providerDisplayName(ctx, providerId)}.` : "No model is selected."
|
|
3093
3317
|
}
|
|
3094
3318
|
};
|
|
3095
3319
|
}
|
|
3096
3320
|
return queryAdapterState(ctx, adapter, "current", force, signal);
|
|
3097
3321
|
};
|
|
3098
3322
|
const refreshCurrentStatus = async (ctx, model, force) => {
|
|
3099
|
-
const adapter =
|
|
3323
|
+
const adapter = adapterForProvider(model?.provider);
|
|
3100
3324
|
if (!adapter || !model) {
|
|
3101
3325
|
const providerId = model?.provider ?? "none";
|
|
3102
3326
|
transitionCurrentIdentity(`unsupported:${providerId}`, providerId);
|
|
@@ -3109,6 +3333,7 @@ function usageExtension(pi, dependencies = {}) {
|
|
|
3109
3333
|
}
|
|
3110
3334
|
statusGeneration += 1;
|
|
3111
3335
|
const generation = statusGeneration;
|
|
3336
|
+
clearStatusCountdownTimer();
|
|
3112
3337
|
statusController?.abort();
|
|
3113
3338
|
const controller = new AbortController();
|
|
3114
3339
|
statusController = controller;
|
|
@@ -3159,7 +3384,7 @@ function usageExtension(pi, dependencies = {}) {
|
|
|
3159
3384
|
if (generation !== statusGeneration || modelIdentity(ctx.model) !== modelIdentity(model)) {
|
|
3160
3385
|
return false;
|
|
3161
3386
|
}
|
|
3162
|
-
const adapter =
|
|
3387
|
+
const adapter = adapterForProvider(model?.provider);
|
|
3163
3388
|
if (outcome.authState === "unavailable") {
|
|
3164
3389
|
if (!adapter) return false;
|
|
3165
3390
|
try {
|
|
@@ -3218,7 +3443,7 @@ function usageExtension(pi, dependencies = {}) {
|
|
|
3218
3443
|
const menuGeneration = statusGeneration;
|
|
3219
3444
|
statusController?.abort();
|
|
3220
3445
|
statusController = void 0;
|
|
3221
|
-
|
|
3446
|
+
clearStatusTimers();
|
|
3222
3447
|
const controller = new AbortController();
|
|
3223
3448
|
activeControllers.add(controller);
|
|
3224
3449
|
try {
|
|
@@ -3283,7 +3508,7 @@ function usageExtension(pi, dependencies = {}) {
|
|
|
3283
3508
|
providers: () => ({
|
|
3284
3509
|
kind: "actions",
|
|
3285
3510
|
title: "Select a configured provider",
|
|
3286
|
-
items: configuredAdapters(ctx
|
|
3511
|
+
items: configuredAdapters(ctx).filter((adapter) => adapter.id !== ctx.model?.provider).map((adapter) => ({
|
|
3287
3512
|
id: adapter.id,
|
|
3288
3513
|
label: adapter.displayName,
|
|
3289
3514
|
action: "provider"
|
|
@@ -3353,14 +3578,9 @@ function usageExtension(pi, dependencies = {}) {
|
|
|
3353
3578
|
settingsRuntime,
|
|
3354
3579
|
controller.signal,
|
|
3355
3580
|
() => statusGeneration === menuGeneration && !controller.signal.aborted,
|
|
3356
|
-
(id
|
|
3357
|
-
if (id
|
|
3358
|
-
|
|
3359
|
-
invalidateProviderState("xai");
|
|
3360
|
-
if (!next) {
|
|
3361
|
-
for (const active of activeControllers) {
|
|
3362
|
-
if (active !== controller) active.abort();
|
|
3363
|
-
}
|
|
3581
|
+
(id) => {
|
|
3582
|
+
if (id === "codexStatusResetCountdown" && stableCurrent && statusGeneration === menuGeneration && !controller.signal.aborted) {
|
|
3583
|
+
publishStableCurrent(ctx, stableCurrent);
|
|
3364
3584
|
}
|
|
3365
3585
|
}
|
|
3366
3586
|
);
|
|
@@ -3546,7 +3766,7 @@ function usageExtension(pi, dependencies = {}) {
|
|
|
3546
3766
|
return { kind: "stay" };
|
|
3547
3767
|
},
|
|
3548
3768
|
another: async () => {
|
|
3549
|
-
const others = configuredAdapters(ctx
|
|
3769
|
+
const others = configuredAdapters(ctx).filter(
|
|
3550
3770
|
(adapter) => adapter.id !== ctx.model?.provider
|
|
3551
3771
|
);
|
|
3552
3772
|
if (others.length === 0) {
|
|
@@ -3556,7 +3776,7 @@ function usageExtension(pi, dependencies = {}) {
|
|
|
3556
3776
|
return { kind: "to", screen: "providers" };
|
|
3557
3777
|
},
|
|
3558
3778
|
provider: async ({ itemId }) => {
|
|
3559
|
-
const adapter = configuredAdapters(ctx
|
|
3779
|
+
const adapter = configuredAdapters(ctx).find(
|
|
3560
3780
|
(candidate) => candidate.id === itemId && candidate.id !== ctx.model?.provider
|
|
3561
3781
|
);
|
|
3562
3782
|
if (!adapter) return { kind: "back" };
|
|
@@ -3582,7 +3802,7 @@ function usageExtension(pi, dependencies = {}) {
|
|
|
3582
3802
|
return { kind: "back" };
|
|
3583
3803
|
},
|
|
3584
3804
|
all: async () => {
|
|
3585
|
-
const adapters = configuredAdapters(ctx
|
|
3805
|
+
const adapters = configuredAdapters(ctx);
|
|
3586
3806
|
const currentProviderId = ctx.model?.provider;
|
|
3587
3807
|
const settled = await runMenuOperation(
|
|
3588
3808
|
ctx,
|
|
@@ -3662,9 +3882,8 @@ function usageExtension(pi, dependencies = {}) {
|
|
|
3662
3882
|
});
|
|
3663
3883
|
pi.on("session_start", (_event, ctx) => {
|
|
3664
3884
|
sessionGeneration += 1;
|
|
3665
|
-
xaiSettingsGeneration += 1;
|
|
3666
3885
|
statusGeneration += 1;
|
|
3667
|
-
|
|
3886
|
+
clearStatusTimers();
|
|
3668
3887
|
for (const controller of activeControllers) controller.abort();
|
|
3669
3888
|
activeControllers.clear();
|
|
3670
3889
|
statusController = void 0;
|
|
@@ -3683,9 +3902,8 @@ function usageExtension(pi, dependencies = {}) {
|
|
|
3683
3902
|
pi.on("session_shutdown", (_event, ctx) => {
|
|
3684
3903
|
sessionActive = false;
|
|
3685
3904
|
sessionGeneration += 1;
|
|
3686
|
-
xaiSettingsGeneration += 1;
|
|
3687
3905
|
statusGeneration += 1;
|
|
3688
|
-
|
|
3906
|
+
clearStatusTimers();
|
|
3689
3907
|
for (const controller of activeControllers) controller.abort();
|
|
3690
3908
|
activeControllers.clear();
|
|
3691
3909
|
statusController = void 0;
|
|
@@ -3730,6 +3948,7 @@ export {
|
|
|
3730
3948
|
loadUsageSettings,
|
|
3731
3949
|
normalizeCodexBackendPayload,
|
|
3732
3950
|
normalizeCodexResetCreditsPayload,
|
|
3951
|
+
normalizeDeepSeekBalancePayload,
|
|
3733
3952
|
normalizeGitHubCopilotUsagePayload,
|
|
3734
3953
|
normalizeKimiCodingUsagePayload,
|
|
3735
3954
|
normalizeOpenCodeZenPayload,
|