@narumitw/pi-usage 0.61.1 → 0.61.2
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 +13 -2
- package/dist/index.ts +116 -14
- package/dist/index.ts.map +3 -3
- package/docs/providers.md +28 -1
- package/package.json +5 -5
- package/src/format.ts +4 -1
- package/src/providers/openai-chatgpt.ts +107 -0
- package/src/query.ts +24 -3
- package/src/usage.ts +56 -9
package/README.md
CHANGED
|
@@ -4,11 +4,13 @@
|
|
|
4
4
|
|
|
5
5
|
Inspect usage and DeepSeek API balance for Pi's active provider account, query other configured providers, and toggle Fast mode for supported OpenAI Codex models.
|
|
6
6
|
The extension keeps each provider's native quota, allowance, and spending semantics instead of treating unlike values as equivalent.
|
|
7
|
+
Native `openai` ChatGPT OAuth shows verified authentication status and a usage-settings link, not numerical quotas or reset times.
|
|
7
8
|
xAI OAuth subscription reporting follows the reviewed Grok Build contract and runs only after an explicit `/usage` action.
|
|
8
9
|
|
|
9
10
|
## ✨ Features
|
|
10
11
|
|
|
11
12
|
- Shows active-account usage and next actions through `/usage`.
|
|
13
|
+
- Recognizes native `/login openai` ChatGPT OAuth separately from unsupported OpenAI API-key usage reporting.
|
|
12
14
|
- Reports subscription allowances, API balances, and spending for the supported providers listed below without mixing their billing semantics.
|
|
13
15
|
- Toggles persistent Codex Fast routing through `/fast` or the usage menu.
|
|
14
16
|
- Redeems eligible Codex resets only after fresh account matching and explicit confirmation.
|
|
@@ -48,6 +50,8 @@ The package declares `dist/index.ts`, so an unbuilt local checkout must run the
|
|
|
48
50
|
Run `/usage` in TUI or RPC mode to inspect the active provider, refresh its usage, or choose another configured provider.
|
|
49
51
|
When a provider exposes several billing targets, `/usage` asks for one target before querying usage.
|
|
50
52
|
Run `/fast` to toggle Fast mode for a supported active Codex model.
|
|
53
|
+
For native `/login openai`, select an `openai` model and run `/usage` to verify ChatGPT authentication and find [ChatGPT usage settings](https://chatgpt.com/settings/usage).
|
|
54
|
+
Native OpenAI Fast mode and earned reset redemption are not implemented; those actions remain specific to legacy `openai-codex`.
|
|
51
55
|
|
|
52
56
|
## 💬 Commands
|
|
53
57
|
|
|
@@ -127,7 +131,8 @@ Currencies and billing targets remain separate.
|
|
|
127
131
|
|
|
128
132
|
| Provider | Reported data |
|
|
129
133
|
| --- | --- |
|
|
130
|
-
| OpenAI
|
|
134
|
+
| OpenAI (native ChatGPT OAuth) | Verified authentication status and usage-settings link; no numerical quota or reset data |
|
|
135
|
+
| OpenAI Codex (legacy) | Subscription windows, credits, resets, and model buckets |
|
|
131
136
|
| Kimi For Coding | Plan request windows and a separate booster wallet |
|
|
132
137
|
| Moonshot AI Global/China | Current API balance in USD/CNY |
|
|
133
138
|
| MiniMax Global/China | Token Plan windows or pay-as-you-go API balance |
|
|
@@ -169,6 +174,8 @@ MiniMax publishes Token Plan window percentages or the regional pay-as-you-go av
|
|
|
169
174
|
Baseten publishes the exact trailing 30-day Model APIs net subtotal after credits.
|
|
170
175
|
xAI is always menu-only and never starts a scheduled status refresh.
|
|
171
176
|
Z.AI statusline usage refreshes every five minutes while the selected model remains on Z.AI.
|
|
177
|
+
Verified native OpenAI OAuth publishes `chatgpt usage: web only`; its five-minute refresh revalidates authentication locally without calling a usage endpoint.
|
|
178
|
+
OpenAI API-key auth clears this status.
|
|
172
179
|
|
|
173
180
|
Queries for another provider or all providers never publish their results to the statusline.
|
|
174
181
|
`@narumitw/pi-statusline` supplies the default `📊` icon; `pi-usage` publishes text-only values.
|
|
@@ -197,6 +204,9 @@ Behavior changes:
|
|
|
197
204
|
Credential candidates are collected synchronously in memory after any compatible readiness promises settle, and are not cached, persisted, logged, formatted, or appended to the Pi session.
|
|
198
205
|
The protocols carry no account name or extension identity.
|
|
199
206
|
Only the selected provider's exact runtime match is used, and secrets are sent only to its validated official origin.
|
|
207
|
+
Native OpenAI status requires fresh Pi OAuth resolution and a matching complete ChatGPT grant, including the direct-token scope and client ID.
|
|
208
|
+
Model-level Authorization overrides cannot inherit subscription status from a stored login.
|
|
209
|
+
The native status adapter sends no usage request and never forwards native tokens to legacy ChatGPT backend endpoints.
|
|
200
210
|
DeepSeek balance requests require Bearer authentication, send only that resolved credential from Pi's runtime auth to `https://api.deepseek.com/user/balance`, and refuse redirects.
|
|
201
211
|
Fireworks spend requests send only that resolved credential to the official `https://api.fireworks.ai` account-listing and billing-summary endpoints and refuse redirects.
|
|
202
212
|
Moonshot balance requests send only the resolved Bearer credential to the matching official Global or China balance origin and refuse redirects.
|
|
@@ -210,7 +220,8 @@ An absent or incompatible peer preserves standalone fallback and fail-closed mis
|
|
|
210
220
|
|
|
211
221
|
## 🚧 Limitations
|
|
212
222
|
|
|
213
|
-
-
|
|
223
|
+
- Numerical usage requires a meaningful provider usage source and verifiable Pi runtime auth.
|
|
224
|
+
- Native `openai` reports authentication only: numerical usage, reset times, Fast mode, and earned reset redemption are not supported, and OpenAI API-key usage reporting remains unsupported.
|
|
214
225
|
- GitHub Copilot quota, Kimi managed usage, Z.AI quota, and OpenAI Codex reset redemption rely on provider-owned endpoints that may change without notice.
|
|
215
226
|
- Codex reset redemption requires a current ChatGPT OAuth credential from Pi's login or a compatible credential source; Codex API keys cannot redeem earned subscription resets.
|
|
216
227
|
- xAI usage supports only a uniquely matched Pi OAuth subscription credential; xAI API keys and Management API credentials are unsupported.
|
package/dist/index.ts
CHANGED
|
@@ -1429,6 +1429,80 @@ function amount(value, label, allowNegative) {
|
|
|
1429
1429
|
return String(value);
|
|
1430
1430
|
}
|
|
1431
1431
|
|
|
1432
|
+
// src/providers/openai-chatgpt.ts
|
|
1433
|
+
var CHATGPT_USAGE_SETTINGS_URL = "https://chatgpt.com/settings/usage";
|
|
1434
|
+
var DIRECT_TOKEN_SCOPE = "chatgpt.tokens.use.direct";
|
|
1435
|
+
var UnsupportedOpenAIUsageAuthError = class extends Error {
|
|
1436
|
+
name = "UnsupportedOpenAIUsageAuthError";
|
|
1437
|
+
constructor() {
|
|
1438
|
+
super("OpenAI API-key usage reporting is not supported; ChatGPT plan status requires native /login openai OAuth.");
|
|
1439
|
+
}
|
|
1440
|
+
};
|
|
1441
|
+
var OPENAI_CHATGPT_ADAPTER = {
|
|
1442
|
+
id: "openai",
|
|
1443
|
+
displayName: "OpenAI",
|
|
1444
|
+
semantics: { kind: "consumer-subscription", label: "ChatGPT plan authentication status" },
|
|
1445
|
+
async query(_auth, signal, _timeoutMs, guard) {
|
|
1446
|
+
if (!guard) throw new Error("ChatGPT plan status requires runtime-auth revalidation.");
|
|
1447
|
+
signal.throwIfAborted();
|
|
1448
|
+
await guard();
|
|
1449
|
+
signal.throwIfAborted();
|
|
1450
|
+
return {
|
|
1451
|
+
providerId: "openai",
|
|
1452
|
+
providerName: "OpenAI",
|
|
1453
|
+
capturedAt: Date.now(),
|
|
1454
|
+
source: "openai-chatgpt-auth",
|
|
1455
|
+
semantics: { kind: "consumer-subscription", label: "ChatGPT plan authentication status" },
|
|
1456
|
+
buckets: [],
|
|
1457
|
+
metrics: [{ id: "plan-auth", label: "ChatGPT plan authentication", value: "Connected (native OAuth)" }],
|
|
1458
|
+
notes: [
|
|
1459
|
+
"Numerical usage and reset times are unavailable in pi-usage for native OpenAI OAuth.",
|
|
1460
|
+
`Manage usage: ${CHATGPT_USAGE_SETTINGS_URL}`,
|
|
1461
|
+
"Fast mode and earned reset redemption remain legacy openai-codex features."
|
|
1462
|
+
]
|
|
1463
|
+
};
|
|
1464
|
+
}
|
|
1465
|
+
};
|
|
1466
|
+
function resolveOpenAIChatGPTAuth(auth, providerAuth, source, model, candidates) {
|
|
1467
|
+
if (source !== "OAuth") throw new UnsupportedOpenAIUsageAuthError();
|
|
1468
|
+
const access = accessToken(auth);
|
|
1469
|
+
if (!access || access !== accessToken(providerAuth)) {
|
|
1470
|
+
throw new Error("The active OpenAI model authorization does not match its resolved OAuth credential.");
|
|
1471
|
+
}
|
|
1472
|
+
const matches = /* @__PURE__ */ new Map();
|
|
1473
|
+
for (const candidate of candidates) {
|
|
1474
|
+
if (!candidate || typeof candidate !== "object" || Array.isArray(candidate)) continue;
|
|
1475
|
+
const credential = candidate;
|
|
1476
|
+
if (credential.type !== "oauth" || credential.access !== access) continue;
|
|
1477
|
+
if (typeof credential.refresh !== "string" || !credential.refresh || typeof credential.expires !== "number" || !Number.isFinite(credential.expires) || typeof credential.clientId !== "string" || !credential.clientId.trim() || !Array.isArray(credential.scopes) || !credential.scopes.every((scope) => typeof scope === "string") || !credential.scopes.includes(DIRECT_TOKEN_SCOPE)) {
|
|
1478
|
+
throw new Error(
|
|
1479
|
+
"The matching OpenAI OAuth credential is not a complete native ChatGPT grant; reconnect with /login openai."
|
|
1480
|
+
);
|
|
1481
|
+
}
|
|
1482
|
+
matches.set(JSON.stringify([credential.clientId, credential.refresh]), credential.refresh);
|
|
1483
|
+
}
|
|
1484
|
+
if (matches.size > 1)
|
|
1485
|
+
throw new Error("Conflicting native ChatGPT credentials match the active OpenAI runtime account.");
|
|
1486
|
+
const refresh = matches.values().next().value;
|
|
1487
|
+
if (!refresh)
|
|
1488
|
+
throw new Error("The active OpenAI runtime account does not match an available native ChatGPT OAuth credential.");
|
|
1489
|
+
const authorization = `Bearer ${access}`;
|
|
1490
|
+
return {
|
|
1491
|
+
apiKey: access,
|
|
1492
|
+
headers: { Authorization: authorization },
|
|
1493
|
+
fingerprint: "",
|
|
1494
|
+
secrets: [access, refresh, authorization],
|
|
1495
|
+
model
|
|
1496
|
+
};
|
|
1497
|
+
}
|
|
1498
|
+
function accessToken(auth) {
|
|
1499
|
+
if (!auth) return void 0;
|
|
1500
|
+
const authorization = Object.entries(auth.headers ?? {}).filter(([name]) => name.toLowerCase() === "authorization");
|
|
1501
|
+
if (authorization.length > 1) throw new Error("OpenAI runtime authorization headers are ambiguous.");
|
|
1502
|
+
if (authorization.length === 1) return /^Bearer\s+(.+)$/iu.exec(authorization[0]?.[1] ?? "")?.[1];
|
|
1503
|
+
return auth.apiKey;
|
|
1504
|
+
}
|
|
1505
|
+
|
|
1432
1506
|
// src/providers/opencode-zen.ts
|
|
1433
1507
|
var ZEN_WINDOWS = [
|
|
1434
1508
|
{ key: "rolling", label: "Rolling" },
|
|
@@ -2144,6 +2218,7 @@ var SUPPORTED_ADAPTERS = [
|
|
|
2144
2218
|
return normalizeBasetenBillingUsagePayload(payload, Date.now());
|
|
2145
2219
|
}
|
|
2146
2220
|
},
|
|
2221
|
+
OPENAI_CHATGPT_ADAPTER,
|
|
2147
2222
|
{
|
|
2148
2223
|
id: "openai-codex",
|
|
2149
2224
|
displayName: "OpenAI Codex",
|
|
@@ -2368,7 +2443,7 @@ async function resolveUsageAuth(ctx, adapter, salt = AUTH_FINGERPRINT_SALT, cred
|
|
|
2368
2443
|
}
|
|
2369
2444
|
const model = candidateModels(ctx, adapter.id).find((candidate) => hasOfficialOrigin(candidate, adapter.id));
|
|
2370
2445
|
if (!model) return void 0;
|
|
2371
|
-
if (["github-copilot", "xai"].includes(adapter.id) && candidateReader?.waitUntilReady && !await candidateReader.waitUntilReady(ctx, adapter.id)) {
|
|
2446
|
+
if (["github-copilot", "openai", "xai"].includes(adapter.id) && candidateReader?.waitUntilReady && !await candidateReader.waitUntilReady(ctx, adapter.id)) {
|
|
2372
2447
|
throw new Error(`${adapter.displayName} OAuth credential readiness failed closed.`);
|
|
2373
2448
|
}
|
|
2374
2449
|
const registry = ctx.modelRegistry;
|
|
@@ -2384,9 +2459,9 @@ async function resolveUsageAuth(ctx, adapter, salt = AUTH_FINGERPRINT_SALT, cred
|
|
|
2384
2459
|
if (!currentModel || typeof registry.getApiKeyAndHeaders !== "function") return void 0;
|
|
2385
2460
|
const result = await registry.getApiKeyAndHeaders(currentModel);
|
|
2386
2461
|
if (!result.ok) throw new Error(redactUsageError(result.error));
|
|
2387
|
-
return authorizationFrom(result) ? result : void 0;
|
|
2462
|
+
return adapter.id === "openai" || authorizationFrom(result) ? result : void 0;
|
|
2388
2463
|
};
|
|
2389
|
-
const resolveSelectedAuthLast = ["deepseek", "minimax", "minimax-cn"].includes(adapter.id);
|
|
2464
|
+
const resolveSelectedAuthLast = ["deepseek", "minimax", "minimax-cn", "openai"].includes(adapter.id);
|
|
2390
2465
|
if (!resolveSelectedAuthLast) modelAuth = await resolveCurrentModelAuth();
|
|
2391
2466
|
if (typeof registry.getProviderAuth !== "function") {
|
|
2392
2467
|
throw new Error("pi-usage requires Pi 0.81.0 or newer to validate resolved provider auth.");
|
|
@@ -2439,6 +2514,15 @@ async function resolveUsageAuth(ctx, adapter, salt = AUTH_FINGERPRINT_SALT, cred
|
|
|
2439
2514
|
)
|
|
2440
2515
|
};
|
|
2441
2516
|
};
|
|
2517
|
+
if (adapter.id === "openai") {
|
|
2518
|
+
if (!providerResult) return void 0;
|
|
2519
|
+
if (providerResult.source !== "OAuth") throw new UnsupportedOpenAIUsageAuthError();
|
|
2520
|
+
const offered = candidateReader ? await candidateReader(ctx, adapter.id) : fallbackOAuthCredentialCandidates(adapter.id, credentialReader);
|
|
2521
|
+
if (!offered.ok) throw new Error("OpenAI OAuth credential discovery failed closed.");
|
|
2522
|
+
return finalize(
|
|
2523
|
+
resolveOpenAIChatGPTAuth(auth, providerResult.auth, providerResult.source, model, offered.candidates)
|
|
2524
|
+
);
|
|
2525
|
+
}
|
|
2442
2526
|
if (adapter.id === "github-copilot") {
|
|
2443
2527
|
const offered = candidateReader ? await candidateReader(ctx, adapter.id) : fallbackOAuthCredentialCandidates(adapter.id, credentialReader);
|
|
2444
2528
|
if (!offered.ok) {
|
|
@@ -2805,6 +2889,7 @@ function hasOfficialUrlOrigin(value, providerId) {
|
|
|
2805
2889
|
if (providerId === "baseten") {
|
|
2806
2890
|
return ["https://inference.baseten.co", "https://api.baseten.co"].includes(url.origin);
|
|
2807
2891
|
}
|
|
2892
|
+
if (providerId === "openai") return url.origin === "https://api.openai.com";
|
|
2808
2893
|
if (providerId === "openai-codex") return url.origin === "https://chatgpt.com";
|
|
2809
2894
|
if (providerId === "deepseek") return url.origin === "https://api.deepseek.com";
|
|
2810
2895
|
if (providerId === "fireworks") return url.origin === "https://api.fireworks.ai";
|
|
@@ -3202,7 +3287,7 @@ var BAR_SEGMENTS = 20;
|
|
|
3202
3287
|
var VALUE_COLUMN = 29;
|
|
3203
3288
|
function formatUsageReport(report2, displayState) {
|
|
3204
3289
|
const stateLabel = displayState === "current" ? "Current" : "Configured";
|
|
3205
|
-
const title = report2.providerId === "baseten" ? "Baseten Model APIs Spend" : report2.providerId === "deepseek" ? "DeepSeek API Balance" : report2.providerId === "fireworks" ? "Fireworks API Spend" : report2.providerId === "vercel-ai-gateway" ? "Vercel AI Gateway Credits" : report2.providerId === "moonshotai" || report2.providerId === "moonshotai-cn" ? `${report2.providerName} Balance` : report2.providerId === "minimax" || report2.providerId === "minimax-cn" ? report2.source === "minimax-account-balance" ? `${report2.providerName} API Balance` : `${report2.providerName} Token Plan` : `${report2.providerName} Usage`;
|
|
3290
|
+
const title = report2.providerId === "baseten" ? "Baseten Model APIs Spend" : report2.providerId === "deepseek" ? "DeepSeek API Balance" : report2.providerId === "fireworks" ? "Fireworks API Spend" : report2.providerId === "vercel-ai-gateway" ? "Vercel AI Gateway Credits" : report2.providerId === "moonshotai" || report2.providerId === "moonshotai-cn" ? `${report2.providerName} Balance` : report2.providerId === "minimax" || report2.providerId === "minimax-cn" ? report2.source === "minimax-account-balance" ? `${report2.providerName} API Balance` : `${report2.providerName} Token Plan` : report2.source === "openai-chatgpt-auth" ? `${report2.providerName} ChatGPT Plan Status` : `${report2.providerName} Usage`;
|
|
3206
3291
|
const lines = [`${title} \xB7 ${stateLabel}`];
|
|
3207
3292
|
if (report2.accountLabel) lines.push(`Account: ${report2.accountLabel}`);
|
|
3208
3293
|
lines.push(`Semantics: ${report2.semantics.label}`, "");
|
|
@@ -3229,6 +3314,7 @@ function formatUsageReport(report2, displayState) {
|
|
|
3229
3314
|
return lines.join("\n").trimEnd();
|
|
3230
3315
|
}
|
|
3231
3316
|
function formatUsageStatusline(report2, model, now = Date.now(), showCodexResetCountdown = true, codexStatusPercentage = "remaining") {
|
|
3317
|
+
if (report2.providerId === "openai" && report2.source === "openai-chatgpt-auth") return "chatgpt usage: web only";
|
|
3232
3318
|
if (report2.providerId === "baseten") return formatBasetenStatusline(report2);
|
|
3233
3319
|
if (report2.providerId === "openai-codex") {
|
|
3234
3320
|
return formatCodexStatusline(report2, model, now, showCodexResetCountdown, codexStatusPercentage);
|
|
@@ -4502,6 +4588,7 @@ function usageExtension(pi, dependencies = {}) {
|
|
|
4502
4588
|
);
|
|
4503
4589
|
} catch (error) {
|
|
4504
4590
|
if (isStaleExtensionContextError(error) || isAbortError3(error)) throw error;
|
|
4591
|
+
const authState = error instanceof UnsupportedOpenAIUsageAuthError ? "unsupported" : adapter.id === "openai" && !isTimeoutError(error) ? "failed" : void 0;
|
|
4505
4592
|
if (displayState === "current") {
|
|
4506
4593
|
transitionCurrentIdentity(`${adapter.id}:auth-error`, adapter.id);
|
|
4507
4594
|
}
|
|
@@ -4510,9 +4597,10 @@ function usageExtension(pi, dependencies = {}) {
|
|
|
4510
4597
|
providerId: adapter.id,
|
|
4511
4598
|
providerName,
|
|
4512
4599
|
displayState,
|
|
4513
|
-
status: isTimeoutError(error) ? "query-failed" : "auth-unavailable",
|
|
4600
|
+
status: authState === "unsupported" ? "unsupported" : isTimeoutError(error) ? "query-failed" : "auth-unavailable",
|
|
4514
4601
|
message: errorMessage(error)
|
|
4515
|
-
}
|
|
4602
|
+
},
|
|
4603
|
+
...authState ? { authState } : {}
|
|
4516
4604
|
};
|
|
4517
4605
|
}
|
|
4518
4606
|
const requiresRequestBoundaryGuard = adapter.targets !== void 0 || [
|
|
@@ -4522,6 +4610,7 @@ function usageExtension(pi, dependencies = {}) {
|
|
|
4522
4610
|
"minimax-cn",
|
|
4523
4611
|
"moonshotai",
|
|
4524
4612
|
"moonshotai-cn",
|
|
4613
|
+
"openai",
|
|
4525
4614
|
"vercel-ai-gateway",
|
|
4526
4615
|
"xai",
|
|
4527
4616
|
"zai",
|
|
@@ -4813,10 +4902,10 @@ function usageExtension(pi, dependencies = {}) {
|
|
|
4813
4902
|
if (generation !== statusGeneration || modelIdentity(ctx.model) !== modelIdentity(model)) {
|
|
4814
4903
|
return false;
|
|
4815
4904
|
}
|
|
4816
|
-
const adapter = adapterForProvider(
|
|
4905
|
+
const adapter = adapterForProvider(outcome.state.providerId);
|
|
4817
4906
|
const selectionStillCurrent = !adapter?.targets || settingsRuntime.get().settings.selectedTargets[adapter.id] === outcome.rememberedTargetId;
|
|
4818
4907
|
if (!selectionStillCurrent) return false;
|
|
4819
|
-
if (outcome.authState
|
|
4908
|
+
if (outcome.authState) {
|
|
4820
4909
|
if (!adapter) return false;
|
|
4821
4910
|
try {
|
|
4822
4911
|
const auth = await awaitWithDeadline(
|
|
@@ -4825,10 +4914,11 @@ function usageExtension(pi, dependencies = {}) {
|
|
|
4825
4914
|
DEFAULT_TIMEOUT_MS,
|
|
4826
4915
|
`revalidating ${adapter.displayName} runtime auth`
|
|
4827
4916
|
);
|
|
4828
|
-
return generation === statusGeneration && modelIdentity(ctx.model) === modelIdentity(model) && (!adapter.targets || settingsRuntime.get().settings.selectedTargets[adapter.id] === outcome.rememberedTargetId) && auth === void 0;
|
|
4917
|
+
return generation === statusGeneration && modelIdentity(ctx.model) === modelIdentity(model) && (!adapter.targets || settingsRuntime.get().settings.selectedTargets[adapter.id] === outcome.rememberedTargetId) && outcome.authState === "unavailable" && auth === void 0;
|
|
4829
4918
|
} catch (error) {
|
|
4830
4919
|
if (isAbortError3(error) || isStaleExtensionContextError(error)) throw error;
|
|
4831
|
-
|
|
4920
|
+
const unchangedFailure = outcome.authState === "unsupported" && error instanceof UnsupportedOpenAIUsageAuthError || outcome.authState === "failed" && outcome.state.status === "auth-unavailable" && !(error instanceof UnsupportedOpenAIUsageAuthError) && !isTimeoutError(error) && errorMessage(error) === outcome.state.message;
|
|
4921
|
+
return unchangedFailure && generation === statusGeneration && modelIdentity(ctx.model) === modelIdentity(model) && (!adapter.targets || settingsRuntime.get().settings.selectedTargets[adapter.id] === outcome.rememberedTargetId);
|
|
4832
4922
|
}
|
|
4833
4923
|
}
|
|
4834
4924
|
if (!outcome.fingerprint) return true;
|
|
@@ -4846,6 +4936,18 @@ function usageExtension(pi, dependencies = {}) {
|
|
|
4846
4936
|
return false;
|
|
4847
4937
|
}
|
|
4848
4938
|
};
|
|
4939
|
+
const queryStableAdapterState = async (ctx, adapter, displayState, force, signal) => {
|
|
4940
|
+
if (adapter.id !== "openai") return queryAdapterState(ctx, adapter, displayState, force, signal);
|
|
4941
|
+
for (let attempt = 0; attempt < 3; attempt += 1) {
|
|
4942
|
+
const model = ctx.model;
|
|
4943
|
+
const generation = statusGeneration;
|
|
4944
|
+
const outcome = await queryAdapterState(ctx, adapter, displayState, force, signal);
|
|
4945
|
+
if (signal.aborted) throw abortError();
|
|
4946
|
+
if (!outcome.authState || await outcomeStillCurrent(ctx, model, generation, outcome, signal)) return outcome;
|
|
4947
|
+
force = false;
|
|
4948
|
+
}
|
|
4949
|
+
throw new Error("OpenAI runtime authentication kept changing; reopen /usage to retry.");
|
|
4950
|
+
};
|
|
4849
4951
|
const queryStableCurrent = async (ctx, force, controller, label) => {
|
|
4850
4952
|
for (let attempt = 0; attempt < 3; attempt += 1) {
|
|
4851
4953
|
const model = ctx.model;
|
|
@@ -5117,7 +5219,7 @@ function usageExtension(pi, dependencies = {}) {
|
|
|
5117
5219
|
ctx,
|
|
5118
5220
|
`Checking ${providerDisplayName(ctx, adapter.id)} usage\u2026`,
|
|
5119
5221
|
controller.signal,
|
|
5120
|
-
(signal) =>
|
|
5222
|
+
(signal) => queryStableAdapterState(ctx, adapter, "configured", true, signal)
|
|
5121
5223
|
);
|
|
5122
5224
|
if (!outcome) return { kind: "stay" };
|
|
5123
5225
|
const revalidated = await queryStableCurrent(ctx, false, controller, "Revalidating current usage\u2026");
|
|
@@ -5320,7 +5422,7 @@ function usageExtension(pi, dependencies = {}) {
|
|
|
5320
5422
|
ctx,
|
|
5321
5423
|
`Checking ${providerDisplayName(ctx, adapter.id)} usage\u2026`,
|
|
5322
5424
|
controller.signal,
|
|
5323
|
-
(signal) =>
|
|
5425
|
+
(signal) => queryStableAdapterState(ctx, adapter, "configured", false, signal)
|
|
5324
5426
|
);
|
|
5325
5427
|
if (!outcome) return { kind: "back" };
|
|
5326
5428
|
if (outcome.state.status === "selection-required") {
|
|
@@ -5331,7 +5433,7 @@ function usageExtension(pi, dependencies = {}) {
|
|
|
5331
5433
|
ctx,
|
|
5332
5434
|
`Checking ${providerDisplayName(ctx, adapter.id)} usage\u2026`,
|
|
5333
5435
|
controller.signal,
|
|
5334
|
-
(signal) =>
|
|
5436
|
+
(signal) => queryStableAdapterState(ctx, adapter, "configured", true, signal)
|
|
5335
5437
|
);
|
|
5336
5438
|
if (!outcome) return { kind: "back" };
|
|
5337
5439
|
}
|
|
@@ -5354,7 +5456,7 @@ function usageExtension(pi, dependencies = {}) {
|
|
|
5354
5456
|
(signal) => runWithConcurrency(
|
|
5355
5457
|
adapters,
|
|
5356
5458
|
ALL_PROVIDER_CONCURRENCY,
|
|
5357
|
-
(adapter, _index, workerSignal) =>
|
|
5459
|
+
(adapter, _index, workerSignal) => queryStableAdapterState(
|
|
5358
5460
|
ctx,
|
|
5359
5461
|
adapter,
|
|
5360
5462
|
adapter.id === currentProviderId ? "current" : "configured",
|