@narumitw/pi-usage 0.61.1 → 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 +46 -3
- 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 +563 -3226
- package/dist/index.ts.map +4 -4
- package/docs/providers.md +50 -1
- package/package.json +5 -5
- package/src/format.ts +33 -2
- package/src/openai-companion-auth.ts +103 -0
- package/src/providers/openai-chatgpt.ts +122 -0
- package/src/providers/openai-companion-usage.ts +146 -0
- package/src/query.ts +28 -3
- package/src/settings.ts +5 -0
- package/src/types.ts +4 -0
- package/src/usage-settings-ui.ts +51 -9
- package/src/usage.ts +141 -12
package/src/usage-settings-ui.ts
CHANGED
|
@@ -9,7 +9,7 @@ const ON = "On";
|
|
|
9
9
|
const REMAINING = "Remaining";
|
|
10
10
|
const USED = "Used";
|
|
11
11
|
|
|
12
|
-
type UsageSettingId = "codexFastMode" | "codexStatusResetCountdown" | "codexStatusPercentage";
|
|
12
|
+
type UsageSettingId = "codexFastMode" | "codexStatusResetCountdown" | "codexStatusPercentage" | "openaiCompanionUsage";
|
|
13
13
|
|
|
14
14
|
function settingValueLabel(id: UsageSettingId, value: UsageSettings[UsageSettingId]): string {
|
|
15
15
|
if (id === "codexStatusPercentage") return value === "used" ? USED : REMAINING;
|
|
@@ -29,11 +29,12 @@ export async function showUsageSettings(
|
|
|
29
29
|
}
|
|
30
30
|
const { HorizontalRule, renderBoundedFrame } = await import("@narumitw/pi-tui-kit");
|
|
31
31
|
if (parentSignal.aborted || !isCurrent()) return false;
|
|
32
|
-
|
|
33
|
-
|
|
32
|
+
let changed = false;
|
|
33
|
+
let selectedId: UsageSettingId | undefined;
|
|
34
|
+
for (;;) {
|
|
35
|
+
const result = await ctx.ui.custom<boolean | "consent">((tui, theme, _keybindings, done) => {
|
|
34
36
|
const localController = new AbortController();
|
|
35
37
|
const signal = AbortSignal.any([parentSignal, localController.signal]);
|
|
36
|
-
let changed = false;
|
|
37
38
|
let closing = false;
|
|
38
39
|
let saveQueue = Promise.resolve();
|
|
39
40
|
const state = settingsRuntime.get();
|
|
@@ -59,18 +60,28 @@ export async function showUsageSettings(
|
|
|
59
60
|
currentValue: settingValueLabel("codexStatusPercentage", state.settings.codexStatusPercentage),
|
|
60
61
|
values: [REMAINING, USED],
|
|
61
62
|
},
|
|
63
|
+
{
|
|
64
|
+
id: "openaiCompanionUsage",
|
|
65
|
+
label: "Experimental ChatGPT companion usage",
|
|
66
|
+
description:
|
|
67
|
+
"Read undocumented plan/app usage using a same-account Codex login; matching registration does not prove identity.",
|
|
68
|
+
currentValue: state.settings.openaiCompanionUsage ? ON : OFF,
|
|
69
|
+
values: [OFF, ON],
|
|
70
|
+
},
|
|
62
71
|
];
|
|
63
72
|
const rule = new HorizontalRule({ ruleStyle: (text) => theme.fg("border", text) });
|
|
64
73
|
|
|
65
74
|
let settingsList: SettingsList;
|
|
66
|
-
const
|
|
75
|
+
const close = (result: boolean | "consent") => {
|
|
67
76
|
if (closing) return;
|
|
68
77
|
closing = true;
|
|
69
78
|
localController.abort();
|
|
70
|
-
done(
|
|
79
|
+
done(result);
|
|
71
80
|
};
|
|
81
|
+
const cancel = () => close(changed);
|
|
72
82
|
const queueUpdate = <Id extends UsageSettingId>(id: Id, requested: UsageSettings[Id], display: string) => {
|
|
73
83
|
saveQueue = saveQueue.then(async () => {
|
|
84
|
+
if (signal.aborted || !isCurrent()) return;
|
|
74
85
|
const previous = settingsRuntime.get().settings[id];
|
|
75
86
|
if (settingsRuntime.get().kind === "invalid") {
|
|
76
87
|
settingsList.updateValue(id, settingValueLabel(id, previous));
|
|
@@ -81,6 +92,14 @@ export async function showUsageSettings(
|
|
|
81
92
|
return;
|
|
82
93
|
}
|
|
83
94
|
try {
|
|
95
|
+
if (id === "openaiCompanionUsage" && requested === true && previous !== true) {
|
|
96
|
+
// Pi's confirm dialog replaces a non-overlay custom screen and restores
|
|
97
|
+
// the editor, not that screen. Complete/dispose Settings first; after
|
|
98
|
+
// consent, mount a fresh instance with the initiating row selected.
|
|
99
|
+
selectedId = id;
|
|
100
|
+
close("consent");
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
84
103
|
const patch: Partial<UsageSettings> = {};
|
|
85
104
|
patch[id] = requested;
|
|
86
105
|
await settingsRuntime.update(patch, signal);
|
|
@@ -91,6 +110,8 @@ export async function showUsageSettings(
|
|
|
91
110
|
tui.requestRender();
|
|
92
111
|
return;
|
|
93
112
|
}
|
|
113
|
+
// A rename can win a cancellation race. Apply owned lifecycle cleanup after a
|
|
114
|
+
// durable save, but let the owner guard its session before touching UI.
|
|
94
115
|
if (previous !== requested) {
|
|
95
116
|
changed = true;
|
|
96
117
|
onApplied(id);
|
|
@@ -106,15 +127,17 @@ export async function showUsageSettings(
|
|
|
106
127
|
getSettingsListTheme(),
|
|
107
128
|
(id, value) => {
|
|
108
129
|
if (closing || signal.aborted || !isCurrent()) return;
|
|
130
|
+
selectedId = id as UsageSettingId;
|
|
109
131
|
if (id === "codexStatusPercentage") {
|
|
110
132
|
queueUpdate(id, value === USED ? "used" : "remaining", value);
|
|
111
|
-
} else if (id === "codexFastMode" || id === "codexStatusResetCountdown") {
|
|
133
|
+
} else if (id === "codexFastMode" || id === "codexStatusResetCountdown" || id === "openaiCompanionUsage") {
|
|
112
134
|
queueUpdate(id, value === ON, value);
|
|
113
135
|
}
|
|
114
136
|
},
|
|
115
137
|
cancel,
|
|
116
138
|
);
|
|
117
139
|
|
|
140
|
+
if (selectedId) settingsList.selectItem(selectedId);
|
|
118
141
|
parentSignal.addEventListener("abort", cancel, { once: true });
|
|
119
142
|
return {
|
|
120
143
|
render(width: number) {
|
|
@@ -147,6 +170,25 @@ export async function showUsageSettings(
|
|
|
147
170
|
parentSignal.removeEventListener("abort", cancel);
|
|
148
171
|
},
|
|
149
172
|
};
|
|
150
|
-
})
|
|
151
|
-
|
|
173
|
+
});
|
|
174
|
+
if (result !== "consent" || parentSignal.aborted || !isCurrent()) return changed;
|
|
175
|
+
try {
|
|
176
|
+
const accepted = await ctx.ui.confirm(
|
|
177
|
+
"Enable experimental ChatGPT companion usage?",
|
|
178
|
+
"Requires /login openai-codex with the same ChatGPT account/workspace as native OpenAI. Only the companion token and its matching account ID are sent to undocumented ChatGPT usage endpoints. Registration matching is not independent proof of the same user/workspace. No reset or allowance mutations are performed.",
|
|
179
|
+
{ signal: parentSignal },
|
|
180
|
+
);
|
|
181
|
+
if (parentSignal.aborted || !isCurrent()) return changed;
|
|
182
|
+
if (accepted && !settingsRuntime.get().settings.openaiCompanionUsage) {
|
|
183
|
+
await settingsRuntime.update({ openaiCompanionUsage: true }, parentSignal);
|
|
184
|
+
changed = true;
|
|
185
|
+
// A durable rename may win cancellation; the owner guards lifecycle cleanup.
|
|
186
|
+
onApplied("openaiCompanionUsage");
|
|
187
|
+
}
|
|
188
|
+
} catch (error) {
|
|
189
|
+
if (parentSignal.aborted || !isCurrent()) return changed;
|
|
190
|
+
ctx.ui.notify(`Could not enable ChatGPT companion usage: ${errorMessage(error)}`, "error");
|
|
191
|
+
}
|
|
192
|
+
if (parentSignal.aborted || !isCurrent()) return changed;
|
|
193
|
+
}
|
|
152
194
|
}
|
package/src/usage.ts
CHANGED
|
@@ -21,7 +21,13 @@ import {
|
|
|
21
21
|
import { abortError, awaitWithDeadline, errorMessage, runWithConcurrency, UsageCache } from "./core.js";
|
|
22
22
|
import { formatProviderStates, formatUsageStatusline } from "./format.js";
|
|
23
23
|
import { createOAuthCredentialCandidateReader } from "./oauth-credential-source.js";
|
|
24
|
-
import {
|
|
24
|
+
import { UnsupportedOpenAIUsageAuthError } from "./providers/openai-chatgpt.js";
|
|
25
|
+
import {
|
|
26
|
+
adapterForProvider,
|
|
27
|
+
isStaleExtensionContextError,
|
|
28
|
+
queryProviderUsage,
|
|
29
|
+
resolveUsageAuth as resolveRuntimeUsageAuth,
|
|
30
|
+
} from "./query.js";
|
|
25
31
|
import { createUsageSettingsRuntime, type UsageSettingsRuntime, type UsageSettingsState } from "./settings.js";
|
|
26
32
|
import type {
|
|
27
33
|
PiModel,
|
|
@@ -65,7 +71,7 @@ type UsageExtensionDependencies = {
|
|
|
65
71
|
type QueryOutcome = {
|
|
66
72
|
state: ProviderUsageState;
|
|
67
73
|
fingerprint?: string;
|
|
68
|
-
authState?: "unavailable";
|
|
74
|
+
authState?: "unavailable" | "unsupported" | "failed";
|
|
69
75
|
rememberedTargetId?: string;
|
|
70
76
|
};
|
|
71
77
|
|
|
@@ -83,10 +89,20 @@ export default function usageExtension(pi: ExtensionAPI, dependencies: UsageExte
|
|
|
83
89
|
const credentialCandidates = createOAuthCredentialCandidateReader(pi, credentialReader);
|
|
84
90
|
const createRedemptionId = dependencies.createRedemptionId ?? randomUUID;
|
|
85
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
|
+
);
|
|
86
101
|
const cache = new UsageCache(CACHE_TTL_MS);
|
|
87
102
|
const failureBackoff = new Map<string, { until: number; message: string }>();
|
|
88
103
|
const latestQueries = new Map<string, number>();
|
|
89
104
|
const activeControllers = new Set<AbortController>();
|
|
105
|
+
const nativeQueryControllers = new Set<AbortController>();
|
|
90
106
|
let querySequence = 0;
|
|
91
107
|
let activeCurrentIdentity: string | undefined;
|
|
92
108
|
let sessionActive = false;
|
|
@@ -182,7 +198,7 @@ export default function usageExtension(pi: ExtensionAPI, dependencies: UsageExte
|
|
|
182
198
|
if (shouldSchedule && sessionActive) scheduleStatusRefresh(ctx, model);
|
|
183
199
|
if (
|
|
184
200
|
sessionActive &&
|
|
185
|
-
showCodexResetCountdown &&
|
|
201
|
+
(showCodexResetCountdown || outcome.state.report.source === "openai-chatgpt-companion") &&
|
|
186
202
|
outcome.state.report.buckets.some(
|
|
187
203
|
(bucket) => bucket.resetsAt !== undefined && Number.isFinite(bucket.resetsAt) && bucket.resetsAt * 1_000 > now,
|
|
188
204
|
)
|
|
@@ -219,7 +235,7 @@ export default function usageExtension(pi: ExtensionAPI, dependencies: UsageExte
|
|
|
219
235
|
activeCurrentIdentity = nextIdentity;
|
|
220
236
|
};
|
|
221
237
|
|
|
222
|
-
const
|
|
238
|
+
const queryAdapterStateInternal = async (
|
|
223
239
|
ctx: ExtensionContext,
|
|
224
240
|
adapter: UsageProviderAdapter,
|
|
225
241
|
displayState: UsageDisplayState,
|
|
@@ -232,6 +248,7 @@ export default function usageExtension(pi: ExtensionAPI, dependencies: UsageExte
|
|
|
232
248
|
const expectedSessionId = ctx.sessionManager.getSessionId();
|
|
233
249
|
const expectedModelIdentity = modelIdentity(ctx.model);
|
|
234
250
|
const expectedTargetId = adapter.targets ? settingsRuntime.get().settings.selectedTargets[adapter.id] : undefined;
|
|
251
|
+
const expectedCompanionUsage = settingsRuntime.get().settings.openaiCompanionUsage;
|
|
235
252
|
const providerName = providerDisplayName(ctx, adapter.id);
|
|
236
253
|
let auth: ResolvedUsageAuth | undefined;
|
|
237
254
|
try {
|
|
@@ -243,6 +260,13 @@ export default function usageExtension(pi: ExtensionAPI, dependencies: UsageExte
|
|
|
243
260
|
);
|
|
244
261
|
} catch (error) {
|
|
245
262
|
if (isStaleExtensionContextError(error) || isAbortError(error)) throw error;
|
|
263
|
+
if (adapter.id === "openai") invalidateProviderState(adapter.id);
|
|
264
|
+
const authState =
|
|
265
|
+
error instanceof UnsupportedOpenAIUsageAuthError
|
|
266
|
+
? "unsupported"
|
|
267
|
+
: adapter.id === "openai" && !isTimeoutError(error)
|
|
268
|
+
? "failed"
|
|
269
|
+
: undefined;
|
|
246
270
|
if (displayState === "current") {
|
|
247
271
|
transitionCurrentIdentity(`${adapter.id}:auth-error`, adapter.id);
|
|
248
272
|
}
|
|
@@ -251,9 +275,11 @@ export default function usageExtension(pi: ExtensionAPI, dependencies: UsageExte
|
|
|
251
275
|
providerId: adapter.id,
|
|
252
276
|
providerName,
|
|
253
277
|
displayState,
|
|
254
|
-
status:
|
|
278
|
+
status:
|
|
279
|
+
authState === "unsupported" ? "unsupported" : isTimeoutError(error) ? "query-failed" : "auth-unavailable",
|
|
255
280
|
message: errorMessage(error),
|
|
256
281
|
},
|
|
282
|
+
...(authState ? { authState } : {}),
|
|
257
283
|
};
|
|
258
284
|
}
|
|
259
285
|
const requiresRequestBoundaryGuard =
|
|
@@ -265,6 +291,7 @@ export default function usageExtension(pi: ExtensionAPI, dependencies: UsageExte
|
|
|
265
291
|
"minimax-cn",
|
|
266
292
|
"moonshotai",
|
|
267
293
|
"moonshotai-cn",
|
|
294
|
+
"openai",
|
|
268
295
|
"vercel-ai-gateway",
|
|
269
296
|
"xai",
|
|
270
297
|
"zai",
|
|
@@ -274,10 +301,12 @@ export default function usageExtension(pi: ExtensionAPI, dependencies: UsageExte
|
|
|
274
301
|
expectedSessionGeneration !== sessionGeneration ||
|
|
275
302
|
ctx.sessionManager.getSessionId() !== expectedSessionId ||
|
|
276
303
|
modelIdentity(ctx.model) !== expectedModelIdentity ||
|
|
304
|
+
(adapter.id === "openai" && settingsRuntime.get().settings.openaiCompanionUsage !== expectedCompanionUsage) ||
|
|
277
305
|
(adapter.targets !== undefined &&
|
|
278
306
|
settingsRuntime.get().settings.selectedTargets[adapter.id] !== expectedTargetId);
|
|
279
307
|
if (requiresRequestBoundaryGuard && requestContextChanged()) throw abortError();
|
|
280
308
|
if (!auth) {
|
|
309
|
+
if (adapter.id === "openai") invalidateProviderState(adapter.id);
|
|
281
310
|
if (displayState === "current") {
|
|
282
311
|
transitionCurrentIdentity(`${adapter.id}:unavailable`, adapter.id);
|
|
283
312
|
}
|
|
@@ -292,6 +321,7 @@ export default function usageExtension(pi: ExtensionAPI, dependencies: UsageExte
|
|
|
292
321
|
authState: "unavailable",
|
|
293
322
|
};
|
|
294
323
|
}
|
|
324
|
+
if (adapter.id === "openai" && !auth.openaiCompanion) invalidateProviderState(adapter.id);
|
|
295
325
|
let retryableAuthChanged = false;
|
|
296
326
|
const guard = async () => {
|
|
297
327
|
if (signal.aborted || requestContextChanged()) throw abortError();
|
|
@@ -456,6 +486,25 @@ export default function usageExtension(pi: ExtensionAPI, dependencies: UsageExte
|
|
|
456
486
|
}
|
|
457
487
|
};
|
|
458
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
|
+
|
|
459
508
|
const loadTargetChoices = async (ctx: ExtensionContext, adapter: UsageProviderAdapter, signal: AbortSignal) => {
|
|
460
509
|
if (!adapter.targets) throw new Error("Provider does not support usage targets.");
|
|
461
510
|
const expectedSessionGeneration = sessionGeneration;
|
|
@@ -598,11 +647,11 @@ export default function usageExtension(pi: ExtensionAPI, dependencies: UsageExte
|
|
|
598
647
|
if (generation !== statusGeneration || modelIdentity(ctx.model) !== modelIdentity(model)) {
|
|
599
648
|
return false;
|
|
600
649
|
}
|
|
601
|
-
const adapter = adapterForProvider(
|
|
650
|
+
const adapter = adapterForProvider(outcome.state.providerId);
|
|
602
651
|
const selectionStillCurrent =
|
|
603
652
|
!adapter?.targets || settingsRuntime.get().settings.selectedTargets[adapter.id] === outcome.rememberedTargetId;
|
|
604
653
|
if (!selectionStillCurrent) return false;
|
|
605
|
-
if (outcome.authState
|
|
654
|
+
if (outcome.authState) {
|
|
606
655
|
if (!adapter) return false;
|
|
607
656
|
try {
|
|
608
657
|
const auth = await awaitWithDeadline(
|
|
@@ -616,11 +665,27 @@ export default function usageExtension(pi: ExtensionAPI, dependencies: UsageExte
|
|
|
616
665
|
modelIdentity(ctx.model) === modelIdentity(model) &&
|
|
617
666
|
(!adapter.targets ||
|
|
618
667
|
settingsRuntime.get().settings.selectedTargets[adapter.id] === outcome.rememberedTargetId) &&
|
|
668
|
+
outcome.authState === "unavailable" &&
|
|
619
669
|
auth === undefined
|
|
620
670
|
);
|
|
621
671
|
} catch (error) {
|
|
622
672
|
if (isAbortError(error) || isStaleExtensionContextError(error)) throw error;
|
|
623
|
-
|
|
673
|
+
// Stable failures are still useful. A recovered credential or changed reason must
|
|
674
|
+
// retry, but confirming the same native failure must not create a refresh loop.
|
|
675
|
+
const unchangedFailure =
|
|
676
|
+
(outcome.authState === "unsupported" && error instanceof UnsupportedOpenAIUsageAuthError) ||
|
|
677
|
+
(outcome.authState === "failed" &&
|
|
678
|
+
outcome.state.status === "auth-unavailable" &&
|
|
679
|
+
!(error instanceof UnsupportedOpenAIUsageAuthError) &&
|
|
680
|
+
!isTimeoutError(error) &&
|
|
681
|
+
errorMessage(error) === outcome.state.message);
|
|
682
|
+
return (
|
|
683
|
+
unchangedFailure &&
|
|
684
|
+
generation === statusGeneration &&
|
|
685
|
+
modelIdentity(ctx.model) === modelIdentity(model) &&
|
|
686
|
+
(!adapter.targets ||
|
|
687
|
+
settingsRuntime.get().settings.selectedTargets[adapter.id] === outcome.rememberedTargetId)
|
|
688
|
+
);
|
|
624
689
|
}
|
|
625
690
|
}
|
|
626
691
|
if (!outcome.fingerprint) return true;
|
|
@@ -645,6 +710,27 @@ export default function usageExtension(pi: ExtensionAPI, dependencies: UsageExte
|
|
|
645
710
|
}
|
|
646
711
|
};
|
|
647
712
|
|
|
713
|
+
const queryStableAdapterState = async (
|
|
714
|
+
ctx: ExtensionContext,
|
|
715
|
+
adapter: UsageProviderAdapter,
|
|
716
|
+
displayState: UsageDisplayState,
|
|
717
|
+
force: boolean,
|
|
718
|
+
signal: AbortSignal,
|
|
719
|
+
): Promise<QueryOutcome> => {
|
|
720
|
+
if (adapter.id !== "openai") return queryAdapterState(ctx, adapter, displayState, force, signal);
|
|
721
|
+
// Configured/all-provider views must confirm native auth failures too, independently
|
|
722
|
+
// of the selected provider's subsequent revalidation.
|
|
723
|
+
for (let attempt = 0; attempt < 3; attempt += 1) {
|
|
724
|
+
const model = ctx.model;
|
|
725
|
+
const generation = statusGeneration;
|
|
726
|
+
const outcome = await queryAdapterState(ctx, adapter, displayState, force, signal);
|
|
727
|
+
if (signal.aborted) throw abortError();
|
|
728
|
+
if (await outcomeStillCurrent(ctx, model, generation, outcome, signal)) return outcome;
|
|
729
|
+
force = false;
|
|
730
|
+
}
|
|
731
|
+
throw new Error("OpenAI runtime authentication kept changing; reopen /usage to retry.");
|
|
732
|
+
};
|
|
733
|
+
|
|
648
734
|
const queryStableCurrent = async (
|
|
649
735
|
ctx: ExtensionCommandContext,
|
|
650
736
|
force: boolean,
|
|
@@ -976,7 +1062,7 @@ export default function usageExtension(pi: ExtensionAPI, dependencies: UsageExte
|
|
|
976
1062
|
ctx,
|
|
977
1063
|
`Checking ${providerDisplayName(ctx, adapter.id)} usage…`,
|
|
978
1064
|
controller.signal,
|
|
979
|
-
(signal) =>
|
|
1065
|
+
(signal) => queryStableAdapterState(ctx, adapter, "configured", true, signal),
|
|
980
1066
|
);
|
|
981
1067
|
if (!outcome) return { kind: "stay" };
|
|
982
1068
|
const revalidated = await queryStableCurrent(ctx, false, controller, "Revalidating current usage…");
|
|
@@ -1004,6 +1090,20 @@ export default function usageExtension(pi: ExtensionAPI, dependencies: UsageExte
|
|
|
1004
1090
|
controller.signal,
|
|
1005
1091
|
() => statusGeneration === menuGeneration && !controller.signal.aborted,
|
|
1006
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
|
+
}
|
|
1007
1107
|
if (
|
|
1008
1108
|
(id === "codexStatusResetCountdown" || id === "codexStatusPercentage") &&
|
|
1009
1109
|
stableCurrent &&
|
|
@@ -1189,7 +1289,7 @@ export default function usageExtension(pi: ExtensionAPI, dependencies: UsageExte
|
|
|
1189
1289
|
ctx,
|
|
1190
1290
|
`Checking ${providerDisplayName(ctx, adapter.id)} usage…`,
|
|
1191
1291
|
controller.signal,
|
|
1192
|
-
(signal) =>
|
|
1292
|
+
(signal) => queryStableAdapterState(ctx, adapter, "configured", false, signal),
|
|
1193
1293
|
);
|
|
1194
1294
|
if (!outcome) return { kind: "back" };
|
|
1195
1295
|
if (outcome.state.status === "selection-required") {
|
|
@@ -1200,12 +1300,23 @@ export default function usageExtension(pi: ExtensionAPI, dependencies: UsageExte
|
|
|
1200
1300
|
ctx,
|
|
1201
1301
|
`Checking ${providerDisplayName(ctx, adapter.id)} usage…`,
|
|
1202
1302
|
controller.signal,
|
|
1203
|
-
(signal) =>
|
|
1303
|
+
(signal) => queryStableAdapterState(ctx, adapter, "configured", true, signal),
|
|
1204
1304
|
);
|
|
1205
1305
|
if (!outcome) return { kind: "back" };
|
|
1206
1306
|
}
|
|
1207
1307
|
const revalidated = await queryStableCurrent(ctx, false, controller, "Revalidating current usage…");
|
|
1208
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
|
+
}
|
|
1209
1320
|
stableCurrent = revalidated;
|
|
1210
1321
|
current = revalidated.outcome;
|
|
1211
1322
|
visibleStates = [
|
|
@@ -1227,7 +1338,7 @@ export default function usageExtension(pi: ExtensionAPI, dependencies: UsageExte
|
|
|
1227
1338
|
adapters,
|
|
1228
1339
|
ALL_PROVIDER_CONCURRENCY,
|
|
1229
1340
|
(adapter, _index, workerSignal) =>
|
|
1230
|
-
|
|
1341
|
+
queryStableAdapterState(
|
|
1231
1342
|
ctx,
|
|
1232
1343
|
adapter,
|
|
1233
1344
|
adapter.id === currentProviderId ? "current" : "configured",
|
|
@@ -1253,6 +1364,22 @@ export default function usageExtension(pi: ExtensionAPI, dependencies: UsageExte
|
|
|
1253
1364
|
});
|
|
1254
1365
|
const revalidated = await queryStableCurrent(ctx, false, controller, "Revalidating current usage…");
|
|
1255
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
|
+
}
|
|
1256
1383
|
stableCurrent = revalidated;
|
|
1257
1384
|
current = revalidated.outcome;
|
|
1258
1385
|
visibleStates = [
|
|
@@ -1309,6 +1436,8 @@ export default function usageExtension(pi: ExtensionAPI, dependencies: UsageExte
|
|
|
1309
1436
|
startStatusRefresh(ctx, ctx.model, false);
|
|
1310
1437
|
});
|
|
1311
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();
|
|
1312
1441
|
startStatusRefresh(ctx, event.model, false);
|
|
1313
1442
|
});
|
|
1314
1443
|
pi.on("turn_start", (_event, ctx) => {
|