@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.
- package/LICENSE +21 -0
- package/README.md +622 -0
- package/bin/cli.js +124 -0
- package/package.json +80 -0
- package/public/app.js +228 -0
- package/public/css/src/input.css +523 -0
- package/public/css/style.css +1 -0
- package/public/favicon.svg +10 -0
- package/public/index.html +381 -0
- package/public/js/components/account-manager.js +245 -0
- package/public/js/components/claude-config.js +420 -0
- package/public/js/components/dashboard/charts.js +589 -0
- package/public/js/components/dashboard/filters.js +362 -0
- package/public/js/components/dashboard/stats.js +110 -0
- package/public/js/components/dashboard.js +236 -0
- package/public/js/components/logs-viewer.js +100 -0
- package/public/js/components/models.js +36 -0
- package/public/js/components/server-config.js +349 -0
- package/public/js/config/constants.js +102 -0
- package/public/js/data-store.js +386 -0
- package/public/js/settings-store.js +58 -0
- package/public/js/store.js +78 -0
- package/public/js/translations/en.js +351 -0
- package/public/js/translations/id.js +396 -0
- package/public/js/translations/pt.js +287 -0
- package/public/js/translations/tr.js +342 -0
- package/public/js/translations/zh.js +357 -0
- package/public/js/utils/account-actions.js +189 -0
- package/public/js/utils/error-handler.js +96 -0
- package/public/js/utils/model-config.js +42 -0
- package/public/js/utils/validators.js +77 -0
- package/public/js/utils.js +69 -0
- package/public/views/accounts.html +329 -0
- package/public/views/dashboard.html +484 -0
- package/public/views/logs.html +97 -0
- package/public/views/models.html +331 -0
- package/public/views/settings.html +1329 -0
- package/src/account-manager/credentials.js +243 -0
- package/src/account-manager/index.js +380 -0
- package/src/account-manager/onboarding.js +117 -0
- package/src/account-manager/rate-limits.js +237 -0
- package/src/account-manager/storage.js +136 -0
- package/src/account-manager/strategies/base-strategy.js +104 -0
- package/src/account-manager/strategies/hybrid-strategy.js +195 -0
- package/src/account-manager/strategies/index.js +79 -0
- package/src/account-manager/strategies/round-robin-strategy.js +76 -0
- package/src/account-manager/strategies/sticky-strategy.js +138 -0
- package/src/account-manager/strategies/trackers/health-tracker.js +162 -0
- package/src/account-manager/strategies/trackers/index.js +8 -0
- package/src/account-manager/strategies/trackers/token-bucket-tracker.js +121 -0
- package/src/auth/database.js +169 -0
- package/src/auth/oauth.js +419 -0
- package/src/auth/token-extractor.js +117 -0
- package/src/cli/accounts.js +512 -0
- package/src/cli/refresh.js +201 -0
- package/src/cli/setup.js +338 -0
- package/src/cloudcode/index.js +29 -0
- package/src/cloudcode/message-handler.js +386 -0
- package/src/cloudcode/model-api.js +248 -0
- package/src/cloudcode/rate-limit-parser.js +181 -0
- package/src/cloudcode/request-builder.js +93 -0
- package/src/cloudcode/session-manager.js +47 -0
- package/src/cloudcode/sse-parser.js +121 -0
- package/src/cloudcode/sse-streamer.js +293 -0
- package/src/cloudcode/streaming-handler.js +492 -0
- package/src/config.js +107 -0
- package/src/constants.js +278 -0
- package/src/errors.js +238 -0
- package/src/fallback-config.js +29 -0
- package/src/format/content-converter.js +193 -0
- package/src/format/index.js +20 -0
- package/src/format/request-converter.js +248 -0
- package/src/format/response-converter.js +120 -0
- package/src/format/schema-sanitizer.js +673 -0
- package/src/format/signature-cache.js +88 -0
- package/src/format/thinking-utils.js +558 -0
- package/src/index.js +146 -0
- package/src/modules/usage-stats.js +205 -0
- package/src/server.js +861 -0
- package/src/utils/claude-config.js +245 -0
- package/src/utils/helpers.js +51 -0
- package/src/utils/logger.js +142 -0
- package/src/utils/native-module-helper.js +162 -0
- package/src/webui/index.js +707 -0
package/src/constants.js
ADDED
|
@@ -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
|
+
}
|