@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/src/usage.ts
CHANGED
|
@@ -56,6 +56,7 @@ import {
|
|
|
56
56
|
import { showUsageSettings } from "./usage-settings-ui.js";
|
|
57
57
|
|
|
58
58
|
const CACHE_TTL_MS = 5 * 60 * 1000;
|
|
59
|
+
const STATUS_COUNTDOWN_REFRESH_MS = 60 * 1000;
|
|
59
60
|
const DEFAULT_TIMEOUT_MS = 15_000;
|
|
60
61
|
const ALL_PROVIDER_CONCURRENCY = 2;
|
|
61
62
|
const FAILURE_BACKOFF_MS = 30_000;
|
|
@@ -103,23 +104,26 @@ export default function usageExtension(
|
|
|
103
104
|
let sessionActive = false;
|
|
104
105
|
let statusGeneration = 0;
|
|
105
106
|
let sessionGeneration = 0;
|
|
106
|
-
let xaiSettingsGeneration = 0;
|
|
107
107
|
let statusRefreshTimer: ReturnType<typeof setTimeout> | undefined;
|
|
108
|
+
let statusCountdownTimer: ReturnType<typeof setTimeout> | undefined;
|
|
108
109
|
let statusController: AbortController | undefined;
|
|
109
110
|
let fastRuntime: ReturnType<typeof registerCodexFastMode>;
|
|
110
111
|
|
|
111
|
-
const
|
|
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
|
-
|
|
118
|
-
const clearStatusTimer = () => {
|
|
112
|
+
const clearStatusRefreshTimer = () => {
|
|
119
113
|
if (statusRefreshTimer) clearTimeout(statusRefreshTimer);
|
|
120
114
|
statusRefreshTimer = undefined;
|
|
121
115
|
};
|
|
122
116
|
|
|
117
|
+
const clearStatusCountdownTimer = () => {
|
|
118
|
+
if (statusCountdownTimer) clearTimeout(statusCountdownTimer);
|
|
119
|
+
statusCountdownTimer = undefined;
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
const clearStatusTimers = () => {
|
|
123
|
+
clearStatusRefreshTimer();
|
|
124
|
+
clearStatusCountdownTimer();
|
|
125
|
+
};
|
|
126
|
+
|
|
123
127
|
const safeSetStatus = (ctx: ExtensionContext, value: string | undefined): boolean => {
|
|
124
128
|
try {
|
|
125
129
|
ctx.ui.setStatus(STATUS_KEY, value);
|
|
@@ -134,12 +138,12 @@ export default function usageExtension(
|
|
|
134
138
|
statusGeneration += 1;
|
|
135
139
|
statusController?.abort();
|
|
136
140
|
statusController = undefined;
|
|
137
|
-
|
|
141
|
+
clearStatusTimers();
|
|
138
142
|
safeSetStatus(ctx, undefined);
|
|
139
143
|
};
|
|
140
144
|
|
|
141
145
|
const scheduleStatusRefresh = (ctx: ExtensionContext, model: PiModel) => {
|
|
142
|
-
|
|
146
|
+
clearStatusRefreshTimer();
|
|
143
147
|
const generation = statusGeneration;
|
|
144
148
|
statusRefreshTimer = setTimeout(() => {
|
|
145
149
|
statusRefreshTimer = undefined;
|
|
@@ -155,13 +159,14 @@ export default function usageExtension(
|
|
|
155
159
|
model: PiModel,
|
|
156
160
|
shouldSchedule: boolean,
|
|
157
161
|
) => {
|
|
158
|
-
|
|
159
|
-
|
|
162
|
+
clearStatusCountdownTimer();
|
|
163
|
+
if (adapterForProvider(model.provider)?.publishesStatusline === false) {
|
|
164
|
+
clearStatusRefreshTimer();
|
|
160
165
|
safeSetStatus(ctx, undefined);
|
|
161
166
|
return;
|
|
162
167
|
}
|
|
163
168
|
if (outcome.state.status === "unsupported") {
|
|
164
|
-
|
|
169
|
+
clearStatusRefreshTimer();
|
|
165
170
|
safeSetStatus(ctx, undefined);
|
|
166
171
|
return;
|
|
167
172
|
}
|
|
@@ -176,10 +181,43 @@ export default function usageExtension(
|
|
|
176
181
|
}
|
|
177
182
|
return;
|
|
178
183
|
}
|
|
179
|
-
const
|
|
184
|
+
const showCodexResetCountdown =
|
|
185
|
+
outcome.state.report.providerId === "openai-codex" &&
|
|
186
|
+
settingsRuntime.get().settings.codexStatusResetCountdown;
|
|
187
|
+
const now = Date.now();
|
|
188
|
+
const rawValue = formatUsageStatusline(
|
|
189
|
+
outcome.state.report,
|
|
190
|
+
model,
|
|
191
|
+
now,
|
|
192
|
+
showCodexResetCountdown,
|
|
193
|
+
);
|
|
180
194
|
const value = rawValue ? fastRuntime.decorateStatus(model, rawValue) : undefined;
|
|
181
195
|
if (!safeSetStatus(ctx, value)) return;
|
|
182
196
|
if (shouldSchedule && sessionActive) scheduleStatusRefresh(ctx, model);
|
|
197
|
+
if (
|
|
198
|
+
sessionActive &&
|
|
199
|
+
showCodexResetCountdown &&
|
|
200
|
+
outcome.state.report.buckets.some(
|
|
201
|
+
(bucket) =>
|
|
202
|
+
bucket.resetsAt !== undefined &&
|
|
203
|
+
Number.isFinite(bucket.resetsAt) &&
|
|
204
|
+
bucket.resetsAt * 1_000 > now,
|
|
205
|
+
)
|
|
206
|
+
) {
|
|
207
|
+
const generation = statusGeneration;
|
|
208
|
+
statusCountdownTimer = setTimeout(() => {
|
|
209
|
+
statusCountdownTimer = undefined;
|
|
210
|
+
if (
|
|
211
|
+
!sessionActive ||
|
|
212
|
+
generation !== statusGeneration ||
|
|
213
|
+
modelIdentity(ctx.model) !== modelIdentity(model)
|
|
214
|
+
) {
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
publishStatus(ctx, outcome, model, false);
|
|
218
|
+
}, STATUS_COUNTDOWN_REFRESH_MS);
|
|
219
|
+
statusCountdownTimer.unref?.();
|
|
220
|
+
}
|
|
183
221
|
};
|
|
184
222
|
|
|
185
223
|
const invalidateProviderState = (providerId: string) => {
|
|
@@ -210,10 +248,10 @@ export default function usageExtension(
|
|
|
210
248
|
displayState: UsageDisplayState,
|
|
211
249
|
force: boolean,
|
|
212
250
|
signal: AbortSignal,
|
|
251
|
+
authRetry = 0,
|
|
252
|
+
deadlineAt = Date.now() + DEFAULT_TIMEOUT_MS,
|
|
213
253
|
): Promise<QueryOutcome> => {
|
|
214
|
-
const startedAt = Date.now();
|
|
215
254
|
const expectedSessionGeneration = sessionGeneration;
|
|
216
|
-
const expectedXaiSettingsGeneration = xaiSettingsGeneration;
|
|
217
255
|
const expectedSessionId = ctx.sessionManager.getSessionId();
|
|
218
256
|
const expectedModelIdentity = modelIdentity(ctx.model);
|
|
219
257
|
let auth: ResolvedUsageAuth | undefined;
|
|
@@ -221,7 +259,7 @@ export default function usageExtension(
|
|
|
221
259
|
auth = await awaitWithDeadline(
|
|
222
260
|
resolveUsageAuth(ctx, adapter, undefined, credentialReader, credentialCandidates),
|
|
223
261
|
signal,
|
|
224
|
-
|
|
262
|
+
Math.max(1, deadlineAt - Date.now()),
|
|
225
263
|
`resolving ${adapter.displayName} runtime auth`,
|
|
226
264
|
);
|
|
227
265
|
} catch (error) {
|
|
@@ -239,16 +277,12 @@ export default function usageExtension(
|
|
|
239
277
|
},
|
|
240
278
|
};
|
|
241
279
|
}
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
modelIdentity(ctx.model) !== expectedModelIdentity)
|
|
249
|
-
) {
|
|
250
|
-
throw abortError();
|
|
251
|
-
}
|
|
280
|
+
const requiresRequestBoundaryGuard = adapter.id === "deepseek" || adapter.id === "xai";
|
|
281
|
+
const requestContextChanged = () =>
|
|
282
|
+
expectedSessionGeneration !== sessionGeneration ||
|
|
283
|
+
ctx.sessionManager.getSessionId() !== expectedSessionId ||
|
|
284
|
+
modelIdentity(ctx.model) !== expectedModelIdentity;
|
|
285
|
+
if (requiresRequestBoundaryGuard && requestContextChanged()) throw abortError();
|
|
252
286
|
if (!auth) {
|
|
253
287
|
if (displayState === "current") {
|
|
254
288
|
transitionCurrentIdentity(`${adapter.id}:unavailable`, adapter.id);
|
|
@@ -301,40 +335,28 @@ export default function usageExtension(
|
|
|
301
335
|
const queryId = querySequence;
|
|
302
336
|
setBoundedMap(latestQueries, failureKey, queryId, MAX_ACCOUNT_STATES);
|
|
303
337
|
|
|
338
|
+
let deepSeekAuthChanged = false;
|
|
304
339
|
try {
|
|
305
|
-
const remainingMs = Math.max(1,
|
|
306
|
-
const guard =
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
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();
|
|
340
|
+
const remainingMs = Math.max(1, deadlineAt - Date.now());
|
|
341
|
+
const guard = requiresRequestBoundaryGuard
|
|
342
|
+
? async () => {
|
|
343
|
+
if (signal.aborted || requestContextChanged()) throw abortError();
|
|
344
|
+
const revalidated = await awaitWithDeadline(
|
|
345
|
+
resolveUsageAuth(ctx, adapter, undefined, credentialReader, credentialCandidates),
|
|
346
|
+
signal,
|
|
347
|
+
Math.max(1, deadlineAt - Date.now()),
|
|
348
|
+
`revalidating ${adapter.displayName} runtime auth`,
|
|
349
|
+
);
|
|
350
|
+
if (signal.aborted || requestContextChanged()) throw abortError();
|
|
351
|
+
if (revalidated?.fingerprint !== auth.fingerprint) {
|
|
352
|
+
if (adapter.id === "deepseek") {
|
|
353
|
+
deepSeekAuthChanged = true;
|
|
354
|
+
throw new Error("DeepSeek runtime credential changed during the balance query.");
|
|
335
355
|
}
|
|
356
|
+
throw abortError();
|
|
336
357
|
}
|
|
337
|
-
|
|
358
|
+
}
|
|
359
|
+
: undefined;
|
|
338
360
|
const report = await queryProviderUsage(adapter, auth, signal, remainingMs, guard);
|
|
339
361
|
if (guard) await guard();
|
|
340
362
|
if (latestQueries.get(failureKey) === queryId) {
|
|
@@ -353,6 +375,24 @@ export default function usageExtension(
|
|
|
353
375
|
};
|
|
354
376
|
} catch (error) {
|
|
355
377
|
if (isStaleExtensionContextError(error) || isAbortError(error)) throw error;
|
|
378
|
+
if (
|
|
379
|
+
deepSeekAuthChanged &&
|
|
380
|
+
authRetry === 0 &&
|
|
381
|
+
!signal.aborted &&
|
|
382
|
+
!requestContextChanged() &&
|
|
383
|
+
Date.now() < deadlineAt
|
|
384
|
+
) {
|
|
385
|
+
if (latestQueries.get(failureKey) === queryId) latestQueries.delete(failureKey);
|
|
386
|
+
return queryAdapterState(
|
|
387
|
+
ctx,
|
|
388
|
+
adapter,
|
|
389
|
+
displayState,
|
|
390
|
+
true,
|
|
391
|
+
signal,
|
|
392
|
+
authRetry + 1,
|
|
393
|
+
deadlineAt,
|
|
394
|
+
);
|
|
395
|
+
}
|
|
356
396
|
const message = errorMessage(error);
|
|
357
397
|
const now = Date.now();
|
|
358
398
|
for (const [key, failure] of failureBackoff) {
|
|
@@ -385,7 +425,7 @@ export default function usageExtension(
|
|
|
385
425
|
force: boolean,
|
|
386
426
|
signal: AbortSignal,
|
|
387
427
|
): Promise<QueryOutcome> => {
|
|
388
|
-
const adapter =
|
|
428
|
+
const adapter = adapterForProvider(model?.provider);
|
|
389
429
|
if (!adapter) {
|
|
390
430
|
const providerId = model?.provider ?? "none";
|
|
391
431
|
transitionCurrentIdentity(`unsupported:${providerId}`, providerId);
|
|
@@ -395,12 +435,9 @@ export default function usageExtension(
|
|
|
395
435
|
providerName: providerDisplayName(ctx, providerId),
|
|
396
436
|
displayState: "current",
|
|
397
437
|
status: "unsupported",
|
|
398
|
-
message:
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
: model
|
|
402
|
-
? `Usage reporting is not supported for ${providerDisplayName(ctx, providerId)}.`
|
|
403
|
-
: "No model is selected.",
|
|
438
|
+
message: model
|
|
439
|
+
? `Usage reporting is not supported for ${providerDisplayName(ctx, providerId)}.`
|
|
440
|
+
: "No model is selected.",
|
|
404
441
|
},
|
|
405
442
|
};
|
|
406
443
|
}
|
|
@@ -412,7 +449,7 @@ export default function usageExtension(
|
|
|
412
449
|
model: PiModel | undefined,
|
|
413
450
|
force: boolean,
|
|
414
451
|
) => {
|
|
415
|
-
const adapter =
|
|
452
|
+
const adapter = adapterForProvider(model?.provider);
|
|
416
453
|
if (!adapter || !model) {
|
|
417
454
|
const providerId = model?.provider ?? "none";
|
|
418
455
|
transitionCurrentIdentity(`unsupported:${providerId}`, providerId);
|
|
@@ -425,6 +462,7 @@ export default function usageExtension(
|
|
|
425
462
|
}
|
|
426
463
|
statusGeneration += 1;
|
|
427
464
|
const generation = statusGeneration;
|
|
465
|
+
clearStatusCountdownTimer();
|
|
428
466
|
statusController?.abort();
|
|
429
467
|
const controller = new AbortController();
|
|
430
468
|
statusController = controller;
|
|
@@ -494,7 +532,7 @@ export default function usageExtension(
|
|
|
494
532
|
if (generation !== statusGeneration || modelIdentity(ctx.model) !== modelIdentity(model)) {
|
|
495
533
|
return false;
|
|
496
534
|
}
|
|
497
|
-
const adapter =
|
|
535
|
+
const adapter = adapterForProvider(model?.provider);
|
|
498
536
|
if (outcome.authState === "unavailable") {
|
|
499
537
|
if (!adapter) return false;
|
|
500
538
|
try {
|
|
@@ -569,7 +607,7 @@ export default function usageExtension(
|
|
|
569
607
|
const menuGeneration = statusGeneration;
|
|
570
608
|
statusController?.abort();
|
|
571
609
|
statusController = undefined;
|
|
572
|
-
|
|
610
|
+
clearStatusTimers();
|
|
573
611
|
const controller = new AbortController();
|
|
574
612
|
activeControllers.add(controller);
|
|
575
613
|
try {
|
|
@@ -668,7 +706,7 @@ export default function usageExtension(
|
|
|
668
706
|
providers: () => ({
|
|
669
707
|
kind: "actions",
|
|
670
708
|
title: "Select a configured provider",
|
|
671
|
-
items: configuredAdapters(ctx
|
|
709
|
+
items: configuredAdapters(ctx)
|
|
672
710
|
.filter((adapter) => adapter.id !== ctx.model?.provider)
|
|
673
711
|
.map((adapter) => ({
|
|
674
712
|
id: adapter.id,
|
|
@@ -742,14 +780,14 @@ export default function usageExtension(
|
|
|
742
780
|
settingsRuntime,
|
|
743
781
|
controller.signal,
|
|
744
782
|
() => statusGeneration === menuGeneration && !controller.signal.aborted,
|
|
745
|
-
(id
|
|
746
|
-
if (
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
783
|
+
(id) => {
|
|
784
|
+
if (
|
|
785
|
+
id === "codexStatusResetCountdown" &&
|
|
786
|
+
stableCurrent &&
|
|
787
|
+
statusGeneration === menuGeneration &&
|
|
788
|
+
!controller.signal.aborted
|
|
789
|
+
) {
|
|
790
|
+
publishStableCurrent(ctx, stableCurrent);
|
|
753
791
|
}
|
|
754
792
|
},
|
|
755
793
|
);
|
|
@@ -946,7 +984,7 @@ export default function usageExtension(
|
|
|
946
984
|
return { kind: "stay" };
|
|
947
985
|
},
|
|
948
986
|
another: async () => {
|
|
949
|
-
const others = configuredAdapters(ctx
|
|
987
|
+
const others = configuredAdapters(ctx).filter(
|
|
950
988
|
(adapter) => adapter.id !== ctx.model?.provider,
|
|
951
989
|
);
|
|
952
990
|
if (others.length === 0) {
|
|
@@ -956,7 +994,7 @@ export default function usageExtension(
|
|
|
956
994
|
return { kind: "to", screen: "providers" };
|
|
957
995
|
},
|
|
958
996
|
provider: async ({ itemId }) => {
|
|
959
|
-
const adapter = configuredAdapters(ctx
|
|
997
|
+
const adapter = configuredAdapters(ctx).find(
|
|
960
998
|
(candidate) => candidate.id === itemId && candidate.id !== ctx.model?.provider,
|
|
961
999
|
);
|
|
962
1000
|
if (!adapter) return { kind: "back" };
|
|
@@ -984,7 +1022,7 @@ export default function usageExtension(
|
|
|
984
1022
|
return { kind: "back" };
|
|
985
1023
|
},
|
|
986
1024
|
all: async () => {
|
|
987
|
-
const adapters = configuredAdapters(ctx
|
|
1025
|
+
const adapters = configuredAdapters(ctx);
|
|
988
1026
|
const currentProviderId = ctx.model?.provider;
|
|
989
1027
|
const settled = await runMenuOperation(
|
|
990
1028
|
ctx,
|
|
@@ -1067,9 +1105,8 @@ export default function usageExtension(
|
|
|
1067
1105
|
});
|
|
1068
1106
|
pi.on("session_start", (_event, ctx) => {
|
|
1069
1107
|
sessionGeneration += 1;
|
|
1070
|
-
xaiSettingsGeneration += 1;
|
|
1071
1108
|
statusGeneration += 1;
|
|
1072
|
-
|
|
1109
|
+
clearStatusTimers();
|
|
1073
1110
|
for (const controller of activeControllers) controller.abort();
|
|
1074
1111
|
activeControllers.clear();
|
|
1075
1112
|
statusController = undefined;
|
|
@@ -1088,9 +1125,8 @@ export default function usageExtension(
|
|
|
1088
1125
|
pi.on("session_shutdown", (_event, ctx) => {
|
|
1089
1126
|
sessionActive = false;
|
|
1090
1127
|
sessionGeneration += 1;
|
|
1091
|
-
xaiSettingsGeneration += 1;
|
|
1092
1128
|
statusGeneration += 1;
|
|
1093
|
-
|
|
1129
|
+
clearStatusTimers();
|
|
1094
1130
|
for (const controller of activeControllers) controller.abort();
|
|
1095
1131
|
activeControllers.clear();
|
|
1096
1132
|
statusController = undefined;
|