@kamel-ahmed/proxy-claude 1.0.0

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 (84) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +622 -0
  3. package/bin/cli.js +124 -0
  4. package/package.json +80 -0
  5. package/public/app.js +228 -0
  6. package/public/css/src/input.css +523 -0
  7. package/public/css/style.css +1 -0
  8. package/public/favicon.svg +10 -0
  9. package/public/index.html +381 -0
  10. package/public/js/components/account-manager.js +245 -0
  11. package/public/js/components/claude-config.js +420 -0
  12. package/public/js/components/dashboard/charts.js +589 -0
  13. package/public/js/components/dashboard/filters.js +362 -0
  14. package/public/js/components/dashboard/stats.js +110 -0
  15. package/public/js/components/dashboard.js +236 -0
  16. package/public/js/components/logs-viewer.js +100 -0
  17. package/public/js/components/models.js +36 -0
  18. package/public/js/components/server-config.js +349 -0
  19. package/public/js/config/constants.js +102 -0
  20. package/public/js/data-store.js +386 -0
  21. package/public/js/settings-store.js +58 -0
  22. package/public/js/store.js +78 -0
  23. package/public/js/translations/en.js +351 -0
  24. package/public/js/translations/id.js +396 -0
  25. package/public/js/translations/pt.js +287 -0
  26. package/public/js/translations/tr.js +342 -0
  27. package/public/js/translations/zh.js +357 -0
  28. package/public/js/utils/account-actions.js +189 -0
  29. package/public/js/utils/error-handler.js +96 -0
  30. package/public/js/utils/model-config.js +42 -0
  31. package/public/js/utils/validators.js +77 -0
  32. package/public/js/utils.js +69 -0
  33. package/public/views/accounts.html +329 -0
  34. package/public/views/dashboard.html +484 -0
  35. package/public/views/logs.html +97 -0
  36. package/public/views/models.html +331 -0
  37. package/public/views/settings.html +1329 -0
  38. package/src/account-manager/credentials.js +243 -0
  39. package/src/account-manager/index.js +380 -0
  40. package/src/account-manager/onboarding.js +117 -0
  41. package/src/account-manager/rate-limits.js +237 -0
  42. package/src/account-manager/storage.js +136 -0
  43. package/src/account-manager/strategies/base-strategy.js +104 -0
  44. package/src/account-manager/strategies/hybrid-strategy.js +195 -0
  45. package/src/account-manager/strategies/index.js +79 -0
  46. package/src/account-manager/strategies/round-robin-strategy.js +76 -0
  47. package/src/account-manager/strategies/sticky-strategy.js +138 -0
  48. package/src/account-manager/strategies/trackers/health-tracker.js +162 -0
  49. package/src/account-manager/strategies/trackers/index.js +8 -0
  50. package/src/account-manager/strategies/trackers/token-bucket-tracker.js +121 -0
  51. package/src/auth/database.js +169 -0
  52. package/src/auth/oauth.js +419 -0
  53. package/src/auth/token-extractor.js +117 -0
  54. package/src/cli/accounts.js +512 -0
  55. package/src/cli/refresh.js +201 -0
  56. package/src/cli/setup.js +338 -0
  57. package/src/cloudcode/index.js +29 -0
  58. package/src/cloudcode/message-handler.js +386 -0
  59. package/src/cloudcode/model-api.js +248 -0
  60. package/src/cloudcode/rate-limit-parser.js +181 -0
  61. package/src/cloudcode/request-builder.js +93 -0
  62. package/src/cloudcode/session-manager.js +47 -0
  63. package/src/cloudcode/sse-parser.js +121 -0
  64. package/src/cloudcode/sse-streamer.js +293 -0
  65. package/src/cloudcode/streaming-handler.js +492 -0
  66. package/src/config.js +107 -0
  67. package/src/constants.js +278 -0
  68. package/src/errors.js +238 -0
  69. package/src/fallback-config.js +29 -0
  70. package/src/format/content-converter.js +193 -0
  71. package/src/format/index.js +20 -0
  72. package/src/format/request-converter.js +248 -0
  73. package/src/format/response-converter.js +120 -0
  74. package/src/format/schema-sanitizer.js +673 -0
  75. package/src/format/signature-cache.js +88 -0
  76. package/src/format/thinking-utils.js +558 -0
  77. package/src/index.js +146 -0
  78. package/src/modules/usage-stats.js +205 -0
  79. package/src/server.js +861 -0
  80. package/src/utils/claude-config.js +245 -0
  81. package/src/utils/helpers.js +51 -0
  82. package/src/utils/logger.js +142 -0
  83. package/src/utils/native-module-helper.js +162 -0
  84. package/src/webui/index.js +707 -0
@@ -0,0 +1,278 @@
1
+ /**
2
+ * Constants for Antigravity Cloud Code API integration
3
+ * Based on: https://github.com/NoeFabris/opencode-antigravity-auth
4
+ */
5
+
6
+ import { homedir, platform, arch } from 'os';
7
+ import { join } from 'path';
8
+ import { config } from './config.js';
9
+
10
+ /**
11
+ * Get the Antigravity database path based on the current platform.
12
+ * - macOS: ~/Library/Application Support/Antigravity/...
13
+ * - Windows: ~/AppData/Roaming/Antigravity/...
14
+ * - Linux/other: ~/.config/Antigravity/...
15
+ * @returns {string} Full path to the Antigravity state database
16
+ */
17
+ function getAntigravityDbPath() {
18
+ const home = homedir();
19
+ switch (platform()) {
20
+ case 'darwin':
21
+ return join(home, 'Library/Application Support/Antigravity/User/globalStorage/state.vscdb');
22
+ case 'win32':
23
+ return join(home, 'AppData/Roaming/Antigravity/User/globalStorage/state.vscdb');
24
+ default: // linux, freebsd, etc.
25
+ return join(home, '.config/Antigravity/User/globalStorage/state.vscdb');
26
+ }
27
+ }
28
+
29
+ /**
30
+ * Generate platform-specific User-Agent string.
31
+ * @returns {string} User-Agent in format "antigravity/version os/arch"
32
+ */
33
+ function getPlatformUserAgent() {
34
+ const os = platform();
35
+ const architecture = arch();
36
+ return `antigravity/1.11.5 ${os}/${architecture}`;
37
+ }
38
+
39
+ // Cloud Code API endpoints (in fallback order)
40
+ const ANTIGRAVITY_ENDPOINT_DAILY = 'https://daily-cloudcode-pa.googleapis.com';
41
+ const ANTIGRAVITY_ENDPOINT_PROD = 'https://cloudcode-pa.googleapis.com';
42
+
43
+ // Endpoint fallback order (daily → prod)
44
+ export const ANTIGRAVITY_ENDPOINT_FALLBACKS = [
45
+ ANTIGRAVITY_ENDPOINT_DAILY,
46
+ ANTIGRAVITY_ENDPOINT_PROD
47
+ ];
48
+
49
+ // Required headers for Antigravity API requests
50
+ export const ANTIGRAVITY_HEADERS = {
51
+ 'User-Agent': getPlatformUserAgent(),
52
+ 'X-Goog-Api-Client': 'google-cloud-sdk vscode_cloudshelleditor/0.1',
53
+ 'Client-Metadata': JSON.stringify({
54
+ ideType: 'IDE_UNSPECIFIED',
55
+ platform: 'PLATFORM_UNSPECIFIED',
56
+ pluginType: 'GEMINI'
57
+ })
58
+ };
59
+
60
+ // Endpoint order for loadCodeAssist (prod first)
61
+ // loadCodeAssist works better on prod for fresh/unprovisioned accounts
62
+ export const LOAD_CODE_ASSIST_ENDPOINTS = [
63
+ ANTIGRAVITY_ENDPOINT_PROD,
64
+ ANTIGRAVITY_ENDPOINT_DAILY
65
+ ];
66
+
67
+ // Endpoint order for onboardUser (same as generateContent fallbacks)
68
+ export const ONBOARD_USER_ENDPOINTS = ANTIGRAVITY_ENDPOINT_FALLBACKS;
69
+
70
+ // Headers for loadCodeAssist API
71
+ export const LOAD_CODE_ASSIST_HEADERS = ANTIGRAVITY_HEADERS;
72
+
73
+ // Default project ID if none can be discovered
74
+ export const DEFAULT_PROJECT_ID = 'rising-fact-p41fc';
75
+
76
+ // Configurable constants - values from config.json take precedence
77
+ export const TOKEN_REFRESH_INTERVAL_MS = config?.tokenCacheTtlMs || (5 * 60 * 1000); // From config or 5 minutes
78
+ export const REQUEST_BODY_LIMIT = config?.requestBodyLimit || '50mb';
79
+ export const ANTIGRAVITY_AUTH_PORT = 9092;
80
+ export const DEFAULT_PORT = config?.port || 8080;
81
+
82
+ // Multi-account configuration
83
+ export const ACCOUNT_CONFIG_PATH = config?.accountConfigPath || join(
84
+ homedir(),
85
+ '.config/antigravity-proxy/accounts.json'
86
+ );
87
+
88
+ // Usage history persistence path
89
+ export const USAGE_HISTORY_PATH = join(
90
+ homedir(),
91
+ '.config/antigravity-proxy/usage-history.json'
92
+ );
93
+
94
+ // Antigravity app database path (for legacy single-account token extraction)
95
+ // Uses platform-specific path detection
96
+ export const ANTIGRAVITY_DB_PATH = getAntigravityDbPath();
97
+
98
+ export const DEFAULT_COOLDOWN_MS = config?.defaultCooldownMs || (10 * 1000); // From config or 10 seconds
99
+ export const MAX_RETRIES = config?.maxRetries || 5; // From config or 5
100
+ export const MAX_EMPTY_RESPONSE_RETRIES = 2; // Max retries for empty API responses (from upstream)
101
+ export const MAX_ACCOUNTS = config?.maxAccounts || 10; // From config or 10
102
+
103
+ // Rate limit wait thresholds
104
+ export const MAX_WAIT_BEFORE_ERROR_MS = config?.maxWaitBeforeErrorMs || 120000; // From config or 2 minutes
105
+
106
+ // Gap 1: Retry deduplication - prevents thundering herd on concurrent rate limits
107
+ export const RATE_LIMIT_DEDUP_WINDOW_MS = config?.rateLimitDedupWindowMs || 5000; // 5 seconds
108
+
109
+ // Gap 2: Consecutive failure tracking - extended cooldown after repeated failures
110
+ export const MAX_CONSECUTIVE_FAILURES = config?.maxConsecutiveFailures || 3;
111
+ export const EXTENDED_COOLDOWN_MS = config?.extendedCooldownMs || 60000; // 1 minute
112
+
113
+ // Gap 4: Capacity exhaustion - shorter retry for model capacity issues (not quota)
114
+ export const CAPACITY_RETRY_DELAY_MS = config?.capacityRetryDelayMs || 2000; // 2 seconds
115
+ export const MAX_CAPACITY_RETRIES = config?.maxCapacityRetries || 3;
116
+
117
+ // Thinking model constants
118
+ export const MIN_SIGNATURE_LENGTH = 50; // Minimum valid thinking signature length
119
+
120
+ // Account selection strategies
121
+ export const SELECTION_STRATEGIES = ['sticky', 'round-robin', 'hybrid'];
122
+ export const DEFAULT_SELECTION_STRATEGY = 'hybrid';
123
+
124
+ // Strategy display labels
125
+ export const STRATEGY_LABELS = {
126
+ 'sticky': 'Sticky (Cache Optimized)',
127
+ 'round-robin': 'Round Robin (Load Balanced)',
128
+ 'hybrid': 'Hybrid (Smart Distribution)'
129
+ };
130
+
131
+ // Gemini-specific limits
132
+ export const GEMINI_MAX_OUTPUT_TOKENS = 16384;
133
+
134
+ // Gemini signature handling
135
+ // Sentinel value to skip thought signature validation when Claude Code strips the field
136
+ // See: https://ai.google.dev/gemini-api/docs/thought-signatures
137
+ export const GEMINI_SKIP_SIGNATURE = 'skip_thought_signature_validator';
138
+
139
+ // Cache TTL for Gemini thoughtSignatures (2 hours)
140
+ export const GEMINI_SIGNATURE_CACHE_TTL_MS = 2 * 60 * 60 * 1000;
141
+
142
+ /**
143
+ * Get the model family from model name (dynamic detection, no hardcoded list).
144
+ * @param {string} modelName - The model name from the request
145
+ * @returns {'claude' | 'gemini' | 'unknown'} The model family
146
+ */
147
+ export function getModelFamily(modelName) {
148
+ const lower = (modelName || '').toLowerCase();
149
+ if (lower.includes('claude')) return 'claude';
150
+ if (lower.includes('gemini')) return 'gemini';
151
+ return 'unknown';
152
+ }
153
+
154
+ /**
155
+ * Check if a model supports thinking/reasoning output.
156
+ * @param {string} modelName - The model name from the request
157
+ * @returns {boolean} True if the model supports thinking blocks
158
+ */
159
+ export function isThinkingModel(modelName) {
160
+ const lower = (modelName || '').toLowerCase();
161
+ // Claude thinking models have "thinking" in the name
162
+ if (lower.includes('claude') && lower.includes('thinking')) return true;
163
+ // Gemini thinking models: explicit "thinking" in name, OR gemini version 3+
164
+ if (lower.includes('gemini')) {
165
+ if (lower.includes('thinking')) return true;
166
+ // Check for gemini-3 or higher (e.g., gemini-3, gemini-3.5, gemini-4, etc.)
167
+ const versionMatch = lower.match(/gemini-(\d+)/);
168
+ if (versionMatch && parseInt(versionMatch[1], 10) >= 3) return true;
169
+ }
170
+ return false;
171
+ }
172
+
173
+ // Google OAuth configuration (from opencode-antigravity-auth)
174
+ export const OAUTH_CONFIG = {
175
+ clientId: '1071006060591-tmhssin2h21lcre235vtolojh4g403ep.apps.googleusercontent.com',
176
+ clientSecret: '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',
177
+ authUrl: 'https://accounts.google.com/o/oauth2/v2/auth',
178
+ tokenUrl: 'https://oauth2.googleapis.com/token',
179
+ userInfoUrl: 'https://www.googleapis.com/oauth2/v1/userinfo',
180
+ callbackPort: 51121,
181
+ scopes: [
182
+ 'https://www.googleapis.com/auth/cloud-platform',
183
+ 'https://www.googleapis.com/auth/userinfo.email',
184
+ 'https://www.googleapis.com/auth/userinfo.profile',
185
+ 'https://www.googleapis.com/auth/cclog',
186
+ 'https://www.googleapis.com/auth/experimentsandconfigs'
187
+ ]
188
+ };
189
+ export const OAUTH_REDIRECT_URI = `http://localhost:${OAUTH_CONFIG.callbackPort}/oauth-callback`;
190
+
191
+ // Minimal Antigravity system instruction (from CLIProxyAPI)
192
+ // Only includes the essential identity portion to reduce token usage and improve response quality
193
+ // Reference: GitHub issue #76, CLIProxyAPI, gcli2api
194
+ export const ANTIGRAVITY_SYSTEM_INSTRUCTION = `You are Antigravity, a powerful agentic AI coding assistant designed by the Google Deepmind team working on Advanced Agentic Coding.You are pair programming with a USER to solve their coding task. The task may require creating a new codebase, modifying or debugging an existing codebase, or simply answering a question.**Absolute paths only****Proactiveness**`;
195
+
196
+ // Model fallback mapping - maps primary model to fallback when quota exhausted
197
+ export const MODEL_FALLBACK_MAP = {
198
+ 'gemini-3-pro-high': 'claude-opus-4-5-thinking',
199
+ 'gemini-3-pro-low': 'claude-sonnet-4-5',
200
+ 'gemini-3-flash': 'claude-sonnet-4-5-thinking',
201
+ 'claude-opus-4-5-thinking': 'gemini-3-pro-high',
202
+ 'claude-sonnet-4-5-thinking': 'gemini-3-flash',
203
+ 'claude-sonnet-4-5': 'gemini-3-flash'
204
+ };
205
+
206
+ // Default test models for each family (used by test suite)
207
+ export const TEST_MODELS = {
208
+ claude: 'claude-sonnet-4-5-thinking',
209
+ gemini: 'gemini-3-flash'
210
+ };
211
+
212
+ // Default Claude CLI presets (used by WebUI settings)
213
+ export const DEFAULT_PRESETS = [
214
+ {
215
+ name: 'Claude Thinking',
216
+ config: {
217
+ ANTHROPIC_AUTH_TOKEN: 'test',
218
+ ANTHROPIC_BASE_URL: 'http://localhost:8080',
219
+ ANTHROPIC_MODEL: 'claude-opus-4-5-thinking',
220
+ ANTHROPIC_DEFAULT_OPUS_MODEL: 'claude-opus-4-5-thinking',
221
+ ANTHROPIC_DEFAULT_SONNET_MODEL: 'claude-sonnet-4-5-thinking',
222
+ ANTHROPIC_DEFAULT_HAIKU_MODEL: 'gemini-2.5-flash-lite[1m]',
223
+ CLAUDE_CODE_SUBAGENT_MODEL: 'claude-sonnet-4-5-thinking',
224
+ ENABLE_EXPERIMENTAL_MCP_CLI: 'true'
225
+ }
226
+ },
227
+ {
228
+ name: 'Gemini 1M',
229
+ config: {
230
+ ANTHROPIC_AUTH_TOKEN: 'test',
231
+ ANTHROPIC_BASE_URL: 'http://localhost:8080',
232
+ ANTHROPIC_MODEL: 'gemini-3-pro-high[1m]',
233
+ ANTHROPIC_DEFAULT_OPUS_MODEL: 'gemini-3-pro-high[1m]',
234
+ ANTHROPIC_DEFAULT_SONNET_MODEL: 'gemini-3-flash[1m]',
235
+ ANTHROPIC_DEFAULT_HAIKU_MODEL: 'gemini-2.5-flash-lite[1m]',
236
+ CLAUDE_CODE_SUBAGENT_MODEL: 'gemini-3-flash[1m]',
237
+ ENABLE_EXPERIMENTAL_MCP_CLI: 'true'
238
+ }
239
+ }
240
+ ];
241
+
242
+ export default {
243
+ ANTIGRAVITY_ENDPOINT_FALLBACKS,
244
+ ANTIGRAVITY_HEADERS,
245
+ LOAD_CODE_ASSIST_ENDPOINTS,
246
+ ONBOARD_USER_ENDPOINTS,
247
+ LOAD_CODE_ASSIST_HEADERS,
248
+ DEFAULT_PROJECT_ID,
249
+ TOKEN_REFRESH_INTERVAL_MS,
250
+ REQUEST_BODY_LIMIT,
251
+ ANTIGRAVITY_AUTH_PORT,
252
+ DEFAULT_PORT,
253
+ ACCOUNT_CONFIG_PATH,
254
+ ANTIGRAVITY_DB_PATH,
255
+ DEFAULT_COOLDOWN_MS,
256
+ MAX_RETRIES,
257
+ MAX_EMPTY_RESPONSE_RETRIES,
258
+ MAX_ACCOUNTS,
259
+ MAX_WAIT_BEFORE_ERROR_MS,
260
+ RATE_LIMIT_DEDUP_WINDOW_MS,
261
+ MAX_CONSECUTIVE_FAILURES,
262
+ EXTENDED_COOLDOWN_MS,
263
+ CAPACITY_RETRY_DELAY_MS,
264
+ MAX_CAPACITY_RETRIES,
265
+ MIN_SIGNATURE_LENGTH,
266
+ GEMINI_MAX_OUTPUT_TOKENS,
267
+ GEMINI_SKIP_SIGNATURE,
268
+ GEMINI_SIGNATURE_CACHE_TTL_MS,
269
+ getModelFamily,
270
+ isThinkingModel,
271
+ OAUTH_CONFIG,
272
+ OAUTH_REDIRECT_URI,
273
+ STRATEGY_LABELS,
274
+ MODEL_FALLBACK_MAP,
275
+ TEST_MODELS,
276
+ DEFAULT_PRESETS,
277
+ ANTIGRAVITY_SYSTEM_INSTRUCTION
278
+ };
package/src/errors.js ADDED
@@ -0,0 +1,238 @@
1
+ /**
2
+ * Custom Error Classes
3
+ *
4
+ * Provides structured error types for better error handling and classification.
5
+ * Replaces string-based error detection with proper error class checking.
6
+ */
7
+
8
+ /**
9
+ * Base error class for Antigravity proxy errors
10
+ */
11
+ export class AntigravityError extends Error {
12
+ /**
13
+ * @param {string} message - Error message
14
+ * @param {string} code - Error code for programmatic handling
15
+ * @param {boolean} retryable - Whether the error is retryable
16
+ * @param {Object} metadata - Additional error metadata
17
+ */
18
+ constructor(message, code, retryable = false, metadata = {}) {
19
+ super(message);
20
+ this.name = 'AntigravityError';
21
+ this.code = code;
22
+ this.retryable = retryable;
23
+ this.metadata = metadata;
24
+ }
25
+
26
+ /**
27
+ * Convert to JSON for API responses
28
+ */
29
+ toJSON() {
30
+ return {
31
+ name: this.name,
32
+ code: this.code,
33
+ message: this.message,
34
+ retryable: this.retryable,
35
+ ...this.metadata
36
+ };
37
+ }
38
+ }
39
+
40
+ /**
41
+ * Rate limit error (429 / RESOURCE_EXHAUSTED)
42
+ */
43
+ export class RateLimitError extends AntigravityError {
44
+ /**
45
+ * @param {string} message - Error message
46
+ * @param {number|null} resetMs - Time in ms until rate limit resets
47
+ * @param {string} accountEmail - Email of the rate-limited account
48
+ */
49
+ constructor(message, resetMs = null, accountEmail = null) {
50
+ super(message, 'RATE_LIMITED', true, { resetMs, accountEmail });
51
+ this.name = 'RateLimitError';
52
+ this.resetMs = resetMs;
53
+ this.accountEmail = accountEmail;
54
+ }
55
+ }
56
+
57
+ /**
58
+ * Authentication error (invalid credentials, token expired, etc.)
59
+ */
60
+ export class AuthError extends AntigravityError {
61
+ /**
62
+ * @param {string} message - Error message
63
+ * @param {string} accountEmail - Email of the account with auth issues
64
+ * @param {string} reason - Specific reason for auth failure
65
+ */
66
+ constructor(message, accountEmail = null, reason = null) {
67
+ super(message, 'AUTH_INVALID', false, { accountEmail, reason });
68
+ this.name = 'AuthError';
69
+ this.accountEmail = accountEmail;
70
+ this.reason = reason;
71
+ }
72
+ }
73
+
74
+ /**
75
+ * No accounts available error
76
+ */
77
+ export class NoAccountsError extends AntigravityError {
78
+ /**
79
+ * @param {string} message - Error message
80
+ * @param {boolean} allRateLimited - Whether all accounts are rate limited
81
+ */
82
+ constructor(message = 'No accounts available', allRateLimited = false) {
83
+ super(message, 'NO_ACCOUNTS', allRateLimited, { allRateLimited });
84
+ this.name = 'NoAccountsError';
85
+ this.allRateLimited = allRateLimited;
86
+ }
87
+ }
88
+
89
+ /**
90
+ * Max retries exceeded error
91
+ */
92
+ export class MaxRetriesError extends AntigravityError {
93
+ /**
94
+ * @param {string} message - Error message
95
+ * @param {number} attempts - Number of attempts made
96
+ */
97
+ constructor(message = 'Max retries exceeded', attempts = 0) {
98
+ super(message, 'MAX_RETRIES', false, { attempts });
99
+ this.name = 'MaxRetriesError';
100
+ this.attempts = attempts;
101
+ }
102
+ }
103
+
104
+ /**
105
+ * API error from upstream service
106
+ */
107
+ export class ApiError extends AntigravityError {
108
+ /**
109
+ * @param {string} message - Error message
110
+ * @param {number} statusCode - HTTP status code
111
+ * @param {string} errorType - Type of API error
112
+ */
113
+ constructor(message, statusCode = 500, errorType = 'api_error') {
114
+ super(message, errorType.toUpperCase(), statusCode >= 500, { statusCode, errorType });
115
+ this.name = 'ApiError';
116
+ this.statusCode = statusCode;
117
+ this.errorType = errorType;
118
+ }
119
+ }
120
+
121
+ /**
122
+ * Native module error (version mismatch, rebuild required)
123
+ */
124
+ export class NativeModuleError extends AntigravityError {
125
+ /**
126
+ * @param {string} message - Error message
127
+ * @param {boolean} rebuildSucceeded - Whether auto-rebuild succeeded
128
+ * @param {boolean} restartRequired - Whether server restart is needed
129
+ */
130
+ constructor(message, rebuildSucceeded = false, restartRequired = false) {
131
+ super(message, 'NATIVE_MODULE_ERROR', false, { rebuildSucceeded, restartRequired });
132
+ this.name = 'NativeModuleError';
133
+ this.rebuildSucceeded = rebuildSucceeded;
134
+ this.restartRequired = restartRequired;
135
+ }
136
+ }
137
+
138
+ /**
139
+ * Empty response error - thrown when API returns no content
140
+ * Used to trigger retry logic in streaming handler
141
+ */
142
+ export class EmptyResponseError extends AntigravityError {
143
+ /**
144
+ * @param {string} message - Error message
145
+ */
146
+ constructor(message = 'No content received from API') {
147
+ super(message, 'EMPTY_RESPONSE', true, {});
148
+ this.name = 'EmptyResponseError';
149
+ }
150
+ }
151
+
152
+ /**
153
+ * Capacity exhausted error - Google's model is at capacity (not user quota)
154
+ * Should retry on same account with shorter delay, not switch accounts immediately
155
+ * Different from QUOTA_EXHAUSTED which indicates user's daily/hourly limit
156
+ */
157
+ export class CapacityExhaustedError extends AntigravityError {
158
+ /**
159
+ * @param {string} message - Error message
160
+ * @param {number|null} retryAfterMs - Suggested retry delay in ms
161
+ */
162
+ constructor(message = 'Model capacity exhausted', retryAfterMs = null) {
163
+ super(message, 'CAPACITY_EXHAUSTED', true, { retryAfterMs });
164
+ this.name = 'CapacityExhaustedError';
165
+ this.retryAfterMs = retryAfterMs;
166
+ }
167
+ }
168
+
169
+ /**
170
+ * Check if an error is a rate limit error
171
+ * Works with both custom error classes and legacy string-based errors
172
+ * @param {Error} error - Error to check
173
+ * @returns {boolean}
174
+ */
175
+ export function isRateLimitError(error) {
176
+ if (error instanceof RateLimitError) return true;
177
+ const msg = (error.message || '').toLowerCase();
178
+ return msg.includes('429') ||
179
+ msg.includes('resource_exhausted') ||
180
+ msg.includes('quota_exhausted') ||
181
+ msg.includes('rate limit');
182
+ }
183
+
184
+ /**
185
+ * Check if an error is an authentication error
186
+ * Works with both custom error classes and legacy string-based errors
187
+ * @param {Error} error - Error to check
188
+ * @returns {boolean}
189
+ */
190
+ export function isAuthError(error) {
191
+ if (error instanceof AuthError) return true;
192
+ const msg = (error.message || '').toUpperCase();
193
+ return msg.includes('AUTH_INVALID') ||
194
+ msg.includes('INVALID_GRANT') ||
195
+ msg.includes('TOKEN REFRESH FAILED');
196
+ }
197
+
198
+ /**
199
+ * Check if an error is an empty response error
200
+ * @param {Error} error - Error to check
201
+ * @returns {boolean}
202
+ */
203
+ export function isEmptyResponseError(error) {
204
+ return error instanceof EmptyResponseError ||
205
+ error?.name === 'EmptyResponseError';
206
+ }
207
+
208
+ /**
209
+ * Check if an error is a capacity exhausted error (model overload, not user quota)
210
+ * This is different from quota exhaustion - capacity issues are temporary infrastructure
211
+ * limits that should be retried on the SAME account with shorter delays
212
+ * @param {Error} error - Error to check
213
+ * @returns {boolean}
214
+ */
215
+ export function isCapacityExhaustedError(error) {
216
+ if (error instanceof CapacityExhaustedError) return true;
217
+ const msg = (error.message || '').toLowerCase();
218
+ return msg.includes('model_capacity_exhausted') ||
219
+ msg.includes('capacity_exhausted') ||
220
+ msg.includes('model is currently overloaded') ||
221
+ msg.includes('service temporarily unavailable');
222
+ }
223
+
224
+ export default {
225
+ AntigravityError,
226
+ RateLimitError,
227
+ AuthError,
228
+ NoAccountsError,
229
+ MaxRetriesError,
230
+ ApiError,
231
+ NativeModuleError,
232
+ EmptyResponseError,
233
+ CapacityExhaustedError,
234
+ isRateLimitError,
235
+ isAuthError,
236
+ isEmptyResponseError,
237
+ isCapacityExhaustedError
238
+ };
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Model Fallback Configuration
3
+ *
4
+ * Defines fallback mappings for when a model's quota is exhausted across all accounts.
5
+ * Enables graceful degradation to alternative models with similar capabilities.
6
+ */
7
+
8
+ import { MODEL_FALLBACK_MAP } from './constants.js';
9
+
10
+ // Re-export for convenience
11
+ export { MODEL_FALLBACK_MAP };
12
+
13
+ /**
14
+ * Get fallback model for a given model ID
15
+ * @param {string} model - Primary model ID
16
+ * @returns {string|null} Fallback model ID or null if no fallback exists
17
+ */
18
+ export function getFallbackModel(model) {
19
+ return MODEL_FALLBACK_MAP[model] || null;
20
+ }
21
+
22
+ /**
23
+ * Check if a model has a fallback configured
24
+ * @param {string} model - Model ID to check
25
+ * @returns {boolean} True if fallback exists
26
+ */
27
+ export function hasFallback(model) {
28
+ return model in MODEL_FALLBACK_MAP;
29
+ }