@narumitw/pi-usage 0.60.9 → 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 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 protocol carries no account name or extension identity.
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
- return (ctx, providerId) => collectOAuthCredentialCandidates(pi, ctx, providerId, credentialReader);
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,