@zgltyq/pi-provider-claude 1.3.2 → 1.3.3

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.
Files changed (3) hide show
  1. package/README.md +6 -4
  2. package/index.ts +38 -6
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -91,12 +91,13 @@ the assistant message (`stopReason: "error"`) via `message_end`, including Claud
91
91
  is hit, the turn errors once, the pool flips (with a UI notification), and you just
92
92
  resend your message to continue on the other account.
93
93
 
94
- **OAuth compatibility and background token refresh (v1.3.2):** The pool now
94
+ **OAuth compatibility and background token refresh (v1.3.3):** The pool now
95
95
  uses its own Anthropic OAuth login/refresh adapter and the login callback bridge
96
96
  matches pi 0.81.x, where the old runtime OAuth export is no longer available.
97
97
  OAuth access tokens are short-lived. An account that sat idle (e.g. your fallback)
98
98
  would have a dead access token by the time failover switched to it — producing a
99
- `401 authentication_error`. v1.3.2 retains the v1.3.0 keep-warm behavior:
99
+ `401 authentication_error`. v1.3.3 also applies `/claude-pool-add` immediately to
100
+ the running session and excludes accounts whose refresh token is expired or revoked:
100
101
 
101
102
  - A `setInterval` keep-warm loop that refreshes **every** pooled account before it
102
103
  expires, even when idle and not the active account (the timer is `unref()`'d so
@@ -124,8 +125,9 @@ so the extension adds a command to snapshot each login into the pool:
124
125
  /claude-pool-status → verify both are present
125
126
  ```
126
127
 
127
- Restart, and failover activates. `/claude-pool-add <label>` only writes
128
- `claude-pool.json` (it never touches the request path).
128
+ Restart after initially creating the pool to activate failover. Once active,
129
+ `/claude-pool-add <label>` updates both `claude-pool.json` and the running session,
130
+ so replacing a revoked credential no longer requires another restart.
129
131
 
130
132
  ### Alternative: harvest via separate profiles
131
133
 
package/index.ts CHANGED
@@ -307,6 +307,7 @@ const POOL_BG_REFRESH_INTERVAL_MS = 4 * 60_000;
307
307
  let poolAccounts: PoolAccount[] = [];
308
308
  let poolActive = 0;
309
309
  let poolCooldownUntil: number[] = [];
310
+ let poolInvalid: boolean[] = [];
310
311
  let poolBgTimer: ReturnType<typeof setInterval> | undefined;
311
312
  let poolRefreshInFlight = false;
312
313
 
@@ -325,6 +326,7 @@ function loadPool(): boolean {
325
326
  if (accts.length < 2) return false;
326
327
  poolAccounts = accts;
327
328
  poolCooldownUntil = accts.map(() => 0);
329
+ poolInvalid = accts.map(() => false);
328
330
  poolActive = 0;
329
331
  return true;
330
332
  } catch {
@@ -354,21 +356,25 @@ function poolSelectActive(): void {
354
356
  // return to it instead of staying on the fallback account.
355
357
  let pick = -1;
356
358
  for (let i = 0; i < poolAccounts.length; i++) {
357
- if (poolCooldownUntil[i] <= now) {
359
+ if (!poolInvalid[i] && poolCooldownUntil[i] <= now) {
358
360
  pick = i;
359
361
  break;
360
362
  }
361
363
  }
362
364
  if (pick === -1) {
363
- // all cooling down pick the one that frees up soonest
365
+ // All usable accounts are cooling down: pick the one that frees up soonest.
364
366
  let min = Number.POSITIVE_INFINITY;
365
367
  for (let i = 0; i < poolCooldownUntil.length; i++) {
366
- if (poolCooldownUntil[i] < min) {
368
+ if (!poolInvalid[i] && poolCooldownUntil[i] < min) {
367
369
  min = poolCooldownUntil[i];
368
370
  pick = i;
369
371
  }
370
372
  }
371
373
  }
374
+ if (pick === -1) {
375
+ poolLog("all pooled accounts have invalid OAuth credentials");
376
+ return;
377
+ }
372
378
  if (pick !== poolActive) {
373
379
  poolActive = pick;
374
380
  poolLog(`switched active → ${poolAccounts[poolActive].label}`);
@@ -386,13 +392,17 @@ async function poolRefreshAccount(i: number, force: boolean): Promise<boolean> {
386
392
  acct.access = next.access;
387
393
  acct.expires = next.expires;
388
394
  if (next.refresh) acct.refresh = next.refresh;
395
+ poolInvalid[i] = false;
389
396
  persistPool();
390
397
  poolLog(`refreshed ${acct.label} (exp ${new Date(acct.expires).toISOString()})`);
391
398
  return true;
392
399
  } catch (e) {
393
- poolLog(
394
- `refresh FAILED ${acct.label}: ${(e as Error)?.message ?? String(e)}`,
395
- );
400
+ const message = (e as Error)?.message ?? String(e);
401
+ poolLog(`refresh FAILED ${acct.label}: ${message}`);
402
+ if (/invalid_grant|refresh token (?:expired|revoked)|authentication_error|\b401\b/i.test(message)) {
403
+ poolInvalid[i] = true;
404
+ poolSelectActive();
405
+ }
396
406
  return false;
397
407
  }
398
408
  }
@@ -517,6 +527,28 @@ function poolUpsert(
517
527
  else cfg.accounts.push(entry);
518
528
  cfg.enabled = cfg.enabled !== false;
519
529
  writeFileSync(POOL_PATH, JSON.stringify(cfg, null, 2), { mode: 0o600 });
530
+
531
+ // Keep a running session in sync with /claude-pool-add. Previously the file
532
+ // changed but getApiKey() kept serving the stale in-memory token until restart.
533
+ const activeLabel = poolAccounts[poolActive]?.label;
534
+ const previousCooldown = new Map(
535
+ poolAccounts.map((account, i) => [account.label, poolCooldownUntil[i] ?? 0]),
536
+ );
537
+ const previousInvalid = new Map(
538
+ poolAccounts.map((account, i) => [account.label, poolInvalid[i] ?? false]),
539
+ );
540
+ poolAccounts = cfg.accounts;
541
+ poolCooldownUntil = poolAccounts.map((account) =>
542
+ account.label === label ? 0 : (previousCooldown.get(account.label) ?? 0),
543
+ );
544
+ poolInvalid = poolAccounts.map((account) =>
545
+ account.label === label ? false : (previousInvalid.get(account.label) ?? false),
546
+ );
547
+ poolActive = Math.max(
548
+ 0,
549
+ poolAccounts.findIndex((account) => account.label === activeLabel),
550
+ );
551
+ poolSelectActive();
520
552
  return cfg.accounts.length;
521
553
  }
522
554
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zgltyq/pi-provider-claude",
3
- "version": "1.3.2",
3
+ "version": "1.3.3",
4
4
  "description": "Claude (Anthropic OAuth/subscription) compatibility layer for pi-coding-agent — keeps ALL extension tools usable under the Claude subscription by mapping unknown flat tool names to mcp__pi__<name> on the wire instead of dropping them, plus optional multi-account failover. Self-contained fork of the tool half of @benvargas/pi-claude-code-use.",
5
5
  "author": "Leechael",
6
6
  "license": "MIT",