pi-antigravity-rotator 1.3.4 → 1.3.5
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 +1 -1
- package/package.json +1 -1
- package/src/rotator.ts +44 -13
- package/src/types.ts +1 -0
package/README.md
CHANGED
|
@@ -148,7 +148,7 @@ Three mechanisms trigger rotation, scoped to the specific model:
|
|
|
148
148
|
|
|
149
149
|
1. **Quota-based** (primary) -- Polls the Google quota API every 5 minutes. When a model's remaining quota drops by `rotateOnQuotaDrop` percentage points (default: 20%), that model rotates to the next account. Other models stay on their current accounts.
|
|
150
150
|
|
|
151
|
-
2. **Request-count** (fallback) --
|
|
151
|
+
2. **Request-count** (fallback) -- Before forwarding a request, the rotator checks how many requests the current account has already served for that specific model and rotates once it reaches `requestsPerRotation` (default: 5). By default this fallback is only used when quota data for that model is still unknown.
|
|
152
152
|
|
|
153
153
|
3. **429 failover** (reactive) -- On rate limit, the account is marked exhausted with a parsed retry cooldown and the affected model immediately switches.
|
|
154
154
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-antigravity-rotator",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.5",
|
|
4
4
|
"description": "Multi-account rotation proxy for Google Antigravity with per-model routing, real-time quota tracking, and infringement detection",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
package/src/rotator.ts
CHANGED
|
@@ -78,6 +78,7 @@ export class AccountRotator {
|
|
|
78
78
|
this.modelState.set(model, {
|
|
79
79
|
activeAccountIndex: Math.min(idx, this.accounts.length - 1),
|
|
80
80
|
quotaAtRotationStart: -1,
|
|
81
|
+
requestsOnActiveAccount: 0,
|
|
81
82
|
});
|
|
82
83
|
}
|
|
83
84
|
}
|
|
@@ -369,6 +370,29 @@ export class AccountRotator {
|
|
|
369
370
|
return best;
|
|
370
371
|
}
|
|
371
372
|
|
|
373
|
+
private countModelAssignment(modelKey: string): void {
|
|
374
|
+
const state = this.modelState.get(modelKey);
|
|
375
|
+
if (state) {
|
|
376
|
+
state.requestsOnActiveAccount++;
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
private shouldRotateBeforeRequest(account: AccountRuntime, modelKey: string, state: ModelRotationState | null): boolean {
|
|
381
|
+
return (
|
|
382
|
+
!!state &&
|
|
383
|
+
this.shouldUseRequestCountRotation(account, modelKey) &&
|
|
384
|
+
state.requestsOnActiveAccount >= this.config.requestsPerRotation
|
|
385
|
+
);
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
private async rotateModelForRequest(modelKey: string, now: number = Date.now(), excludeIdx?: number): Promise<AccountRuntime | null> {
|
|
389
|
+
const account = await this.rotateModel(modelKey, now, excludeIdx);
|
|
390
|
+
if (account) {
|
|
391
|
+
this.countModelAssignment(modelKey);
|
|
392
|
+
}
|
|
393
|
+
return account;
|
|
394
|
+
}
|
|
395
|
+
|
|
372
396
|
// =========================================================================
|
|
373
397
|
// Account Selection (per-model)
|
|
374
398
|
// =========================================================================
|
|
@@ -388,12 +412,26 @@ export class AccountRotator {
|
|
|
388
412
|
if (current && this.isAvailable(current, now)) {
|
|
389
413
|
// Check if this account has quota for the requested model
|
|
390
414
|
if (modelKey) {
|
|
415
|
+
if (this.shouldRotateBeforeRequest(current, modelKey, state ?? null)) {
|
|
416
|
+
this.log(
|
|
417
|
+
`${current.config.label || current.config.email} [${modelKey}]: hit rotation threshold (${this.config.requestsPerRotation})`,
|
|
418
|
+
);
|
|
419
|
+
const rotated = await this.rotateModelForRequest(modelKey, now, idx);
|
|
420
|
+
if (rotated) {
|
|
421
|
+
current.requestsSinceRotation = 0;
|
|
422
|
+
return rotated;
|
|
423
|
+
}
|
|
424
|
+
this.log(
|
|
425
|
+
`${current.config.label || current.config.email} [${modelKey}]: threshold reached but no replacement is available, staying`,
|
|
426
|
+
"warn",
|
|
427
|
+
);
|
|
428
|
+
}
|
|
391
429
|
const quota = this.getModelQuota(current, modelKey);
|
|
392
430
|
if (quota === 0) {
|
|
393
431
|
this.log(
|
|
394
432
|
`${current.config.label || current.config.email} [${modelKey}]: 0% quota, skipping`,
|
|
395
433
|
);
|
|
396
|
-
return this.
|
|
434
|
+
return this.rotateModelForRequest(modelKey);
|
|
397
435
|
}
|
|
398
436
|
if (!this.isFreshWindowAllowed(current, modelKey)) {
|
|
399
437
|
const label = current.config.label || current.config.email;
|
|
@@ -403,12 +441,13 @@ export class AccountRotator {
|
|
|
403
441
|
: `${label} [${modelKey}]: fresh window blocked by operator toggle`,
|
|
404
442
|
"warn",
|
|
405
443
|
);
|
|
406
|
-
return this.
|
|
444
|
+
return this.rotateModelForRequest(modelKey);
|
|
407
445
|
}
|
|
408
446
|
}
|
|
409
447
|
this.startRequest(current);
|
|
410
448
|
try {
|
|
411
449
|
await this.ensureValidToken(current);
|
|
450
|
+
if (modelKey) this.countModelAssignment(modelKey);
|
|
412
451
|
return current;
|
|
413
452
|
} catch (err) {
|
|
414
453
|
this.finishRequest(current);
|
|
@@ -418,7 +457,7 @@ export class AccountRotator {
|
|
|
418
457
|
|
|
419
458
|
// Current unavailable, or no per-model assignment yet
|
|
420
459
|
if (modelKey) {
|
|
421
|
-
return this.
|
|
460
|
+
return this.rotateModelForRequest(modelKey, now, state ? idx : -1);
|
|
422
461
|
}
|
|
423
462
|
return this.rotateDefault();
|
|
424
463
|
}
|
|
@@ -438,6 +477,7 @@ export class AccountRotator {
|
|
|
438
477
|
this.modelState.set(modelKey, {
|
|
439
478
|
activeAccountIndex: newIdx,
|
|
440
479
|
quotaAtRotationStart: quota,
|
|
480
|
+
requestsOnActiveAccount: 0,
|
|
441
481
|
});
|
|
442
482
|
this.log(
|
|
443
483
|
`[${modelKey}] Rotated to ${best.config.label || best.config.email} [${timerType}] (quota: ${quota >= 0 ? quota + "%" : "unknown"})`,
|
|
@@ -540,17 +580,8 @@ export class AccountRotator {
|
|
|
540
580
|
account.consecutiveErrors = 0;
|
|
541
581
|
account.lastError = null;
|
|
542
582
|
|
|
543
|
-
const shouldRotate =
|
|
544
|
-
this.shouldUseRequestCountRotation(account, model) &&
|
|
545
|
-
account.requestsSinceRotation >= this.config.requestsPerRotation;
|
|
546
|
-
if (shouldRotate) {
|
|
547
|
-
account.requestsSinceRotation = 0;
|
|
548
|
-
this.log(
|
|
549
|
-
`${account.config.label || account.config.email}: hit rotation threshold (${this.config.requestsPerRotation})`,
|
|
550
|
-
);
|
|
551
|
-
}
|
|
552
583
|
this.saveState();
|
|
553
|
-
return
|
|
584
|
+
return false;
|
|
554
585
|
}
|
|
555
586
|
|
|
556
587
|
// Mark an account as exhausted (429 or quota exceeded)
|
package/src/types.ts
CHANGED
|
@@ -117,6 +117,7 @@ export interface AccountRuntime {
|
|
|
117
117
|
export interface ModelRotationState {
|
|
118
118
|
activeAccountIndex: number;
|
|
119
119
|
quotaAtRotationStart: number; // quota % when this account became active for this model
|
|
120
|
+
requestsOnActiveAccount: number;
|
|
120
121
|
}
|
|
121
122
|
|
|
122
123
|
// Persisted state across restarts
|