antigravity-claude-proxy 2.7.1 → 2.7.2

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/src/errors.js CHANGED
@@ -166,6 +166,37 @@ export class CapacityExhaustedError extends AntigravityError {
166
166
  }
167
167
  }
168
168
 
169
+ /**
170
+ * Account forbidden error (403 VALIDATION_REQUIRED / PERMISSION_DENIED)
171
+ * These are account-level errors where the account needs validation or has been
172
+ * disabled. Trying different endpoints won't help - need to rotate to another account.
173
+ */
174
+ export class AccountForbiddenError extends AntigravityError {
175
+ /**
176
+ * @param {string} message - Error message
177
+ * @param {string} accountEmail - Email of the forbidden account
178
+ */
179
+ constructor(message, accountEmail = null) {
180
+ super(message, 'ACCOUNT_FORBIDDEN', false, { accountEmail });
181
+ this.name = 'AccountForbiddenError';
182
+ this.accountEmail = accountEmail;
183
+ }
184
+ }
185
+
186
+ /**
187
+ * Check if an error is an account forbidden error (403 VALIDATION_REQUIRED / PERMISSION_DENIED)
188
+ * These errors indicate the account itself is blocked and need account rotation, not endpoint rotation.
189
+ * @param {Error} error - Error to check
190
+ * @returns {boolean}
191
+ */
192
+ export function isAccountForbiddenError(error) {
193
+ if (error instanceof AccountForbiddenError) return true;
194
+ // Fallback string check only for errors that couldn't use the typed class
195
+ // (e.g., errors crossing module boundaries). Only match our own prefixed format.
196
+ const msg = (error.message || '');
197
+ return msg.startsWith('ACCOUNT_FORBIDDEN:');
198
+ }
199
+
169
200
  /**
170
201
  * Check if an error is a rate limit error
171
202
  * Works with both custom error classes and legacy string-based errors
@@ -225,6 +256,7 @@ export default {
225
256
  AntigravityError,
226
257
  RateLimitError,
227
258
  AuthError,
259
+ AccountForbiddenError,
228
260
  NoAccountsError,
229
261
  MaxRetriesError,
230
262
  ApiError,
@@ -233,6 +265,7 @@ export default {
233
265
  CapacityExhaustedError,
234
266
  isRateLimitError,
235
267
  isAuthError,
268
+ isAccountForbiddenError,
236
269
  isEmptyResponseError,
237
270
  isCapacityExhaustedError
238
271
  };
@@ -1,6 +1,7 @@
1
1
  import { readFileSync } from 'fs';
2
2
  import { fileURLToPath } from 'url';
3
3
  import path from 'path';
4
+ import { config } from '../config.js';
4
5
 
5
6
  /**
6
7
  * Shared Utility Functions
@@ -71,6 +72,23 @@ export function isNetworkError(error) {
71
72
  );
72
73
  }
73
74
 
75
+ /**
76
+ * Throttled fetch that applies a configurable delay before each request
77
+ * Only applies delay when requestThrottlingEnabled is true
78
+ * @param {string|URL} url - The URL to fetch
79
+ * @param {RequestInit} [options] - Fetch options
80
+ * @returns {Promise<Response>} Fetch response
81
+ */
82
+ export async function throttledFetch(url, options) {
83
+ if (config.requestThrottlingEnabled) {
84
+ const delayMs = config.requestDelayMs || 200;
85
+ if (delayMs > 0) {
86
+ await sleep(delayMs);
87
+ }
88
+ }
89
+ return fetch(url, options);
90
+ }
91
+
74
92
  /**
75
93
  * Generate random jitter for backoff timing (Thundering Herd Prevention)
76
94
  * Prevents all clients from retrying at the exact same moment after errors.
@@ -292,6 +292,15 @@ export function mountWebUI(app, dirname, accountManager) {
292
292
  const { email } = req.params;
293
293
  accountManager.clearTokenCache(email);
294
294
  accountManager.clearProjectCache(email);
295
+
296
+ // For verification errors (403 VALIDATION_REQUIRED), clear isInvalid on refresh.
297
+ // The user has completed verification on Google's site and clicks Refresh to re-enable.
298
+ // Auth errors (no verifyUrl) still require OAuth re-auth via FIX button.
299
+ const account = accountManager.getAllAccounts().find(a => a.email === email);
300
+ if (account && account.isInvalid && account.verifyUrl) {
301
+ accountManager.clearInvalid(email);
302
+ }
303
+
295
304
  res.json({
296
305
  status: 'ok',
297
306
  message: `Token cache cleared for ${email}`
@@ -644,7 +653,7 @@ export function mountWebUI(app, dirname, accountManager) {
644
653
  */
645
654
  app.post('/api/config', async (req, res) => {
646
655
  try {
647
- const { debug, devMode, logLevel, persistTokenCache } = req.body;
656
+ const { debug, devMode, logLevel, persistTokenCache, requestThrottlingEnabled, requestDelayMs } = req.body;
648
657
 
649
658
  // Validate tunable config fields via shared helper
650
659
  const updates = validateConfigFields(req.body);
@@ -665,6 +674,12 @@ export function mountWebUI(app, dirname, accountManager) {
665
674
  if (typeof persistTokenCache === 'boolean') {
666
675
  updates.persistTokenCache = persistTokenCache;
667
676
  }
677
+ if (typeof requestThrottlingEnabled === 'boolean') {
678
+ updates.requestThrottlingEnabled = requestThrottlingEnabled;
679
+ }
680
+ if (typeof requestDelayMs === 'number' && requestDelayMs >= 100 && requestDelayMs <= 5000) {
681
+ updates.requestDelayMs = requestDelayMs;
682
+ }
668
683
 
669
684
  if (Object.keys(updates).length === 0) {
670
685
  return res.status(400).json({