@narumitw/pi-usage 0.61.2 → 0.62.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 +38 -6
- package/dist/chunks/chunk-WEXCFYZX.ts +3005 -0
- package/dist/chunks/chunk-WEXCFYZX.ts.map +7 -0
- package/dist/chunks/openai-companion-auth-Q456G46T.ts +85 -0
- package/dist/chunks/openai-companion-auth-Q456G46T.ts.map +7 -0
- package/dist/chunks/openai-companion-usage-Y3WTMUB2.ts +120 -0
- package/dist/chunks/openai-companion-usage-Y3WTMUB2.ts.map +7 -0
- package/dist/index.ts +544 -3309
- package/dist/index.ts.map +4 -4
- package/docs/providers.md +29 -7
- package/package.json +4 -4
- package/src/format.ts +29 -1
- package/src/openai-companion-auth.ts +103 -0
- package/src/providers/openai-chatgpt.ts +24 -9
- package/src/providers/openai-companion-usage.ts +146 -0
- package/src/query.ts +5 -1
- package/src/settings.ts +5 -0
- package/src/types.ts +4 -0
- package/src/usage-settings-ui.ts +51 -9
- package/src/usage.ts +86 -4
package/src/usage.ts
CHANGED
|
@@ -22,7 +22,12 @@ import { abortError, awaitWithDeadline, errorMessage, runWithConcurrency, UsageC
|
|
|
22
22
|
import { formatProviderStates, formatUsageStatusline } from "./format.js";
|
|
23
23
|
import { createOAuthCredentialCandidateReader } from "./oauth-credential-source.js";
|
|
24
24
|
import { UnsupportedOpenAIUsageAuthError } from "./providers/openai-chatgpt.js";
|
|
25
|
-
import {
|
|
25
|
+
import {
|
|
26
|
+
adapterForProvider,
|
|
27
|
+
isStaleExtensionContextError,
|
|
28
|
+
queryProviderUsage,
|
|
29
|
+
resolveUsageAuth as resolveRuntimeUsageAuth,
|
|
30
|
+
} from "./query.js";
|
|
26
31
|
import { createUsageSettingsRuntime, type UsageSettingsRuntime, type UsageSettingsState } from "./settings.js";
|
|
27
32
|
import type {
|
|
28
33
|
PiModel,
|
|
@@ -84,10 +89,20 @@ export default function usageExtension(pi: ExtensionAPI, dependencies: UsageExte
|
|
|
84
89
|
const credentialCandidates = createOAuthCredentialCandidateReader(pi, credentialReader);
|
|
85
90
|
const createRedemptionId = dependencies.createRedemptionId ?? randomUUID;
|
|
86
91
|
const settingsRuntime = dependencies.settingsRuntime ?? createUsageSettingsRuntime();
|
|
92
|
+
const resolveUsageAuth: typeof resolveRuntimeUsageAuth = (ctx, adapter, salt, reader, candidates) =>
|
|
93
|
+
resolveRuntimeUsageAuth(
|
|
94
|
+
ctx,
|
|
95
|
+
adapter,
|
|
96
|
+
salt,
|
|
97
|
+
reader,
|
|
98
|
+
candidates,
|
|
99
|
+
settingsRuntime.get().settings.openaiCompanionUsage,
|
|
100
|
+
);
|
|
87
101
|
const cache = new UsageCache(CACHE_TTL_MS);
|
|
88
102
|
const failureBackoff = new Map<string, { until: number; message: string }>();
|
|
89
103
|
const latestQueries = new Map<string, number>();
|
|
90
104
|
const activeControllers = new Set<AbortController>();
|
|
105
|
+
const nativeQueryControllers = new Set<AbortController>();
|
|
91
106
|
let querySequence = 0;
|
|
92
107
|
let activeCurrentIdentity: string | undefined;
|
|
93
108
|
let sessionActive = false;
|
|
@@ -183,7 +198,7 @@ export default function usageExtension(pi: ExtensionAPI, dependencies: UsageExte
|
|
|
183
198
|
if (shouldSchedule && sessionActive) scheduleStatusRefresh(ctx, model);
|
|
184
199
|
if (
|
|
185
200
|
sessionActive &&
|
|
186
|
-
showCodexResetCountdown &&
|
|
201
|
+
(showCodexResetCountdown || outcome.state.report.source === "openai-chatgpt-companion") &&
|
|
187
202
|
outcome.state.report.buckets.some(
|
|
188
203
|
(bucket) => bucket.resetsAt !== undefined && Number.isFinite(bucket.resetsAt) && bucket.resetsAt * 1_000 > now,
|
|
189
204
|
)
|
|
@@ -220,7 +235,7 @@ export default function usageExtension(pi: ExtensionAPI, dependencies: UsageExte
|
|
|
220
235
|
activeCurrentIdentity = nextIdentity;
|
|
221
236
|
};
|
|
222
237
|
|
|
223
|
-
const
|
|
238
|
+
const queryAdapterStateInternal = async (
|
|
224
239
|
ctx: ExtensionContext,
|
|
225
240
|
adapter: UsageProviderAdapter,
|
|
226
241
|
displayState: UsageDisplayState,
|
|
@@ -233,6 +248,7 @@ export default function usageExtension(pi: ExtensionAPI, dependencies: UsageExte
|
|
|
233
248
|
const expectedSessionId = ctx.sessionManager.getSessionId();
|
|
234
249
|
const expectedModelIdentity = modelIdentity(ctx.model);
|
|
235
250
|
const expectedTargetId = adapter.targets ? settingsRuntime.get().settings.selectedTargets[adapter.id] : undefined;
|
|
251
|
+
const expectedCompanionUsage = settingsRuntime.get().settings.openaiCompanionUsage;
|
|
236
252
|
const providerName = providerDisplayName(ctx, adapter.id);
|
|
237
253
|
let auth: ResolvedUsageAuth | undefined;
|
|
238
254
|
try {
|
|
@@ -244,6 +260,7 @@ export default function usageExtension(pi: ExtensionAPI, dependencies: UsageExte
|
|
|
244
260
|
);
|
|
245
261
|
} catch (error) {
|
|
246
262
|
if (isStaleExtensionContextError(error) || isAbortError(error)) throw error;
|
|
263
|
+
if (adapter.id === "openai") invalidateProviderState(adapter.id);
|
|
247
264
|
const authState =
|
|
248
265
|
error instanceof UnsupportedOpenAIUsageAuthError
|
|
249
266
|
? "unsupported"
|
|
@@ -284,10 +301,12 @@ export default function usageExtension(pi: ExtensionAPI, dependencies: UsageExte
|
|
|
284
301
|
expectedSessionGeneration !== sessionGeneration ||
|
|
285
302
|
ctx.sessionManager.getSessionId() !== expectedSessionId ||
|
|
286
303
|
modelIdentity(ctx.model) !== expectedModelIdentity ||
|
|
304
|
+
(adapter.id === "openai" && settingsRuntime.get().settings.openaiCompanionUsage !== expectedCompanionUsage) ||
|
|
287
305
|
(adapter.targets !== undefined &&
|
|
288
306
|
settingsRuntime.get().settings.selectedTargets[adapter.id] !== expectedTargetId);
|
|
289
307
|
if (requiresRequestBoundaryGuard && requestContextChanged()) throw abortError();
|
|
290
308
|
if (!auth) {
|
|
309
|
+
if (adapter.id === "openai") invalidateProviderState(adapter.id);
|
|
291
310
|
if (displayState === "current") {
|
|
292
311
|
transitionCurrentIdentity(`${adapter.id}:unavailable`, adapter.id);
|
|
293
312
|
}
|
|
@@ -302,6 +321,7 @@ export default function usageExtension(pi: ExtensionAPI, dependencies: UsageExte
|
|
|
302
321
|
authState: "unavailable",
|
|
303
322
|
};
|
|
304
323
|
}
|
|
324
|
+
if (adapter.id === "openai" && !auth.openaiCompanion) invalidateProviderState(adapter.id);
|
|
305
325
|
let retryableAuthChanged = false;
|
|
306
326
|
const guard = async () => {
|
|
307
327
|
if (signal.aborted || requestContextChanged()) throw abortError();
|
|
@@ -466,6 +486,25 @@ export default function usageExtension(pi: ExtensionAPI, dependencies: UsageExte
|
|
|
466
486
|
}
|
|
467
487
|
};
|
|
468
488
|
|
|
489
|
+
const queryAdapterState = async (...args: Parameters<typeof queryAdapterStateInternal>): Promise<QueryOutcome> => {
|
|
490
|
+
const [ctx, adapter, displayState, force, signal, ...rest] = args;
|
|
491
|
+
if (adapter.id !== "openai") return queryAdapterStateInternal(...args);
|
|
492
|
+
const controller = new AbortController();
|
|
493
|
+
nativeQueryControllers.add(controller);
|
|
494
|
+
try {
|
|
495
|
+
return await queryAdapterStateInternal(
|
|
496
|
+
ctx,
|
|
497
|
+
adapter,
|
|
498
|
+
displayState,
|
|
499
|
+
force,
|
|
500
|
+
AbortSignal.any([signal, controller.signal]),
|
|
501
|
+
...rest,
|
|
502
|
+
);
|
|
503
|
+
} finally {
|
|
504
|
+
nativeQueryControllers.delete(controller);
|
|
505
|
+
}
|
|
506
|
+
};
|
|
507
|
+
|
|
469
508
|
const loadTargetChoices = async (ctx: ExtensionContext, adapter: UsageProviderAdapter, signal: AbortSignal) => {
|
|
470
509
|
if (!adapter.targets) throw new Error("Provider does not support usage targets.");
|
|
471
510
|
const expectedSessionGeneration = sessionGeneration;
|
|
@@ -686,7 +725,7 @@ export default function usageExtension(pi: ExtensionAPI, dependencies: UsageExte
|
|
|
686
725
|
const generation = statusGeneration;
|
|
687
726
|
const outcome = await queryAdapterState(ctx, adapter, displayState, force, signal);
|
|
688
727
|
if (signal.aborted) throw abortError();
|
|
689
|
-
if (
|
|
728
|
+
if (await outcomeStillCurrent(ctx, model, generation, outcome, signal)) return outcome;
|
|
690
729
|
force = false;
|
|
691
730
|
}
|
|
692
731
|
throw new Error("OpenAI runtime authentication kept changing; reopen /usage to retry.");
|
|
@@ -1051,6 +1090,20 @@ export default function usageExtension(pi: ExtensionAPI, dependencies: UsageExte
|
|
|
1051
1090
|
controller.signal,
|
|
1052
1091
|
() => statusGeneration === menuGeneration && !controller.signal.aborted,
|
|
1053
1092
|
(id) => {
|
|
1093
|
+
if (
|
|
1094
|
+
id === "openaiCompanionUsage" &&
|
|
1095
|
+
statusGeneration === menuGeneration &&
|
|
1096
|
+
!controller.signal.aborted
|
|
1097
|
+
) {
|
|
1098
|
+
for (const active of nativeQueryControllers) active.abort();
|
|
1099
|
+
invalidateProviderState("openai");
|
|
1100
|
+
if (ctx.model?.provider === "openai") {
|
|
1101
|
+
statusController?.abort();
|
|
1102
|
+
statusController = undefined;
|
|
1103
|
+
clearStatusTimers();
|
|
1104
|
+
safeSetStatus(ctx, undefined);
|
|
1105
|
+
}
|
|
1106
|
+
}
|
|
1054
1107
|
if (
|
|
1055
1108
|
(id === "codexStatusResetCountdown" || id === "codexStatusPercentage") &&
|
|
1056
1109
|
stableCurrent &&
|
|
@@ -1253,6 +1306,17 @@ export default function usageExtension(pi: ExtensionAPI, dependencies: UsageExte
|
|
|
1253
1306
|
}
|
|
1254
1307
|
const revalidated = await queryStableCurrent(ctx, false, controller, "Revalidating current usage…");
|
|
1255
1308
|
if (!revalidated) return { kind: "back" };
|
|
1309
|
+
// Current-provider revalidation can await unrelated work after the configured
|
|
1310
|
+
// native report was checked. Refresh its identity at the publication boundary.
|
|
1311
|
+
if (adapter.id === "openai") {
|
|
1312
|
+
outcome = await runMenuOperation(
|
|
1313
|
+
ctx,
|
|
1314
|
+
"Revalidating ChatGPT companion usage…",
|
|
1315
|
+
controller.signal,
|
|
1316
|
+
(signal) => queryStableAdapterState(ctx, adapter, "configured", false, signal),
|
|
1317
|
+
);
|
|
1318
|
+
if (!outcome) return { kind: "back" };
|
|
1319
|
+
}
|
|
1256
1320
|
stableCurrent = revalidated;
|
|
1257
1321
|
current = revalidated.outcome;
|
|
1258
1322
|
visibleStates = [
|
|
@@ -1300,6 +1364,22 @@ export default function usageExtension(pi: ExtensionAPI, dependencies: UsageExte
|
|
|
1300
1364
|
});
|
|
1301
1365
|
const revalidated = await queryStableCurrent(ctx, false, controller, "Revalidating current usage…");
|
|
1302
1366
|
if (!revalidated) return { kind: "stay" };
|
|
1367
|
+
const nativeIndex = adapters.findIndex(
|
|
1368
|
+
(adapter) => adapter.id === "openai" && adapter.id !== currentProviderId,
|
|
1369
|
+
);
|
|
1370
|
+
if (nativeIndex >= 0) {
|
|
1371
|
+
const nativeAdapter = adapters[nativeIndex];
|
|
1372
|
+
if (nativeAdapter) {
|
|
1373
|
+
const native = await runMenuOperation(
|
|
1374
|
+
ctx,
|
|
1375
|
+
"Revalidating ChatGPT companion usage…",
|
|
1376
|
+
controller.signal,
|
|
1377
|
+
(signal) => queryStableAdapterState(ctx, nativeAdapter, "configured", false, signal),
|
|
1378
|
+
);
|
|
1379
|
+
if (!native) return { kind: "stay" };
|
|
1380
|
+
queriedStates[nativeIndex] = native.state;
|
|
1381
|
+
}
|
|
1382
|
+
}
|
|
1303
1383
|
stableCurrent = revalidated;
|
|
1304
1384
|
current = revalidated.outcome;
|
|
1305
1385
|
visibleStates = [
|
|
@@ -1356,6 +1436,8 @@ export default function usageExtension(pi: ExtensionAPI, dependencies: UsageExte
|
|
|
1356
1436
|
startStatusRefresh(ctx, ctx.model, false);
|
|
1357
1437
|
});
|
|
1358
1438
|
pi.on("model_select", (event, ctx) => {
|
|
1439
|
+
// Cancel read-only native queries, not confirmed legacy reset mutations.
|
|
1440
|
+
for (const controller of nativeQueryControllers) controller.abort();
|
|
1359
1441
|
startStatusRefresh(ctx, event.model, false);
|
|
1360
1442
|
});
|
|
1361
1443
|
pi.on("turn_start", (_event, ctx) => {
|