@narumitw/pi-usage 0.53.0 → 0.54.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 +94 -7
- package/dist/index.ts +843 -47
- package/dist/index.ts.map +4 -4
- package/package.json +10 -4
- package/src/format.ts +90 -0
- package/src/index.ts +7 -0
- package/src/providers/kimi-coding.ts +276 -0
- package/src/providers/xai.ts +186 -0
- package/src/query.ts +196 -3
- package/src/settings.ts +7 -0
- package/src/types.ts +23 -2
- package/src/usage-helpers.ts +3 -3
- package/src/usage-settings-ui.ts +128 -0
- package/src/usage.ts +119 -13
package/src/usage.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
// This orchestrator intentionally remains over 1,000 lines because its menu, query generations,
|
|
2
|
+
// account cache, cancellation, and session lifecycle share one consistency boundary.
|
|
1
3
|
import { randomUUID } from "node:crypto";
|
|
2
4
|
import type {
|
|
3
5
|
ExtensionAPI,
|
|
@@ -20,7 +22,13 @@ import {
|
|
|
20
22
|
resetOptionExpiration,
|
|
21
23
|
resolveCodexResetAuth,
|
|
22
24
|
} from "./codex-resets.js";
|
|
23
|
-
import {
|
|
25
|
+
import {
|
|
26
|
+
abortError,
|
|
27
|
+
awaitWithDeadline,
|
|
28
|
+
errorMessage,
|
|
29
|
+
runWithConcurrency,
|
|
30
|
+
UsageCache,
|
|
31
|
+
} from "./core.js";
|
|
24
32
|
import { formatProviderStates, formatUsageStatusline } from "./format.js";
|
|
25
33
|
import { createOAuthCredentialCandidateReader } from "./oauth-credential-source.js";
|
|
26
34
|
import {
|
|
@@ -45,6 +53,7 @@ import {
|
|
|
45
53
|
providerDisplayName,
|
|
46
54
|
setBoundedMap,
|
|
47
55
|
} from "./usage-helpers.js";
|
|
56
|
+
import { showUsageSettings } from "./usage-settings-ui.js";
|
|
48
57
|
|
|
49
58
|
const CACHE_TTL_MS = 5 * 60 * 1000;
|
|
50
59
|
const DEFAULT_TIMEOUT_MS = 15_000;
|
|
@@ -57,6 +66,7 @@ const REFRESH_CURRENT = "Refresh current usage";
|
|
|
57
66
|
const VIEW_ANOTHER = "View another configured provider…";
|
|
58
67
|
const VIEW_ALL = "View all configured providers…";
|
|
59
68
|
const CLOSE = "Close";
|
|
69
|
+
const SETTINGS = "Settings";
|
|
60
70
|
const REDEEM_CODEX_RESET = "Redeem usage limit reset…";
|
|
61
71
|
|
|
62
72
|
type UsageExtensionDependencies = {
|
|
@@ -92,10 +102,19 @@ export default function usageExtension(
|
|
|
92
102
|
let activeCurrentIdentity: string | undefined;
|
|
93
103
|
let sessionActive = false;
|
|
94
104
|
let statusGeneration = 0;
|
|
105
|
+
let sessionGeneration = 0;
|
|
106
|
+
let xaiSettingsGeneration = 0;
|
|
95
107
|
let statusRefreshTimer: ReturnType<typeof setTimeout> | undefined;
|
|
96
108
|
let statusController: AbortController | undefined;
|
|
97
109
|
let fastRuntime: ReturnType<typeof registerCodexFastMode>;
|
|
98
110
|
|
|
111
|
+
const xaiUsageEnabled = () => {
|
|
112
|
+
const state = settingsRuntime.get();
|
|
113
|
+
return state.kind !== "invalid" && state.settings.xaiUsage;
|
|
114
|
+
};
|
|
115
|
+
const activeAdapterForProvider = (providerId: string | undefined) =>
|
|
116
|
+
adapterForProvider(providerId, xaiUsageEnabled());
|
|
117
|
+
|
|
99
118
|
const clearStatusTimer = () => {
|
|
100
119
|
if (statusRefreshTimer) clearTimeout(statusRefreshTimer);
|
|
101
120
|
statusRefreshTimer = undefined;
|
|
@@ -136,7 +155,7 @@ export default function usageExtension(
|
|
|
136
155
|
model: PiModel,
|
|
137
156
|
shouldSchedule: boolean,
|
|
138
157
|
) => {
|
|
139
|
-
if (
|
|
158
|
+
if (activeAdapterForProvider(model.provider)?.publishesStatusline === false) {
|
|
140
159
|
clearStatusTimer();
|
|
141
160
|
safeSetStatus(ctx, undefined);
|
|
142
161
|
return;
|
|
@@ -193,6 +212,10 @@ export default function usageExtension(
|
|
|
193
212
|
signal: AbortSignal,
|
|
194
213
|
): Promise<QueryOutcome> => {
|
|
195
214
|
const startedAt = Date.now();
|
|
215
|
+
const expectedSessionGeneration = sessionGeneration;
|
|
216
|
+
const expectedXaiSettingsGeneration = xaiSettingsGeneration;
|
|
217
|
+
const expectedSessionId = ctx.sessionManager.getSessionId();
|
|
218
|
+
const expectedModelIdentity = modelIdentity(ctx.model);
|
|
196
219
|
let auth: ResolvedUsageAuth | undefined;
|
|
197
220
|
try {
|
|
198
221
|
auth = await awaitWithDeadline(
|
|
@@ -216,6 +239,16 @@ export default function usageExtension(
|
|
|
216
239
|
},
|
|
217
240
|
};
|
|
218
241
|
}
|
|
242
|
+
if (
|
|
243
|
+
adapter.id === "xai" &&
|
|
244
|
+
(expectedSessionGeneration !== sessionGeneration ||
|
|
245
|
+
expectedXaiSettingsGeneration !== xaiSettingsGeneration ||
|
|
246
|
+
!xaiUsageEnabled() ||
|
|
247
|
+
ctx.sessionManager.getSessionId() !== expectedSessionId ||
|
|
248
|
+
modelIdentity(ctx.model) !== expectedModelIdentity)
|
|
249
|
+
) {
|
|
250
|
+
throw abortError();
|
|
251
|
+
}
|
|
219
252
|
if (!auth) {
|
|
220
253
|
if (displayState === "current") {
|
|
221
254
|
transitionCurrentIdentity(`${adapter.id}:unavailable`, adapter.id);
|
|
@@ -270,7 +303,40 @@ export default function usageExtension(
|
|
|
270
303
|
|
|
271
304
|
try {
|
|
272
305
|
const remainingMs = Math.max(1, DEFAULT_TIMEOUT_MS - (Date.now() - startedAt));
|
|
273
|
-
const
|
|
306
|
+
const guard =
|
|
307
|
+
adapter.id === "xai"
|
|
308
|
+
? async () => {
|
|
309
|
+
if (
|
|
310
|
+
signal.aborted ||
|
|
311
|
+
expectedSessionGeneration !== sessionGeneration ||
|
|
312
|
+
expectedXaiSettingsGeneration !== xaiSettingsGeneration ||
|
|
313
|
+
!xaiUsageEnabled() ||
|
|
314
|
+
ctx.sessionManager.getSessionId() !== expectedSessionId ||
|
|
315
|
+
modelIdentity(ctx.model) !== expectedModelIdentity
|
|
316
|
+
) {
|
|
317
|
+
throw abortError();
|
|
318
|
+
}
|
|
319
|
+
const revalidated = await awaitWithDeadline(
|
|
320
|
+
resolveUsageAuth(ctx, adapter, undefined, credentialReader, credentialCandidates),
|
|
321
|
+
signal,
|
|
322
|
+
Math.max(1, DEFAULT_TIMEOUT_MS - (Date.now() - startedAt)),
|
|
323
|
+
"revalidating xAI runtime auth",
|
|
324
|
+
);
|
|
325
|
+
if (
|
|
326
|
+
signal.aborted ||
|
|
327
|
+
expectedSessionGeneration !== sessionGeneration ||
|
|
328
|
+
expectedXaiSettingsGeneration !== xaiSettingsGeneration ||
|
|
329
|
+
!xaiUsageEnabled() ||
|
|
330
|
+
ctx.sessionManager.getSessionId() !== expectedSessionId ||
|
|
331
|
+
modelIdentity(ctx.model) !== expectedModelIdentity ||
|
|
332
|
+
revalidated?.fingerprint !== auth.fingerprint
|
|
333
|
+
) {
|
|
334
|
+
throw abortError();
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
: undefined;
|
|
338
|
+
const report = await queryProviderUsage(adapter, auth, signal, remainingMs, guard);
|
|
339
|
+
if (guard) await guard();
|
|
274
340
|
if (latestQueries.get(failureKey) === queryId) {
|
|
275
341
|
cache.set(adapter.id, auth.fingerprint, report);
|
|
276
342
|
failureBackoff.delete(failureKey);
|
|
@@ -319,7 +385,7 @@ export default function usageExtension(
|
|
|
319
385
|
force: boolean,
|
|
320
386
|
signal: AbortSignal,
|
|
321
387
|
): Promise<QueryOutcome> => {
|
|
322
|
-
const adapter =
|
|
388
|
+
const adapter = activeAdapterForProvider(model?.provider);
|
|
323
389
|
if (!adapter) {
|
|
324
390
|
const providerId = model?.provider ?? "none";
|
|
325
391
|
transitionCurrentIdentity(`unsupported:${providerId}`, providerId);
|
|
@@ -329,9 +395,12 @@ export default function usageExtension(
|
|
|
329
395
|
providerName: providerDisplayName(ctx, providerId),
|
|
330
396
|
displayState: "current",
|
|
331
397
|
status: "unsupported",
|
|
332
|
-
message:
|
|
333
|
-
|
|
334
|
-
|
|
398
|
+
message:
|
|
399
|
+
providerId === "xai" && !xaiUsageEnabled()
|
|
400
|
+
? "xAI usage is disabled. Open Settings to enable it."
|
|
401
|
+
: model
|
|
402
|
+
? `Usage reporting is not supported for ${providerDisplayName(ctx, providerId)}.`
|
|
403
|
+
: "No model is selected.",
|
|
335
404
|
},
|
|
336
405
|
};
|
|
337
406
|
}
|
|
@@ -343,7 +412,7 @@ export default function usageExtension(
|
|
|
343
412
|
model: PiModel | undefined,
|
|
344
413
|
force: boolean,
|
|
345
414
|
) => {
|
|
346
|
-
const adapter =
|
|
415
|
+
const adapter = activeAdapterForProvider(model?.provider);
|
|
347
416
|
if (!adapter || !model) {
|
|
348
417
|
const providerId = model?.provider ?? "none";
|
|
349
418
|
transitionCurrentIdentity(`unsupported:${providerId}`, providerId);
|
|
@@ -425,7 +494,7 @@ export default function usageExtension(
|
|
|
425
494
|
if (generation !== statusGeneration || modelIdentity(ctx.model) !== modelIdentity(model)) {
|
|
426
495
|
return false;
|
|
427
496
|
}
|
|
428
|
-
const adapter =
|
|
497
|
+
const adapter = activeAdapterForProvider(model?.provider);
|
|
429
498
|
if (outcome.authState === "unavailable") {
|
|
430
499
|
if (!adapter) return false;
|
|
431
500
|
try {
|
|
@@ -533,6 +602,7 @@ export default function usageExtension(
|
|
|
533
602
|
| "reset-error";
|
|
534
603
|
type Action =
|
|
535
604
|
| "refresh"
|
|
605
|
+
| "settings"
|
|
536
606
|
| "toggle-fast"
|
|
537
607
|
| "another"
|
|
538
608
|
| "all"
|
|
@@ -560,6 +630,7 @@ export default function usageExtension(
|
|
|
560
630
|
lines: [...formatProviderStates(visibleStates).split("\n"), ...fastLines],
|
|
561
631
|
items: [
|
|
562
632
|
{ id: "refresh", label: REFRESH_CURRENT, action: "refresh" },
|
|
633
|
+
{ id: "settings", label: SETTINGS, action: "settings" },
|
|
563
634
|
...(fastAvailability.kind === "available"
|
|
564
635
|
? [
|
|
565
636
|
{
|
|
@@ -597,7 +668,7 @@ export default function usageExtension(
|
|
|
597
668
|
providers: () => ({
|
|
598
669
|
kind: "actions",
|
|
599
670
|
title: "Select a configured provider",
|
|
600
|
-
items: configuredAdapters(ctx)
|
|
671
|
+
items: configuredAdapters(ctx, xaiUsageEnabled())
|
|
601
672
|
.filter((adapter) => adapter.id !== ctx.model?.provider)
|
|
602
673
|
.map((adapter) => ({
|
|
603
674
|
id: adapter.id,
|
|
@@ -665,6 +736,37 @@ export default function usageExtension(
|
|
|
665
736
|
}),
|
|
666
737
|
},
|
|
667
738
|
actions: {
|
|
739
|
+
settings: async () => {
|
|
740
|
+
await showUsageSettings(
|
|
741
|
+
ctx,
|
|
742
|
+
settingsRuntime,
|
|
743
|
+
controller.signal,
|
|
744
|
+
() => statusGeneration === menuGeneration && !controller.signal.aborted,
|
|
745
|
+
(id, _previous, next) => {
|
|
746
|
+
if (id !== "xaiUsage") return;
|
|
747
|
+
xaiSettingsGeneration += 1;
|
|
748
|
+
invalidateProviderState("xai");
|
|
749
|
+
if (!next) {
|
|
750
|
+
for (const active of activeControllers) {
|
|
751
|
+
if (active !== controller) active.abort();
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
},
|
|
755
|
+
);
|
|
756
|
+
fastState = settingsRuntime.get();
|
|
757
|
+
const revalidated = await queryStableCurrent(
|
|
758
|
+
ctx,
|
|
759
|
+
false,
|
|
760
|
+
controller,
|
|
761
|
+
"Applying usage settings…",
|
|
762
|
+
);
|
|
763
|
+
if (!revalidated) return { kind: "stay" };
|
|
764
|
+
stableCurrent = revalidated;
|
|
765
|
+
current = revalidated.outcome;
|
|
766
|
+
visibleStates = [current.state];
|
|
767
|
+
publishStableCurrent(ctx, revalidated);
|
|
768
|
+
return { kind: "stay" };
|
|
769
|
+
},
|
|
668
770
|
"toggle-fast": async () => {
|
|
669
771
|
const availability = fastRuntime.availability(ctx.model);
|
|
670
772
|
if (availability.kind !== "available" || fastState.kind === "invalid") {
|
|
@@ -844,7 +946,7 @@ export default function usageExtension(
|
|
|
844
946
|
return { kind: "stay" };
|
|
845
947
|
},
|
|
846
948
|
another: async () => {
|
|
847
|
-
const others = configuredAdapters(ctx).filter(
|
|
949
|
+
const others = configuredAdapters(ctx, xaiUsageEnabled()).filter(
|
|
848
950
|
(adapter) => adapter.id !== ctx.model?.provider,
|
|
849
951
|
);
|
|
850
952
|
if (others.length === 0) {
|
|
@@ -854,7 +956,7 @@ export default function usageExtension(
|
|
|
854
956
|
return { kind: "to", screen: "providers" };
|
|
855
957
|
},
|
|
856
958
|
provider: async ({ itemId }) => {
|
|
857
|
-
const adapter = configuredAdapters(ctx).find(
|
|
959
|
+
const adapter = configuredAdapters(ctx, xaiUsageEnabled()).find(
|
|
858
960
|
(candidate) => candidate.id === itemId && candidate.id !== ctx.model?.provider,
|
|
859
961
|
);
|
|
860
962
|
if (!adapter) return { kind: "back" };
|
|
@@ -882,7 +984,7 @@ export default function usageExtension(
|
|
|
882
984
|
return { kind: "back" };
|
|
883
985
|
},
|
|
884
986
|
all: async () => {
|
|
885
|
-
const adapters = configuredAdapters(ctx);
|
|
987
|
+
const adapters = configuredAdapters(ctx, xaiUsageEnabled());
|
|
886
988
|
const currentProviderId = ctx.model?.provider;
|
|
887
989
|
const settled = await runMenuOperation(
|
|
888
990
|
ctx,
|
|
@@ -964,6 +1066,8 @@ export default function usageExtension(
|
|
|
964
1066
|
},
|
|
965
1067
|
});
|
|
966
1068
|
pi.on("session_start", (_event, ctx) => {
|
|
1069
|
+
sessionGeneration += 1;
|
|
1070
|
+
xaiSettingsGeneration += 1;
|
|
967
1071
|
statusGeneration += 1;
|
|
968
1072
|
clearStatusTimer();
|
|
969
1073
|
for (const controller of activeControllers) controller.abort();
|
|
@@ -983,6 +1087,8 @@ export default function usageExtension(
|
|
|
983
1087
|
});
|
|
984
1088
|
pi.on("session_shutdown", (_event, ctx) => {
|
|
985
1089
|
sessionActive = false;
|
|
1090
|
+
sessionGeneration += 1;
|
|
1091
|
+
xaiSettingsGeneration += 1;
|
|
986
1092
|
statusGeneration += 1;
|
|
987
1093
|
clearStatusTimer();
|
|
988
1094
|
for (const controller of activeControllers) controller.abort();
|