@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.
- package/README.md +6 -4
- package/index.ts +38 -6
- 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.
|
|
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.
|
|
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
|
|
128
|
-
`claude-pool.json`
|
|
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
|
-
//
|
|
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
|
-
|
|
394
|
-
|
|
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.
|
|
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",
|