antigravity-proxy / src /errors.js
Yash030's picture
Initial Commit
bd925df
/**
* Custom Error Classes
*
* Provides structured error types for better error handling and classification.
* Replaces string-based error detection with proper error class checking.
*/
/**
* Base error class for Antigravity proxy errors
*/
export class AntigravityError extends Error {
/**
* @param {string} message - Error message
* @param {string} code - Error code for programmatic handling
* @param {boolean} retryable - Whether the error is retryable
* @param {Object} metadata - Additional error metadata
*/
constructor(message, code, retryable = false, metadata = {}) {
super(message);
this.name = 'AntigravityError';
this.code = code;
this.retryable = retryable;
this.metadata = metadata;
}
/**
* Convert to JSON for API responses
*/
toJSON() {
return {
name: this.name,
code: this.code,
message: this.message,
retryable: this.retryable,
...this.metadata
};
}
}
/**
* Rate limit error (429 / RESOURCE_EXHAUSTED)
*/
export class RateLimitError extends AntigravityError {
/**
* @param {string} message - Error message
* @param {number|null} resetMs - Time in ms until rate limit resets
* @param {string} accountEmail - Email of the rate-limited account
*/
constructor(message, resetMs = null, accountEmail = null) {
super(message, 'RATE_LIMITED', true, { resetMs, accountEmail });
this.name = 'RateLimitError';
this.resetMs = resetMs;
this.accountEmail = accountEmail;
}
}
/**
* Authentication error (invalid credentials, token expired, etc.)
*/
export class AuthError extends AntigravityError {
/**
* @param {string} message - Error message
* @param {string} accountEmail - Email of the account with auth issues
* @param {string} reason - Specific reason for auth failure
*/
constructor(message, accountEmail = null, reason = null) {
super(message, 'AUTH_INVALID', false, { accountEmail, reason });
this.name = 'AuthError';
this.accountEmail = accountEmail;
this.reason = reason;
}
}
/**
* No accounts available error
*/
export class NoAccountsError extends AntigravityError {
/**
* @param {string} message - Error message
* @param {boolean} allRateLimited - Whether all accounts are rate limited
*/
constructor(message = 'No accounts available', allRateLimited = false) {
super(message, 'NO_ACCOUNTS', allRateLimited, { allRateLimited });
this.name = 'NoAccountsError';
this.allRateLimited = allRateLimited;
}
}
/**
* Max retries exceeded error
*/
export class MaxRetriesError extends AntigravityError {
/**
* @param {string} message - Error message
* @param {number} attempts - Number of attempts made
*/
constructor(message = 'Max retries exceeded', attempts = 0) {
super(message, 'MAX_RETRIES', false, { attempts });
this.name = 'MaxRetriesError';
this.attempts = attempts;
}
}
/**
* API error from upstream service
*/
export class ApiError extends AntigravityError {
/**
* @param {string} message - Error message
* @param {number} statusCode - HTTP status code
* @param {string} errorType - Type of API error
*/
constructor(message, statusCode = 500, errorType = 'api_error') {
super(message, errorType.toUpperCase(), statusCode >= 500, { statusCode, errorType });
this.name = 'ApiError';
this.statusCode = statusCode;
this.errorType = errorType;
}
}
/**
* Native module error (version mismatch, rebuild required)
*/
export class NativeModuleError extends AntigravityError {
/**
* @param {string} message - Error message
* @param {boolean} rebuildSucceeded - Whether auto-rebuild succeeded
* @param {boolean} restartRequired - Whether server restart is needed
*/
constructor(message, rebuildSucceeded = false, restartRequired = false) {
super(message, 'NATIVE_MODULE_ERROR', false, { rebuildSucceeded, restartRequired });
this.name = 'NativeModuleError';
this.rebuildSucceeded = rebuildSucceeded;
this.restartRequired = restartRequired;
}
}
/**
* Empty response error - thrown when API returns no content
* Used to trigger retry logic in streaming handler
*/
export class EmptyResponseError extends AntigravityError {
/**
* @param {string} message - Error message
*/
constructor(message = 'No content received from API') {
super(message, 'EMPTY_RESPONSE', true, {});
this.name = 'EmptyResponseError';
}
}
/**
* Capacity exhausted error - Google's model is at capacity (not user quota)
* Should retry on same account with shorter delay, not switch accounts immediately
* Different from QUOTA_EXHAUSTED which indicates user's daily/hourly limit
*/
export class CapacityExhaustedError extends AntigravityError {
/**
* @param {string} message - Error message
* @param {number|null} retryAfterMs - Suggested retry delay in ms
*/
constructor(message = 'Model capacity exhausted', retryAfterMs = null) {
super(message, 'CAPACITY_EXHAUSTED', true, { retryAfterMs });
this.name = 'CapacityExhaustedError';
this.retryAfterMs = retryAfterMs;
}
}
/**
* Account forbidden error (403 VALIDATION_REQUIRED / PERMISSION_DENIED)
* These are account-level errors where the account needs validation or has been
* disabled. Trying different endpoints won't help - need to rotate to another account.
*/
export class AccountForbiddenError extends AntigravityError {
/**
* @param {string} message - Error message
* @param {string} accountEmail - Email of the forbidden account
*/
constructor(message, accountEmail = null) {
super(message, 'ACCOUNT_FORBIDDEN', false, { accountEmail });
this.name = 'AccountForbiddenError';
this.accountEmail = accountEmail;
}
}
/**
* Check if an error is an account forbidden error (403 VALIDATION_REQUIRED / PERMISSION_DENIED)
* These errors indicate the account itself is blocked and need account rotation, not endpoint rotation.
* @param {Error} error - Error to check
* @returns {boolean}
*/
export function isAccountForbiddenError(error) {
if (error instanceof AccountForbiddenError) return true;
// Fallback string check only for errors that couldn't use the typed class
// (e.g., errors crossing module boundaries). Only match our own prefixed format.
const msg = (error.message || '');
return msg.startsWith('ACCOUNT_FORBIDDEN:');
}
/**
* Check if an error is a rate limit error
* Works with both custom error classes and legacy string-based errors
* @param {Error} error - Error to check
* @returns {boolean}
*/
export function isRateLimitError(error) {
if (error instanceof RateLimitError) return true;
const msg = (error.message || '').toLowerCase();
return msg.includes('429') ||
msg.includes('resource_exhausted') ||
msg.includes('quota_exhausted') ||
msg.includes('rate limit');
}
/**
* Check if an error is an authentication error
* Works with both custom error classes and legacy string-based errors
* @param {Error} error - Error to check
* @returns {boolean}
*/
export function isAuthError(error) {
if (error instanceof AuthError) return true;
const msg = (error.message || '').toUpperCase();
return msg.includes('AUTH_INVALID') ||
msg.includes('INVALID_GRANT') ||
msg.includes('TOKEN REFRESH FAILED');
}
/**
* Check if an error is an empty response error
* @param {Error} error - Error to check
* @returns {boolean}
*/
export function isEmptyResponseError(error) {
return error instanceof EmptyResponseError ||
error?.name === 'EmptyResponseError';
}
/**
* Check if an error is a capacity exhausted error (model overload, not user quota)
* This is different from quota exhaustion - capacity issues are temporary infrastructure
* limits that should be retried on the SAME account with shorter delays
* @param {Error} error - Error to check
* @returns {boolean}
*/
export function isCapacityExhaustedError(error) {
if (error instanceof CapacityExhaustedError) return true;
const msg = (error.message || '').toLowerCase();
return msg.includes('model_capacity_exhausted') ||
msg.includes('capacity_exhausted') ||
msg.includes('model is currently overloaded') ||
msg.includes('service temporarily unavailable');
}
export default {
AntigravityError,
RateLimitError,
AuthError,
AccountForbiddenError,
NoAccountsError,
MaxRetriesError,
ApiError,
NativeModuleError,
EmptyResponseError,
CapacityExhaustedError,
isRateLimitError,
isAuthError,
isAccountForbiddenError,
isEmptyResponseError,
isCapacityExhaustedError
};