@narumitw/pi-usage 0.60.10 → 0.60.11
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 +4 -3
- package/dist/index.ts +39 -5
- package/dist/index.ts.map +2 -2
- package/package.json +1 -1
- package/src/codex-resets.ts +7 -4
- package/src/oauth-credential-source.ts +39 -2
- package/src/query.ts +9 -2
package/README.md
CHANGED
|
@@ -14,7 +14,7 @@ xAI OAuth subscription reporting follows the reviewed Grok Build contract and ru
|
|
|
14
14
|
- Redeems eligible Codex resets only after fresh account matching and explicit confirmation.
|
|
15
15
|
- Refreshes one or all configured providers with bounded concurrency while preserving partial results.
|
|
16
16
|
- Scopes statusline and cache data to the active provider and runtime account.
|
|
17
|
-
- Resolves credentials through Pi or the process-local OAuth credential-source protocol and validates the effective provider endpoint before sending them.
|
|
17
|
+
- Resolves credentials through Pi or the process-local OAuth credential-source protocol, waits for compatible pending account activation, and validates the effective provider endpoint before sending them.
|
|
18
18
|
|
|
19
19
|
## 📦 Install
|
|
20
20
|
|
|
@@ -138,6 +138,7 @@ Codex reset redemption requires a freshly matched current OAuth account and expl
|
|
|
138
138
|
The extension selects one provider target for one query and never flattens targets into provider rows or aggregates every visible target.
|
|
139
139
|
Provider adapters own target discovery and validation; core owns one-target selection, persistence, cache identity, cancellation, and UI.
|
|
140
140
|
A compatible credential owner may offer the verified active named account through the versioned process-local protocol without exposing its account label or storage.
|
|
141
|
+
When that owner is still activating the requested provider in the current session, `/usage` waits through the extension-neutral `oauth:credential-readiness:v1` protocol before collecting the synchronous credential offer.
|
|
141
142
|
Without such an owner, `pi-usage` retains its standalone Pi `auth.json` behavior.
|
|
142
143
|
An older or incompatible owner degrades to the existing authentication-unavailable result when the stored login does not match runtime auth.
|
|
143
144
|
After the active runtime credential changes, the next command, turn, or scheduled refresh resolves auth again and cannot reuse another account's cached report.
|
|
@@ -179,8 +180,8 @@ Behavior changes:
|
|
|
179
180
|
|
|
180
181
|
## 🔒 Security and privacy
|
|
181
182
|
|
|
182
|
-
Credential candidates are collected synchronously in memory and are not cached, persisted, logged, formatted, or appended to the Pi session.
|
|
183
|
-
The
|
|
183
|
+
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.
|
|
184
|
+
The protocols carry no account name or extension identity.
|
|
184
185
|
Only the selected provider's exact runtime match is used, and secrets are sent only to its validated official origin.
|
|
185
186
|
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.
|
|
186
187
|
Fireworks spend requests send only that resolved credential to the official `https://api.fireworks.ai` account-listing and billing-summary endpoints and refuse redirects.
|
package/dist/index.ts
CHANGED
|
@@ -280,8 +280,11 @@ function truncate(value, maxChars) {
|
|
|
280
280
|
// src/oauth-credential-source.ts
|
|
281
281
|
import { readStoredCredential } from "@earendil-works/pi-coding-agent";
|
|
282
282
|
var OAUTH_CREDENTIAL_SOURCE_CHANNEL = "oauth:credential-source:v1";
|
|
283
|
+
var OAUTH_CREDENTIAL_READINESS_CHANNEL = "oauth:credential-readiness:v1";
|
|
283
284
|
function createOAuthCredentialCandidateReader(pi, credentialReader = readStoredCredential) {
|
|
284
|
-
|
|
285
|
+
const reader = (ctx, providerId) => collectOAuthCredentialCandidates(pi, ctx, providerId, credentialReader);
|
|
286
|
+
reader.waitUntilReady = (ctx, providerId) => waitForOAuthCredentialSources(pi, ctx, providerId);
|
|
287
|
+
return reader;
|
|
285
288
|
}
|
|
286
289
|
function collectOAuthCredentialCandidates(pi, ctx, providerId, credentialReader = readStoredCredential) {
|
|
287
290
|
const candidates = [];
|
|
@@ -310,6 +313,31 @@ function collectOAuthCredentialCandidates(pi, ctx, providerId, credentialReader
|
|
|
310
313
|
}
|
|
311
314
|
return { ok: true, candidates, offeredCount };
|
|
312
315
|
}
|
|
316
|
+
async function waitForOAuthCredentialSources(pi, ctx, providerId) {
|
|
317
|
+
const pending = [];
|
|
318
|
+
let collecting = true;
|
|
319
|
+
const request = Object.freeze({
|
|
320
|
+
session: ctx.sessionManager,
|
|
321
|
+
provider: providerId,
|
|
322
|
+
waitUntil(value) {
|
|
323
|
+
if (!collecting || !(value instanceof Promise)) return;
|
|
324
|
+
pending.push(value);
|
|
325
|
+
}
|
|
326
|
+
});
|
|
327
|
+
try {
|
|
328
|
+
pi.events.emit(OAUTH_CREDENTIAL_READINESS_CHANNEL, request);
|
|
329
|
+
} catch {
|
|
330
|
+
return false;
|
|
331
|
+
} finally {
|
|
332
|
+
collecting = false;
|
|
333
|
+
}
|
|
334
|
+
try {
|
|
335
|
+
await Promise.all(pending);
|
|
336
|
+
return true;
|
|
337
|
+
} catch {
|
|
338
|
+
return false;
|
|
339
|
+
}
|
|
340
|
+
}
|
|
313
341
|
function fallbackOAuthCredentialCandidates(providerId, credentialReader) {
|
|
314
342
|
try {
|
|
315
343
|
const credential = cloneOAuthCredential(credentialReader(providerId));
|
|
@@ -2301,6 +2329,9 @@ async function resolveUsageAuth(ctx, adapter, salt = AUTH_FINGERPRINT_SALT, cred
|
|
|
2301
2329
|
}
|
|
2302
2330
|
const model = candidateModels(ctx, adapter.id).find((candidate) => hasOfficialOrigin(candidate, adapter.id));
|
|
2303
2331
|
if (!model) return void 0;
|
|
2332
|
+
if (["github-copilot", "xai"].includes(adapter.id) && candidateReader?.waitUntilReady && !await candidateReader.waitUntilReady(ctx, adapter.id)) {
|
|
2333
|
+
throw new Error(`${adapter.displayName} OAuth credential readiness failed closed.`);
|
|
2334
|
+
}
|
|
2304
2335
|
const registry = ctx.modelRegistry;
|
|
2305
2336
|
const provider = registry.getProvider?.(adapter.id);
|
|
2306
2337
|
if (provider?.baseUrl && !hasOfficialUrlOrigin(provider.baseUrl, adapter.id)) {
|
|
@@ -2370,14 +2401,14 @@ async function resolveUsageAuth(ctx, adapter, salt = AUTH_FINGERPRINT_SALT, cred
|
|
|
2370
2401
|
};
|
|
2371
2402
|
};
|
|
2372
2403
|
if (adapter.id === "github-copilot") {
|
|
2373
|
-
const offered = candidateReader ? candidateReader(ctx, adapter.id) : fallbackOAuthCredentialCandidates(adapter.id, credentialReader);
|
|
2404
|
+
const offered = candidateReader ? await candidateReader(ctx, adapter.id) : fallbackOAuthCredentialCandidates(adapter.id, credentialReader);
|
|
2374
2405
|
if (!offered.ok) {
|
|
2375
2406
|
throw new Error("GitHub Copilot OAuth credential discovery failed closed.");
|
|
2376
2407
|
}
|
|
2377
2408
|
return finalize(resolveGitHubCopilotUsageAuth(auth, model, salt, offered.candidates, offered.offeredCount === 0));
|
|
2378
2409
|
}
|
|
2379
2410
|
if (adapter.id === "xai") {
|
|
2380
|
-
const offered = candidateReader ? candidateReader(ctx, adapter.id) : fallbackOAuthCredentialCandidates(adapter.id, credentialReader);
|
|
2411
|
+
const offered = candidateReader ? await candidateReader(ctx, adapter.id) : fallbackOAuthCredentialCandidates(adapter.id, credentialReader);
|
|
2381
2412
|
if (!offered.ok) throw new Error("xAI OAuth credential discovery failed closed.");
|
|
2382
2413
|
return finalize(resolveXaiUsageAuth(auth, model, salt, offered.candidates));
|
|
2383
2414
|
}
|
|
@@ -2929,19 +2960,22 @@ async function resolveCodexResetAuth(ctx, salt = AUTH_FINGERPRINT_SALT, credenti
|
|
|
2929
2960
|
const expectedModel = `${model.provider}/${model.id}`;
|
|
2930
2961
|
const adapter = adapterForProvider("openai-codex");
|
|
2931
2962
|
if (!adapter) throw new Error("OpenAI Codex usage support is unavailable.");
|
|
2963
|
+
if (candidateReader?.waitUntilReady && !await candidateReader.waitUntilReady(ctx, "openai-codex")) {
|
|
2964
|
+
throw new Error("OpenAI Codex OAuth credential readiness failed closed.");
|
|
2965
|
+
}
|
|
2932
2966
|
const auth = await resolveUsageAuth(ctx, adapter, salt, credentialReader);
|
|
2933
2967
|
if (`${ctx.model?.provider}/${ctx.model?.id}` !== expectedModel) {
|
|
2934
2968
|
throw new Error("The current model changed while resolving Codex reset authentication.");
|
|
2935
2969
|
}
|
|
2936
2970
|
if (!auth) throw new Error("No runtime credential is configured for OpenAI Codex.");
|
|
2971
|
+
const offered = candidateReader ? await candidateReader(ctx, "openai-codex") : fallbackOAuthCredentialCandidates("openai-codex", credentialReader);
|
|
2972
|
+
if (!offered.ok) throw new Error("OpenAI Codex OAuth credential discovery failed closed.");
|
|
2937
2973
|
const resolvedAccess = bearerToken2(headerValue2(auth.headers, "Authorization")) ?? auth.apiKey;
|
|
2938
2974
|
if (!resolvedAccess) throw new Error("OpenAI Codex OAuth credentials were incomplete.");
|
|
2939
2975
|
const resolvedAccountId = codexAccountIdFromAccessToken(resolvedAccess);
|
|
2940
2976
|
if (!resolvedAccountId) {
|
|
2941
2977
|
throw new Error("The active OpenAI Codex access token did not contain a valid account ID.");
|
|
2942
2978
|
}
|
|
2943
|
-
const offered = candidateReader ? candidateReader(ctx, "openai-codex") : fallbackOAuthCredentialCandidates("openai-codex", credentialReader);
|
|
2944
|
-
if (!offered.ok) throw new Error("OpenAI Codex OAuth credential discovery failed closed.");
|
|
2945
2979
|
const { accountId, storedAccess } = selectCodexResetCredential(
|
|
2946
2980
|
offered.candidates,
|
|
2947
2981
|
resolvedAccess,
|